diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc
index 5e8987a..99f725f 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
new file mode 100644
index 0000000..6c9b14a
Binary files /dev/null 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 a251b5f..5862b35 100644
Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ
diff --git a/core/__pycache__/tests.cpython-311.pyc b/core/__pycache__/tests.cpython-311.pyc
new file mode 100644
index 0000000..325de17
Binary files /dev/null and b/core/__pycache__/tests.cpython-311.pyc differ
diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc
index f705988..d2978f6 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 2f0989c..af92eb0 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 8c38f3f..c862f0a 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -1,3 +1,25 @@
from django.contrib import admin
-# Register your models here.
+from .models import Category, MomentumEntry
+
+
+@admin.register(Category)
+class CategoryAdmin(admin.ModelAdmin):
+ list_display = ("name", "slug", "accent_color")
+ prepopulated_fields = {"slug": ("name",)}
+ search_fields = ("name", "description")
+
+
+@admin.register(MomentumEntry)
+class MomentumEntryAdmin(admin.ModelAdmin):
+ list_display = (
+ "title",
+ "entry_date",
+ "category",
+ "focus_score",
+ "energy_score",
+ "deep_work_minutes",
+ )
+ list_filter = ("category", "entry_date")
+ search_fields = ("title", "takeaway", "reflection")
+ date_hierarchy = "entry_date"
diff --git a/core/forms.py b/core/forms.py
new file mode 100644
index 0000000..7345475
--- /dev/null
+++ b/core/forms.py
@@ -0,0 +1,66 @@
+from django import forms
+from django.utils import timezone
+
+from .models import MomentumEntry
+
+
+class MomentumEntryForm(forms.ModelForm):
+ class Meta:
+ model = MomentumEntry
+ fields = [
+ "title",
+ "category",
+ "entry_date",
+ "focus_score",
+ "energy_score",
+ "deep_work_minutes",
+ "takeaway",
+ "reflection",
+ ]
+ widgets = {
+ "title": forms.TextInput(
+ attrs={"placeholder": "Shipped a habit, finished a lesson, or stayed focused"}
+ ),
+ "entry_date": forms.DateInput(attrs={"type": "date"}),
+ "focus_score": forms.NumberInput(attrs={"min": 1, "max": 10}),
+ "energy_score": forms.NumberInput(attrs={"min": 1, "max": 10}),
+ "deep_work_minutes": forms.NumberInput(attrs={"min": 0, "max": 960, "step": 5}),
+ "takeaway": forms.TextInput(
+ attrs={"placeholder": "One sentence that captures today’s momentum"}
+ ),
+ "reflection": forms.Textarea(
+ attrs={"rows": 4, "placeholder": "What worked, what felt hard, what should tomorrow look like?"}
+ ),
+ }
+ labels = {
+ "title": "What did you move forward?",
+ "category": "Category",
+ "entry_date": "Date",
+ "focus_score": "Focus score",
+ "energy_score": "Energy score",
+ "deep_work_minutes": "Deep-work minutes",
+ "takeaway": "Main takeaway",
+ "reflection": "Reflection",
+ }
+ help_texts = {
+ "focus_score": "1 = distracted, 10 = locked in.",
+ "energy_score": "1 = drained, 10 = fully charged.",
+ "deep_work_minutes": "Minutes spent on meaningful work today.",
+ }
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields["entry_date"].initial = timezone.localdate()
+ for name, field in self.fields.items():
+ css_class = "form-select" if isinstance(field.widget, forms.Select) else "form-control"
+ if name in {"focus_score", "energy_score", "deep_work_minutes", "entry_date"}:
+ css_class = "form-control"
+ field.widget.attrs["class"] = f"{field.widget.attrs.get('class', '')} {css_class}".strip()
+ field.widget.attrs.setdefault("autocomplete", "off")
+ self.fields["reflection"].required = False
+
+ def clean_takeaway(self):
+ takeaway = self.cleaned_data["takeaway"].strip()
+ if len(takeaway.split()) < 3:
+ raise forms.ValidationError("Write a short sentence with at least three words.")
+ return takeaway
diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py
new file mode 100644
index 0000000..8dc8c18
--- /dev/null
+++ b/core/migrations/0001_initial.py
@@ -0,0 +1,47 @@
+# Generated by Django 5.2.7 on 2026-04-16 11:08
+
+import django.core.validators
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Category',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80, unique=True)),
+ ('slug', models.SlugField(max_length=80, unique=True)),
+ ('description', models.CharField(max_length=160)),
+ ('accent_color', models.CharField(default='#0F766E', max_length=7)),
+ ],
+ options={
+ 'ordering': ['name'],
+ },
+ ),
+ migrations.CreateModel(
+ name='MomentumEntry',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=120)),
+ ('entry_date', models.DateField()),
+ ('focus_score', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(10)])),
+ ('energy_score', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(10)])),
+ ('deep_work_minutes', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(960)])),
+ ('takeaway', models.CharField(max_length=160)),
+ ('reflection', models.TextField(blank=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='core.category')),
+ ],
+ options={
+ 'ordering': ['-entry_date', '-created_at'],
+ },
+ ),
+ ]
diff --git a/core/migrations/0002_seed_demo_data.py b/core/migrations/0002_seed_demo_data.py
new file mode 100644
index 0000000..a1df0d3
--- /dev/null
+++ b/core/migrations/0002_seed_demo_data.py
@@ -0,0 +1,115 @@
+from datetime import date
+
+from django.db import migrations
+
+
+CATEGORIES = [
+ {
+ "name": "Learning",
+ "slug": "learning",
+ "description": "Study sessions, experiments, and small breakthroughs.",
+ "accent_color": "#0F766E",
+ },
+ {
+ "name": "Deep Work",
+ "slug": "deep-work",
+ "description": "Quiet execution blocks that move the hard work forward.",
+ "accent_color": "#F97316",
+ },
+ {
+ "name": "Wellbeing",
+ "slug": "wellbeing",
+ "description": "Energy, routines, and recovery habits that support momentum.",
+ "accent_color": "#F59E0B",
+ },
+]
+
+
+def seed_demo_data(apps, schema_editor):
+ Category = apps.get_model("core", "Category")
+ MomentumEntry = apps.get_model("core", "MomentumEntry")
+
+ category_map = {}
+ for item in CATEGORIES:
+ category, _ = Category.objects.get_or_create(
+ slug=item["slug"],
+ defaults={
+ "name": item["name"],
+ "description": item["description"],
+ "accent_color": item["accent_color"],
+ },
+ )
+ category_map[item["slug"]] = category
+
+ if MomentumEntry.objects.exists():
+ return
+
+ MomentumEntry.objects.bulk_create(
+ [
+ MomentumEntry(
+ category=category_map["learning"],
+ title="Finished a Python walkthrough",
+ entry_date=date(2026, 4, 10),
+ focus_score=8,
+ energy_score=7,
+ deep_work_minutes=95,
+ takeaway="Turned a tutorial into notes I can actually reuse later.",
+ reflection="The best part was rewriting the idea in my own words instead of copying line by line.",
+ ),
+ MomentumEntry(
+ category=category_map["deep-work"],
+ title="Protected a no-notification build block",
+ entry_date=date(2026, 4, 12),
+ focus_score=9,
+ energy_score=8,
+ deep_work_minutes=140,
+ takeaway="A single distraction-free block created more progress than a scattered full day.",
+ reflection="Turning off notifications before starting made the session feel calm and fast.",
+ ),
+ MomentumEntry(
+ category=category_map["wellbeing"],
+ title="Reset the afternoon slump",
+ entry_date=date(2026, 4, 14),
+ focus_score=6,
+ energy_score=8,
+ deep_work_minutes=60,
+ takeaway="A walk and lighter task list rescued the day instead of writing it off.",
+ reflection="I should use recovery intentionally, not only when things already feel off.",
+ ),
+ MomentumEntry(
+ category=category_map["learning"],
+ title="Built the first Django tracker flow",
+ entry_date=date(2026, 4, 15),
+ focus_score=8,
+ energy_score=8,
+ deep_work_minutes=125,
+ takeaway="Shipping a tiny end-to-end slice feels more motivating than polishing ideas in isolation.",
+ reflection="Seeing create, list, and detail views together makes the project feel real.",
+ ),
+ ]
+ )
+
+
+def reverse_seed_demo_data(apps, schema_editor):
+ Category = apps.get_model("core", "Category")
+ MomentumEntry = apps.get_model("core", "MomentumEntry")
+ MomentumEntry.objects.filter(
+ title__in=[
+ "Finished a Python walkthrough",
+ "Protected a no-notification build block",
+ "Reset the afternoon slump",
+ "Built the first Django tracker flow",
+ ]
+ ).delete()
+ Category.objects.filter(slug__in=["learning", "deep-work", "wellbeing"]).delete()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("core", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.RunPython(seed_demo_data, reverse_seed_demo_data),
+ ]
diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc
new file mode 100644
index 0000000..5fd75c8
Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ
diff --git a/core/migrations/__pycache__/0002_seed_demo_data.cpython-311.pyc b/core/migrations/__pycache__/0002_seed_demo_data.cpython-311.pyc
new file mode 100644
index 0000000..1141550
Binary files /dev/null and b/core/migrations/__pycache__/0002_seed_demo_data.cpython-311.pyc differ
diff --git a/core/models.py b/core/models.py
index 71a8362..2de2282 100644
--- a/core/models.py
+++ b/core/models.py
@@ -1,3 +1,47 @@
+from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
+from django.urls import reverse
-# Create your models here.
+
+class Category(models.Model):
+ name = models.CharField(max_length=80, unique=True)
+ slug = models.SlugField(max_length=80, unique=True)
+ description = models.CharField(max_length=160)
+ accent_color = models.CharField(max_length=7, default="#0F766E")
+
+ class Meta:
+ ordering = ["name"]
+
+ def __str__(self):
+ return self.name
+
+
+class MomentumEntry(models.Model):
+ category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="entries")
+ title = models.CharField(max_length=120)
+ entry_date = models.DateField()
+ focus_score = models.PositiveSmallIntegerField(
+ validators=[MinValueValidator(1), MaxValueValidator(10)]
+ )
+ energy_score = models.PositiveSmallIntegerField(
+ validators=[MinValueValidator(1), MaxValueValidator(10)]
+ )
+ deep_work_minutes = models.PositiveIntegerField(
+ validators=[MinValueValidator(0), MaxValueValidator(960)]
+ )
+ takeaway = models.CharField(max_length=160)
+ reflection = models.TextField(blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ ordering = ["-entry_date", "-created_at"]
+
+ def __str__(self):
+ return f"{self.title} ({self.entry_date:%Y-%m-%d})"
+
+ @property
+ def momentum_score(self):
+ return round((self.focus_score + self.energy_score) / 2, 1)
+
+ def get_absolute_url(self):
+ return reverse("entry_detail", args=[self.pk])
diff --git a/core/templates/base.html b/core/templates/base.html
index 1e7e5fb..fc2bed1 100644
--- a/core/templates/base.html
+++ b/core/templates/base.html
@@ -1,25 +1,29 @@
+{% load static %}
- {% block title %}Knowledge Base{% endblock %}
- {% if project_description %}
-
-
-
- {% endif %}
+
+ {% block title %}{{ page_title|default:"Momentum Atlas" }}{% endblock %}
+
+
+
{% if project_image_url %}
{% endif %}
- {% load static %}
+
+
+
+
{% block head %}{% endblock %}
{% block content %}{% endblock %}
+
diff --git a/core/templates/core/entry_detail.html b/core/templates/core/entry_detail.html
new file mode 100644
index 0000000..ce26df7
--- /dev/null
+++ b/core/templates/core/entry_detail.html
@@ -0,0 +1,75 @@
+{% extends "base.html" %}
+
+{% block title %}{{ page_title }}{% endblock %}
+{% block meta_description %}{{ meta_description }}{% endblock %}
+
+{% block content %}
+
+
+ {% if created %}
+
+ Nice—your check-in was saved successfully. This detail page is the confirmation step of the MVP workflow.
+
+ {% endif %}
+
+
+
+
+
+ {{ entry.category.name }}
+ {{ entry.entry_date|date:"F j, Y" }}
+
+ {{ entry.title }}
+ {{ entry.takeaway }}
+
+
+ Focus
+ {{ entry.focus_score }}/10
+
+
+ Energy
+ {{ entry.energy_score }}/10
+
+
+ Deep work
+ {{ entry.deep_work_minutes }}m
+
+
+ Momentum
+ {{ entry.momentum_score }}/10
+
+
+
+ Reflection
+ {{ entry.reflection|default:"No extra reflection was added for this day."|linebreaksbr }}
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/core/templates/core/entry_list.html b/core/templates/core/entry_list.html
new file mode 100644
index 0000000..ae3a103
--- /dev/null
+++ b/core/templates/core/entry_list.html
@@ -0,0 +1,57 @@
+{% extends "base.html" %}
+
+{% block title %}{{ page_title }}{% endblock %}
+{% block meta_description %}{{ meta_description }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ {% if entries %}
+
+ {% for entry in entries %}
+
+
+
+ {{ entry.category.name }}
+ {{ entry.entry_date|date:"M j, Y" }}
+
+
+ {{ entry.takeaway }}
+
+ Focus {{ entry.focus_score }}/10
+ Energy {{ entry.energy_score }}/10
+ {{ entry.deep_work_minutes }} min
+
+
+
+ {% endfor %}
+
+ {% else %}
+
+
No entries for this filter
+
Try a different category or create a new check-in from the dashboard.
+
Create an entry
+
+ {% endif %}
+
+
+{% endblock %}
diff --git a/core/templates/core/index.html b/core/templates/core/index.html
index faec813..22e4b66 100644
--- a/core/templates/core/index.html
+++ b/core/templates/core/index.html
@@ -1,145 +1,227 @@
{% extends "base.html" %}
-{% block title %}{{ project_name }}{% endblock %}
-
-{% block head %}
-
-
-
-
-{% endblock %}
+{% block title %}{{ page_title }}{% endblock %}
+{% block meta_description %}{{ meta_description }}{% endblock %}
{% block content %}
-
-
-
Analyzing your requirements and generating your app…
-
- Loading…
-
-
AppWizzy AI is collecting your requirements and applying the first changes.
-
This page will refresh automatically as the plan is implemented.
-
- Runtime: Django {{ django_version }} · Python {{ python_version }}
- — UTC {{ current_time|date:"Y-m-d H:i:s" }}
-
-
-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
-
-{% endblock %}
\ No newline at end of file
+
+
+
+
+
+
+ {% if messages %}
+
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
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.
+
+
+
+
+
+ What this MVP already does
+
Thin slice, real workflow.
+
+
+
+ Create
+ Submit a structured daily entry with validation, categories, and tangible metrics.
+
+
+ Confirm
+ Each new entry opens its own detail page with a success state and related history.
+
+
+ Review
+ Use weekly bars and the history page to notice patterns instead of collecting dead data.
+
+
+
+
+
+
+
+
+
+
+
+ 7-day insight
+
Focus and energy trend
+
+
Open full history
+
+
+
+ {% for day in weekly_trend %}
+
+
+ {{ day.focus }}/{{ day.energy }}
+ {{ day.label }}
+ {{ day.minutes }}m
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+ Recent check-ins
+
Momentum history preview
+
+
See every entry
+
+ {% if recent_entries %}
+
+ {% for entry in recent_entries %}
+
+
+
+ {{ entry.category.name }}
+ {{ entry.entry_date|date:"M j" }}
+
+
+ {{ entry.takeaway }}
+
+ Focus {{ entry.focus_score }}/10
+ Energy {{ entry.energy_score }}/10
+ {{ entry.deep_work_minutes }} min
+
+
+
+ {% endfor %}
+
+ {% else %}
+
+
No entries yet
+
Start with one check-in above and this dashboard will instantly become your first useful Python product demo.
+
+ {% endif %}
+
+
+
+
+{% endblock %}
diff --git a/core/tests.py b/core/tests.py
index 7ce503c..c54a625 100644
--- a/core/tests.py
+++ b/core/tests.py
@@ -1,3 +1,37 @@
from django.test import TestCase
+from django.urls import reverse
+from django.utils import timezone
-# Create your tests here.
+from .models import Category, MomentumEntry
+
+
+class MomentumViewsTests(TestCase):
+ def setUp(self):
+ self.category = Category.objects.create(
+ name="Learning",
+ slug="learning",
+ description="Tracking study sessions",
+ accent_color="#0F766E",
+ )
+
+ def test_home_page_loads(self):
+ response = self.client.get(reverse("home"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Momentum Atlas")
+
+ def test_post_creates_entry_and_redirects(self):
+ response = self.client.post(
+ reverse("home"),
+ {
+ "title": "Finished a Python kata",
+ "category": self.category.pk,
+ "entry_date": timezone.localdate().isoformat(),
+ "focus_score": 8,
+ "energy_score": 7,
+ "deep_work_minutes": 90,
+ "takeaway": "Made steady progress with functions today.",
+ "reflection": "Felt strong after removing distractions.",
+ },
+ )
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(MomentumEntry.objects.count(), 1)
diff --git a/core/urls.py b/core/urls.py
index 6299e3d..2bbe4bc 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -1,7 +1,9 @@
from django.urls import path
-from .views import home
+from .views import entry_detail, entry_list, home
urlpatterns = [
path("", home, name="home"),
+ path("entries/", entry_list, name="entry_list"),
+ path("entries//", entry_detail, name="entry_detail"),
]
diff --git a/core/views.py b/core/views.py
index c9aed12..cb3cbf6 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1,25 +1,164 @@
import os
import platform
+from datetime import timedelta
from django import get_version as django_version
-from django.shortcuts import render
+from django.contrib import messages
+from django.db.models import Avg, Count, Sum
+from django.db.models.functions import Coalesce
+from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
+from .forms import MomentumEntryForm
+from .models import Category, MomentumEntry
+
+
+APP_NAME = "Momentum Atlas"
+APP_TAGLINE = "A polished personal dashboard for tracking focus, energy, and small wins."
+
+
+def _build_weekly_trend(entries):
+ today = timezone.localdate()
+ start_date = today - timedelta(days=6)
+ trend_source = (
+ entries.filter(entry_date__gte=start_date, entry_date__lte=today)
+ .values("entry_date")
+ .annotate(
+ avg_focus=Coalesce(Avg("focus_score"), 0.0),
+ avg_energy=Coalesce(Avg("energy_score"), 0.0),
+ total_minutes=Coalesce(Sum("deep_work_minutes"), 0),
+ )
+ )
+ by_date = {row["entry_date"]: row for row in trend_source}
+
+ trend = []
+ for offset in range(7):
+ day = start_date + timedelta(days=offset)
+ row = by_date.get(day, {})
+ focus = float(row.get("avg_focus") or 0)
+ energy = float(row.get("avg_energy") or 0)
+ minutes = int(row.get("total_minutes") or 0)
+ focus_level = int(round((focus / 10) * 10) * 10) if focus else 0
+ energy_level = int(round((energy / 10) * 10) * 10) if energy else 0
+ trend.append(
+ {
+ "date": day,
+ "label": day.strftime("%a"),
+ "focus": round(focus, 1),
+ "energy": round(energy, 1),
+ "minutes": minutes,
+ "focus_level": max(0, min(100, focus_level)),
+ "energy_level": max(0, min(100, energy_level)),
+ }
+ )
+ return trend
+
+
+def _dashboard_context():
+ entries = MomentumEntry.objects.select_related("category")
+ recent_entries = entries[:6]
+ last_30_days = timezone.localdate() - timedelta(days=29)
+ stats_window = entries.filter(entry_date__gte=last_30_days)
+ totals = stats_window.aggregate(
+ total_entries=Count("id"),
+ avg_focus=Coalesce(Avg("focus_score"), 0.0),
+ avg_energy=Coalesce(Avg("energy_score"), 0.0),
+ total_minutes=Coalesce(Sum("deep_work_minutes"), 0),
+ )
+ active_days = stats_window.values("entry_date").distinct().count()
+ top_category = (
+ stats_window.values("category__name")
+ .annotate(total=Count("id"))
+ .order_by("-total", "category__name")
+ .first()
+ )
+ weekly_trend = _build_weekly_trend(entries)
+
+ focus_average = round(float(totals["avg_focus"] or 0), 1)
+ energy_average = round(float(totals["avg_energy"] or 0), 1)
+ if focus_average >= 8:
+ 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:
+ spotlight = "A reset week might help—shrink the task list and aim for one clear win each day."
+
+ return {
+ "recent_entries": recent_entries,
+ "categories": Category.objects.all(),
+ "weekly_trend": weekly_trend,
+ "stats": {
+ "total_entries": totals["total_entries"],
+ "avg_focus": focus_average,
+ "avg_energy": energy_average,
+ "total_minutes": totals["total_minutes"],
+ "active_days": active_days,
+ "top_category": top_category["category__name"] if top_category else "No category yet",
+ "spotlight": spotlight,
+ },
+ }
+
def home(request):
- """Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
+ if request.method == "POST":
+ form = MomentumEntryForm(request.POST)
+ if form.is_valid():
+ entry = form.save()
+ messages.success(request, "Momentum captured. Your new 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:
+ form = MomentumEntryForm()
+
context = {
- "project_name": "New Style",
+ "project_name": APP_NAME,
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
- "project_description": os.getenv("PROJECT_DESCRIPTION", ""),
+ "project_description": os.getenv("PROJECT_DESCRIPTION", APP_TAGLINE),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
+ "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(),
}
return render(request, "core/index.html", context)
+
+
+def entry_list(request):
+ selected_slug = request.GET.get("category", "")
+ entries = MomentumEntry.objects.select_related("category")
+ if selected_slug:
+ entries = entries.filter(category__slug=selected_slug)
+
+ context = {
+ "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")),
+ "selected_slug": selected_slug,
+ }
+ 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]
+ )
+ context = {
+ "page_title": f"{entry.title} | {APP_NAME}",
+ "meta_description": entry.takeaway,
+ "entry": entry,
+ "related_entries": related_entries,
+ "created": request.GET.get("created") == "1",
+ }
+ return render(request, "core/entry_detail.html", context)
diff --git a/static/css/custom.css b/static/css/custom.css
index 925f6ed..ecc15bc 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -1,4 +1,540 @@
-/* Custom styles for the application */
-body {
- font-family: system-ui, -apple-system, sans-serif;
+/* Momentum Atlas theme */
+:root {
+ --brand-ink: #0f172a;
+ --brand-muted: #475569;
+ --brand-surface: #fffaf4;
+ --brand-surface-strong: #ffffff;
+ --brand-border: rgba(15, 23, 42, 0.08);
+ --brand-primary: #0f766e;
+ --brand-primary-dark: #115e59;
+ --brand-secondary: #f59e0b;
+ --brand-accent: #f97316;
+ --brand-highlight: #14b8a6;
+ --brand-glow: rgba(249, 115, 22, 0.18);
+ --brand-shadow: 0 28px 80px rgba(15, 23, 42, 0.12);
+ --radius-xl: 28px;
+ --radius-lg: 20px;
+ --radius-md: 16px;
+ --section-gap: clamp(4rem, 8vw, 7rem);
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+ color: var(--brand-ink);
+ background:
+ radial-gradient(circle at top left, rgba(20, 184, 166, 0.14), transparent 28%),
+ radial-gradient(circle at bottom right, rgba(245, 158, 11, 0.14), transparent 30%),
+ linear-gradient(180deg, #fffdf8 0%, #fff7ed 46%, #f8fafc 100%);
+ min-height: 100vh;
+}
+
+h1,
+h2,
+h3,
+h4,
+.brand-mark {
+ font-family: 'Manrope', 'Inter', sans-serif;
+ letter-spacing: -0.03em;
+}
+
+p {
+ color: var(--brand-muted);
+ line-height: 1.7;
+}
+
+.site-shell,
+.subpage-shell {
+ position: relative;
+ overflow: hidden;
+}
+
+.site-header {
+ position: relative;
+}
+
+.main-nav {
+ background: rgba(15, 23, 42, 0.32);
+ backdrop-filter: blur(18px);
+ -webkit-backdrop-filter: blur(18px);
+}
+
+.brand-mark {
+ color: #ffffff;
+ font-size: 1.35rem;
+ font-weight: 800;
+}
+
+.brand-mark:hover,
+.brand-mark:focus,
+.nav-link:hover,
+.nav-link:focus {
+ color: #fff7ed;
+}
+
+.nav-link {
+ color: rgba(255, 255, 255, 0.82);
+ font-weight: 600;
+}
+
+.nav-pill {
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ border-radius: 999px;
+ padding: 0.55rem 1rem !important;
+}
+
+.hero-section {
+ position: relative;
+ padding: 1rem 0 4rem;
+ background: linear-gradient(135deg, #0f172a 0%, #0f766e 48%, #f97316 100%);
+ color: #ffffff;
+}
+
+.hero-orb {
+ position: absolute;
+ border-radius: 50%;
+ filter: blur(18px);
+ opacity: 0.75;
+}
+
+.hero-orb-one {
+ width: 260px;
+ height: 260px;
+ top: 7rem;
+ right: 8%;
+ background: rgba(20, 184, 166, 0.32);
+}
+
+.hero-orb-two {
+ width: 190px;
+ height: 190px;
+ bottom: 3rem;
+ left: 6%;
+ background: rgba(245, 158, 11, 0.28);
+}
+
+.hero-title {
+ font-size: clamp(2.8rem, 5vw, 5rem);
+ line-height: 0.98;
+ margin: 0 0 1.5rem;
+}
+
+.hero-copy {
+ color: rgba(255, 255, 255, 0.86);
+ font-size: 1.1rem;
+ max-width: 40rem;
+}
+
+.eyebrow {
+ display: inline-block;
+ margin-bottom: 1rem;
+ font-size: 0.82rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ color: var(--brand-accent);
+}
+
+.hero-section .eyebrow,
+.hero-section .hero-meta-label {
+ color: rgba(255, 247, 237, 0.78);
+}
+
+.hero-actions,
+.hero-meta {
+ position: relative;
+ z-index: 2;
+}
+
+.hero-meta strong {
+ display: block;
+ font-size: 1rem;
+}
+
+.hero-meta-label,
+.panel-label,
+.form-hint {
+ font-size: 0.82rem;
+ color: #64748b;
+}
+
+.glass-panel {
+ background: rgba(255, 255, 255, 0.78);
+ border: 1px solid rgba(255, 255, 255, 0.42);
+ border-radius: var(--radius-xl);
+ box-shadow: var(--brand-shadow);
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+}
+
+.insight-panel {
+ padding: 2rem;
+}
+
+.metric-card {
+ height: 100%;
+ padding: 1.2rem;
+ border-radius: var(--radius-md);
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 247, 237, 0.76));
+ border: 1px solid var(--brand-border);
+}
+
+.metric-card span {
+ display: block;
+ font-size: 0.88rem;
+ color: var(--brand-muted);
+}
+
+.metric-card strong {
+ font-size: clamp(1.55rem, 3vw, 2rem);
+ color: var(--brand-ink);
+}
+
+.spotlight-note {
+ padding: 1.1rem 1.2rem;
+ border-radius: var(--radius-md);
+ background: rgba(255, 255, 255, 0.66);
+ border: 1px solid rgba(15, 23, 42, 0.06);
+}
+
+.spotlight-note p {
+ margin: 0.45rem 0 0.8rem;
+}
+
+.chip-row,
+.filter-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+}
+
+.trend-chip,
+.filter-chip,
+.category-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 0.55rem 0.9rem;
+ border-radius: 999px;
+ font-weight: 700;
+ font-size: 0.88rem;
+ text-decoration: none;
+}
+
+.trend-chip,
+.category-badge {
+ background: rgba(15, 118, 110, 0.1);
+ color: var(--brand-primary-dark);
+}
+
+.filter-chip {
+ background: rgba(255, 255, 255, 0.78);
+ color: var(--brand-ink);
+ border: 1px solid var(--brand-border);
+}
+
+.filter-chip.active,
+.filter-chip:hover,
+.filter-chip:focus {
+ background: var(--brand-primary);
+ color: #ffffff;
+ border-color: transparent;
+}
+
+.section-block {
+ padding: var(--section-gap) 0;
+}
+
+.section-muted {
+ background: rgba(255, 255, 255, 0.42);
+}
+
+.section-heading h2,
+.subpage-header h1,
+.detail-panel h1 {
+ font-size: clamp(2rem, 3vw, 3rem);
+ margin-bottom: 0.8rem;
+}
+
+.section-heading.compact h2 {
+ font-size: clamp(1.6rem, 2vw, 2.2rem);
+}
+
+.form-panel,
+.chart-panel,
+.detail-panel,
+.sidebar-panel,
+.subpage-header,
+.empty-state {
+ padding: 2rem;
+}
+
+.form-panel .form-control,
+.form-panel .form-select {
+ border-radius: 16px;
+ border: 1px solid rgba(148, 163, 184, 0.35);
+ padding: 0.9rem 1rem;
+ min-height: 3.3rem;
+ background: rgba(255, 255, 255, 0.92);
+}
+
+.form-panel textarea.form-control {
+ min-height: 8.5rem;
+}
+
+.form-control:focus,
+.form-select:focus,
+.btn:focus,
+.btn:active:focus,
+.related-item:focus,
+.entry-card a:focus {
+ box-shadow: 0 0 0 0.28rem rgba(20, 184, 166, 0.2);
+ border-color: rgba(20, 184, 166, 0.6);
+}
+
+.form-label {
+ font-weight: 700;
+ margin-bottom: 0.55rem;
+}
+
+.btn {
+ border-radius: 999px;
+ padding: 0.9rem 1.35rem;
+ font-weight: 700;
+ border: none;
+}
+
+.btn-accent {
+ background: linear-gradient(135deg, var(--brand-secondary), var(--brand-accent));
+ color: #ffffff;
+ box-shadow: 0 18px 40px var(--brand-glow);
+}
+
+.btn-accent:hover,
+.btn-accent:focus {
+ color: #ffffff;
+ transform: translateY(-1px);
+}
+
+.btn-ghost {
+ background: rgba(255, 255, 255, 0.18);
+ color: var(--brand-ink);
+ border: 1px solid rgba(15, 23, 42, 0.12);
+}
+
+.hero-section .btn-ghost {
+ color: #ffffff;
+ border-color: rgba(255, 255, 255, 0.28);
+ background: rgba(255, 255, 255, 0.12);
+}
+
+.stack-grid {
+ display: grid;
+ gap: 1rem;
+}
+
+.feature-card {
+ padding: 1.5rem;
+}
+
+.feature-card h3 {
+ margin-bottom: 0.6rem;
+}
+
+.trend-chart {
+ display: grid;
+ grid-template-columns: repeat(7, minmax(0, 1fr));
+ gap: 1rem;
+ align-items: end;
+}
+
+.trend-day {
+ text-align: center;
+}
+
+.trend-bars {
+ display: flex;
+ align-items: end;
+ justify-content: center;
+ gap: 0.45rem;
+ height: 220px;
+ margin-bottom: 1rem;
+}
+
+.trend-bar {
+ width: 22px;
+ border-radius: 999px 999px 12px 12px;
+ min-height: 14px;
+ box-shadow: inset 0 -8px 18px rgba(255, 255, 255, 0.18);
+}
+
+.trend-bar.focus {
+ background: linear-gradient(180deg, var(--brand-primary), var(--brand-highlight));
+}
+
+.trend-bar.energy {
+ background: linear-gradient(180deg, var(--brand-secondary), var(--brand-accent));
+}
+
+.trend-bar.level-0 { height: 14px; }
+.trend-bar.level-10 { height: 10%; }
+.trend-bar.level-20 { height: 20%; }
+.trend-bar.level-30 { height: 30%; }
+.trend-bar.level-40 { height: 40%; }
+.trend-bar.level-50 { height: 50%; }
+.trend-bar.level-60 { height: 60%; }
+.trend-bar.level-70 { height: 70%; }
+.trend-bar.level-80 { height: 80%; }
+.trend-bar.level-90 { height: 90%; }
+.trend-bar.level-100 { height: 100%; }
+
+.trend-values,
+.entry-date,
+.related-item span {
+ color: var(--brand-muted);
+ font-size: 0.9rem;
+}
+
+.entry-card {
+ padding: 1.5rem;
+ transition: transform 0.25s ease, box-shadow 0.25s ease;
+}
+
+.entry-card:hover,
+.related-item:hover {
+ transform: translateY(-4px);
+}
+
+.entry-card-top {
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ align-items: center;
+}
+
+.entry-card h3,
+.entry-card h2 {
+ margin: 1rem 0 0.75rem;
+ font-size: 1.35rem;
+}
+
+.entry-card a,
+.text-link,
+.related-item {
+ color: var(--brand-ink);
+ text-decoration: none;
+}
+
+.text-link {
+ font-weight: 700;
+}
+
+.entry-stats,
+.detail-metrics {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ margin-top: 1.2rem;
+}
+
+.entry-stats span {
+ padding: 0.45rem 0.7rem;
+ border-radius: 999px;
+ background: rgba(248, 250, 252, 0.95);
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ font-size: 0.88rem;
+}
+
+.detail-panel .detail-lead {
+ font-size: 1.1rem;
+ margin-bottom: 1.5rem;
+}
+
+.detail-metric {
+ min-width: 140px;
+ flex: 1 1 0;
+}
+
+.reflection-block {
+ margin-top: 2rem;
+ padding-top: 1.5rem;
+ border-top: 1px solid rgba(148, 163, 184, 0.2);
+}
+
+.related-list {
+ display: grid;
+ gap: 0.85rem;
+}
+
+.related-item {
+ display: block;
+ padding: 1rem;
+ border-radius: 16px;
+ background: rgba(255, 255, 255, 0.74);
+ border: 1px solid var(--brand-border);
+}
+
+.related-item strong {
+ display: block;
+ margin-bottom: 0.35rem;
+}
+
+.custom-alert {
+ border: none;
+ border-radius: 18px;
+ box-shadow: 0 14px 32px rgba(15, 23, 42, 0.08);
+}
+
+.empty-state {
+ text-align: center;
+}
+
+.subpage-shell {
+ padding: 1.5rem 0 3rem;
+}
+
+.subpage-header {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 1.5rem;
+ align-items: center;
+}
+
+@media (max-width: 991.98px) {
+ .hero-section {
+ padding-bottom: 3rem;
+ }
+
+ .trend-chart {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 767.98px) {
+ .hero-title {
+ font-size: 2.8rem;
+ }
+
+ .trend-chart {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .trend-bars {
+ height: 180px;
+ }
+
+ .form-panel,
+ .chart-panel,
+ .detail-panel,
+ .sidebar-panel,
+ .subpage-header,
+ .empty-state,
+ .insight-panel {
+ padding: 1.4rem;
+ }
}
diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css
index 108056f..ecc15bc 100644
--- a/staticfiles/css/custom.css
+++ b/staticfiles/css/custom.css
@@ -1,21 +1,540 @@
-
+/* Momentum Atlas theme */
:root {
- --bg-color-start: #6a11cb;
- --bg-color-end: #2575fc;
- --text-color: #ffffff;
- --card-bg-color: rgba(255, 255, 255, 0.01);
- --card-border-color: rgba(255, 255, 255, 0.1);
+ --brand-ink: #0f172a;
+ --brand-muted: #475569;
+ --brand-surface: #fffaf4;
+ --brand-surface-strong: #ffffff;
+ --brand-border: rgba(15, 23, 42, 0.08);
+ --brand-primary: #0f766e;
+ --brand-primary-dark: #115e59;
+ --brand-secondary: #f59e0b;
+ --brand-accent: #f97316;
+ --brand-highlight: #14b8a6;
+ --brand-glow: rgba(249, 115, 22, 0.18);
+ --brand-shadow: 0 28px 80px rgba(15, 23, 42, 0.12);
+ --radius-xl: 28px;
+ --radius-lg: 20px;
+ --radius-md: 16px;
+ --section-gap: clamp(4rem, 8vw, 7rem);
}
+
+html {
+ scroll-behavior: smooth;
+}
+
body {
margin: 0;
- font-family: 'Inter', sans-serif;
- background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
- color: var(--text-color);
- display: flex;
- justify-content: center;
- align-items: center;
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+ color: var(--brand-ink);
+ background:
+ radial-gradient(circle at top left, rgba(20, 184, 166, 0.14), transparent 28%),
+ radial-gradient(circle at bottom right, rgba(245, 158, 11, 0.14), transparent 30%),
+ linear-gradient(180deg, #fffdf8 0%, #fff7ed 46%, #f8fafc 100%);
min-height: 100vh;
- text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+.brand-mark {
+ font-family: 'Manrope', 'Inter', sans-serif;
+ letter-spacing: -0.03em;
+}
+
+p {
+ color: var(--brand-muted);
+ line-height: 1.7;
+}
+
+.site-shell,
+.subpage-shell {
+ position: relative;
overflow: hidden;
+}
+
+.site-header {
position: relative;
}
+
+.main-nav {
+ background: rgba(15, 23, 42, 0.32);
+ backdrop-filter: blur(18px);
+ -webkit-backdrop-filter: blur(18px);
+}
+
+.brand-mark {
+ color: #ffffff;
+ font-size: 1.35rem;
+ font-weight: 800;
+}
+
+.brand-mark:hover,
+.brand-mark:focus,
+.nav-link:hover,
+.nav-link:focus {
+ color: #fff7ed;
+}
+
+.nav-link {
+ color: rgba(255, 255, 255, 0.82);
+ font-weight: 600;
+}
+
+.nav-pill {
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ border-radius: 999px;
+ padding: 0.55rem 1rem !important;
+}
+
+.hero-section {
+ position: relative;
+ padding: 1rem 0 4rem;
+ background: linear-gradient(135deg, #0f172a 0%, #0f766e 48%, #f97316 100%);
+ color: #ffffff;
+}
+
+.hero-orb {
+ position: absolute;
+ border-radius: 50%;
+ filter: blur(18px);
+ opacity: 0.75;
+}
+
+.hero-orb-one {
+ width: 260px;
+ height: 260px;
+ top: 7rem;
+ right: 8%;
+ background: rgba(20, 184, 166, 0.32);
+}
+
+.hero-orb-two {
+ width: 190px;
+ height: 190px;
+ bottom: 3rem;
+ left: 6%;
+ background: rgba(245, 158, 11, 0.28);
+}
+
+.hero-title {
+ font-size: clamp(2.8rem, 5vw, 5rem);
+ line-height: 0.98;
+ margin: 0 0 1.5rem;
+}
+
+.hero-copy {
+ color: rgba(255, 255, 255, 0.86);
+ font-size: 1.1rem;
+ max-width: 40rem;
+}
+
+.eyebrow {
+ display: inline-block;
+ margin-bottom: 1rem;
+ font-size: 0.82rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ color: var(--brand-accent);
+}
+
+.hero-section .eyebrow,
+.hero-section .hero-meta-label {
+ color: rgba(255, 247, 237, 0.78);
+}
+
+.hero-actions,
+.hero-meta {
+ position: relative;
+ z-index: 2;
+}
+
+.hero-meta strong {
+ display: block;
+ font-size: 1rem;
+}
+
+.hero-meta-label,
+.panel-label,
+.form-hint {
+ font-size: 0.82rem;
+ color: #64748b;
+}
+
+.glass-panel {
+ background: rgba(255, 255, 255, 0.78);
+ border: 1px solid rgba(255, 255, 255, 0.42);
+ border-radius: var(--radius-xl);
+ box-shadow: var(--brand-shadow);
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+}
+
+.insight-panel {
+ padding: 2rem;
+}
+
+.metric-card {
+ height: 100%;
+ padding: 1.2rem;
+ border-radius: var(--radius-md);
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 247, 237, 0.76));
+ border: 1px solid var(--brand-border);
+}
+
+.metric-card span {
+ display: block;
+ font-size: 0.88rem;
+ color: var(--brand-muted);
+}
+
+.metric-card strong {
+ font-size: clamp(1.55rem, 3vw, 2rem);
+ color: var(--brand-ink);
+}
+
+.spotlight-note {
+ padding: 1.1rem 1.2rem;
+ border-radius: var(--radius-md);
+ background: rgba(255, 255, 255, 0.66);
+ border: 1px solid rgba(15, 23, 42, 0.06);
+}
+
+.spotlight-note p {
+ margin: 0.45rem 0 0.8rem;
+}
+
+.chip-row,
+.filter-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+}
+
+.trend-chip,
+.filter-chip,
+.category-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 0.55rem 0.9rem;
+ border-radius: 999px;
+ font-weight: 700;
+ font-size: 0.88rem;
+ text-decoration: none;
+}
+
+.trend-chip,
+.category-badge {
+ background: rgba(15, 118, 110, 0.1);
+ color: var(--brand-primary-dark);
+}
+
+.filter-chip {
+ background: rgba(255, 255, 255, 0.78);
+ color: var(--brand-ink);
+ border: 1px solid var(--brand-border);
+}
+
+.filter-chip.active,
+.filter-chip:hover,
+.filter-chip:focus {
+ background: var(--brand-primary);
+ color: #ffffff;
+ border-color: transparent;
+}
+
+.section-block {
+ padding: var(--section-gap) 0;
+}
+
+.section-muted {
+ background: rgba(255, 255, 255, 0.42);
+}
+
+.section-heading h2,
+.subpage-header h1,
+.detail-panel h1 {
+ font-size: clamp(2rem, 3vw, 3rem);
+ margin-bottom: 0.8rem;
+}
+
+.section-heading.compact h2 {
+ font-size: clamp(1.6rem, 2vw, 2.2rem);
+}
+
+.form-panel,
+.chart-panel,
+.detail-panel,
+.sidebar-panel,
+.subpage-header,
+.empty-state {
+ padding: 2rem;
+}
+
+.form-panel .form-control,
+.form-panel .form-select {
+ border-radius: 16px;
+ border: 1px solid rgba(148, 163, 184, 0.35);
+ padding: 0.9rem 1rem;
+ min-height: 3.3rem;
+ background: rgba(255, 255, 255, 0.92);
+}
+
+.form-panel textarea.form-control {
+ min-height: 8.5rem;
+}
+
+.form-control:focus,
+.form-select:focus,
+.btn:focus,
+.btn:active:focus,
+.related-item:focus,
+.entry-card a:focus {
+ box-shadow: 0 0 0 0.28rem rgba(20, 184, 166, 0.2);
+ border-color: rgba(20, 184, 166, 0.6);
+}
+
+.form-label {
+ font-weight: 700;
+ margin-bottom: 0.55rem;
+}
+
+.btn {
+ border-radius: 999px;
+ padding: 0.9rem 1.35rem;
+ font-weight: 700;
+ border: none;
+}
+
+.btn-accent {
+ background: linear-gradient(135deg, var(--brand-secondary), var(--brand-accent));
+ color: #ffffff;
+ box-shadow: 0 18px 40px var(--brand-glow);
+}
+
+.btn-accent:hover,
+.btn-accent:focus {
+ color: #ffffff;
+ transform: translateY(-1px);
+}
+
+.btn-ghost {
+ background: rgba(255, 255, 255, 0.18);
+ color: var(--brand-ink);
+ border: 1px solid rgba(15, 23, 42, 0.12);
+}
+
+.hero-section .btn-ghost {
+ color: #ffffff;
+ border-color: rgba(255, 255, 255, 0.28);
+ background: rgba(255, 255, 255, 0.12);
+}
+
+.stack-grid {
+ display: grid;
+ gap: 1rem;
+}
+
+.feature-card {
+ padding: 1.5rem;
+}
+
+.feature-card h3 {
+ margin-bottom: 0.6rem;
+}
+
+.trend-chart {
+ display: grid;
+ grid-template-columns: repeat(7, minmax(0, 1fr));
+ gap: 1rem;
+ align-items: end;
+}
+
+.trend-day {
+ text-align: center;
+}
+
+.trend-bars {
+ display: flex;
+ align-items: end;
+ justify-content: center;
+ gap: 0.45rem;
+ height: 220px;
+ margin-bottom: 1rem;
+}
+
+.trend-bar {
+ width: 22px;
+ border-radius: 999px 999px 12px 12px;
+ min-height: 14px;
+ box-shadow: inset 0 -8px 18px rgba(255, 255, 255, 0.18);
+}
+
+.trend-bar.focus {
+ background: linear-gradient(180deg, var(--brand-primary), var(--brand-highlight));
+}
+
+.trend-bar.energy {
+ background: linear-gradient(180deg, var(--brand-secondary), var(--brand-accent));
+}
+
+.trend-bar.level-0 { height: 14px; }
+.trend-bar.level-10 { height: 10%; }
+.trend-bar.level-20 { height: 20%; }
+.trend-bar.level-30 { height: 30%; }
+.trend-bar.level-40 { height: 40%; }
+.trend-bar.level-50 { height: 50%; }
+.trend-bar.level-60 { height: 60%; }
+.trend-bar.level-70 { height: 70%; }
+.trend-bar.level-80 { height: 80%; }
+.trend-bar.level-90 { height: 90%; }
+.trend-bar.level-100 { height: 100%; }
+
+.trend-values,
+.entry-date,
+.related-item span {
+ color: var(--brand-muted);
+ font-size: 0.9rem;
+}
+
+.entry-card {
+ padding: 1.5rem;
+ transition: transform 0.25s ease, box-shadow 0.25s ease;
+}
+
+.entry-card:hover,
+.related-item:hover {
+ transform: translateY(-4px);
+}
+
+.entry-card-top {
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ align-items: center;
+}
+
+.entry-card h3,
+.entry-card h2 {
+ margin: 1rem 0 0.75rem;
+ font-size: 1.35rem;
+}
+
+.entry-card a,
+.text-link,
+.related-item {
+ color: var(--brand-ink);
+ text-decoration: none;
+}
+
+.text-link {
+ font-weight: 700;
+}
+
+.entry-stats,
+.detail-metrics {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ margin-top: 1.2rem;
+}
+
+.entry-stats span {
+ padding: 0.45rem 0.7rem;
+ border-radius: 999px;
+ background: rgba(248, 250, 252, 0.95);
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ font-size: 0.88rem;
+}
+
+.detail-panel .detail-lead {
+ font-size: 1.1rem;
+ margin-bottom: 1.5rem;
+}
+
+.detail-metric {
+ min-width: 140px;
+ flex: 1 1 0;
+}
+
+.reflection-block {
+ margin-top: 2rem;
+ padding-top: 1.5rem;
+ border-top: 1px solid rgba(148, 163, 184, 0.2);
+}
+
+.related-list {
+ display: grid;
+ gap: 0.85rem;
+}
+
+.related-item {
+ display: block;
+ padding: 1rem;
+ border-radius: 16px;
+ background: rgba(255, 255, 255, 0.74);
+ border: 1px solid var(--brand-border);
+}
+
+.related-item strong {
+ display: block;
+ margin-bottom: 0.35rem;
+}
+
+.custom-alert {
+ border: none;
+ border-radius: 18px;
+ box-shadow: 0 14px 32px rgba(15, 23, 42, 0.08);
+}
+
+.empty-state {
+ text-align: center;
+}
+
+.subpage-shell {
+ padding: 1.5rem 0 3rem;
+}
+
+.subpage-header {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 1.5rem;
+ align-items: center;
+}
+
+@media (max-width: 991.98px) {
+ .hero-section {
+ padding-bottom: 3rem;
+ }
+
+ .trend-chart {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 767.98px) {
+ .hero-title {
+ font-size: 2.8rem;
+ }
+
+ .trend-chart {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .trend-bars {
+ height: 180px;
+ }
+
+ .form-panel,
+ .chart-panel,
+ .detail-panel,
+ .sidebar-panel,
+ .subpage-header,
+ .empty-state,
+ .insight-panel {
+ padding: 1.4rem;
+ }
+}