diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 8cf22af..ac2aba6 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/urls.py b/config/urls.py index bcfc074..1b0f05a 100644 --- a/config/urls.py +++ b/config/urls.py @@ -18,8 +18,10 @@ from django.contrib import admin from django.urls import include, path from django.conf import settings from django.conf.urls.static import static +from django.views.generic.base import RedirectView urlpatterns = [ + path("favicon.ico", RedirectView.as_view(url=settings.STATIC_URL + "images/favicon.svg", permanent=False)), path("admin/", admin.site.urls), path("", include("core.urls")), ] diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 99f725f..5187f36 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 6c9b14a..3874769 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 5862b35..c82372b 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index d2978f6..4b5f752 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index af92eb0..1df2733 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index c862f0a..5112d54 100644 --- a/core/admin.py +++ b/core/admin.py @@ -14,12 +14,13 @@ class CategoryAdmin(admin.ModelAdmin): class MomentumEntryAdmin(admin.ModelAdmin): list_display = ( "title", + "user", "entry_date", "category", "focus_score", "energy_score", "deep_work_minutes", ) - list_filter = ("category", "entry_date") - search_fields = ("title", "takeaway", "reflection") + list_filter = ("user", "category", "entry_date") + search_fields = ("title", "takeaway", "reflection", "user__username") date_hierarchy = "entry_date" diff --git a/core/forms.py b/core/forms.py index 7345475..f388dc1 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,4 +1,6 @@ from django import forms +from django.contrib.auth.forms import AuthenticationForm, UserCreationForm +from django.contrib.auth.models import User from django.utils import timezone from .models import MomentumEntry @@ -64,3 +66,39 @@ class MomentumEntryForm(forms.ModelForm): if len(takeaway.split()) < 3: raise forms.ValidationError("Write a short sentence with at least three words.") return takeaway + + +class LoginForm(AuthenticationForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["username"].help_text = "Use the username you chose when creating your account." + self.fields["password"].help_text = "Enter the same password you used during sign up." + for field in self.fields.values(): + field.widget.attrs["class"] = f"{field.widget.attrs.get('class', '')} form-control".strip() + field.widget.attrs.setdefault("autocomplete", "off") + +class SignUpForm(UserCreationForm): + email = forms.EmailField(required=False) + + class Meta: + model = User + fields = ("username", "email", "password1", "password2") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["username"].help_text = "Pick a simple username for your private dashboard." + self.fields["email"].help_text = "Optional, but useful if you later add notifications." + self.fields["password1"].help_text = "Use at least 8 characters so your account is harder to guess." + self.fields["password2"].help_text = "Type the same password again to confirm it." + for name, field in self.fields.items(): + field.widget.attrs["class"] = f"{field.widget.attrs.get('class', '')} form-control".strip() + if name == "username": + field.widget.attrs.setdefault("autofocus", True) + field.widget.attrs.setdefault("autocomplete", "off") + + def save(self, commit=True): + user = super().save(commit=False) + user.email = self.cleaned_data.get("email", "") + if commit: + user.save() + return user diff --git a/core/migrations/0003_momentumentry_user.py b/core/migrations/0003_momentumentry_user.py new file mode 100644 index 0000000..97234b7 --- /dev/null +++ b/core/migrations/0003_momentumentry_user.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.7 on 2026-04-16 11:22 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_seed_demo_data'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='momentumentry', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='momentum_entries', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/core/migrations/__pycache__/0003_momentumentry_user.cpython-311.pyc b/core/migrations/__pycache__/0003_momentumentry_user.cpython-311.pyc new file mode 100644 index 0000000..4705d5e Binary files /dev/null and b/core/migrations/__pycache__/0003_momentumentry_user.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 2de2282..b84bc57 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.urls import reverse @@ -17,6 +18,13 @@ class Category(models.Model): class MomentumEntry(models.Model): + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="momentum_entries", + null=True, + blank=True, + ) category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="entries") title = models.CharField(max_length=120) entry_date = models.DateField() diff --git a/core/templates/base.html b/core/templates/base.html index fc2bed1..a039170 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -17,6 +17,7 @@ + {% block head %}{% endblock %} diff --git a/core/templates/core/entry_detail.html b/core/templates/core/entry_detail.html index ce26df7..6c664fc 100644 --- a/core/templates/core/entry_detail.html +++ b/core/templates/core/entry_detail.html @@ -6,6 +6,14 @@ {% block content %}
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + {% if created %}

More in {{ entry.category.name }}

diff --git a/core/templates/core/entry_list.html b/core/templates/core/entry_list.html index ae3a103..9e171ff 100644 --- a/core/templates/core/entry_list.html +++ b/core/templates/core/entry_list.html @@ -6,15 +6,32 @@ {% block content %}
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} +
History -

All momentum entries

-

Filter by category and open any check-in for its confirmation/detail view.

+

{% if is_demo_mode %}Demo momentum entries{% else %}Your momentum entries{% endif %}

+

{% if is_demo_mode %}Browse the seeded sample history, then create an account when you want private tracking.{% else %}Filter your private check-ins by category and open any one for its detail view.{% endif %}

-
+
Back to dashboard + {% if request.user.is_authenticated %} New check-in +
+ {% csrf_token %} + +
+ {% else %} + Log in + Create account + {% endif %}
@@ -47,9 +64,13 @@
{% else %}
-

No entries for this filter

-

Try a different category or create a new check-in from the dashboard.

+

{% if is_demo_mode %}No demo entries for this filter{% else %}No private entries for this filter{% endif %}

+

{% if is_demo_mode %}Try a different category or create an account to start tracking your own days.{% else %}Try a different category or create a new check-in from the dashboard.{% endif %}

+ {% if request.user.is_authenticated %} Create an entry + {% else %} + Create your account + {% endif %}
{% endif %}
diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 22e4b66..81b349f 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -14,9 +14,21 @@ @@ -29,12 +41,23 @@
- Interesting Python project · daily tracker MVP + {% if request.user.is_authenticated %}Private momentum tracker{% else %}Interesting Python project · try the demo{% endif %}

Track your momentum like a product, not a spreadsheet.

-

Log a single daily check-in, watch your focus and energy trend over time, and turn a simple Django app into a polished personal dashboard you can actually share.

+

+ {% if request.user.is_authenticated %} + Welcome back, {{ request.user.username }}. Your entries are now private to your account, so this dashboard reflects your own focus, energy, and small wins. + {% else %} + Explore the tracker with demo data first, then create a free account when you are ready to save private daily check-ins. + {% endif %} +

+ {% if request.user.is_authenticated %} Log today’s momentum - Browse history + Browse your history + {% else %} + Create free account + Browse demo history + {% endif %}
@@ -49,7 +72,7 @@
-
30-day snapshot
+
{% if is_demo_mode %}Demo 30-day snapshot{% else %}Your 30-day snapshot{% endif %}
@@ -105,10 +128,17 @@
Workflow widget -

Capture one meaningful check-in.

-

Each entry becomes a usable artifact: you log the day, land on a confirmation detail view, and keep building a real momentum history.

+

{% if request.user.is_authenticated %}Capture one meaningful check-in.{% else %}Create your account to start saving check-ins.{% endif %}

+

+ {% if request.user.is_authenticated %} + Each entry becomes a private artifact: you log the day, land on a confirmation detail view, and build your own momentum history. + {% else %} + You can already browse the demo dashboard. Once you sign up, new entries will belong only to your account. + {% endif %} +

+ {% if request.user.is_authenticated %}
{% csrf_token %}
@@ -132,6 +162,19 @@ Default categories are ready, and you can manage them later from Django Admin.
+ {% else %} +
+
+ Private entries + Login + signup ready +
+

This prevents your personal notes from mixing with the demo sample data. It is a common first step when an app starts storing user-specific information.

+ +
+ {% endif %}
@@ -142,7 +185,7 @@

Create

-

Submit a structured daily entry with validation, categories, and tangible metrics.

+

{% if request.user.is_authenticated %}Submit a structured daily entry with validation, categories, and personal ownership.{% else %}Create an account in a few seconds so the app can keep your data separate from demo content.{% endif %}

Confirm

@@ -163,9 +206,9 @@
7-day insight -

Focus and energy trend

+

{% if is_demo_mode %}Demo focus and energy trend{% else %}Your focus and energy trend{% endif %}

- Open full history + {% if is_demo_mode %}Open demo history{% else %}Open your history{% endif %}
@@ -190,9 +233,9 @@
Recent check-ins -

Momentum history preview

+

{% if is_demo_mode %}Demo momentum history preview{% else %}Your momentum history preview{% endif %}

- See every entry + {% if is_demo_mode %}See every demo entry{% else %}See every entry{% endif %}
{% if recent_entries %}
@@ -216,8 +259,14 @@
{% else %}
-

No entries yet

-

Start with one check-in above and this dashboard will instantly become your first useful Python product demo.

+

{% if request.user.is_authenticated %}No private entries yet{% else %}No demo entries yet{% endif %}

+

+ {% if request.user.is_authenticated %} + Start with one check-in above and this dashboard will instantly become your own useful Python product demo. + {% else %} + Create an account to start filling this dashboard with your own progress history. + {% endif %} +

{% endif %}
diff --git a/core/templates/core/signup.html b/core/templates/core/signup.html new file mode 100644 index 0000000..121f2eb --- /dev/null +++ b/core/templates/core/signup.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% block title %}{{ page_title }}{% endblock %} +{% block meta_description %}{{ meta_description }}{% endblock %} + +{% block content %} +
+
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + +
+
+
+ Create account +

Start your private tracker

+

This is the simple user system behind the app: once you sign up, each new entry is stored under your account instead of the public demo sample.

+ +
+ {% csrf_token %} + +
+ {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %}{{ error }}{% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+ {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %}{{ error }}{% endfor %} +
+ {% endif %} +
+ + I already have an account +
+
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/registration/login.html b/core/templates/registration/login.html new file mode 100644 index 0000000..a3fab8a --- /dev/null +++ b/core/templates/registration/login.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% block title %}{{ page_title|default:"Log in | Momentum Atlas" }}{% endblock %} +{% block meta_description %}Log in to Momentum Atlas and continue your private daily tracking workflow.{% endblock %} + +{% block content %} +
+
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + +
+
+
+ Account access +

Log in to your dashboard

+

Once logged in, new check-ins belong to your account and only you will see them.

+ +
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %}{{ error }}{% endfor %} +
+ {% endif %} +
+ {% endfor %} + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %}{{ error }}{% endfor %} +
+ {% endif %} + {% if next %}{% endif %} + +
+ + +
+
+
+
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 2bbe4bc..1f47f61 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,9 +1,22 @@ +from django.contrib.auth import views as auth_views from django.urls import path -from .views import entry_detail, entry_list, home +from .forms import LoginForm +from .views import entry_detail, entry_list, home, signup urlpatterns = [ path("", home, name="home"), path("entries/", entry_list, name="entry_list"), path("entries//", entry_detail, name="entry_detail"), + path( + "login/", + auth_views.LoginView.as_view( + template_name="registration/login.html", + redirect_authenticated_user=True, + authentication_form=LoginForm, + ), + name="login", + ), + path("logout/", auth_views.LogoutView.as_view(next_page="home"), name="logout"), + path("signup/", signup, name="signup"), ] diff --git a/core/views.py b/core/views.py index cb3cbf6..992803c 100644 --- a/core/views.py +++ b/core/views.py @@ -4,12 +4,14 @@ from datetime import timedelta from django import get_version as django_version from django.contrib import messages -from django.db.models import Avg, Count, Sum +from django.contrib.auth import login +from django.db.models import Avg, Count, Q, Sum from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse from django.utils import timezone -from .forms import MomentumEntryForm +from .forms import MomentumEntryForm, SignUpForm from .models import Category, MomentumEntry @@ -17,6 +19,21 @@ APP_NAME = "Momentum Atlas" APP_TAGLINE = "A polished personal dashboard for tracking focus, energy, and small wins." +def _entries_for_request(request): + entries = MomentumEntry.objects.select_related("category") + if request.user.is_authenticated: + return entries.filter(user=request.user) + return entries.filter(user__isnull=True) + + +def _categories_for_request(request): + if request.user.is_authenticated: + entry_filter = Q(entries__user=request.user) + else: + entry_filter = Q(entries__user__isnull=True) + return Category.objects.annotate(entry_total=Count("entries", filter=entry_filter)) + + def _build_weekly_trend(entries): today = timezone.localdate() start_date = today - timedelta(days=6) @@ -54,8 +71,8 @@ def _build_weekly_trend(entries): return trend -def _dashboard_context(): - entries = MomentumEntry.objects.select_related("category") +def _dashboard_context(request): + entries = _entries_for_request(request) recent_entries = entries[:6] last_30_days = timezone.localdate() - timedelta(days=29) stats_window = entries.filter(entry_date__gte=last_30_days) @@ -80,13 +97,16 @@ def _dashboard_context(): spotlight = "Your recent focus trend is excellent—keep protecting that deep-work time." elif focus_average >= 6: spotlight = "Momentum is building. A little more consistency could turn this into a real streak." - else: + elif totals["total_entries"]: spotlight = "A reset week might help—shrink the task list and aim for one clear win each day." + else: + spotlight = "Your dashboard will start filling in as soon as you save the first check-in." return { "recent_entries": recent_entries, - "categories": Category.objects.all(), + "categories": _categories_for_request(request), "weekly_trend": weekly_trend, + "is_demo_mode": not request.user.is_authenticated, "stats": { "total_entries": totals["total_entries"], "avg_focus": focus_average, @@ -105,10 +125,16 @@ def home(request): now = timezone.now() if request.method == "POST": + if not request.user.is_authenticated: + messages.info(request, "Create a free account or log in to save personal check-ins.") + return redirect(f"{reverse('login')}?next={reverse('home')}") + form = MomentumEntryForm(request.POST) if form.is_valid(): - entry = form.save() - messages.success(request, "Momentum captured. Your new check-in is ready.") + entry = form.save(commit=False) + entry.user = request.user + entry.save() + messages.success(request, "Momentum captured. Your new private check-in is ready.") return redirect(f"{entry.get_absolute_url()}?created=1") messages.error(request, "Please fix the form errors and try again.") else: @@ -126,14 +152,14 @@ def home(request): "page_title": f"{APP_NAME} | Daily focus dashboard", "meta_description": "Track daily focus, energy, and deep-work minutes in a polished Python dashboard.", "form": form, - **_dashboard_context(), + **_dashboard_context(request), } return render(request, "core/index.html", context) def entry_list(request): selected_slug = request.GET.get("category", "") - entries = MomentumEntry.objects.select_related("category") + entries = _entries_for_request(request) if selected_slug: entries = entries.filter(category__slug=selected_slug) @@ -141,24 +167,47 @@ def entry_list(request): "page_title": f"All check-ins | {APP_NAME}", "meta_description": "Browse recent check-ins and filter your momentum history by category.", "entries": entries, - "categories": Category.objects.annotate(entry_total=Count("entries")), + "categories": _categories_for_request(request), "selected_slug": selected_slug, + "is_demo_mode": not request.user.is_authenticated, } return render(request, "core/entry_list.html", context) def entry_detail(request, pk): - entry = get_object_or_404(MomentumEntry.objects.select_related("category"), pk=pk) - related_entries = ( - MomentumEntry.objects.select_related("category") - .filter(category=entry.category) - .exclude(pk=entry.pk)[:3] - ) + scoped_entries = _entries_for_request(request) + entry = get_object_or_404(scoped_entries, pk=pk) + related_entries = scoped_entries.filter(category=entry.category).exclude(pk=entry.pk)[:3] context = { "page_title": f"{entry.title} | {APP_NAME}", "meta_description": entry.takeaway, "entry": entry, "related_entries": related_entries, "created": request.GET.get("created") == "1", + "is_demo_mode": not request.user.is_authenticated, } return render(request, "core/entry_detail.html", context) + + +def signup(request): + if request.user.is_authenticated: + return redirect("home") + + if request.method == "POST": + form = SignUpForm(request.POST) + if form.is_valid(): + user = form.save() + login(request, user) + messages.success(request, "Your account is ready. You can now save private momentum entries.") + return redirect(request.POST.get("next") or "home") + messages.error(request, "Please fix the sign-up form and try again.") + else: + form = SignUpForm() + + context = { + "page_title": f"Create account | {APP_NAME}", + "meta_description": "Create a private Momentum Atlas account to save personal check-ins.", + "form": form, + "next_url": request.GET.get("next") or request.POST.get("next") or reverse("home"), + } + return render(request, "core/signup.html", context) diff --git a/static/css/custom.css b/static/css/custom.css index ecc15bc..951a829 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -87,6 +87,27 @@ p { padding: 0.55rem 1rem !important; } +.nav-user { + display: inline-flex; + align-items: center; + min-height: 2.75rem; + padding: 0.55rem 0.9rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + color: #ffffff; + font-weight: 700; +} + +.nav-form, +.inline-form { + margin: 0; +} + +.nav-button { + background: transparent; + cursor: pointer; +} + .hero-section { position: relative; padding: 1rem 0 4rem; @@ -206,7 +227,8 @@ p { } .chip-row, -.filter-row { +.filter-row, +.message-stack { display: flex; flex-wrap: wrap; gap: 0.75rem; @@ -255,7 +277,8 @@ p { .section-heading h2, .subpage-header h1, -.detail-panel h1 { +.detail-panel h1, +.auth-card h1 { font-size: clamp(2rem, 3vw, 3rem); margin-bottom: 0.8rem; } @@ -269,12 +292,14 @@ p { .detail-panel, .sidebar-panel, .subpage-header, -.empty-state { +.empty-state, +.auth-card { padding: 2rem; } .form-panel .form-control, -.form-panel .form-select { +.form-panel .form-select, +.auth-card .form-control { border-radius: 16px; border: 1px solid rgba(148, 163, 184, 0.35); padding: 0.9rem 1rem; @@ -345,6 +370,27 @@ p { margin-bottom: 0.6rem; } +.auth-callout { + display: grid; + gap: 0.75rem; +} + +.auth-shell { + padding: 1rem 0 3rem; +} + +.auth-card { + max-width: 100%; +} + +.auth-footer { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + margin-top: 1.25rem; +} + .trend-chart { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); @@ -534,7 +580,12 @@ p { .sidebar-panel, .subpage-header, .empty-state, - .insight-panel { + .insight-panel, + .auth-card { padding: 1.4rem; } + + .nav-user { + width: fit-content; + } } diff --git a/static/images/favicon.svg b/static/images/favicon.svg new file mode 100644 index 0000000..90f19b9 --- /dev/null +++ b/static/images/favicon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index ecc15bc..951a829 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -87,6 +87,27 @@ p { padding: 0.55rem 1rem !important; } +.nav-user { + display: inline-flex; + align-items: center; + min-height: 2.75rem; + padding: 0.55rem 0.9rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + color: #ffffff; + font-weight: 700; +} + +.nav-form, +.inline-form { + margin: 0; +} + +.nav-button { + background: transparent; + cursor: pointer; +} + .hero-section { position: relative; padding: 1rem 0 4rem; @@ -206,7 +227,8 @@ p { } .chip-row, -.filter-row { +.filter-row, +.message-stack { display: flex; flex-wrap: wrap; gap: 0.75rem; @@ -255,7 +277,8 @@ p { .section-heading h2, .subpage-header h1, -.detail-panel h1 { +.detail-panel h1, +.auth-card h1 { font-size: clamp(2rem, 3vw, 3rem); margin-bottom: 0.8rem; } @@ -269,12 +292,14 @@ p { .detail-panel, .sidebar-panel, .subpage-header, -.empty-state { +.empty-state, +.auth-card { padding: 2rem; } .form-panel .form-control, -.form-panel .form-select { +.form-panel .form-select, +.auth-card .form-control { border-radius: 16px; border: 1px solid rgba(148, 163, 184, 0.35); padding: 0.9rem 1rem; @@ -345,6 +370,27 @@ p { margin-bottom: 0.6rem; } +.auth-callout { + display: grid; + gap: 0.75rem; +} + +.auth-shell { + padding: 1rem 0 3rem; +} + +.auth-card { + max-width: 100%; +} + +.auth-footer { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + margin-top: 1.25rem; +} + .trend-chart { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); @@ -534,7 +580,12 @@ p { .sidebar-panel, .subpage-header, .empty-state, - .insight-panel { + .insight-panel, + .auth-card { padding: 1.4rem; } + + .nav-user { + width: fit-content; + } } diff --git a/staticfiles/images/favicon.svg b/staticfiles/images/favicon.svg new file mode 100644 index 0000000..90f19b9 --- /dev/null +++ b/staticfiles/images/favicon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + +