From 28d370c2246fb57585277d6a13f1ff3d43570b2d Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 16 Apr 2026 11:11:36 +0000 Subject: [PATCH] Auto commit: 2026-04-16T11:11:36.691Z --- core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 1283 bytes core/__pycache__/forms.cpython-311.pyc | Bin 0 -> 3904 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 3698 bytes core/__pycache__/tests.cpython-311.pyc | Bin 0 -> 2574 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 535 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 9694 bytes core/admin.py | 24 +- core/forms.py | 66 +++ core/migrations/0001_initial.py | 47 ++ core/migrations/0002_seed_demo_data.py | 115 ++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 3233 bytes .../0002_seed_demo_data.cpython-311.pyc | Bin 0 -> 4212 bytes core/models.py | 46 +- core/templates/base.html | 18 +- core/templates/core/entry_detail.html | 75 +++ core/templates/core/entry_list.html | 57 ++ core/templates/core/index.html | 364 +++++++----- core/tests.py | 36 +- core/urls.py | 4 +- core/views.py | 147 ++++- static/css/custom.css | 542 ++++++++++++++++- staticfiles/css/custom.css | 545 +++++++++++++++++- 22 files changed, 1914 insertions(+), 172 deletions(-) create mode 100644 core/__pycache__/forms.cpython-311.pyc create mode 100644 core/__pycache__/tests.cpython-311.pyc create mode 100644 core/forms.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_seed_demo_data.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_seed_demo_data.cpython-311.pyc create mode 100644 core/templates/core/entry_detail.html create mode 100644 core/templates/core/entry_list.html diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 5e8987a0cd478c226e501b5189550e758662c560..99f725f5c15e2fade2026e51d5a25ffe9198402c 100644 GIT binary patch literal 1283 zcma)5F;Ck-6uz?^$ApAZ5=5aAEp1iFqL86ORjH~}Rj5M=OE)i;@g2cn$L`q{&E|n0 zz*5n%6II2}sM^dBeQXN8d8 zxG0)FnwDKlgg6A0 zWU5l@I02Vh?5Ay6bzB!tvE9%Qg}@S+RE*)EZ(t=+}Qy|D}rzM z+{KdQ7Hei%)X;na;SRzidW`3F*zo*>3o(UXCNL;_)8hiw?}|Fw=7LP)Y^DHYi=P7g z6?CAL8+x`nm?ZV3?0vuSB5U=h7qT_%Lkjd>GsobNEvM5Cm)#I_@YV(XT2hC*!u?p_ zzbUX@(h$52_k;^sY2jY3;;^Q99M@yJG+qsF+iQVwVY%X;00Wg$+9wZt*Pj!z*t_i+ z=tP;!rg!J}j3YXGOlJp*N}mnLP1svGZag`|jSGY`l_+yp_SJ%=c0}io>HI($r;i5Y WCVZJbo;A;K;{pL$9^ACg_4@9oR^o20SM}RR%F@(>Bo~58Aa`cfUM~ZDGVu$ISjdsQH+cXDNMl(n#?aj zN;Mg8u_mVEX6E^6GTq`!$x6&i&(}-N&nqd)Oe$gqDwzC_(ah=>hfQvNN@-52T@eRR aj1h>7Wr4&8W=2NF8w?5;u%RM0pb7xjBqccj diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c9b14adbf347d8d5531350a61f703af48589919 GIT binary patch literal 3904 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 VK;i>4BO~Jt2AK=kP!StY1pq{|BY6M- diff --git a/core/__pycache__/tests.cpython-311.pyc b/core/__pycache__/tests.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..325de175896a37556a952755b950f45f2c26aa57 GIT binary patch literal 2574 zcmbsrOKcNIbk_FTYsaAtpDXP@LD01P{kodC9fz*hn!F`oDTO(RXvw&DJU26wacbt1&>$!!E0WTxw2fm;!0kz+G^VvWf7k1fK~`2d4-aEh$?qU zK1@S^BQy+Hr4g_}^>=qw-L%=FKud(x)q#4V@Sx=#Pe|l-V7@1}8tU?1e^Z5qXqZNR zRo5bLe|XxkM2wyImfBI{}%aMD7 z%kX;+n@C?kFg1gK{077dSt9c&62NLNAYBf=WjC3Drpg+~toP*lXJ0EzWVIKX*L5r@ z^S$uV&>Dn$y%%x?Y*>Bmj92@X6dHb0HVAlR#*d(bxBdj->aMQa^h?q)MK%iIacl^l zA1*M-@nCGbVg3Sw^?$=mLy#uDz`hEl9Dq|gFprfUz#8E74o%1^R?ar)m%%EhDvi<@)gEa5 zArF#D+ok{*kW^S00nm8=t{|y)+$HyP_YgAQvws!+wGG~|GV$gaK{5`^@cpo}x_8=k zZ868FZt9;@%J}^{T;8?{a2@a0i!khJY(Ud%cf`<^HGx%puUx^(F zDhKr&?ItUX_H9g;8k|v^V+XFD{imAyubo)O;vt|;5pE8TZVZn;Ne+KE{q6O8*Po_O zHAnOZ*BT=qJjonwWKPyIryH5mo0$t6nG5yI#YX00^N`-u4>yk-YmQuPX0En|;)A=ZptnLDE0gOeSZvb2E}q)Qa8 z!eD~s(aw#)(z7_lmKIq5QoGQ;@kRb2@E{k7_y$lbtSCy8=(YaQB!_DKqe%|e`o~l9 Zer>1GiXK$pw%gJD)HCG1*kLI!^lycSaG(GH literal 0 HcmV?d00001 diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index f705988a223abc7b5af333902925bc196186bbf5..d2978f62d9d20ee38c228870d9a84ef84fa27923 100644 GIT binary patch literal 535 zcmZ`!J5K^Z5Z>kHj>mzo(h#Xh3>QOZ456^L(drs=vKu(LSN87Y(MU;YC#99fXyJeG z4;%>vxr)S!j)V>ycMmBvI{TXM`(|cmo(hE=;-LOCE?; zhno0X3$H>a2sPTJjWBST)^SdS;781sI zii*+CJwr#)|9hbzIXgHV?hP+SN(7}blqOJ`LWXnuclv`BL3Iq(2~-m<$`(h~+d%~N TG1MneU!-9>HBiN3TfB!au_}%^ delta 232 zcmbQva+|4sIWI340}#~ttjNp)(vLwL7+{4mKHC5p(-~42QW$d>av7r-85vTTQkZj? za+#x;85x)uQW;ZNQkhd&*RU;PW?)zi#1N3q7{!vp9?YQ0@e(AU$#{#UAh9IlB_ouR zA)23?dW)e5WD!U*FEKaOPm^`BAfw!5OGaNVKA<>AZL!zn35+sk;ujcD&<8dKR?Y^O g4xx^a8Eh9=k diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2f0989c7137f7930a63ea76fcd63516c4974e834..af92eb02403ae342dcb4fb60d1f1579f01414ce6 100644 GIT binary patch literal 9694 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 P3F*>}?-Q>QWpV!pH5sl< delta 804 zcmZuvO-vI(6rQ%b(`^e|5iKQ9ScK3tG1U_oLIABuP%NZi3|wl{9olVncXW23*r0Im z=0!3GJ#f&AV!W7m@#xK)DM?QoHD0|nAzr-b+fokxyv+OFd*8hG^4?^=-e`o?SDl?< z1oCjH$g5as2}sa$IpjhBgL7Hr+HOPW(;gxFfu;APFE1z1&s8;#hT2BWq`Y_92q7Yx%$i^uWd z@meTLs@gKYZxXkADYtliHnWnyI=h_7%`dIYFJ@ijcAe1kH!si4=5OT|Qi^!jyS-D3 z7AYea40zD3beUUbD(ouWbC;>ZUD>2n!k7#jx&fP3nd*7UU9-T~DYKZjvg@agi=hPO zV-gT4*$pysZ-Y3TbwSKJJ&1X!!6LOe+2W4ZQ3Darf){&SsZ1@x62OH(ofiPR=$k0@ zO~_4*dPl^i{?XMB{=>mcV=(h2F}S~4PfonQa+sKIB&M5Pp(t)5fZe$tXHduKC&_x> z`GeA7WULVxt4m`?&<>@-EDnbC0JvDM6zmdZ-X~$I_%$4x@&7(NLT3doQBt3gl6o=*@S!RJ1eAQJBh`M?qDmh%#g)-}~q8k%X?G*~wg*6nH+ z%bWAv&dYH>^8s`F6$yzIpDI-Rl7IszwvdE;APKEPB<_Pmc>O4_AKZiakKl8knuJ0e z8Sx}2jKuxOo&+fPj!{vL;yGfnx1eIDbjA1k_C(MGpZkEl{!^HDP+~uM+MXIf=TJx$ zHF5uZZ{}e3>yZxp50MU@vOIK#cf%YL^ZeZd=8@MC!21h)Jno5b-)g_dufF4Py9tkb zy+x_sT)bs&^7T0$kJ7z!RcZfXuLOL1_jl}DhWmETi{~w|pPjdn-bmjv_w2l7d*_~+ zw;cEFCzSLq#ere3tjq zxYBHxa@E2xhG=Zv(&~z}C9mNv8nLmZDZ2Im)A$CqmQ5SWhEm7W&*;%zaFEw8r6udw zP#Y^W*fg|nn*gx&rb=T7+f_?jXSMBUm!UqGiXVUU(cBTs z0w@e2TvM8Q!^MV83xOMkn)g}kz|mk$!#c8Q$mEJ`+E+5`psiV-Ga3+EprJK%eAHst zD99lg1hLUr&8#+U*{+%vK4N9*h}E>bNTVANBS1{crg3awOWkr&G>I@?mp4soO|EN3 zvw>}kRYvUzRC5|`C~H{RRJLf;!ZjUNS(u+{NO<6&RhO6E} zgqVXGwjcB{x5hnr-bW**A;VpO!SlF*xe%ie=^?y{eOi!PhGYuccF1K+DFy1rn*ro-I{LA0tEs!9DUTM{de?_&5e#Um@dfN-2`$qw&n2u74c%S* zYH4nEiAK$J+`V{z5MF{S!Y_X+tiWC>G?$yOp9;8zs}QwSU^~EGSE{(c+%BA4*oBFS ziAlMe%klLsi(UGhu>03ufP6kYexLWBdpO_mivz)jUv|PmB>T6h6COECUVNS$B*SCh zZ8*bY2mYs5NJS=wMTYJ6u(hek+l zVmIjICJtsvZn~YDc5>4sF>{#AlF`ZCA!l^*AW!aoO-2{nql?byB1zoq#DW9;orI9Q zxO0E!PRAz=_IJ+5BheQEkXMX!(kHIxeuL{5NP2ANs*@gjTqWtLc6!Q5Pm#pU!(@hJ z3p*Q5w$RQ_I@w8*_~0-J9#1omr=Q;c>CaB7`mEIaxzr>Zo9$A|DYZI+@E37Ldc~Te z+k7qkhxMw`zug8PNUBLMSWk5!sszH4xWbxIjST?>9Y8^nT$Ol2W>`XIp4NU^bIRyh zdE@8u2H9-2%Ue!)3t-#gZ8m3HoOhc=W_Mde%r0vHf+dwH-v&z@$qkl8ZspP{8v+U= zK$+b&uzQ7Eo!PB9SN~7DE3RE0)wXM}EOIN?*4PkGunUyg)xqvXlDWQf-^pA*I8QRS z+L>EU<`zkO`r4vAMkIT)oxSN~Z<53(eBeX2S|2_cAlYI&TXeETl9=bJ#+j<|gP|W& z&W#1<#-ekhLNp6(wIEy2jND;P00k$2GA9)cc1)?5{t&E^Grx$0v$nCn$&`M98?mLm@}J-=Xk6IqTO6UK4{I;Z5>g Q)Ja@hLjT6RioXtCUdXtiN`bU zof(3oWR+Gtq}57AtCiq^hp4LPR`6f+A4nprj7C~1QlH!>BakZPsppIx69>|*D)riP z=6;@gKJNM5GkP_eV~ z2zd@qz05{g?+i&DtYR}2@Fb?#9NzJwmJ^DtnTF-bQ(O^-X5>6sZ`2mg&e#qsQ;S;` z&KaTnL5T~k$PF7BiovK=RCJxr39c0EyfB?y-byba|sE8%`#LQz#bjhssv%qlEmV>q-(R=-%enDkhC6r2$?R{%?_}h7roD9*cG07X>dEZi(B72+z##G zb{P2evm-3I54V9`+>XA(?SnnsjsZ8y2HEj_xb4<-=pAk+_HgS2Zo}*(`*1I}ob)UJ zn!{5s2sr=-Kb!U>)fC*5uK^h3Oi}jQZ#542gkg)aKwAk!6cIy$=#~oY?FjnQJel#V z;^?*|A~0?H=nc)#tUPB_p|{I+9%6Sv(RUz1`zLU%;?bxg(<*LM#f_>NAd4U0b%fzB zwH@0Onxa$9uuW>1Hn->`RTYCOs_g*sl&Rp3#iW~q#GbwC9=SN7lOlu#8%ps<$JU~3=y98G6+1gs{|R_K)Kw`j22SEvP2>zp#pvIW=%7K~;E z2OSVGsS5avT#MjuS`ubX0HPy|f+48|8-Vp-IO_nGGo_qfoJJR*A|N6c2Tu%ltZv4ESQSgLlQ zE_iaPh#`wNulUEsXP|4jJWQsxVimw2;9C%0bwFHX1k&#I-;S>}8zoOKel|(1yy<}9 z@RT$G9*z_E4~&FIXap6crU8d(Awf z-z_B*R7F839$7r2RI^Mt<{&hvHN){vYxs4Z_}5tQ>RXy&RCqY4C6%;15-(t z%3eonm-rYq8Gy%f0S{M*+u8HSZ~pMjlkD33M(22?bA07fxA*Yl>F=i3&i!zCqxWK^ z_u|T}t)7D`Q$KbcsgYPX?UPl>?fan8ck*T5na#d4Zuh}Q+3yFo4i7x}{At2HHneuD za_qEAPpo}ip=WAQsjs6(kc7#hv#&_>Q2Y&&-?y#Gj|OX92As=6t zk8eeLE75_M(V@-g&_;B)5*@CQK)BPDJ60oGQg=n_e<=-bO2b=;{?%J<|M1#;rGMP* zJNsku*qXGa{BU3+IZ{cExC2MsqaV1(Pq_UvZc@~`W9=QU2#_~OYQ3cWNPT&s(tq0R zyS&fxVYk2FCJR5ce0-nfu=|{w%Y)TS6*fEG?7)c5Ud^*JZdoYvH49E!(x&1552YMKlG2!V4Kk|su2=qrQ)aUR$72@W~A#rMRvxD10|!DD>~oX!($q_7W>cEkknUBad+R%YaRJ0q;b|6B3(ISSpvQ!h*#IN6K^DjbW3rTJ#o z%OgD6nsdV(nFS$h3wg0yJKt=npzka%0oXSVj({{r(Wg@}PtIiUvn!MF;u*wsN5^_1 zllcwgOpTEQ<0ZI>xS?v?5-6M(yW<%3WX*?3-^5+BnU*NCK61h!do`ngO z4Ih~Ped5KKf5WEPoG+mm#ZA$EzHD_FCb5au_T}1^FVz+PL`*>+#*4K9q!tPU0xs!a zZ~bo(y59N+dApNt*Ae&7w0n5477HV~An73818!H+J#>4Y{(G(c$)1kjO1w7mNdOo$ h$<<(6;PXI@?4koQ&UlwRidVW$yuyJu?-}(~{x=zHMEC#z literal 0 HcmV?d00001 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 %} + +{% 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 %} +
+
+
+
+ History +

All momentum entries

+

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

+
+ +
+ +
+ All categories + {% for category in categories %} + {{ category.name }} · {{ category.entry_total }} + {% endfor %} +
+ + {% if entries %} +
+ {% for entry in entries %} +
+
+
+ {{ entry.category.name }} + +
+

{{ entry.title }}

+

{{ 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 %} + + {% 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.

+
+
+
+ {% 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 %} +
+
+ + Default categories are ready, and you can manage them later from Django Admin. +
+
+
+
+
+
+ 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.title }}

+

{{ 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; + } +}