···11+# Clean Craft UI Overhaul Implementation Plan
22+33+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
44+55+**Goal:** Overhaul Arabica's visual design from gradient-heavy brown-on-brown to clean white cards on warm cream, with CSS custom properties for future dark mode support.
66+77+**Architecture:** The overhaul is split into two layers: (1) CSS-only changes that redefine existing component classes — this covers ~80% of the visual change with zero template edits, and (2) targeted template edits for structural changes like removing nested content boxes and adding type indicators. CSS custom properties are introduced from the start so dark mode is a color swap later, not a rewrite.
88+99+**Tech Stack:** Tailwind CSS (config + `@apply` in app.css), Templ templates, no new dependencies.
1010+1111+**Key files:**
1212+- `static/css/app.css` — all component class definitions
1313+- `tailwind.config.js` — color palette and theme
1414+- `internal/web/components/layout.templ` — body background, CSS version
1515+- `internal/web/components/header.templ` — nav bar
1616+- `internal/web/components/footer.templ` — footer
1717+- `internal/web/components/shared.templ` — shared components (WelcomeCard, PageHeader, etc.)
1818+- `internal/web/pages/feed.templ` — feed card structure
1919+- `internal/web/components/record_*.templ` — feed content boxes (brew, bean, roaster, grinder, brewer)
2020+- `internal/web/components/entity_tables.templ` — entity list cards
2121+- `internal/web/components/action_bar.templ` — feed card wrappers
2222+- `internal/web/components/profile_brew_card.templ` — profile brew cards
2323+2424+---
2525+2626+## Task 1: CSS Custom Properties Foundation
2727+2828+Introduce CSS custom properties for all semantic colors so that component classes reference tokens instead of hardcoded Tailwind values. This is the dark-mode foundation — changing these variables later switches the entire theme.
2929+3030+**Files:**
3131+- Modify: `static/css/app.css` (add `:root` block at top, before `@tailwind` directives)
3232+- Modify: `tailwind.config.js` (add `cream` color)
3333+3434+**Step 1: Add CSS custom properties to app.css**
3535+3636+Add this block at the very top of `static/css/app.css`, before the `@tailwind` directives but after the `@font-face` declarations:
3737+3838+```css
3939+/* ========================================
4040+ Design Tokens (CSS Custom Properties)
4141+ Light theme (default)
4242+ ======================================== */
4343+:root {
4444+ /* Page */
4545+ --page-bg: #FAF7F5;
4646+ --page-text: #3d2319;
4747+4848+ /* Cards */
4949+ --card-bg: #FFFFFF;
5050+ --card-border: #eaddd7;
5151+ --card-shadow: rgba(61, 35, 25, 0.06);
5252+ --card-shadow-hover: rgba(61, 35, 25, 0.10);
5353+5454+ /* Surfaces (inset areas inside cards) */
5555+ --surface-bg: rgba(250, 247, 245, 0.5);
5656+ --surface-border: #f2e8e5;
5757+5858+ /* Header */
5959+ --header-bg-from: #4a2c2a;
6060+ --header-bg-to: #3d2319;
6161+ --header-border: #7f5539;
6262+ --header-text: #FAF7F5;
6363+6464+ /* Text hierarchy */
6565+ --text-primary: #3d2319;
6666+ --text-secondary: #4a2c2a;
6767+ --text-muted: #7f5539;
6868+ --text-faint: #bfa094;
6969+ --text-placeholder: #d2bab0;
7070+7171+ /* Interactive */
7272+ --btn-primary-bg: #4a2c2a;
7373+ --btn-primary-bg-hover: #3d2319;
7474+ --btn-primary-text: #FAF7F5;
7575+ --btn-secondary-bg: #FFFFFF;
7676+ --btn-secondary-border: #e0cec7;
7777+ --btn-secondary-text: #6b4423;
7878+ --btn-secondary-bg-hover: #FAF7F5;
7979+8080+ /* Forms */
8181+ --input-bg: #FFFFFF;
8282+ --input-border: #e0cec7;
8383+ --input-border-focus: #7f5539;
8484+ --input-ring-focus: rgba(127, 85, 57, 0.15);
8585+ --input-bg-focus: rgba(250, 247, 245, 0.3);
8686+8787+ /* Tables */
8888+ --table-bg: #FFFFFF;
8989+ --table-header-bg: #FAF7F5;
9090+ --table-border: #eaddd7;
9191+ --table-row-hover: #FAF7F5;
9292+ --table-divider: #f2e8e5;
9393+9494+ /* Modals */
9595+ --modal-bg: #FFFFFF;
9696+ --modal-border: #eaddd7;
9797+ --modal-backdrop: rgba(0, 0, 0, 0.4);
9898+9999+ /* Feed type indicators (left border) */
100100+ --type-brew: #6b4423;
101101+ --type-bean: #d97706;
102102+ --type-recipe: #bfa094;
103103+ --type-roaster: #d2bab0;
104104+ --type-grinder: #d2bab0;
105105+ --type-brewer: #d2bab0;
106106+107107+ /* Shadows */
108108+ --shadow-sm: 0 1px 3px var(--card-shadow);
109109+ --shadow-md: 0 4px 12px var(--card-shadow-hover);
110110+ --shadow-lg: 0 10px 25px var(--card-shadow-hover);
111111+112112+ /* Footer */
113113+ --footer-bg: #FAF7F5;
114114+ --footer-border: #eaddd7;
115115+}
116116+```
117117+118118+**Step 2: Add `cream` color to tailwind.config.js**
119119+120120+Add a `cream` color to the colors object in `tailwind.config.js`:
121121+122122+```js
123123+cream: {
124124+ 50: "#FAF7F5",
125125+},
126126+```
127127+128128+This lets templates use `bg-cream-50` for the page background if needed.
129129+130130+**Step 3: Verify build**
131131+132132+Run: `just style && go vet ./...`
133133+Expected: Clean build, no errors. No visual changes yet (properties defined but not consumed).
134134+135135+**Step 4: Commit**
136136+137137+```bash
138138+git add static/css/app.css tailwind.config.js
139139+git commit -m "feat: add CSS custom properties foundation for theme support"
140140+```
141141+142142+---
143143+144144+## Task 2: Redefine Core Component Classes
145145+146146+Rewrite the component class definitions in `app.css` to use the CSS custom properties and implement the Clean Craft visual style. This single file change transforms the entire app's appearance.
147147+148148+**Files:**
149149+- Modify: `static/css/app.css` (rewrite `@layer components` block)
150150+151151+**Step 1: Rewrite card classes**
152152+153153+Replace the existing card definitions:
154154+155155+```css
156156+/* Cards and Containers */
157157+.card {
158158+ background: var(--card-bg);
159159+ border: 1px solid var(--card-border);
160160+ @apply rounded-xl;
161161+ box-shadow: var(--shadow-sm);
162162+ transition: box-shadow 200ms ease;
163163+}
164164+165165+.card:hover {
166166+ box-shadow: var(--shadow-md);
167167+}
168168+169169+.card-inner {
170170+ @apply p-6;
171171+}
172172+173173+.card-sm {
174174+ background: var(--card-bg);
175175+ border: 1px solid var(--card-border);
176176+ @apply rounded-lg;
177177+ box-shadow: var(--shadow-sm);
178178+}
179179+180180+/* Section box for lighter content areas */
181181+.section-box {
182182+ background: var(--surface-bg);
183183+ @apply rounded-lg p-4;
184184+}
185185+```
186186+187187+**Step 2: Rewrite button classes**
188188+189189+```css
190190+/* Buttons */
191191+.btn {
192192+ @apply inline-flex items-center justify-center px-4 py-2 rounded-lg font-medium transition-colors cursor-pointer;
193193+}
194194+195195+.btn-primary {
196196+ @apply btn text-white;
197197+ background: var(--btn-primary-bg);
198198+}
199199+200200+.btn-primary:hover {
201201+ background: var(--btn-primary-bg-hover);
202202+}
203203+204204+.btn-secondary {
205205+ @apply btn;
206206+ background: var(--btn-secondary-bg);
207207+ color: var(--btn-secondary-text);
208208+ border: 1px solid var(--btn-secondary-border);
209209+}
210210+211211+.btn-secondary:hover {
212212+ background: var(--btn-secondary-bg-hover);
213213+}
214214+215215+.btn-tertiary {
216216+ @apply btn text-white;
217217+ background: var(--btn-primary-bg);
218218+}
219219+220220+.btn-tertiary:hover {
221221+ background: var(--btn-primary-bg-hover);
222222+}
223223+224224+.btn-link {
225225+ color: var(--text-muted);
226226+ @apply font-medium underline transition-colors cursor-pointer;
227227+}
228228+229229+.btn-link:hover {
230230+ color: var(--text-primary);
231231+}
232232+233233+.btn-danger {
234234+ @apply text-red-600 hover:text-red-800 font-medium underline transition-colors cursor-pointer;
235235+}
236236+```
237237+238238+Note: `.btn-tertiary` is redefined to match `.btn-primary` (no more gradient). It's used in 1 place (`shared.templ:206`). We keep the class to avoid template churn but visually unify it.
239239+240240+**Step 3: Rewrite form classes**
241241+242242+```css
243243+/* Forms */
244244+.form-label {
245245+ @apply block text-sm font-medium mb-2;
246246+ color: var(--text-primary);
247247+}
248248+249249+.form-input {
250250+ @apply rounded-lg shadow-sm text-base py-2 px-3;
251251+ background: var(--input-bg);
252252+ border: 1px solid var(--input-border);
253253+ color: var(--text-primary);
254254+ transition: border-color 150ms ease, box-shadow 150ms ease, background-color 150ms ease;
255255+}
256256+257257+.form-input:focus {
258258+ border-color: var(--input-border-focus);
259259+ box-shadow: 0 0 0 2px var(--input-ring-focus);
260260+ background: var(--input-bg-focus);
261261+ outline: none;
262262+}
263263+264264+.form-input::placeholder {
265265+ color: var(--text-placeholder);
266266+}
267267+268268+.form-input-lg {
269269+ @apply form-input py-3 px-4;
270270+}
271271+272272+.form-select {
273273+ @apply form-input truncate max-w-full min-w-0;
274274+}
275275+276276+.form-textarea {
277277+ @apply form-input min-h-[100px];
278278+}
279279+```
280280+281281+**Step 4: Rewrite table classes**
282282+283283+```css
284284+/* Tables */
285285+.table-container {
286286+ background: var(--table-bg);
287287+ border: 1px solid var(--table-border);
288288+ @apply rounded-lg overflow-hidden;
289289+ box-shadow: var(--shadow-sm);
290290+}
291291+292292+.table {
293293+ @apply min-w-full;
294294+ border-collapse: collapse;
295295+}
296296+297297+.table-header {
298298+ background: var(--table-header-bg);
299299+ border-bottom: 1px solid var(--table-border);
300300+}
301301+302302+.table-th {
303303+ @apply px-6 py-3 text-left text-xs font-medium uppercase tracking-wider;
304304+ color: var(--text-muted);
305305+}
306306+307307+.table-body {
308308+ background: var(--table-bg);
309309+}
310310+311311+.table-body tr {
312312+ border-bottom: 1px solid var(--table-divider);
313313+}
314314+315315+.table-body tr:last-child {
316316+ border-bottom: none;
317317+}
318318+319319+.table-row {
320320+ transition: background-color 150ms ease;
321321+}
322322+323323+.table-row:hover {
324324+ background: var(--table-row-hover);
325325+}
326326+327327+.table-td {
328328+ @apply px-6 py-4 whitespace-nowrap text-sm;
329329+ color: var(--text-secondary);
330330+}
331331+```
332332+333333+**Step 5: Rewrite modal classes**
334334+335335+```css
336336+/* Modals */
337337+.modal-backdrop {
338338+ @apply fixed inset-0 flex items-center justify-center z-50 p-4;
339339+ background: var(--modal-backdrop);
340340+ backdrop-filter: blur(4px);
341341+}
342342+343343+.modal-content {
344344+ background: var(--modal-bg);
345345+ border: 1px solid var(--modal-border);
346346+ @apply rounded-xl p-6 max-w-md w-full max-h-[90vh] overflow-y-auto;
347347+ box-shadow: var(--shadow-lg);
348348+}
349349+350350+.modal-title {
351351+ @apply text-xl font-semibold mb-4;
352352+ color: var(--text-primary);
353353+}
354354+355355+/* Native Dialog Element */
356356+.modal-dialog {
357357+ @apply p-0 bg-transparent border-none shadow-none max-w-md w-full;
358358+}
359359+360360+.modal-dialog::backdrop {
361361+ background: var(--modal-backdrop);
362362+ backdrop-filter: blur(4px);
363363+}
364364+365365+/* Dialog content wrapper (nested inside dialog) */
366366+.modal-dialog .modal-content {
367367+ background: var(--modal-bg);
368368+ border: 1px solid var(--modal-border);
369369+ @apply rounded-xl p-6 w-full max-h-[90vh] overflow-y-auto;
370370+ box-shadow: var(--shadow-lg);
371371+}
372372+```
373373+374374+**Step 6: Rewrite feed component classes**
375375+376376+```css
377377+/* Feed Components */
378378+.feed-card {
379379+ background: var(--card-bg);
380380+ border: 1px solid var(--card-border);
381381+ @apply rounded-lg p-3 sm:p-4 transition-shadow;
382382+ box-shadow: var(--shadow-sm);
383383+}
384384+385385+.feed-card:hover {
386386+ box-shadow: var(--shadow-md);
387387+}
388388+389389+.feed-content-box {
390390+ background: var(--surface-bg);
391391+ @apply rounded-lg p-3 sm:p-4;
392392+}
393393+394394+.feed-content-box-sm {
395395+ background: var(--surface-bg);
396396+ @apply rounded-lg p-2 sm:p-3;
397397+}
398398+```
399399+400400+Note: We keep `.feed-content-box` and `.feed-content-box-sm` but restyle them as subtle surface tints (no border, no backdrop-blur). This way existing templates work immediately. Task 4 removes the wrapper elements from templates where possible.
401401+402402+**Step 7: Rewrite remaining component classes**
403403+404404+Update avatar, text utility, badge, link, action, dropdown, and comment classes. The key changes are:
405405+406406+- Avatar rings: keep as-is (they're fine)
407407+- Text utilities: reference CSS variables
408408+- Badges: keep as-is (amber accent works)
409409+- Links: reference CSS variables
410410+- Action buttons: remove brown-100 background, use transparent with hover
411411+- Dropdowns: use card-bg variable
412412+- Comments: reference variables for borders/backgrounds
413413+414414+For text utilities:
415415+```css
416416+/* Text Utilities */
417417+.text-helper {
418418+ @apply text-sm mt-1;
419419+ color: var(--text-muted);
420420+}
421421+422422+.text-meta {
423423+ @apply text-xs;
424424+ color: var(--text-muted);
425425+}
426426+427427+.text-meta-sm {
428428+ @apply text-sm;
429429+ color: var(--text-muted);
430430+}
431431+432432+.text-label {
433433+ color: var(--text-muted);
434434+}
435435+```
436436+437437+For action buttons and bars:
438438+```css
439439+/* Action Bar */
440440+.action-bar {
441441+ @apply flex items-center gap-2 mt-3 pt-3;
442442+ border-top: 1px solid var(--surface-border);
443443+}
444444+445445+.brew-view-actions .action-bar {
446446+ @apply mt-0 pt-0 border-t-0;
447447+}
448448+449449+.comment-item .action-bar {
450450+ @apply mt-1 border-t-0 gap-1 rounded-lg px-1.5 py-1 inline-flex items-center;
451451+ background: var(--surface-bg);
452452+}
453453+454454+.action-btn {
455455+ @apply inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors cursor-pointer min-h-[44px];
456456+ color: var(--text-muted);
457457+ background: transparent;
458458+}
459459+460460+.action-btn:hover {
461461+ background: var(--surface-bg);
462462+ color: var(--text-secondary);
463463+}
464464+```
465465+466466+For dropdowns:
467467+```css
468468+.action-menu {
469469+ @apply absolute left-1/2 -translate-x-1/2 w-36 rounded-lg py-1 z-50;
470470+ background: var(--card-bg);
471471+ border: 1px solid var(--card-border);
472472+ box-shadow: var(--shadow-md);
473473+}
474474+475475+.dropdown-menu {
476476+ @apply absolute right-0 mt-2 w-48 rounded-lg py-1 z-50;
477477+ background: var(--card-bg);
478478+ border: 1px solid var(--card-border);
479479+ box-shadow: var(--shadow-md);
480480+}
481481+482482+.dropdown-item {
483483+ @apply block px-4 py-2 text-sm transition-colors;
484484+ color: var(--text-muted);
485485+}
486486+487487+.dropdown-item:hover {
488488+ background: var(--surface-bg);
489489+}
490490+```
491491+492492+For like/share/comment buttons, suggestions:
493493+```css
494494+.like-btn {
495495+ @apply inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors min-h-[44px];
496496+}
497497+498498+.like-btn-liked {
499499+ @apply like-btn text-red-600;
500500+ background: transparent;
501501+ animation: like-pop 400ms ease-out;
502502+}
503503+504504+.like-btn-liked:hover {
505505+ background: var(--surface-bg);
506506+}
507507+508508+.like-btn-unliked {
509509+ @apply like-btn;
510510+ color: var(--text-muted);
511511+ background: transparent;
512512+ animation: like-shrink 200ms ease-out;
513513+}
514514+515515+.like-btn-unliked:hover {
516516+ background: var(--surface-bg);
517517+}
518518+519519+.share-btn {
520520+ @apply inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors min-h-[44px];
521521+ color: var(--text-muted);
522522+ background: transparent;
523523+}
524524+525525+.share-btn:hover {
526526+ background: var(--surface-bg);
527527+}
528528+529529+.comment-btn {
530530+ @apply inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors min-h-[44px];
531531+ color: var(--text-muted);
532532+ background: transparent;
533533+}
534534+535535+.comment-btn:hover {
536536+ background: var(--surface-bg);
537537+}
538538+```
539539+540540+For suggestions dropdown:
541541+```css
542542+.suggestions-dropdown {
543543+ @apply absolute z-50 left-0 right-0 mt-1 rounded-lg max-h-48 overflow-y-auto;
544544+ background: var(--card-bg);
545545+ border: 1px solid var(--card-border);
546546+ box-shadow: var(--shadow-md);
547547+}
548548+549549+.suggestions-item {
550550+ @apply w-full text-left px-3 py-2 flex items-center gap-2 transition-colors cursor-pointer last:border-b-0;
551551+ border-bottom: 1px solid var(--surface-border);
552552+}
553553+554554+.suggestions-item:hover {
555555+ background: var(--surface-bg);
556556+}
557557+```
558558+559559+For comments:
560560+```css
561561+.comment-section {
562562+ @apply mt-8 pt-6;
563563+ border-top: 2px solid var(--card-border);
564564+}
565565+566566+.comment-login-prompt {
567567+ @apply flex items-center gap-3 rounded-lg p-4 mb-5 border border-dashed;
568568+ background: var(--surface-bg);
569569+ border-color: var(--card-border);
570570+}
571571+572572+.comment-compose {
573573+ @apply rounded-lg p-4 mb-5 flex flex-col gap-2;
574574+ background: var(--surface-bg);
575575+ border: 1px solid var(--card-border);
576576+}
577577+578578+.comment-textarea {
579579+ @apply w-full rounded-lg px-3 py-2.5 text-base resize-none transition-colors focus:ring-0 focus:outline-none;
580580+ background: var(--card-bg);
581581+ border: 1px solid var(--card-border);
582582+ color: var(--text-primary);
583583+}
584584+585585+.comment-textarea::placeholder {
586586+ color: var(--text-placeholder);
587587+}
588588+589589+.comment-textarea:focus {
590590+ border-color: var(--input-border-focus);
591591+}
592592+593593+.comment-item {
594594+ @apply relative rounded-lg p-3 transition-colors;
595595+}
596596+597597+.comment-item:hover {
598598+ background: var(--surface-bg);
599599+}
600600+601601+.comment-thread-line {
602602+ @apply absolute left-0 top-3 bottom-3 w-0.5 rounded-full;
603603+ background: var(--card-border);
604604+}
605605+606606+.comment-reply-btn {
607607+ @apply inline-flex items-center gap-1 transition-colors text-xs font-medium;
608608+ color: var(--text-placeholder);
609609+}
610610+611611+.comment-reply-btn:hover {
612612+ color: var(--text-muted);
613613+}
614614+615615+.comment-delete-btn {
616616+ @apply transition-colors;
617617+ color: var(--text-placeholder);
618618+}
619619+620620+.comment-delete-btn:hover {
621621+ color: var(--text-muted);
622622+}
623623+624624+.comment-reply-form {
625625+ @apply flex flex-col gap-2 rounded-lg p-3;
626626+ background: var(--surface-bg);
627627+ border: 1px solid var(--card-border);
628628+}
629629+```
630630+631631+**Step 8: Rebuild CSS and verify build**
632632+633633+Run: `just style && go vet ./... && go build ./...`
634634+Expected: Clean build.
635635+636636+**Step 9: Commit**
637637+638638+```bash
639639+git add static/css/app.css
640640+git commit -m "feat: redefine component classes with Clean Craft styling and CSS variables"
641641+```
642642+643643+---
644644+645645+## Task 3: Update Layout, Header, and Footer
646646+647647+Update the structural templates to use the new color system.
648648+649649+**Files:**
650650+- Modify: `internal/web/components/layout.templ`
651651+- Modify: `internal/web/components/header.templ`
652652+- Modify: `internal/web/components/footer.templ`
653653+654654+**Step 1: Update layout.templ**
655655+656656+Change the `<html>` tag's inline background:
657657+```
658658+style="background-color: #fdf8f6;" → style="background-color: #FAF7F5;"
659659+```
660660+661661+Change the `<body>` tag:
662662+```
663663+class="bg-brown-50 min-h-full flex flex-col"
664664+style="background-color: #fdf8f6;"
665665+```
666666+to:
667667+```
668668+class="min-h-full flex flex-col"
669669+style="background-color: var(--page-bg); color: var(--page-text);"
670670+```
671671+672672+Bump the CSS version:
673673+```
674674+output.css?v=0.6.1 → output.css?v=0.7.0
675675+```
676676+677677+**Step 2: Update header.templ**
678678+679679+Change the nav element from:
680680+```
681681+class="sticky top-0 z-50 bg-gradient-to-br from-brown-800 to-brown-900 text-white shadow-xl border-b-2 border-brown-600"
682682+```
683683+to:
684684+```
685685+class="sticky top-0 z-50 text-white"
686686+style="background: linear-gradient(135deg, var(--header-bg-from), var(--header-bg-to)); border-bottom: 1px solid var(--header-border);"
687687+```
688688+689689+Remove `shadow-xl` from the nav — the border provides sufficient separation. Add `box-shadow: var(--shadow-sm);` to the style attribute if a subtle shadow is wanted.
690690+691691+Reduce padding in the container div:
692692+```
693693+class="container mx-auto px-4 py-4" → class="container mx-auto px-4 py-3"
694694+```
695695+696696+Make the ALPHA badge smaller:
697697+```
698698+class="text-xs bg-amber-400 text-brown-900 px-2 py-1 rounded-md font-semibold shadow-sm"
699699+```
700700+to:
701701+```
702702+class="text-[10px] bg-amber-400 text-brown-900 px-1.5 py-0.5 rounded font-semibold"
703703+```
704704+705705+**Step 3: Update footer.templ**
706706+707707+Change the footer from:
708708+```
709709+class="mt-auto border-t border-brown-200 bg-brown-50"
710710+```
711711+to:
712712+```
713713+class="mt-auto"
714714+style="background: var(--footer-bg); border-top: 1px solid var(--footer-border);"
715715+```
716716+717717+**Step 4: Regenerate templ, rebuild CSS, verify**
718718+719719+Run: `templ generate && just style && go vet ./... && go build ./...`
720720+Expected: Clean build.
721721+722722+**Step 5: Commit**
723723+724724+```bash
725725+git add internal/web/components/layout.templ internal/web/components/header.templ internal/web/components/footer.templ
726726+git commit -m "feat: update layout, header, footer for Clean Craft theme"
727727+```
728728+729729+---
730730+731731+## Task 4: Add Feed Card Type Indicators
732732+733733+Add colored left borders to feed cards to distinguish record types (brew, bean, recipe, etc.) at a glance.
734734+735735+**Files:**
736736+- Modify: `static/css/app.css` (add type indicator classes)
737737+- Modify: `internal/web/components/action_bar.templ` (add type class to feed card wrapper)
738738+- Modify: `internal/web/pages/feed.templ` (add type class where feed cards are rendered)
739739+740740+**Step 1: Add type indicator CSS classes**
741741+742742+Add to `app.css` after the `.feed-card` definition:
743743+744744+```css
745745+/* Feed card type indicators */
746746+.feed-card-brew {
747747+ border-left: 3px solid var(--type-brew);
748748+}
749749+750750+.feed-card-bean {
751751+ border-left: 3px solid var(--type-bean);
752752+}
753753+754754+.feed-card-recipe {
755755+ border-left: 3px solid var(--type-recipe);
756756+}
757757+758758+.feed-card-roaster {
759759+ border-left: 3px solid var(--type-roaster);
760760+}
761761+762762+.feed-card-grinder {
763763+ border-left: 3px solid var(--type-grinder);
764764+}
765765+766766+.feed-card-brewer {
767767+ border-left: 3px solid var(--type-brewer);
768768+}
769769+```
770770+771771+**Step 2: Identify where feed cards are rendered with type context**
772772+773773+Read the following files to understand how the feed card type is available in the template context:
774774+- `internal/web/components/action_bar.templ` — the `FeedCard` component that wraps all feed items
775775+- `internal/web/pages/feed.templ` — where feed items are rendered
776776+777777+The feed card wrapper likely receives a type string (e.g., from `FeedItem.Collection` or similar). Add the appropriate `feed-card-{type}` class based on this value.
778778+779779+**Important:** Read the actual template code to determine exact prop names and conditional logic. The plan cannot specify exact line numbers because the template structure may vary. The key pattern is:
780780+781781+```go
782782+// In the feed card wrapper component, add the type class:
783783+class={ templ.Classes(
784784+ "feed-card",
785785+ templ.KV("feed-card-brew", props.Type == "brew"),
786786+ templ.KV("feed-card-bean", props.Type == "bean"),
787787+ // ... etc
788788+) }
789789+```
790790+791791+**Step 3: Rebuild and verify**
792792+793793+Run: `templ generate && just style && go vet ./... && go build ./...`
794794+795795+**Step 4: Commit**
796796+797797+```bash
798798+git add static/css/app.css internal/web/components/action_bar.templ internal/web/pages/feed.templ
799799+git commit -m "feat: add colored left-border type indicators to feed cards"
800800+```
801801+802802+---
803803+804804+## Task 5: Clean Up Template Inline Styles
805805+806806+Several templates use inline Tailwind gradient classes and shadow overrides that bypass the component classes. These need updating to match the new system.
807807+808808+**Files:**
809809+- Modify: `internal/web/components/shared.templ`
810810+- Modify: `internal/web/pages/about.templ`
811811+- Modify: `internal/web/pages/atproto.templ`
812812+813813+**Step 1: Update shared.templ**
814814+815815+In `WelcomeCard`: change `class="card p-8 mb-8"` — the `card` class now handles styling. Keep `p-8 mb-8`.
816816+817817+In `WelcomeAuthenticated`: remove the inline `shadow-lg hover:shadow-xl` from button links. The `.btn-primary` and `.btn-tertiary` classes handle it now. Example:
818818+```
819819+class="btn-primary block text-center py-4 px-6 rounded-xl shadow-lg hover:shadow-xl"
820820+```
821821+becomes:
822822+```
823823+class="btn-primary block text-center py-4 px-6 rounded-xl"
824824+```
825825+826826+In `EmptyState`: remove the inline `shadow-lg hover:shadow-xl` from the action link.
827827+828828+In `PageHeader`: change the action button default from `"btn-primary shadow-lg hover:shadow-xl"` to just `"btn-primary"`.
829829+830830+In `AboutInfoCard`: change from inline gradient classes:
831831+```
832832+class="bg-gradient-to-br from-amber-50 to-brown-100 rounded-xl p-6 border-2 border-brown-300 shadow-lg mb-6"
833833+```
834834+to:
835835+```
836836+class="card p-6 mb-6"
837837+```
838838+(or keep as a special card with amber tint if desired — read the actual usage context first)
839839+840840+**Step 2: Update about.templ and atproto.templ**
841841+842842+These pages use extensive inline gradient classes for feature sections. Read each file and replace:
843843+- `bg-gradient-to-br from-brown-100 to-brown-200` → `card` class or inline `background: var(--card-bg);`
844844+- `shadow-xl` / `shadow-lg` → remove (cards get shadow from class)
845845+- `border-2 border-brown-300` → `border border-brown-200` or let card class handle it
846846+847847+Be careful with these pages — they have custom layouts. Don't break the structure, just update the color/shadow treatment.
848848+849849+**Step 3: Rebuild and verify**
850850+851851+Run: `templ generate && just style && go vet ./... && go build ./...`
852852+853853+**Step 4: Commit**
854854+855855+```bash
856856+git add internal/web/components/shared.templ internal/web/pages/about.templ internal/web/pages/atproto.templ
857857+git commit -m "refactor: remove inline gradient/shadow overrides from templates"
858858+```
859859+860860+---
861861+862862+## Task 6: Typography Refinements
863863+864864+Downsize the typography scale and update section title treatment.
865865+866866+**Files:**
867867+- Modify: `static/css/app.css` (update typography classes)
868868+- Modify: `internal/web/components/shared.templ` (PageHeader title size)
869869+870870+**Step 1: Update typography classes in app.css**
871871+872872+```css
873873+/* Typography */
874874+.section-title {
875875+ @apply text-xs font-semibold uppercase tracking-widest mb-4;
876876+ color: var(--text-faint);
877877+}
878878+879879+.page-title {
880880+ @apply text-2xl font-semibold;
881881+ color: var(--text-primary);
882882+}
883883+```
884884+885885+**Step 2: Update PageHeader in shared.templ**
886886+887887+Change the heading from `text-3xl font-bold` to use the `.page-title` class:
888888+```
889889+<h2 class="text-3xl font-bold text-brown-900">{ props.Title }</h2>
890890+```
891891+becomes:
892892+```
893893+<h2 class="page-title">{ props.Title }</h2>
894894+```
895895+896896+**Step 3: Search for other `text-3xl` usages**
897897+898898+Grep for `text-3xl` across templ files. Update each to `text-2xl font-semibold` or use `.page-title` class. Key locations:
899899+- `manage.templ` — page title
900900+- `notifications.templ` — page title
901901+- `recipe_explore.templ` — page title
902902+- `brew_form.templ` — page title
903903+- `shared.templ` — WelcomeCard title
904904+905905+**Step 4: Rebuild and verify**
906906+907907+Run: `templ generate && just style && go vet ./... && go build ./...`
908908+909909+**Step 5: Commit**
910910+911911+```bash
912912+git add static/css/app.css internal/web/components/shared.templ [other modified templ files]
913913+git commit -m "feat: refine typography scale — smaller titles, uppercase section labels"
914914+```
915915+916916+---
917917+918918+## Task 7: Remove Table Row Stagger Animation
919919+920920+The stagger animation on table rows feels gimmicky for data tables. Remove it while keeping feed card stagger.
921921+922922+**Files:**
923923+- Modify: `static/css/app.css` (remove table row animation rules)
924924+925925+**Step 1: Remove table row stagger CSS**
926926+927927+Delete these rules from app.css (around lines 556-577):
928928+929929+```css
930930+/* Table rows slide in with stagger effect (dynamic content) */
931931+.table-body tr {
932932+ animation: fade-in-slide-up 300ms ease-out backwards;
933933+}
934934+935935+.table-body tr:nth-child(1) { animation-delay: 0ms; }
936936+.table-body tr:nth-child(2) { animation-delay: 30ms; }
937937+.table-body tr:nth-child(3) { animation-delay: 60ms; }
938938+.table-body tr:nth-child(4) { animation-delay: 90ms; }
939939+.table-body tr:nth-child(5) { animation-delay: 120ms; }
940940+.table-body tr:nth-child(n + 6) { animation-delay: 150ms; }
941941+```
942942+943943+**Step 2: Rebuild**
944944+945945+Run: `just style`
946946+947947+**Step 3: Commit**
948948+949949+```bash
950950+git add static/css/app.css
951951+git commit -m "refactor: remove table row stagger animation"
952952+```
953953+954954+---
955955+956956+## Task 8: Update Form Input Focus Behavior
957957+958958+Remove the `translateY(-1px)` focus lift on form elements. Clean Craft uses a subtle background tint change on focus instead, which is already handled by the new `.form-input` definition.
959959+960960+**Files:**
961961+- Modify: `static/css/app.css` (remove focus transform rules)
962962+963963+**Step 1: Remove focus transform**
964964+965965+Delete these rules (around lines 647-660):
966966+967967+```css
968968+.form-input:focus,
969969+.form-select:focus,
970970+.form-textarea:focus {
971971+ transform: translateY(-1px);
972972+}
973973+```
974974+975975+Also update the transition rule to remove `transform`:
976976+```css
977977+.form-input,
978978+.form-select,
979979+.form-textarea {
980980+ transition:
981981+ border-color 100ms ease,
982982+ box-shadow 100ms ease,
983983+ transform 50ms ease;
984984+}
985985+```
986986+Change to:
987987+```css
988988+.form-input,
989989+.form-select,
990990+.form-textarea {
991991+ transition:
992992+ border-color 150ms ease,
993993+ box-shadow 150ms ease,
994994+ background-color 150ms ease;
995995+}
996996+```
997997+998998+Note: If the new `.form-input` definition in Task 2 already includes its own transition, this separate rule may be redundant. Check whether it's still needed after Task 2 is applied. If the Task 2 definition already has transition on the class itself, delete this separate rule entirely.
999999+10001000+**Step 2: Rebuild**
10011001+10021002+Run: `just style`
10031003+10041004+**Step 3: Commit**
10051005+10061006+```bash
10071007+git add static/css/app.css
10081008+git commit -m "refactor: replace form focus lift with background tint transition"
10091009+```
10101010+10111011+---
10121012+10131013+## Task 9: Visual QA and Polish
10141014+10151015+Manual review pass to catch inconsistencies.
10161016+10171017+**Files:** Various — depends on findings.
10181018+10191019+**Step 1: Run the dev server**
10201020+10211021+Run: `go run cmd/server/main.go`
10221022+10231023+**Step 2: Visual checklist**
10241024+10251025+Check each page and verify:
10261026+10271027+- [ ] **Home page:** WelcomeCard renders as white card on cream background. No gradient. Login form inputs have 1px borders.
10281028+- [ ] **Feed:** Feed cards are white with subtle shadow. Type indicators show colored left borders. Action buttons are transparent (no background) until hover.
10291029+- [ ] **Brew form:** All inputs have 1px borders. Focus shows tint change + ring. No translateY lift.
10301030+- [ ] **Brew view:** Detail fields use section-box with subtle tint. Card is white.
10311031+- [ ] **Manage page:** Tables are white with light header. No gradient backgrounds.
10321032+- [ ] **Profile:** Stats cards are white. Tab content loads properly.
10331033+- [ ] **Recipe explore:** Cards are white. Detail panel matches.
10341034+- [ ] **Modals:** White background, subtle border, no gradient.
10351035+- [ ] **Header:** Slightly shorter, ALPHA badge smaller. Shadow subtle or absent.
10361036+- [ ] **Footer:** Clean, matches cream background.
10371037+- [ ] **Mobile:** Check all of the above at < 640px width.
10381038+10391039+**Step 3: Fix any issues found**
10401040+10411041+Address visual inconsistencies discovered during QA. Common issues to watch for:
10421042+- Templates with hardcoded `bg-brown-100` or `bg-brown-50` that should now be `bg-white` or use variables
10431043+- Inline `shadow-*` classes that override the component class shadow
10441044+- `border-2` on inputs that weren't caught in the component class rewrite (inline overrides in templates)
10451045+- Text color classes that should be updated (`text-brown-800` → just inherit from parent or use variable)
10461046+10471047+**Step 4: Commit fixes**
10481048+10491049+```bash
10501050+git add -A
10511051+git commit -m "fix: visual QA polish for Clean Craft overhaul"
10521052+```
10531053+10541054+---
10551055+10561056+## Task 10: Bump CSS Version and Final Build Check
10571057+10581058+**Files:**
10591059+- Modify: `internal/web/components/layout.templ` (verify CSS version bumped)
10601060+10611061+**Step 1: Verify CSS version**
10621062+10631063+The version should already be `0.7.0` from Task 3. Confirm it's correct.
10641064+10651065+**Step 2: Full build and vet**
10661066+10671067+Run: `templ generate && just style && go vet ./... && go build ./... && go test ./...`
10681068+10691069+Expected: All pass.
10701070+10711071+**Step 3: Final commit if needed**
10721072+10731073+If any last fixes were made:
10741074+```bash
10751075+git add -A
10761076+git commit -m "chore: final Clean Craft overhaul build verification"
10771077+```
10781078+10791079+---
10801080+10811081+## Future Work (Not in This Plan)
10821082+10831083+These are noted for later and should NOT be done in this implementation:
10841084+10851085+1. **Dark mode (Option B):** Add `@media (prefers-color-scheme: dark)` block redefining all CSS variables with espresso/cream values. Also add a manual toggle. All the structural work is done — this is purely a color variable swap.
10861086+10871087+2. **SVG icon system:** Replace emoji icons (📍🔥🌱⚖️🏭) with SVG icons from Lucide or Phosphor. Separate task, requires icon selection and template updates.
10881088+10891089+3. **Feed content box removal:** The `.feed-content-box` wrappers are restyled but still exist in templates. A future cleanup can remove them entirely and let content sit directly in the feed card, but this is optional since the restyled version (subtle tint, no border) already looks clean.
+10
docs/recipes.norg
···6969 users (not sure how they would be rated though, since that would probably
7070 need to be part of this)
71717272+*** Using Other User's Recipes In Brews
7373+7474+ Currently, this behavior does not work as expected, as the server tries to
7575+ look up the record in the logged-in user's PDS, rather than the one
7676+ belonging to the owner. This prevents other users from using recipes that
7777+ don't belong to them and is not the intended behavior.
7878+7979+ (Brewer fuzzy finding might also not work, but its hard to say since the
8080+ recipe lookup fails first)
8181+7282** Open Questions
73837484 For links between a brew and recipe, which should have the optional ref?
···11+# Option A: "Clean Craft" — Modern Minimal with Warmth
22+33+**Vibe:** A well-designed tool for coffee people. Clean surfaces, generous whitespace, subtle depth. Feels professional without losing the coffee identity.
44+55+**Core principle:** Remove visual noise so the *data* becomes the design.
66+77+## Design Direction
88+99+Strip away gradients, heavy shadows, and nested containers. Replace with flat white cards on a warm cream background. Let typography weight and spacing create hierarchy instead of color and depth.
1010+1111+The monospace font stays everywhere — it's the brand. But we size it down (monospace reads ~15% larger than proportional) and use weight contrast more aggressively to create hierarchy.
1212+1313+## Color Changes
1414+1515+| Token | Current | Proposed | Reason |
1616+|-------|---------|----------|--------|
1717+| Page background | `brown-50` (#fdf8f6) | `#FAF7F5` (new `cream`) | Warmer, less pink-tinted |
1818+| Card background | gradient brown-100→200 | `#FFFFFF` flat white | Cards pop via contrast with cream bg |
1919+| Card border | `brown-300` | `brown-200` | Lighter border, less "boxed in" |
2020+| Card shadow | `shadow-xl` | `shadow-sm`, `shadow-md` on hover | Quieter at rest, responsive on interaction |
2121+| Feed card bg | gradient brown-50→100 | `#FFFFFF` flat white | Same as primary cards |
2222+| Table bg | gradient brown-100→200 | `#FFFFFF` with `brown-100` header | Clean, scannable |
2323+| Modal bg | gradient brown-100→200 | `#FFFFFF` | Consistent with card treatment |
2424+2525+Keep the existing brown palette for text, borders, and accents. Keep amber for ratings and badges. The palette itself is good — the problem is overuse of mid-tones as backgrounds.
2626+2727+## Typography Changes
2828+2929+| Element | Current | Proposed |
3030+|---------|---------|----------|
3131+| Page title | `text-3xl font-bold` | `text-2xl font-semibold` |
3232+| Section title | `text-2xl font-bold` | `text-base font-semibold uppercase tracking-wider text-brown-500` |
3333+| Card heading | `text-xl font-bold` | `text-base font-semibold` |
3434+| Body text | `text-base` | `text-sm` |
3535+| Meta/labels | `text-xs` | `text-xs font-medium text-brown-500` |
3636+3737+Rationale: Monospace at `text-base` (16px) feels large. Dropping body to `text-sm` (14px) and headings proportionally gives the same visual weight as a proportional font at standard sizes.
3838+3939+Section titles become small uppercase labels — a common pattern in tools like Linear, Notion, and GitHub that creates clear sections without shouting.
4040+4141+## Card System
4242+4343+### Current
4444+```
4545+.card = gradient bg + rounded-xl + shadow-xl + border-brown-300
4646+.feed-card = gradient bg + rounded-lg + shadow-md + border-brown-200
4747+.section-box = bg-brown-50 + rounded-lg + border-brown-200
4848+```
4949+5050+Three card types, all slightly different. Feed cards have a nested `.feed-content-box` inside them creating card-in-card.
5151+5252+### Proposed
5353+```
5454+.card = bg-white rounded-xl border border-brown-200 shadow-sm
5555+ hover:shadow-md transition-shadow
5656+.card-sm = bg-white rounded-lg border border-brown-200 shadow-sm
5757+.feed-card = bg-white rounded-lg border border-brown-200 shadow-sm
5858+ hover:shadow-md transition-shadow
5959+```
6060+6161+Key changes:
6262+- **Kill the nested content box.** Feed card content lives directly in the card. No `.feed-content-box` wrapper.
6363+- **One visual language.** All cards are white, rounded, with thin borders and minimal shadow.
6464+- **Type indicator via left border.** Feed cards get a `border-l-3` colored by record type:
6565+ - Brew: `brown-700`
6666+ - Bean: `amber-600`
6767+ - Recipe: `brown-500`
6868+ - Roaster/Grinder/Brewer: `brown-400`
6969+7070+This replaces the need for emoji or labels to distinguish record types at a glance.
7171+7272+### Section box
7373+```
7474+.section-box = bg-brown-50/50 rounded-lg p-4
7575+ (no border — just the subtle background tint)
7676+```
7777+7878+Used inside detail views for grouping related data. Lighter treatment than a card.
7979+8080+## Button System
8181+8282+### Current (5 variants)
8383+```
8484+.btn-primary = gradient brown-700→900 + shadow-md
8585+.btn-secondary = bg-brown-300
8686+.btn-tertiary = gradient brown-500→600
8787+.btn-link = text underline
8888+.btn-danger = red text underline
8989+```
9090+9191+### Proposed (3 variants)
9292+```
9393+.btn-primary = bg-brown-800 text-white rounded-lg
9494+ hover:bg-brown-900 transition-colors
9595+.btn-secondary = bg-white border border-brown-300 text-brown-700 rounded-lg
9696+ hover:bg-brown-50 transition-colors
9797+.btn-danger = text-red-600 hover:text-red-800 underline
9898+```
9999+100100+Drop gradients on buttons. Drop `.btn-tertiary` — audit uses and convert to primary or secondary. The link-style `.btn-link` merges into the general `.link` class.
101101+102102+## Form System
103103+104104+### Current
105105+```
106106+.form-input = border-2 border-brown-300 rounded-lg
107107+ focus:border-brown-600 focus:ring-brown-600
108108+```
109109+110110+### Proposed
111111+```
112112+.form-input = border border-brown-300 rounded-lg bg-white
113113+ focus:border-brown-600 focus:ring-1 focus:ring-brown-600
114114+ focus:bg-brown-50/30
115115+ placeholder:text-brown-400
116116+```
117117+118118+Changes:
119119+- Border from 2px to 1px (less heavy)
120120+- Subtle background tint on focus (instead of just border change)
121121+- Keep the focus `translateY(-1px)` lift — it's a nice touch
122122+- Ring reduced to `ring-1` (thinner, more refined)
123123+124124+## Shadow Depth Scale
125125+126126+| Level | Class | Usage |
127127+|-------|-------|-------|
128128+| 0 | `shadow-none` | Flat surfaces, inline elements |
129129+| 1 | `shadow-sm` | Cards at rest, tables, section boxes |
130130+| 2 | `shadow-md` | Hovered cards, dropdowns, action menus |
131131+| 3 | `shadow-lg` | Modals, popovers, floating UI |
132132+133133+Current uses shadow-sm through shadow-2xl inconsistently. This simplifies to 3 levels with clear rules.
134134+135135+## Navigation
136136+137137+**Keep** the dark brown gradient header — it's a strong anchor that works well.
138138+139139+**Refine:**
140140+- Reduce ALPHA badge prominence (smaller, `text-[10px]`)
141141+- Replace hard `border-b` with `shadow-sm` for softer separation
142142+- Shrink header height slightly (48px instead of ~56px)
143143+144144+## Feed Cards — Detailed Layout
145145+146146+```
147147+┌─ border-l-3 brown-700 ─────────────────┐
148148+│ │
149149+│ ○ Display Name · @handle · 2h │
150150+│ brewed with Ethiopian Sidamo │
151151+│ │
152152+│ ┌─ bg-brown-50/50 ──────────────────┐ │
153153+│ │ Sweet Bloom · V60 │ │
154154+│ │ 15g → 250g · 1:16.7 · ⭐ 8.5 │ │
155155+│ │ │ │
156156+│ │ "Bright citrus, chocolate finish" │ │
157157+│ └────────────────────────────────────┘ │
158158+│ │
159159+│ 💬 3 ♡ 12 ↗ Share │
160160+└──────────────────────────────────────────┘
161161+```
162162+163163+The inner area uses a section-box (subtle bg tint, no border) instead of the current bordered content box. Action bar has no top border — just spacing.
164164+165165+## Table Styling
166166+167167+### Current
168168+```
169169+.table-container = gradient bg + shadow-md + border
170170+.table-header = bg-brown-200
171171+.table-body = bg-brown-100
172172+```
173173+174174+### Proposed
175175+```
176176+.table-container = bg-white rounded-lg border border-brown-200 shadow-sm overflow-hidden
177177+.table-header = bg-brown-50
178178+.table-body = bg-white divide-y divide-brown-100
179179+.table-row = hover:bg-brown-50 transition-colors
180180+```
181181+182182+Clean, standard table styling. Header is barely tinted. Rows divide with thin lines. Hover highlights the row.
183183+184184+## Animations
185185+186186+**Keep:** Staggered feed card entry, modal transitions, like pop/shrink, form focus lift.
187187+188188+**Remove:** Table row stagger (too much motion for data tables — feels gimmicky).
189189+190190+**Add:** Subtle `opacity` transition on card border-left color when filtering feed by type.
191191+192192+## Implementation Phases
193193+194194+### Phase 1: Foundation (CSS-only, no template changes)
195195+1. Update `tailwind.config.js` — add `cream` color
196196+2. Rewrite card/button/form/table classes in `app.css`
197197+3. Update `layout.templ` body background
198198+4. Bump CSS cache version
199199+200200+### Phase 2: Template Cleanup
201201+1. Remove `.feed-content-box` wrappers from feed templates
202202+2. Add `border-l-3` type indicators to feed cards
203203+3. Simplify section titles (uppercase label pattern)
204204+4. Remove `.btn-tertiary` uses
205205+206206+### Phase 3: Detail Polish
207207+1. Refine form layouts
208208+2. Update modal content styling
209209+3. Audit shadow usage across all templates
210210+4. Test responsive behavior
211211+212212+## Tradeoffs
213213+214214+| Pro | Con |
215215+|-----|-----|
216216+| Immediately more professional | Less personality than current |
217217+| Easier to maintain (fewer special cases) | Could feel generic if not careful |
218218+| Better feed scannability at scale | Left-border type system is a new concept to learn |
219219+| Lighter page weight (no gradients) | White cards on cream is a common pattern |
220220+| Clear visual hierarchy | Less "cozy coffee shop" feeling |
221221+| Works well with future dark mode | Requires discipline to not drift back to decoration |
222222+223223+## Risk: Becoming Generic
224224+225225+The biggest risk with clean minimal is looking like every other SaaS tool. Mitigations:
226226+- **Monospace font** is the primary differentiator — keep it everywhere
227227+- **Brown palette** prevents blue/gray sameness
228228+- **Grain overlay** (keep at current opacity) adds tactile quality
229229+- **Left-border accents** give the feed a distinctive pattern
230230+- **Dark nav** provides a strong visual anchor
231231+232232+The identity comes from the font + color combination, not from gradients and shadows.
+246
docs/ui-overhaul-option-b.md
···11+# Option B: "Roasted" — Bold, Dark, Editorial
22+33+**Vibe:** Specialty coffee packaging meets editorial magazine. Dark surfaces with warm highlights. The app *feels* like coffee — like opening a bag of fresh beans.
44+55+**Core principle:** High contrast and bold typography make data dramatic.
66+77+## Design Direction
88+99+Invert the current model. Instead of brown-tinted light backgrounds, go dark. Deep espresso-brown as the base with cream/white cards floating above. The dark ground creates natural depth without shadows. Typography goes bigger and bolder — every page has a clear headline moment.
1010+1111+The monospace font becomes a statement rather than a quirk. At large sizes against dark backgrounds, monospace reads as intentional and editorial.
1212+1313+## Color System
1414+1515+### New Dark Palette
1616+1717+| Token | Hex | Usage |
1818+|-------|-----|-------|
1919+| `espresso-950` | `#0F0A08` | Deepest background (page bg) |
2020+| `espresso-900` | `#1C1210` | Card backgrounds, primary surface |
2121+| `espresso-850` | `#241A16` | Elevated surfaces, hover states |
2222+| `espresso-800` | `#2E211B` | Borders, dividers |
2323+| `espresso-700` | `#3D2D24` | Secondary borders, subtle elements |
2424+| `cream-50` | `#FAF7F5` | Primary text on dark |
2525+| `cream-100` | `#F2E8E0` | Secondary text on dark |
2626+| `cream-200` | `#E0CEC4` | Muted text, labels |
2727+| `cream-300` | `#C4A898` | Placeholder text, disabled |
2828+| `amber-400` | `#FBBF24` | Primary accent — ratings, highlights |
2929+| `ember-500` | `#C4553A` | Secondary accent — actions, CTAs |
3030+| `ember-600` | `#A3412D` | Hover state for ember accent |
3131+3232+### Usage Rules
3333+3434+- **Dark surfaces layered:** Page (`950`) → Section (`900`) → Card (`850`) creates depth without shadows
3535+- **Warm borders:** `espresso-800` borders, never gray
3636+- **Text contrast:** `cream-50` for headings (≥7:1 ratio), `cream-100` for body (≥4.5:1), `cream-200` for meta
3737+- **Accent restraint:** Amber for data (ratings, stats). Ember for interactive (buttons, links). Never both in the same element.
3838+- **No gradients on surfaces.** Flat dark colors. Gradients only on accent elements (primary button, hero treatments).
3939+4040+## Typography Changes
4141+4242+| Element | Current | Proposed |
4343+|---------|---------|----------|
4444+| Page title | `text-3xl font-bold` | `text-3xl font-semibold text-cream-50 tracking-tight` |
4545+| Section title | `text-2xl font-bold` | `text-lg font-semibold text-amber-400 uppercase tracking-widest` |
4646+| Card heading | `text-xl font-bold` | `text-lg font-semibold text-cream-50` |
4747+| Body text | `text-base` | `text-sm text-cream-100` |
4848+| Meta/labels | `text-xs` | `text-xs font-medium text-cream-300 uppercase tracking-wider` |
4949+| Data values | same as body | `text-sm font-medium text-cream-50 tabular-nums` |
5050+5151+Key difference from Option A: **Section titles use amber accent** as a color label, giving each section a warm highlight. Data values get their own treatment — medium weight, tabular numbers — because in a coffee tracking app, the numbers *are* the content.
5252+5353+## Card System
5454+5555+### Proposed
5656+```
5757+.card = bg-espresso-900 rounded-xl border border-espresso-800
5858+ (no shadow — depth comes from surface layering)
5959+.card-hover = hover:bg-espresso-850 hover:border-espresso-700 transition-colors
6060+.feed-card = bg-espresso-900 rounded-lg border border-espresso-800
6161+ hover:bg-espresso-850 transition-colors
6262+```
6363+6464+**No shadows at all on cards.** Dark themes get depth from layered surface colors (Material Design 3 dark theme pattern). Shadows on dark backgrounds look muddy.
6565+6666+**Content areas** inside cards use a slightly lighter surface:
6767+```
6868+.card-inset = bg-espresso-850 rounded-lg p-3
6969+ (no border — just the shade difference)
7070+```
7171+7272+This replaces both `.section-box` and `.feed-content-box` — a single "recessed area" concept.
7373+7474+### Type indicator
7575+Instead of left-border (which can get lost on dark), use a **small colored dot** before the action text:
7676+- Brew: `amber-400` dot
7777+- Bean: `cream-200` dot
7878+- Recipe: `ember-500` dot
7979+8080+Or a subtle top-border accent (2px) on the card — visible but not dominant.
8181+8282+## Button System
8383+8484+### Proposed
8585+```
8686+.btn-primary = bg-gradient-to-r from-ember-500 to-ember-600 text-cream-50 rounded-lg
8787+ hover:from-ember-600 hover:to-ember-600 transition-all
8888+.btn-secondary = bg-espresso-850 border border-espresso-700 text-cream-100 rounded-lg
8989+ hover:bg-espresso-800 hover:border-espresso-700 transition-colors
9090+.btn-danger = text-red-400 hover:text-red-300 underline
9191+```
9292+9393+Primary button gets the one gradient in the system — the warm ember accent. This makes CTAs unmistakable against the dark background. Secondary buttons are ghost-style (slightly lighter than the surface).
9494+9595+## Form System
9696+9797+```
9898+.form-input = bg-espresso-850 border border-espresso-700 rounded-lg
9999+ text-cream-50 placeholder:text-cream-300
100100+ focus:border-amber-400 focus:ring-1 focus:ring-amber-400
101101+```
102102+103103+Dark inputs with amber focus ring. The focus state is dramatic and clear — amber on dark brown is high contrast without being harsh.
104104+105105+**Form labels:** `text-cream-200 text-xs font-medium uppercase tracking-wider` — small, quiet, functional.
106106+107107+## Navigation
108108+109109+### Proposed
110110+The current dark header is already close to the right direction. Refinements:
111111+112112+```
113113+Header bg: espresso-950 (deepest dark, matches page)
114114+ with a subtle bottom border in espresso-800
115115+```
116116+117117+Since the page is now dark too, the header blends seamlessly. It's separated by the border, not a background change. This creates a more immersive, app-like feel.
118118+119119+**ALPHA badge:** `bg-amber-400 text-espresso-950` (inverted — amber background, dark text). Small, punchy.
120120+121121+**User dropdown:** `bg-espresso-900 border border-espresso-800` — matches card styling.
122122+123123+## Feed Cards — Detailed Layout
124124+125125+```
126126+┌─ bg-espresso-900 border-espresso-800 ───┐
127127+│ │
128128+│ ○ Display Name · @handle · 2h │
129129+│ ● brewed with Ethiopian Sidamo │ ← amber dot for brew type
130130+│ │
131131+│ ┌─ bg-espresso-850 ─────────────────┐ │
132132+│ │ Sweet Bloom · V60 │ │ ← cream-100 text
133133+│ │ 15g → 250g · 1:16.7 │ │ ← cream-50 data values
134134+│ │ │ │
135135+│ │ ⭐ 8.5 │ │ ← amber badge
136136+│ │ │ │
137137+│ │ "Bright citrus, chocolate finish" │ │ ← cream-200 italic
138138+│ └────────────────────────────────────┘ │
139139+│ │
140140+│ 💬 3 ♡ 12 ↗ Share │ ← cream-300, hover cream-50
141141+└──────────────────────────────────────────┘
142142+```
143143+144144+The inset area (`.card-inset`) provides visual grouping without borders. On dark backgrounds, even a small shade difference reads clearly.
145145+146146+## Table Styling
147147+148148+```
149149+.table-container = bg-espresso-900 rounded-lg border border-espresso-800 overflow-hidden
150150+.table-header = bg-espresso-850 border-b border-espresso-800
151151+.table-th = text-cream-300 text-xs font-medium uppercase tracking-wider
152152+.table-body = divide-y divide-espresso-800
153153+.table-row = hover:bg-espresso-850 transition-colors
154154+.table-td = text-cream-100 text-sm
155155+```
156156+157157+Clean, dark table. Header row is barely differentiated. Dividers between rows. On hover, rows lighten slightly.
158158+159159+## Animations
160160+161161+**Keep:** Staggered feed card entry, modal transitions, like pop/shrink.
162162+163163+**Modify:**
164164+- Feed card entry: Use `opacity` + `translateY(6px)` (shorter travel on dark — movement reads more clearly against dark backgrounds)
165165+- Modal backdrop: `bg-black/60` (needs to be darker since the page is already dark)
166166+167167+**Add:**
168168+- Subtle `glow` on amber accent elements: `box-shadow: 0 0 20px rgba(251,191,36,0.1)` — very subtle warm halo
169169+- Card hover: border transitions from `espresso-800` to `espresso-700` (warm reveal)
170170+171171+**Remove:** Table row stagger, form focus lift (feels odd on dark).
172172+173173+## Texture & Atmosphere
174174+175175+**Grain overlay:** Increase from `0.025` to `0.04` opacity. Grain reads better on dark backgrounds and adds significant tactile quality. This is one of the biggest differentiators — the paper-grain-on-dark effect feels like a coffee bag or craft packaging.
176176+177177+**Optional:** Subtle warm vignette on the page body:
178178+```css
179179+body::after {
180180+ content: "";
181181+ position: fixed;
182182+ inset: 0;
183183+ pointer-events: none;
184184+ background: radial-gradient(ellipse at center, transparent 50%, rgba(15,10,8,0.3) 100%);
185185+}
186186+```
187187+188188+This darkens the edges slightly, creating a cozy, focused feel. Can be skipped if it feels heavy.
189189+190190+## Implementation Phases
191191+192192+### Phase 1: Foundation
193193+1. Extend `tailwind.config.js` with `espresso` and `cream` color scales
194194+2. Rewrite `app.css` component classes for dark surfaces
195195+3. Update `layout.templ` body background + text colors
196196+4. Update `header.templ` to match dark theme
197197+5. Bump CSS cache version
198198+199199+### Phase 2: Template Updates
200200+1. Update all page templates — swap brown-* text utilities to cream-*
201201+2. Replace `.feed-content-box` with `.card-inset`
202202+3. Update form styling (dark inputs, amber focus)
203203+4. Update modal styling
204204+205205+### Phase 3: Polish
206206+1. Audit contrast ratios (WCAG AA minimum)
207207+2. Add subtle glow effects on accent elements
208208+3. Tune grain overlay opacity
209209+4. Test all states (hover, focus, active, disabled) on dark
210210+211211+### Phase 4: Accessibility Audit
212212+Dark themes have higher risk of contrast failures. Must verify:
213213+- All text meets WCAG AA (4.5:1 for body, 3:1 for large text)
214214+- Focus indicators are visible
215215+- Disabled states are distinguishable
216216+- Form validation errors are readable
217217+218218+## Tradeoffs
219219+220220+| Pro | Con |
221221+|-----|-----|
222222+| Extremely distinctive — memorable identity | Harder to implement correctly (contrast, accessibility) |
223223+| Dark mode is practical (morning/evening brew logging) | More CSS to maintain (dark needs different strategies) |
224224+| High contrast makes data pop | Polarizing — some users hate dark UIs |
225225+| Grain texture reads beautifully on dark | Heavier visual treatment, more code for atmosphere |
226226+| Feels premium, like specialty coffee packaging | Template changes are more extensive (every text color) |
227227+| Monospace font becomes a bold statement | No easy "light mode" toggle without a full second theme |
228228+| Natural depth from surface layering (no shadows needed) | Photos/avatars need extra treatment to not look jarring |
229229+230230+## Risk: Too Dark / Oppressive
231231+232232+The biggest risk is the UI feeling heavy or hard to read. Mitigations:
233233+- **Cream text, not white.** Pure white (#fff) on dark brown is harsh. Warm cream (#FAF7F5) reduces eye strain.
234234+- **Layered surfaces** prevent "black void" feeling — there's always subtle differentiation.
235235+- **Amber accents** add warmth and break up the dark expanse.
236236+- **Generous spacing** — dark UIs need more whitespace (darkspace?) to breathe than light ones.
237237+- **Grain overlay** prevents "screen" feeling, adds organic quality.
238238+239239+## Risk: Light Mode Demand
240240+241241+If users request light mode later, you'd need to:
242242+1. Define all component colors via CSS custom properties (not Tailwind classes directly)
243243+2. Create a parallel set of light-theme values
244244+3. Use `prefers-color-scheme` or a toggle
245245+246246+This is significant work. If you think light mode will be needed within 6 months, Option A is a safer starting point (and can *add* dark mode later more easily than B can add light mode).
+1-1
internal/web/components/entity_tables.templ
···27272828// BeanCard renders a single bean as a compact card
2929templ BeanCard(bean *models.Bean, showActions bool, ownerHandle string) {
3030- <div class="feed-card">
3030+ <div class="feed-card feed-card-bean">
3131 <div class="feed-content-box-sm">
3232 <div class="flex items-start justify-between gap-2 mb-2">
3333 <div class="min-w-0">