Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee
17
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: ui redesign part 1, cards and coloring

authored by

Patrick Dewey and committed by tangled.org b1c91fad 51449209

+3006 -156
+1089
docs/plans/2026-03-24-clean-craft-ui-overhaul.md
··· 1 + # Clean Craft UI Overhaul Implementation Plan 2 + 3 + > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 + 5 + **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. 6 + 7 + **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. 8 + 9 + **Tech Stack:** Tailwind CSS (config + `@apply` in app.css), Templ templates, no new dependencies. 10 + 11 + **Key files:** 12 + - `static/css/app.css` — all component class definitions 13 + - `tailwind.config.js` — color palette and theme 14 + - `internal/web/components/layout.templ` — body background, CSS version 15 + - `internal/web/components/header.templ` — nav bar 16 + - `internal/web/components/footer.templ` — footer 17 + - `internal/web/components/shared.templ` — shared components (WelcomeCard, PageHeader, etc.) 18 + - `internal/web/pages/feed.templ` — feed card structure 19 + - `internal/web/components/record_*.templ` — feed content boxes (brew, bean, roaster, grinder, brewer) 20 + - `internal/web/components/entity_tables.templ` — entity list cards 21 + - `internal/web/components/action_bar.templ` — feed card wrappers 22 + - `internal/web/components/profile_brew_card.templ` — profile brew cards 23 + 24 + --- 25 + 26 + ## Task 1: CSS Custom Properties Foundation 27 + 28 + 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. 29 + 30 + **Files:** 31 + - Modify: `static/css/app.css` (add `:root` block at top, before `@tailwind` directives) 32 + - Modify: `tailwind.config.js` (add `cream` color) 33 + 34 + **Step 1: Add CSS custom properties to app.css** 35 + 36 + Add this block at the very top of `static/css/app.css`, before the `@tailwind` directives but after the `@font-face` declarations: 37 + 38 + ```css 39 + /* ======================================== 40 + Design Tokens (CSS Custom Properties) 41 + Light theme (default) 42 + ======================================== */ 43 + :root { 44 + /* Page */ 45 + --page-bg: #FAF7F5; 46 + --page-text: #3d2319; 47 + 48 + /* Cards */ 49 + --card-bg: #FFFFFF; 50 + --card-border: #eaddd7; 51 + --card-shadow: rgba(61, 35, 25, 0.06); 52 + --card-shadow-hover: rgba(61, 35, 25, 0.10); 53 + 54 + /* Surfaces (inset areas inside cards) */ 55 + --surface-bg: rgba(250, 247, 245, 0.5); 56 + --surface-border: #f2e8e5; 57 + 58 + /* Header */ 59 + --header-bg-from: #4a2c2a; 60 + --header-bg-to: #3d2319; 61 + --header-border: #7f5539; 62 + --header-text: #FAF7F5; 63 + 64 + /* Text hierarchy */ 65 + --text-primary: #3d2319; 66 + --text-secondary: #4a2c2a; 67 + --text-muted: #7f5539; 68 + --text-faint: #bfa094; 69 + --text-placeholder: #d2bab0; 70 + 71 + /* Interactive */ 72 + --btn-primary-bg: #4a2c2a; 73 + --btn-primary-bg-hover: #3d2319; 74 + --btn-primary-text: #FAF7F5; 75 + --btn-secondary-bg: #FFFFFF; 76 + --btn-secondary-border: #e0cec7; 77 + --btn-secondary-text: #6b4423; 78 + --btn-secondary-bg-hover: #FAF7F5; 79 + 80 + /* Forms */ 81 + --input-bg: #FFFFFF; 82 + --input-border: #e0cec7; 83 + --input-border-focus: #7f5539; 84 + --input-ring-focus: rgba(127, 85, 57, 0.15); 85 + --input-bg-focus: rgba(250, 247, 245, 0.3); 86 + 87 + /* Tables */ 88 + --table-bg: #FFFFFF; 89 + --table-header-bg: #FAF7F5; 90 + --table-border: #eaddd7; 91 + --table-row-hover: #FAF7F5; 92 + --table-divider: #f2e8e5; 93 + 94 + /* Modals */ 95 + --modal-bg: #FFFFFF; 96 + --modal-border: #eaddd7; 97 + --modal-backdrop: rgba(0, 0, 0, 0.4); 98 + 99 + /* Feed type indicators (left border) */ 100 + --type-brew: #6b4423; 101 + --type-bean: #d97706; 102 + --type-recipe: #bfa094; 103 + --type-roaster: #d2bab0; 104 + --type-grinder: #d2bab0; 105 + --type-brewer: #d2bab0; 106 + 107 + /* Shadows */ 108 + --shadow-sm: 0 1px 3px var(--card-shadow); 109 + --shadow-md: 0 4px 12px var(--card-shadow-hover); 110 + --shadow-lg: 0 10px 25px var(--card-shadow-hover); 111 + 112 + /* Footer */ 113 + --footer-bg: #FAF7F5; 114 + --footer-border: #eaddd7; 115 + } 116 + ``` 117 + 118 + **Step 2: Add `cream` color to tailwind.config.js** 119 + 120 + Add a `cream` color to the colors object in `tailwind.config.js`: 121 + 122 + ```js 123 + cream: { 124 + 50: "#FAF7F5", 125 + }, 126 + ``` 127 + 128 + This lets templates use `bg-cream-50` for the page background if needed. 129 + 130 + **Step 3: Verify build** 131 + 132 + Run: `just style && go vet ./...` 133 + Expected: Clean build, no errors. No visual changes yet (properties defined but not consumed). 134 + 135 + **Step 4: Commit** 136 + 137 + ```bash 138 + git add static/css/app.css tailwind.config.js 139 + git commit -m "feat: add CSS custom properties foundation for theme support" 140 + ``` 141 + 142 + --- 143 + 144 + ## Task 2: Redefine Core Component Classes 145 + 146 + 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. 147 + 148 + **Files:** 149 + - Modify: `static/css/app.css` (rewrite `@layer components` block) 150 + 151 + **Step 1: Rewrite card classes** 152 + 153 + Replace the existing card definitions: 154 + 155 + ```css 156 + /* Cards and Containers */ 157 + .card { 158 + background: var(--card-bg); 159 + border: 1px solid var(--card-border); 160 + @apply rounded-xl; 161 + box-shadow: var(--shadow-sm); 162 + transition: box-shadow 200ms ease; 163 + } 164 + 165 + .card:hover { 166 + box-shadow: var(--shadow-md); 167 + } 168 + 169 + .card-inner { 170 + @apply p-6; 171 + } 172 + 173 + .card-sm { 174 + background: var(--card-bg); 175 + border: 1px solid var(--card-border); 176 + @apply rounded-lg; 177 + box-shadow: var(--shadow-sm); 178 + } 179 + 180 + /* Section box for lighter content areas */ 181 + .section-box { 182 + background: var(--surface-bg); 183 + @apply rounded-lg p-4; 184 + } 185 + ``` 186 + 187 + **Step 2: Rewrite button classes** 188 + 189 + ```css 190 + /* Buttons */ 191 + .btn { 192 + @apply inline-flex items-center justify-center px-4 py-2 rounded-lg font-medium transition-colors cursor-pointer; 193 + } 194 + 195 + .btn-primary { 196 + @apply btn text-white; 197 + background: var(--btn-primary-bg); 198 + } 199 + 200 + .btn-primary:hover { 201 + background: var(--btn-primary-bg-hover); 202 + } 203 + 204 + .btn-secondary { 205 + @apply btn; 206 + background: var(--btn-secondary-bg); 207 + color: var(--btn-secondary-text); 208 + border: 1px solid var(--btn-secondary-border); 209 + } 210 + 211 + .btn-secondary:hover { 212 + background: var(--btn-secondary-bg-hover); 213 + } 214 + 215 + .btn-tertiary { 216 + @apply btn text-white; 217 + background: var(--btn-primary-bg); 218 + } 219 + 220 + .btn-tertiary:hover { 221 + background: var(--btn-primary-bg-hover); 222 + } 223 + 224 + .btn-link { 225 + color: var(--text-muted); 226 + @apply font-medium underline transition-colors cursor-pointer; 227 + } 228 + 229 + .btn-link:hover { 230 + color: var(--text-primary); 231 + } 232 + 233 + .btn-danger { 234 + @apply text-red-600 hover:text-red-800 font-medium underline transition-colors cursor-pointer; 235 + } 236 + ``` 237 + 238 + 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. 239 + 240 + **Step 3: Rewrite form classes** 241 + 242 + ```css 243 + /* Forms */ 244 + .form-label { 245 + @apply block text-sm font-medium mb-2; 246 + color: var(--text-primary); 247 + } 248 + 249 + .form-input { 250 + @apply rounded-lg shadow-sm text-base py-2 px-3; 251 + background: var(--input-bg); 252 + border: 1px solid var(--input-border); 253 + color: var(--text-primary); 254 + transition: border-color 150ms ease, box-shadow 150ms ease, background-color 150ms ease; 255 + } 256 + 257 + .form-input:focus { 258 + border-color: var(--input-border-focus); 259 + box-shadow: 0 0 0 2px var(--input-ring-focus); 260 + background: var(--input-bg-focus); 261 + outline: none; 262 + } 263 + 264 + .form-input::placeholder { 265 + color: var(--text-placeholder); 266 + } 267 + 268 + .form-input-lg { 269 + @apply form-input py-3 px-4; 270 + } 271 + 272 + .form-select { 273 + @apply form-input truncate max-w-full min-w-0; 274 + } 275 + 276 + .form-textarea { 277 + @apply form-input min-h-[100px]; 278 + } 279 + ``` 280 + 281 + **Step 4: Rewrite table classes** 282 + 283 + ```css 284 + /* Tables */ 285 + .table-container { 286 + background: var(--table-bg); 287 + border: 1px solid var(--table-border); 288 + @apply rounded-lg overflow-hidden; 289 + box-shadow: var(--shadow-sm); 290 + } 291 + 292 + .table { 293 + @apply min-w-full; 294 + border-collapse: collapse; 295 + } 296 + 297 + .table-header { 298 + background: var(--table-header-bg); 299 + border-bottom: 1px solid var(--table-border); 300 + } 301 + 302 + .table-th { 303 + @apply px-6 py-3 text-left text-xs font-medium uppercase tracking-wider; 304 + color: var(--text-muted); 305 + } 306 + 307 + .table-body { 308 + background: var(--table-bg); 309 + } 310 + 311 + .table-body tr { 312 + border-bottom: 1px solid var(--table-divider); 313 + } 314 + 315 + .table-body tr:last-child { 316 + border-bottom: none; 317 + } 318 + 319 + .table-row { 320 + transition: background-color 150ms ease; 321 + } 322 + 323 + .table-row:hover { 324 + background: var(--table-row-hover); 325 + } 326 + 327 + .table-td { 328 + @apply px-6 py-4 whitespace-nowrap text-sm; 329 + color: var(--text-secondary); 330 + } 331 + ``` 332 + 333 + **Step 5: Rewrite modal classes** 334 + 335 + ```css 336 + /* Modals */ 337 + .modal-backdrop { 338 + @apply fixed inset-0 flex items-center justify-center z-50 p-4; 339 + background: var(--modal-backdrop); 340 + backdrop-filter: blur(4px); 341 + } 342 + 343 + .modal-content { 344 + background: var(--modal-bg); 345 + border: 1px solid var(--modal-border); 346 + @apply rounded-xl p-6 max-w-md w-full max-h-[90vh] overflow-y-auto; 347 + box-shadow: var(--shadow-lg); 348 + } 349 + 350 + .modal-title { 351 + @apply text-xl font-semibold mb-4; 352 + color: var(--text-primary); 353 + } 354 + 355 + /* Native Dialog Element */ 356 + .modal-dialog { 357 + @apply p-0 bg-transparent border-none shadow-none max-w-md w-full; 358 + } 359 + 360 + .modal-dialog::backdrop { 361 + background: var(--modal-backdrop); 362 + backdrop-filter: blur(4px); 363 + } 364 + 365 + /* Dialog content wrapper (nested inside dialog) */ 366 + .modal-dialog .modal-content { 367 + background: var(--modal-bg); 368 + border: 1px solid var(--modal-border); 369 + @apply rounded-xl p-6 w-full max-h-[90vh] overflow-y-auto; 370 + box-shadow: var(--shadow-lg); 371 + } 372 + ``` 373 + 374 + **Step 6: Rewrite feed component classes** 375 + 376 + ```css 377 + /* Feed Components */ 378 + .feed-card { 379 + background: var(--card-bg); 380 + border: 1px solid var(--card-border); 381 + @apply rounded-lg p-3 sm:p-4 transition-shadow; 382 + box-shadow: var(--shadow-sm); 383 + } 384 + 385 + .feed-card:hover { 386 + box-shadow: var(--shadow-md); 387 + } 388 + 389 + .feed-content-box { 390 + background: var(--surface-bg); 391 + @apply rounded-lg p-3 sm:p-4; 392 + } 393 + 394 + .feed-content-box-sm { 395 + background: var(--surface-bg); 396 + @apply rounded-lg p-2 sm:p-3; 397 + } 398 + ``` 399 + 400 + 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. 401 + 402 + **Step 7: Rewrite remaining component classes** 403 + 404 + Update avatar, text utility, badge, link, action, dropdown, and comment classes. The key changes are: 405 + 406 + - Avatar rings: keep as-is (they're fine) 407 + - Text utilities: reference CSS variables 408 + - Badges: keep as-is (amber accent works) 409 + - Links: reference CSS variables 410 + - Action buttons: remove brown-100 background, use transparent with hover 411 + - Dropdowns: use card-bg variable 412 + - Comments: reference variables for borders/backgrounds 413 + 414 + For text utilities: 415 + ```css 416 + /* Text Utilities */ 417 + .text-helper { 418 + @apply text-sm mt-1; 419 + color: var(--text-muted); 420 + } 421 + 422 + .text-meta { 423 + @apply text-xs; 424 + color: var(--text-muted); 425 + } 426 + 427 + .text-meta-sm { 428 + @apply text-sm; 429 + color: var(--text-muted); 430 + } 431 + 432 + .text-label { 433 + color: var(--text-muted); 434 + } 435 + ``` 436 + 437 + For action buttons and bars: 438 + ```css 439 + /* Action Bar */ 440 + .action-bar { 441 + @apply flex items-center gap-2 mt-3 pt-3; 442 + border-top: 1px solid var(--surface-border); 443 + } 444 + 445 + .brew-view-actions .action-bar { 446 + @apply mt-0 pt-0 border-t-0; 447 + } 448 + 449 + .comment-item .action-bar { 450 + @apply mt-1 border-t-0 gap-1 rounded-lg px-1.5 py-1 inline-flex items-center; 451 + background: var(--surface-bg); 452 + } 453 + 454 + .action-btn { 455 + @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]; 456 + color: var(--text-muted); 457 + background: transparent; 458 + } 459 + 460 + .action-btn:hover { 461 + background: var(--surface-bg); 462 + color: var(--text-secondary); 463 + } 464 + ``` 465 + 466 + For dropdowns: 467 + ```css 468 + .action-menu { 469 + @apply absolute left-1/2 -translate-x-1/2 w-36 rounded-lg py-1 z-50; 470 + background: var(--card-bg); 471 + border: 1px solid var(--card-border); 472 + box-shadow: var(--shadow-md); 473 + } 474 + 475 + .dropdown-menu { 476 + @apply absolute right-0 mt-2 w-48 rounded-lg py-1 z-50; 477 + background: var(--card-bg); 478 + border: 1px solid var(--card-border); 479 + box-shadow: var(--shadow-md); 480 + } 481 + 482 + .dropdown-item { 483 + @apply block px-4 py-2 text-sm transition-colors; 484 + color: var(--text-muted); 485 + } 486 + 487 + .dropdown-item:hover { 488 + background: var(--surface-bg); 489 + } 490 + ``` 491 + 492 + For like/share/comment buttons, suggestions: 493 + ```css 494 + .like-btn { 495 + @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]; 496 + } 497 + 498 + .like-btn-liked { 499 + @apply like-btn text-red-600; 500 + background: transparent; 501 + animation: like-pop 400ms ease-out; 502 + } 503 + 504 + .like-btn-liked:hover { 505 + background: var(--surface-bg); 506 + } 507 + 508 + .like-btn-unliked { 509 + @apply like-btn; 510 + color: var(--text-muted); 511 + background: transparent; 512 + animation: like-shrink 200ms ease-out; 513 + } 514 + 515 + .like-btn-unliked:hover { 516 + background: var(--surface-bg); 517 + } 518 + 519 + .share-btn { 520 + @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]; 521 + color: var(--text-muted); 522 + background: transparent; 523 + } 524 + 525 + .share-btn:hover { 526 + background: var(--surface-bg); 527 + } 528 + 529 + .comment-btn { 530 + @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]; 531 + color: var(--text-muted); 532 + background: transparent; 533 + } 534 + 535 + .comment-btn:hover { 536 + background: var(--surface-bg); 537 + } 538 + ``` 539 + 540 + For suggestions dropdown: 541 + ```css 542 + .suggestions-dropdown { 543 + @apply absolute z-50 left-0 right-0 mt-1 rounded-lg max-h-48 overflow-y-auto; 544 + background: var(--card-bg); 545 + border: 1px solid var(--card-border); 546 + box-shadow: var(--shadow-md); 547 + } 548 + 549 + .suggestions-item { 550 + @apply w-full text-left px-3 py-2 flex items-center gap-2 transition-colors cursor-pointer last:border-b-0; 551 + border-bottom: 1px solid var(--surface-border); 552 + } 553 + 554 + .suggestions-item:hover { 555 + background: var(--surface-bg); 556 + } 557 + ``` 558 + 559 + For comments: 560 + ```css 561 + .comment-section { 562 + @apply mt-8 pt-6; 563 + border-top: 2px solid var(--card-border); 564 + } 565 + 566 + .comment-login-prompt { 567 + @apply flex items-center gap-3 rounded-lg p-4 mb-5 border border-dashed; 568 + background: var(--surface-bg); 569 + border-color: var(--card-border); 570 + } 571 + 572 + .comment-compose { 573 + @apply rounded-lg p-4 mb-5 flex flex-col gap-2; 574 + background: var(--surface-bg); 575 + border: 1px solid var(--card-border); 576 + } 577 + 578 + .comment-textarea { 579 + @apply w-full rounded-lg px-3 py-2.5 text-base resize-none transition-colors focus:ring-0 focus:outline-none; 580 + background: var(--card-bg); 581 + border: 1px solid var(--card-border); 582 + color: var(--text-primary); 583 + } 584 + 585 + .comment-textarea::placeholder { 586 + color: var(--text-placeholder); 587 + } 588 + 589 + .comment-textarea:focus { 590 + border-color: var(--input-border-focus); 591 + } 592 + 593 + .comment-item { 594 + @apply relative rounded-lg p-3 transition-colors; 595 + } 596 + 597 + .comment-item:hover { 598 + background: var(--surface-bg); 599 + } 600 + 601 + .comment-thread-line { 602 + @apply absolute left-0 top-3 bottom-3 w-0.5 rounded-full; 603 + background: var(--card-border); 604 + } 605 + 606 + .comment-reply-btn { 607 + @apply inline-flex items-center gap-1 transition-colors text-xs font-medium; 608 + color: var(--text-placeholder); 609 + } 610 + 611 + .comment-reply-btn:hover { 612 + color: var(--text-muted); 613 + } 614 + 615 + .comment-delete-btn { 616 + @apply transition-colors; 617 + color: var(--text-placeholder); 618 + } 619 + 620 + .comment-delete-btn:hover { 621 + color: var(--text-muted); 622 + } 623 + 624 + .comment-reply-form { 625 + @apply flex flex-col gap-2 rounded-lg p-3; 626 + background: var(--surface-bg); 627 + border: 1px solid var(--card-border); 628 + } 629 + ``` 630 + 631 + **Step 8: Rebuild CSS and verify build** 632 + 633 + Run: `just style && go vet ./... && go build ./...` 634 + Expected: Clean build. 635 + 636 + **Step 9: Commit** 637 + 638 + ```bash 639 + git add static/css/app.css 640 + git commit -m "feat: redefine component classes with Clean Craft styling and CSS variables" 641 + ``` 642 + 643 + --- 644 + 645 + ## Task 3: Update Layout, Header, and Footer 646 + 647 + Update the structural templates to use the new color system. 648 + 649 + **Files:** 650 + - Modify: `internal/web/components/layout.templ` 651 + - Modify: `internal/web/components/header.templ` 652 + - Modify: `internal/web/components/footer.templ` 653 + 654 + **Step 1: Update layout.templ** 655 + 656 + Change the `<html>` tag's inline background: 657 + ``` 658 + style="background-color: #fdf8f6;" → style="background-color: #FAF7F5;" 659 + ``` 660 + 661 + Change the `<body>` tag: 662 + ``` 663 + class="bg-brown-50 min-h-full flex flex-col" 664 + style="background-color: #fdf8f6;" 665 + ``` 666 + to: 667 + ``` 668 + class="min-h-full flex flex-col" 669 + style="background-color: var(--page-bg); color: var(--page-text);" 670 + ``` 671 + 672 + Bump the CSS version: 673 + ``` 674 + output.css?v=0.6.1 → output.css?v=0.7.0 675 + ``` 676 + 677 + **Step 2: Update header.templ** 678 + 679 + Change the nav element from: 680 + ``` 681 + 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" 682 + ``` 683 + to: 684 + ``` 685 + class="sticky top-0 z-50 text-white" 686 + style="background: linear-gradient(135deg, var(--header-bg-from), var(--header-bg-to)); border-bottom: 1px solid var(--header-border);" 687 + ``` 688 + 689 + 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. 690 + 691 + Reduce padding in the container div: 692 + ``` 693 + class="container mx-auto px-4 py-4" → class="container mx-auto px-4 py-3" 694 + ``` 695 + 696 + Make the ALPHA badge smaller: 697 + ``` 698 + class="text-xs bg-amber-400 text-brown-900 px-2 py-1 rounded-md font-semibold shadow-sm" 699 + ``` 700 + to: 701 + ``` 702 + class="text-[10px] bg-amber-400 text-brown-900 px-1.5 py-0.5 rounded font-semibold" 703 + ``` 704 + 705 + **Step 3: Update footer.templ** 706 + 707 + Change the footer from: 708 + ``` 709 + class="mt-auto border-t border-brown-200 bg-brown-50" 710 + ``` 711 + to: 712 + ``` 713 + class="mt-auto" 714 + style="background: var(--footer-bg); border-top: 1px solid var(--footer-border);" 715 + ``` 716 + 717 + **Step 4: Regenerate templ, rebuild CSS, verify** 718 + 719 + Run: `templ generate && just style && go vet ./... && go build ./...` 720 + Expected: Clean build. 721 + 722 + **Step 5: Commit** 723 + 724 + ```bash 725 + git add internal/web/components/layout.templ internal/web/components/header.templ internal/web/components/footer.templ 726 + git commit -m "feat: update layout, header, footer for Clean Craft theme" 727 + ``` 728 + 729 + --- 730 + 731 + ## Task 4: Add Feed Card Type Indicators 732 + 733 + Add colored left borders to feed cards to distinguish record types (brew, bean, recipe, etc.) at a glance. 734 + 735 + **Files:** 736 + - Modify: `static/css/app.css` (add type indicator classes) 737 + - Modify: `internal/web/components/action_bar.templ` (add type class to feed card wrapper) 738 + - Modify: `internal/web/pages/feed.templ` (add type class where feed cards are rendered) 739 + 740 + **Step 1: Add type indicator CSS classes** 741 + 742 + Add to `app.css` after the `.feed-card` definition: 743 + 744 + ```css 745 + /* Feed card type indicators */ 746 + .feed-card-brew { 747 + border-left: 3px solid var(--type-brew); 748 + } 749 + 750 + .feed-card-bean { 751 + border-left: 3px solid var(--type-bean); 752 + } 753 + 754 + .feed-card-recipe { 755 + border-left: 3px solid var(--type-recipe); 756 + } 757 + 758 + .feed-card-roaster { 759 + border-left: 3px solid var(--type-roaster); 760 + } 761 + 762 + .feed-card-grinder { 763 + border-left: 3px solid var(--type-grinder); 764 + } 765 + 766 + .feed-card-brewer { 767 + border-left: 3px solid var(--type-brewer); 768 + } 769 + ``` 770 + 771 + **Step 2: Identify where feed cards are rendered with type context** 772 + 773 + Read the following files to understand how the feed card type is available in the template context: 774 + - `internal/web/components/action_bar.templ` — the `FeedCard` component that wraps all feed items 775 + - `internal/web/pages/feed.templ` — where feed items are rendered 776 + 777 + 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. 778 + 779 + **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: 780 + 781 + ```go 782 + // In the feed card wrapper component, add the type class: 783 + class={ templ.Classes( 784 + "feed-card", 785 + templ.KV("feed-card-brew", props.Type == "brew"), 786 + templ.KV("feed-card-bean", props.Type == "bean"), 787 + // ... etc 788 + ) } 789 + ``` 790 + 791 + **Step 3: Rebuild and verify** 792 + 793 + Run: `templ generate && just style && go vet ./... && go build ./...` 794 + 795 + **Step 4: Commit** 796 + 797 + ```bash 798 + git add static/css/app.css internal/web/components/action_bar.templ internal/web/pages/feed.templ 799 + git commit -m "feat: add colored left-border type indicators to feed cards" 800 + ``` 801 + 802 + --- 803 + 804 + ## Task 5: Clean Up Template Inline Styles 805 + 806 + Several templates use inline Tailwind gradient classes and shadow overrides that bypass the component classes. These need updating to match the new system. 807 + 808 + **Files:** 809 + - Modify: `internal/web/components/shared.templ` 810 + - Modify: `internal/web/pages/about.templ` 811 + - Modify: `internal/web/pages/atproto.templ` 812 + 813 + **Step 1: Update shared.templ** 814 + 815 + In `WelcomeCard`: change `class="card p-8 mb-8"` — the `card` class now handles styling. Keep `p-8 mb-8`. 816 + 817 + In `WelcomeAuthenticated`: remove the inline `shadow-lg hover:shadow-xl` from button links. The `.btn-primary` and `.btn-tertiary` classes handle it now. Example: 818 + ``` 819 + class="btn-primary block text-center py-4 px-6 rounded-xl shadow-lg hover:shadow-xl" 820 + ``` 821 + becomes: 822 + ``` 823 + class="btn-primary block text-center py-4 px-6 rounded-xl" 824 + ``` 825 + 826 + In `EmptyState`: remove the inline `shadow-lg hover:shadow-xl` from the action link. 827 + 828 + In `PageHeader`: change the action button default from `"btn-primary shadow-lg hover:shadow-xl"` to just `"btn-primary"`. 829 + 830 + In `AboutInfoCard`: change from inline gradient classes: 831 + ``` 832 + class="bg-gradient-to-br from-amber-50 to-brown-100 rounded-xl p-6 border-2 border-brown-300 shadow-lg mb-6" 833 + ``` 834 + to: 835 + ``` 836 + class="card p-6 mb-6" 837 + ``` 838 + (or keep as a special card with amber tint if desired — read the actual usage context first) 839 + 840 + **Step 2: Update about.templ and atproto.templ** 841 + 842 + These pages use extensive inline gradient classes for feature sections. Read each file and replace: 843 + - `bg-gradient-to-br from-brown-100 to-brown-200` → `card` class or inline `background: var(--card-bg);` 844 + - `shadow-xl` / `shadow-lg` → remove (cards get shadow from class) 845 + - `border-2 border-brown-300` → `border border-brown-200` or let card class handle it 846 + 847 + Be careful with these pages — they have custom layouts. Don't break the structure, just update the color/shadow treatment. 848 + 849 + **Step 3: Rebuild and verify** 850 + 851 + Run: `templ generate && just style && go vet ./... && go build ./...` 852 + 853 + **Step 4: Commit** 854 + 855 + ```bash 856 + git add internal/web/components/shared.templ internal/web/pages/about.templ internal/web/pages/atproto.templ 857 + git commit -m "refactor: remove inline gradient/shadow overrides from templates" 858 + ``` 859 + 860 + --- 861 + 862 + ## Task 6: Typography Refinements 863 + 864 + Downsize the typography scale and update section title treatment. 865 + 866 + **Files:** 867 + - Modify: `static/css/app.css` (update typography classes) 868 + - Modify: `internal/web/components/shared.templ` (PageHeader title size) 869 + 870 + **Step 1: Update typography classes in app.css** 871 + 872 + ```css 873 + /* Typography */ 874 + .section-title { 875 + @apply text-xs font-semibold uppercase tracking-widest mb-4; 876 + color: var(--text-faint); 877 + } 878 + 879 + .page-title { 880 + @apply text-2xl font-semibold; 881 + color: var(--text-primary); 882 + } 883 + ``` 884 + 885 + **Step 2: Update PageHeader in shared.templ** 886 + 887 + Change the heading from `text-3xl font-bold` to use the `.page-title` class: 888 + ``` 889 + <h2 class="text-3xl font-bold text-brown-900">{ props.Title }</h2> 890 + ``` 891 + becomes: 892 + ``` 893 + <h2 class="page-title">{ props.Title }</h2> 894 + ``` 895 + 896 + **Step 3: Search for other `text-3xl` usages** 897 + 898 + Grep for `text-3xl` across templ files. Update each to `text-2xl font-semibold` or use `.page-title` class. Key locations: 899 + - `manage.templ` — page title 900 + - `notifications.templ` — page title 901 + - `recipe_explore.templ` — page title 902 + - `brew_form.templ` — page title 903 + - `shared.templ` — WelcomeCard title 904 + 905 + **Step 4: Rebuild and verify** 906 + 907 + Run: `templ generate && just style && go vet ./... && go build ./...` 908 + 909 + **Step 5: Commit** 910 + 911 + ```bash 912 + git add static/css/app.css internal/web/components/shared.templ [other modified templ files] 913 + git commit -m "feat: refine typography scale — smaller titles, uppercase section labels" 914 + ``` 915 + 916 + --- 917 + 918 + ## Task 7: Remove Table Row Stagger Animation 919 + 920 + The stagger animation on table rows feels gimmicky for data tables. Remove it while keeping feed card stagger. 921 + 922 + **Files:** 923 + - Modify: `static/css/app.css` (remove table row animation rules) 924 + 925 + **Step 1: Remove table row stagger CSS** 926 + 927 + Delete these rules from app.css (around lines 556-577): 928 + 929 + ```css 930 + /* Table rows slide in with stagger effect (dynamic content) */ 931 + .table-body tr { 932 + animation: fade-in-slide-up 300ms ease-out backwards; 933 + } 934 + 935 + .table-body tr:nth-child(1) { animation-delay: 0ms; } 936 + .table-body tr:nth-child(2) { animation-delay: 30ms; } 937 + .table-body tr:nth-child(3) { animation-delay: 60ms; } 938 + .table-body tr:nth-child(4) { animation-delay: 90ms; } 939 + .table-body tr:nth-child(5) { animation-delay: 120ms; } 940 + .table-body tr:nth-child(n + 6) { animation-delay: 150ms; } 941 + ``` 942 + 943 + **Step 2: Rebuild** 944 + 945 + Run: `just style` 946 + 947 + **Step 3: Commit** 948 + 949 + ```bash 950 + git add static/css/app.css 951 + git commit -m "refactor: remove table row stagger animation" 952 + ``` 953 + 954 + --- 955 + 956 + ## Task 8: Update Form Input Focus Behavior 957 + 958 + 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. 959 + 960 + **Files:** 961 + - Modify: `static/css/app.css` (remove focus transform rules) 962 + 963 + **Step 1: Remove focus transform** 964 + 965 + Delete these rules (around lines 647-660): 966 + 967 + ```css 968 + .form-input:focus, 969 + .form-select:focus, 970 + .form-textarea:focus { 971 + transform: translateY(-1px); 972 + } 973 + ``` 974 + 975 + Also update the transition rule to remove `transform`: 976 + ```css 977 + .form-input, 978 + .form-select, 979 + .form-textarea { 980 + transition: 981 + border-color 100ms ease, 982 + box-shadow 100ms ease, 983 + transform 50ms ease; 984 + } 985 + ``` 986 + Change to: 987 + ```css 988 + .form-input, 989 + .form-select, 990 + .form-textarea { 991 + transition: 992 + border-color 150ms ease, 993 + box-shadow 150ms ease, 994 + background-color 150ms ease; 995 + } 996 + ``` 997 + 998 + 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. 999 + 1000 + **Step 2: Rebuild** 1001 + 1002 + Run: `just style` 1003 + 1004 + **Step 3: Commit** 1005 + 1006 + ```bash 1007 + git add static/css/app.css 1008 + git commit -m "refactor: replace form focus lift with background tint transition" 1009 + ``` 1010 + 1011 + --- 1012 + 1013 + ## Task 9: Visual QA and Polish 1014 + 1015 + Manual review pass to catch inconsistencies. 1016 + 1017 + **Files:** Various — depends on findings. 1018 + 1019 + **Step 1: Run the dev server** 1020 + 1021 + Run: `go run cmd/server/main.go` 1022 + 1023 + **Step 2: Visual checklist** 1024 + 1025 + Check each page and verify: 1026 + 1027 + - [ ] **Home page:** WelcomeCard renders as white card on cream background. No gradient. Login form inputs have 1px borders. 1028 + - [ ] **Feed:** Feed cards are white with subtle shadow. Type indicators show colored left borders. Action buttons are transparent (no background) until hover. 1029 + - [ ] **Brew form:** All inputs have 1px borders. Focus shows tint change + ring. No translateY lift. 1030 + - [ ] **Brew view:** Detail fields use section-box with subtle tint. Card is white. 1031 + - [ ] **Manage page:** Tables are white with light header. No gradient backgrounds. 1032 + - [ ] **Profile:** Stats cards are white. Tab content loads properly. 1033 + - [ ] **Recipe explore:** Cards are white. Detail panel matches. 1034 + - [ ] **Modals:** White background, subtle border, no gradient. 1035 + - [ ] **Header:** Slightly shorter, ALPHA badge smaller. Shadow subtle or absent. 1036 + - [ ] **Footer:** Clean, matches cream background. 1037 + - [ ] **Mobile:** Check all of the above at < 640px width. 1038 + 1039 + **Step 3: Fix any issues found** 1040 + 1041 + Address visual inconsistencies discovered during QA. Common issues to watch for: 1042 + - Templates with hardcoded `bg-brown-100` or `bg-brown-50` that should now be `bg-white` or use variables 1043 + - Inline `shadow-*` classes that override the component class shadow 1044 + - `border-2` on inputs that weren't caught in the component class rewrite (inline overrides in templates) 1045 + - Text color classes that should be updated (`text-brown-800` → just inherit from parent or use variable) 1046 + 1047 + **Step 4: Commit fixes** 1048 + 1049 + ```bash 1050 + git add -A 1051 + git commit -m "fix: visual QA polish for Clean Craft overhaul" 1052 + ``` 1053 + 1054 + --- 1055 + 1056 + ## Task 10: Bump CSS Version and Final Build Check 1057 + 1058 + **Files:** 1059 + - Modify: `internal/web/components/layout.templ` (verify CSS version bumped) 1060 + 1061 + **Step 1: Verify CSS version** 1062 + 1063 + The version should already be `0.7.0` from Task 3. Confirm it's correct. 1064 + 1065 + **Step 2: Full build and vet** 1066 + 1067 + Run: `templ generate && just style && go vet ./... && go build ./... && go test ./...` 1068 + 1069 + Expected: All pass. 1070 + 1071 + **Step 3: Final commit if needed** 1072 + 1073 + If any last fixes were made: 1074 + ```bash 1075 + git add -A 1076 + git commit -m "chore: final Clean Craft overhaul build verification" 1077 + ``` 1078 + 1079 + --- 1080 + 1081 + ## Future Work (Not in This Plan) 1082 + 1083 + These are noted for later and should NOT be done in this implementation: 1084 + 1085 + 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. 1086 + 1087 + 2. **SVG icon system:** Replace emoji icons (📍🔥🌱⚖️🏭) with SVG icons from Lucide or Phosphor. Separate task, requires icon selection and template updates. 1088 + 1089 + 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
··· 69 69 users (not sure how they would be rated though, since that would probably 70 70 need to be part of this) 71 71 72 + *** Using Other User's Recipes In Brews 73 + 74 + Currently, this behavior does not work as expected, as the server tries to 75 + look up the record in the logged-in user's PDS, rather than the one 76 + belonging to the owner. This prevents other users from using recipes that 77 + don't belong to them and is not the intended behavior. 78 + 79 + (Brewer fuzzy finding might also not work, but its hard to say since the 80 + recipe lookup fails first) 81 + 72 82 ** Open Questions 73 83 74 84 For links between a brew and recipe, which should have the optional ref?
+987
docs/ui-comparison.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Arabica UI Overhaul — Option Comparison</title> 7 + <style> 8 + /* ===== Shared Font ===== */ 9 + @font-face { 10 + font-family: 'Iosevka Patrick'; 11 + src: url('../static/fonts/IosevkaPatrickNerdFont-Regular.woff2') format('woff2'); 12 + font-weight: 400; 13 + font-style: normal; 14 + font-display: swap; 15 + } 16 + @font-face { 17 + font-family: 'Iosevka Patrick'; 18 + src: url('../static/fonts/IosevkaPatrickNerdFont-Medium.woff2') format('woff2'); 19 + font-weight: 500; 20 + font-style: normal; 21 + font-display: swap; 22 + } 23 + @font-face { 24 + font-family: 'Iosevka Patrick'; 25 + src: url('../static/fonts/IosevkaPatrickNerdFont-SemiBold.woff2') format('woff2'); 26 + font-weight: 600; 27 + font-style: normal; 28 + font-display: swap; 29 + } 30 + 31 + * { margin: 0; padding: 0; box-sizing: border-box; } 32 + 33 + body { 34 + font-family: 'Iosevka Patrick', ui-monospace, monospace; 35 + background: #1a1a1a; 36 + color: #ccc; 37 + min-height: 100vh; 38 + } 39 + 40 + /* ===== Layout ===== */ 41 + .picker { 42 + display: flex; 43 + justify-content: center; 44 + gap: 12px; 45 + padding: 24px 16px 16px; 46 + position: sticky; 47 + top: 0; 48 + z-index: 100; 49 + background: #1a1a1a; 50 + border-bottom: 1px solid #333; 51 + } 52 + .picker button { 53 + font-family: inherit; 54 + padding: 8px 24px; 55 + border-radius: 8px; 56 + border: 1px solid #444; 57 + background: #222; 58 + color: #aaa; 59 + font-size: 13px; 60 + font-weight: 500; 61 + cursor: pointer; 62 + transition: all 150ms; 63 + } 64 + .picker button:hover { border-color: #666; color: #ddd; } 65 + .picker button.active-a { background: #FAF7F5; color: #3d2319; border-color: #d2bab0; } 66 + .picker button.active-b { background: #1C1210; color: #FAF7F5; border-color: #3D2D24; box-shadow: 0 0 12px rgba(251,191,36,0.15); } 67 + 68 + .panels { 69 + display: grid; 70 + grid-template-columns: 1fr 1fr; 71 + gap: 0; 72 + max-width: 1400px; 73 + margin: 0 auto; 74 + } 75 + @media (max-width: 900px) { 76 + .panels { grid-template-columns: 1fr; } 77 + } 78 + 79 + .panel { 80 + padding: 32px 24px; 81 + min-height: 100vh; 82 + } 83 + .panel-label { 84 + font-size: 11px; 85 + font-weight: 600; 86 + letter-spacing: 0.1em; 87 + text-transform: uppercase; 88 + margin-bottom: 24px; 89 + padding-bottom: 8px; 90 + } 91 + .component-group { 92 + margin-bottom: 32px; 93 + } 94 + .component-group h3 { 95 + font-size: 11px; 96 + font-weight: 500; 97 + text-transform: uppercase; 98 + letter-spacing: 0.08em; 99 + margin-bottom: 12px; 100 + opacity: 0.5; 101 + } 102 + 103 + /* ===== Divider ===== */ 104 + .divider { 105 + width: 1px; 106 + background: #333; 107 + position: absolute; 108 + left: 50%; 109 + top: 0; 110 + bottom: 0; 111 + } 112 + 113 + /* ========================================================== 114 + OPTION A: "Clean Craft" 115 + ========================================================== */ 116 + .a { 117 + background: #FAF7F5; 118 + color: #3d2319; 119 + } 120 + .a .panel-label { 121 + color: #bfa094; 122 + border-bottom: 1px solid #eaddd7; 123 + } 124 + .a .component-group h3 { color: #7f5539; } 125 + 126 + /* -- A: Header -- */ 127 + .a-header { 128 + display: flex; 129 + align-items: center; 130 + justify-content: space-between; 131 + background: linear-gradient(135deg, #4a2c2a, #3d2319); 132 + padding: 0 16px; 133 + height: 48px; 134 + border-radius: 10px 10px 0 0; 135 + margin-bottom: 0; 136 + } 137 + .a-header-logo { 138 + color: #FAF7F5; 139 + font-weight: 600; 140 + font-size: 14px; 141 + display: flex; 142 + align-items: center; 143 + gap: 8px; 144 + } 145 + .a-header-badge { 146 + font-size: 9px; 147 + font-weight: 600; 148 + background: #fbbf24; 149 + color: #3d2319; 150 + padding: 2px 6px; 151 + border-radius: 4px; 152 + letter-spacing: 0.05em; 153 + } 154 + .a-header-avatar { 155 + width: 28px; 156 + height: 28px; 157 + border-radius: 50%; 158 + background: #6b4423; 159 + border: 2px solid #bfa094; 160 + display: flex; 161 + align-items: center; 162 + justify-content: center; 163 + color: #e0cec7; 164 + font-size: 11px; 165 + font-weight: 500; 166 + } 167 + 168 + /* -- A: Cards -- */ 169 + .a-card { 170 + background: #FFFFFF; 171 + border: 1px solid #eaddd7; 172 + border-radius: 12px; 173 + padding: 16px; 174 + box-shadow: 0 1px 3px rgba(61,35,25,0.06); 175 + transition: box-shadow 200ms; 176 + } 177 + .a-card:hover { 178 + box-shadow: 0 4px 12px rgba(61,35,25,0.1); 179 + } 180 + 181 + /* -- A: Feed Card -- */ 182 + .a-feed-card { 183 + background: #FFFFFF; 184 + border: 1px solid #eaddd7; 185 + border-left: 3px solid #6b4423; 186 + border-radius: 12px; 187 + padding: 16px; 188 + box-shadow: 0 1px 3px rgba(61,35,25,0.06); 189 + transition: box-shadow 200ms; 190 + } 191 + .a-feed-card:hover { 192 + box-shadow: 0 4px 12px rgba(61,35,25,0.1); 193 + } 194 + .a-feed-card.type-bean { border-left-color: #d97706; } 195 + .a-feed-card.type-recipe { border-left-color: #bfa094; } 196 + 197 + .a-feed-meta { 198 + display: flex; 199 + align-items: center; 200 + gap: 8px; 201 + margin-bottom: 8px; 202 + } 203 + .a-feed-avatar { 204 + width: 28px; 205 + height: 28px; 206 + border-radius: 50%; 207 + background: #e0cec7; 208 + flex-shrink: 0; 209 + display: flex; 210 + align-items: center; 211 + justify-content: center; 212 + font-size: 11px; 213 + font-weight: 500; 214 + color: #7f5539; 215 + } 216 + .a-feed-meta-text { 217 + font-size: 12px; 218 + color: #7f5539; 219 + } 220 + .a-feed-meta-text strong { 221 + color: #3d2319; 222 + font-weight: 600; 223 + } 224 + .a-feed-action { 225 + font-size: 13px; 226 + color: #4a2c2a; 227 + margin-bottom: 12px; 228 + } 229 + .a-feed-action a { 230 + color: #6b4423; 231 + text-decoration: underline; 232 + text-underline-offset: 2px; 233 + text-decoration-color: #d2bab0; 234 + } 235 + 236 + .a-feed-inset { 237 + background: rgba(253,248,246,0.5); 238 + border-radius: 8px; 239 + padding: 12px; 240 + margin-bottom: 12px; 241 + } 242 + .a-feed-inset-title { 243 + font-size: 13px; 244 + font-weight: 600; 245 + color: #3d2319; 246 + margin-bottom: 4px; 247 + } 248 + .a-feed-inset-detail { 249 + font-size: 12px; 250 + color: #7f5539; 251 + line-height: 1.6; 252 + } 253 + .a-feed-inset-detail .val { 254 + color: #4a2c2a; 255 + font-weight: 500; 256 + } 257 + .a-rating { 258 + display: inline-flex; 259 + align-items: center; 260 + gap: 4px; 261 + font-size: 12px; 262 + font-weight: 500; 263 + background: #fef3c7; 264 + color: #78350f; 265 + padding: 2px 10px; 266 + border-radius: 99px; 267 + } 268 + .a-tasting { 269 + font-size: 12px; 270 + font-style: italic; 271 + color: #4a2c2a; 272 + margin-top: 8px; 273 + padding-top: 8px; 274 + border-top: 1px solid #f2e8e5; 275 + } 276 + 277 + .a-action-bar { 278 + display: flex; 279 + align-items: center; 280 + gap: 4px; 281 + padding-top: 8px; 282 + } 283 + .a-action-btn { 284 + display: inline-flex; 285 + align-items: center; 286 + gap: 4px; 287 + padding: 6px 10px; 288 + border-radius: 6px; 289 + font-size: 12px; 290 + color: #7f5539; 291 + background: transparent; 292 + border: none; 293 + cursor: pointer; 294 + font-family: inherit; 295 + transition: background 150ms; 296 + } 297 + .a-action-btn:hover { background: #f2e8e5; } 298 + .a-action-btn svg { width: 14px; height: 14px; } 299 + 300 + /* -- A: Buttons -- */ 301 + .a-btn-primary { 302 + display: inline-flex; 303 + align-items: center; 304 + justify-content: center; 305 + padding: 8px 20px; 306 + border-radius: 8px; 307 + font-family: inherit; 308 + font-size: 13px; 309 + font-weight: 500; 310 + background: #4a2c2a; 311 + color: #FAF7F5; 312 + border: none; 313 + cursor: pointer; 314 + transition: background 150ms; 315 + } 316 + .a-btn-primary:hover { background: #3d2319; } 317 + 318 + .a-btn-secondary { 319 + display: inline-flex; 320 + align-items: center; 321 + justify-content: center; 322 + padding: 8px 20px; 323 + border-radius: 8px; 324 + font-family: inherit; 325 + font-size: 13px; 326 + font-weight: 500; 327 + background: #FFFFFF; 328 + color: #6b4423; 329 + border: 1px solid #e0cec7; 330 + cursor: pointer; 331 + transition: all 150ms; 332 + } 333 + .a-btn-secondary:hover { background: #FAF7F5; border-color: #d2bab0; } 334 + 335 + /* -- A: Form -- */ 336 + .a-form-group { margin-bottom: 12px; } 337 + .a-form-label { 338 + display: block; 339 + font-size: 11px; 340 + font-weight: 500; 341 + color: #3d2319; 342 + margin-bottom: 4px; 343 + text-transform: uppercase; 344 + letter-spacing: 0.03em; 345 + } 346 + .a-form-input { 347 + width: 100%; 348 + padding: 8px 12px; 349 + border-radius: 8px; 350 + border: 1px solid #e0cec7; 351 + font-family: inherit; 352 + font-size: 13px; 353 + color: #3d2319; 354 + background: #FFFFFF; 355 + outline: none; 356 + transition: all 150ms; 357 + } 358 + .a-form-input:focus { 359 + border-color: #7f5539; 360 + box-shadow: 0 0 0 2px rgba(127,85,57,0.15); 361 + background: rgba(253,248,246,0.3); 362 + } 363 + .a-form-input::placeholder { color: #d2bab0; } 364 + 365 + /* -- A: Section Title -- */ 366 + .a-section-title { 367 + font-size: 11px; 368 + font-weight: 600; 369 + color: #bfa094; 370 + text-transform: uppercase; 371 + letter-spacing: 0.1em; 372 + margin-bottom: 12px; 373 + } 374 + 375 + /* -- A: Table -- */ 376 + .a-table-wrap { 377 + background: #FFFFFF; 378 + border: 1px solid #eaddd7; 379 + border-radius: 10px; 380 + overflow: hidden; 381 + box-shadow: 0 1px 3px rgba(61,35,25,0.06); 382 + } 383 + .a-table { width: 100%; border-collapse: collapse; } 384 + .a-table th { 385 + text-align: left; 386 + padding: 10px 14px; 387 + font-size: 10px; 388 + font-weight: 600; 389 + color: #7f5539; 390 + text-transform: uppercase; 391 + letter-spacing: 0.08em; 392 + background: #FAF7F5; 393 + border-bottom: 1px solid #eaddd7; 394 + } 395 + .a-table td { 396 + padding: 10px 14px; 397 + font-size: 12px; 398 + color: #4a2c2a; 399 + border-bottom: 1px solid #f2e8e5; 400 + } 401 + .a-table tr:last-child td { border-bottom: none; } 402 + .a-table tr:hover td { background: #FAF7F5; } 403 + 404 + 405 + /* ========================================================== 406 + OPTION B: "Roasted" 407 + ========================================================== */ 408 + .b { 409 + background: #0F0A08; 410 + color: #FAF7F5; 411 + } 412 + .b .panel-label { 413 + color: #C4A898; 414 + border-bottom: 1px solid #2E211B; 415 + } 416 + .b .component-group h3 { color: #C4A898; } 417 + 418 + /* grain */ 419 + .b { position: relative; } 420 + .b::before { 421 + content: ""; 422 + position: absolute; 423 + inset: 0; 424 + pointer-events: none; 425 + opacity: 0.04; 426 + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); 427 + background-repeat: repeat; 428 + background-size: 256px; 429 + z-index: 1; 430 + } 431 + .b > * { position: relative; z-index: 2; } 432 + 433 + /* -- B: Header -- */ 434 + .b-header { 435 + display: flex; 436 + align-items: center; 437 + justify-content: space-between; 438 + background: #0F0A08; 439 + padding: 0 16px; 440 + height: 48px; 441 + border-radius: 10px 10px 0 0; 442 + border-bottom: 1px solid #2E211B; 443 + } 444 + .b-header-logo { 445 + color: #FAF7F5; 446 + font-weight: 600; 447 + font-size: 14px; 448 + display: flex; 449 + align-items: center; 450 + gap: 8px; 451 + } 452 + .b-header-badge { 453 + font-size: 9px; 454 + font-weight: 600; 455 + background: #fbbf24; 456 + color: #0F0A08; 457 + padding: 2px 6px; 458 + border-radius: 4px; 459 + letter-spacing: 0.05em; 460 + } 461 + .b-header-avatar { 462 + width: 28px; 463 + height: 28px; 464 + border-radius: 50%; 465 + background: #3D2D24; 466 + border: 2px solid #C4A898; 467 + display: flex; 468 + align-items: center; 469 + justify-content: center; 470 + color: #E0CEC4; 471 + font-size: 11px; 472 + font-weight: 500; 473 + } 474 + 475 + /* -- B: Cards -- */ 476 + .b-card { 477 + background: #1C1210; 478 + border: 1px solid #2E211B; 479 + border-radius: 12px; 480 + padding: 16px; 481 + transition: background 200ms, border-color 200ms; 482 + } 483 + .b-card:hover { 484 + background: #241A16; 485 + border-color: #3D2D24; 486 + } 487 + 488 + /* -- B: Feed Card -- */ 489 + .b-feed-card { 490 + background: #1C1210; 491 + border: 1px solid #2E211B; 492 + border-radius: 12px; 493 + padding: 16px; 494 + transition: background 200ms, border-color 200ms; 495 + } 496 + .b-feed-card:hover { 497 + background: #241A16; 498 + border-color: #3D2D24; 499 + } 500 + 501 + .b-feed-meta { 502 + display: flex; 503 + align-items: center; 504 + gap: 8px; 505 + margin-bottom: 8px; 506 + } 507 + .b-feed-avatar { 508 + width: 28px; 509 + height: 28px; 510 + border-radius: 50%; 511 + background: #3D2D24; 512 + flex-shrink: 0; 513 + display: flex; 514 + align-items: center; 515 + justify-content: center; 516 + font-size: 11px; 517 + font-weight: 500; 518 + color: #C4A898; 519 + } 520 + .b-feed-meta-text { 521 + font-size: 12px; 522 + color: #C4A898; 523 + } 524 + .b-feed-meta-text strong { 525 + color: #FAF7F5; 526 + font-weight: 600; 527 + } 528 + .b-type-dot { 529 + display: inline-block; 530 + width: 6px; 531 + height: 6px; 532 + border-radius: 50%; 533 + background: #fbbf24; 534 + margin-right: 4px; 535 + vertical-align: middle; 536 + } 537 + .b-type-dot.bean { background: #E0CEC4; } 538 + .b-type-dot.recipe { background: #C4553A; } 539 + 540 + .b-feed-action { 541 + font-size: 13px; 542 + color: #E0CEC4; 543 + margin-bottom: 12px; 544 + } 545 + .b-feed-action a { 546 + color: #fbbf24; 547 + text-decoration: underline; 548 + text-underline-offset: 2px; 549 + text-decoration-color: rgba(251,191,36,0.3); 550 + } 551 + 552 + .b-feed-inset { 553 + background: #241A16; 554 + border-radius: 8px; 555 + padding: 12px; 556 + margin-bottom: 12px; 557 + } 558 + .b-feed-inset-title { 559 + font-size: 13px; 560 + font-weight: 600; 561 + color: #FAF7F5; 562 + margin-bottom: 4px; 563 + } 564 + .b-feed-inset-detail { 565 + font-size: 12px; 566 + color: #C4A898; 567 + line-height: 1.6; 568 + } 569 + .b-feed-inset-detail .val { 570 + color: #FAF7F5; 571 + font-weight: 500; 572 + } 573 + .b-rating { 574 + display: inline-flex; 575 + align-items: center; 576 + gap: 4px; 577 + font-size: 12px; 578 + font-weight: 500; 579 + background: rgba(251,191,36,0.15); 580 + color: #fbbf24; 581 + padding: 2px 10px; 582 + border-radius: 99px; 583 + } 584 + .b-tasting { 585 + font-size: 12px; 586 + font-style: italic; 587 + color: #C4A898; 588 + margin-top: 8px; 589 + padding-top: 8px; 590 + border-top: 1px solid #2E211B; 591 + } 592 + 593 + .b-action-bar { 594 + display: flex; 595 + align-items: center; 596 + gap: 4px; 597 + padding-top: 8px; 598 + } 599 + .b-action-btn { 600 + display: inline-flex; 601 + align-items: center; 602 + gap: 4px; 603 + padding: 6px 10px; 604 + border-radius: 6px; 605 + font-size: 12px; 606 + color: #C4A898; 607 + background: transparent; 608 + border: none; 609 + cursor: pointer; 610 + font-family: inherit; 611 + transition: all 150ms; 612 + } 613 + .b-action-btn:hover { background: #241A16; color: #FAF7F5; } 614 + .b-action-btn svg { width: 14px; height: 14px; } 615 + 616 + /* -- B: Section Title -- */ 617 + .b-section-title { 618 + font-size: 11px; 619 + font-weight: 600; 620 + color: #fbbf24; 621 + text-transform: uppercase; 622 + letter-spacing: 0.15em; 623 + margin-bottom: 12px; 624 + } 625 + 626 + /* -- B: Buttons -- */ 627 + .b-btn-primary { 628 + display: inline-flex; 629 + align-items: center; 630 + justify-content: center; 631 + padding: 8px 20px; 632 + border-radius: 8px; 633 + font-family: inherit; 634 + font-size: 13px; 635 + font-weight: 500; 636 + background: linear-gradient(135deg, #C4553A, #A3412D); 637 + color: #FAF7F5; 638 + border: none; 639 + cursor: pointer; 640 + transition: all 150ms; 641 + } 642 + .b-btn-primary:hover { filter: brightness(1.1); } 643 + 644 + .b-btn-secondary { 645 + display: inline-flex; 646 + align-items: center; 647 + justify-content: center; 648 + padding: 8px 20px; 649 + border-radius: 8px; 650 + font-family: inherit; 651 + font-size: 13px; 652 + font-weight: 500; 653 + background: #241A16; 654 + color: #E0CEC4; 655 + border: 1px solid #3D2D24; 656 + cursor: pointer; 657 + transition: all 150ms; 658 + } 659 + .b-btn-secondary:hover { background: #2E211B; border-color: #4a3830; } 660 + 661 + /* -- B: Form -- */ 662 + .b-form-group { margin-bottom: 12px; } 663 + .b-form-label { 664 + display: block; 665 + font-size: 11px; 666 + font-weight: 500; 667 + color: #C4A898; 668 + margin-bottom: 4px; 669 + text-transform: uppercase; 670 + letter-spacing: 0.05em; 671 + } 672 + .b-form-input { 673 + width: 100%; 674 + padding: 8px 12px; 675 + border-radius: 8px; 676 + border: 1px solid #3D2D24; 677 + font-family: inherit; 678 + font-size: 13px; 679 + color: #FAF7F5; 680 + background: #241A16; 681 + outline: none; 682 + transition: all 150ms; 683 + } 684 + .b-form-input:focus { 685 + border-color: #fbbf24; 686 + box-shadow: 0 0 0 2px rgba(251,191,36,0.15); 687 + } 688 + .b-form-input::placeholder { color: #5a4a40; } 689 + 690 + /* -- B: Table -- */ 691 + .b-table-wrap { 692 + background: #1C1210; 693 + border: 1px solid #2E211B; 694 + border-radius: 10px; 695 + overflow: hidden; 696 + } 697 + .b-table { width: 100%; border-collapse: collapse; } 698 + .b-table th { 699 + text-align: left; 700 + padding: 10px 14px; 701 + font-size: 10px; 702 + font-weight: 600; 703 + color: #C4A898; 704 + text-transform: uppercase; 705 + letter-spacing: 0.08em; 706 + background: #241A16; 707 + border-bottom: 1px solid #2E211B; 708 + } 709 + .b-table td { 710 + padding: 10px 14px; 711 + font-size: 12px; 712 + color: #E0CEC4; 713 + border-bottom: 1px solid #1C1210; 714 + } 715 + .b-table tr:last-child td { border-bottom: none; } 716 + .b-table tr:hover td { background: #241A16; } 717 + </style> 718 + </head> 719 + <body> 720 + 721 + <div class="picker"> 722 + <button class="active-a" disabled>← Option A: "Clean Craft"</button> 723 + <button class="active-b" disabled>Option B: "Roasted" →</button> 724 + </div> 725 + 726 + <div class="panels" style="position:relative;"> 727 + 728 + <!-- ==================== OPTION A ==================== --> 729 + <div class="panel a"> 730 + <div class="panel-label">Option A — "Clean Craft"</div> 731 + 732 + <!-- Header --> 733 + <div class="component-group"> 734 + <h3>Header</h3> 735 + <div class="a-header"> 736 + <div class="a-header-logo">☕ arabica <span class="a-header-badge">ALPHA</span></div> 737 + <div class="a-header-avatar">PK</div> 738 + </div> 739 + </div> 740 + 741 + <!-- Feed Card: Brew --> 742 + <div class="component-group"> 743 + <h3>Feed Card — Brew</h3> 744 + <div class="a-feed-card"> 745 + <div class="a-feed-meta"> 746 + <div class="a-feed-avatar">JM</div> 747 + <div class="a-feed-meta-text"><strong>Jordan M.</strong> · @jordan.coffee · 2h</div> 748 + </div> 749 + <div class="a-feed-action">brewed with <a href="#">Ethiopian Sidamo</a></div> 750 + <div class="a-feed-inset"> 751 + <div class="a-feed-inset-title">Sweet Bloom · V60</div> 752 + <div class="a-feed-inset-detail"> 753 + <span class="val">15g</span> → <span class="val">250g</span> · <span class="val">1:16.7</span> · <span class="val">93°C</span> · <span class="val">3:15</span> 754 + </div> 755 + <div style="margin-top:6px;"><span class="a-rating">⭐ 8.5</span></div> 756 + <div class="a-tasting">"Bright citrus with chocolate finish, clean body"</div> 757 + </div> 758 + <div class="a-action-bar"> 759 + <button class="a-action-btn"> 760 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg> 761 + 3 762 + </button> 763 + <button class="a-action-btn"> 764 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg> 765 + 12 766 + </button> 767 + <button class="a-action-btn"> 768 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg> 769 + Share 770 + </button> 771 + </div> 772 + </div> 773 + </div> 774 + 775 + <!-- Feed Card: Bean --> 776 + <div class="component-group"> 777 + <h3>Feed Card — Bean</h3> 778 + <div class="a-feed-card type-bean"> 779 + <div class="a-feed-meta"> 780 + <div class="a-feed-avatar">SK</div> 781 + <div class="a-feed-meta-text"><strong>Sam K.</strong> · @samk.bsky · 5h</div> 782 + </div> 783 + <div class="a-feed-action">added a new bean: <a href="#">Guatemala Huehuetenango</a></div> 784 + <div class="a-feed-inset"> 785 + <div class="a-feed-inset-detail"> 786 + Onyx Coffee Lab · Medium roast · Washed<br> 787 + Toffee, red apple, cocoa 788 + </div> 789 + </div> 790 + <div class="a-action-bar"> 791 + <button class="a-action-btn"> 792 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg> 793 + 4 794 + </button> 795 + </div> 796 + </div> 797 + </div> 798 + 799 + <!-- Buttons --> 800 + <div class="component-group"> 801 + <h3>Buttons</h3> 802 + <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;"> 803 + <button class="a-btn-primary">Log Brew</button> 804 + <button class="a-btn-secondary">Cancel</button> 805 + </div> 806 + </div> 807 + 808 + <!-- Form --> 809 + <div class="component-group"> 810 + <h3>Form Inputs</h3> 811 + <div class="a-card" style="max-width:320px;"> 812 + <div class="a-form-group"> 813 + <label class="a-form-label">Coffee (g)</label> 814 + <input class="a-form-input" type="text" placeholder="15" /> 815 + </div> 816 + <div class="a-form-group"> 817 + <label class="a-form-label">Water (g)</label> 818 + <input class="a-form-input" type="text" placeholder="250" /> 819 + </div> 820 + <div class="a-form-group"> 821 + <label class="a-form-label">Tasting Notes</label> 822 + <input class="a-form-input" type="text" placeholder="Bright, citrus, clean..." /> 823 + </div> 824 + <div style="margin-top:16px;"> 825 + <button class="a-btn-primary" style="width:100%;">Save Brew</button> 826 + </div> 827 + </div> 828 + </div> 829 + 830 + <!-- Section Title --> 831 + <div class="component-group"> 832 + <h3>Section Title</h3> 833 + <div class="a-section-title">Recent Brews</div> 834 + <div style="font-size:12px;color:#7f5539;">Section content would appear here...</div> 835 + </div> 836 + 837 + <!-- Table --> 838 + <div class="component-group"> 839 + <h3>Table</h3> 840 + <div class="a-table-wrap"> 841 + <table class="a-table"> 842 + <thead> 843 + <tr><th>Bean</th><th>Roaster</th><th>Origin</th><th>Roast</th></tr> 844 + </thead> 845 + <tbody> 846 + <tr><td>Ethiopian Sidamo</td><td>Sweet Bloom</td><td>Ethiopia</td><td>Light</td></tr> 847 + <tr><td>Guatemala Huehue</td><td>Onyx</td><td>Guatemala</td><td>Medium</td></tr> 848 + <tr><td>Kenya Nyeri AA</td><td>Counter Culture</td><td>Kenya</td><td>Light</td></tr> 849 + </tbody> 850 + </table> 851 + </div> 852 + </div> 853 + 854 + </div> 855 + 856 + <!-- ==================== OPTION B ==================== --> 857 + <div class="panel b"> 858 + <div class="panel-label">Option B — "Roasted"</div> 859 + 860 + <!-- Header --> 861 + <div class="component-group"> 862 + <h3>Header</h3> 863 + <div class="b-header"> 864 + <div class="b-header-logo">☕ arabica <span class="b-header-badge">ALPHA</span></div> 865 + <div class="b-header-avatar">PK</div> 866 + </div> 867 + </div> 868 + 869 + <!-- Feed Card: Brew --> 870 + <div class="component-group"> 871 + <h3>Feed Card — Brew</h3> 872 + <div class="b-feed-card"> 873 + <div class="b-feed-meta"> 874 + <div class="b-feed-avatar">JM</div> 875 + <div class="b-feed-meta-text"><strong>Jordan M.</strong> · @jordan.coffee · 2h</div> 876 + </div> 877 + <div class="b-feed-action"><span class="b-type-dot"></span>brewed with <a href="#">Ethiopian Sidamo</a></div> 878 + <div class="b-feed-inset"> 879 + <div class="b-feed-inset-title">Sweet Bloom · V60</div> 880 + <div class="b-feed-inset-detail"> 881 + <span class="val">15g</span> → <span class="val">250g</span> · <span class="val">1:16.7</span> · <span class="val">93°C</span> · <span class="val">3:15</span> 882 + </div> 883 + <div style="margin-top:6px;"><span class="b-rating">⭐ 8.5</span></div> 884 + <div class="b-tasting">"Bright citrus with chocolate finish, clean body"</div> 885 + </div> 886 + <div class="b-action-bar"> 887 + <button class="b-action-btn"> 888 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg> 889 + 3 890 + </button> 891 + <button class="b-action-btn"> 892 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg> 893 + 12 894 + </button> 895 + <button class="b-action-btn"> 896 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg> 897 + Share 898 + </button> 899 + </div> 900 + </div> 901 + </div> 902 + 903 + <!-- Feed Card: Bean --> 904 + <div class="component-group"> 905 + <h3>Feed Card — Bean</h3> 906 + <div class="b-feed-card"> 907 + <div class="b-feed-meta"> 908 + <div class="b-feed-avatar">SK</div> 909 + <div class="b-feed-meta-text"><strong>Sam K.</strong> · @samk.bsky · 5h</div> 910 + </div> 911 + <div class="b-feed-action"><span class="b-type-dot bean"></span>added a new bean: <a href="#">Guatemala Huehuetenango</a></div> 912 + <div class="b-feed-inset"> 913 + <div class="b-feed-inset-detail"> 914 + Onyx Coffee Lab · Medium roast · Washed<br> 915 + Toffee, red apple, cocoa 916 + </div> 917 + </div> 918 + <div class="b-action-bar"> 919 + <button class="b-action-btn"> 920 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg> 921 + 4 922 + </button> 923 + </div> 924 + </div> 925 + </div> 926 + 927 + <!-- Buttons --> 928 + <div class="component-group"> 929 + <h3>Buttons</h3> 930 + <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;"> 931 + <button class="b-btn-primary">Log Brew</button> 932 + <button class="b-btn-secondary">Cancel</button> 933 + </div> 934 + </div> 935 + 936 + <!-- Form --> 937 + <div class="component-group"> 938 + <h3>Form Inputs</h3> 939 + <div class="b-card" style="max-width:320px;"> 940 + <div class="b-form-group"> 941 + <label class="b-form-label">Coffee (g)</label> 942 + <input class="b-form-input" type="text" placeholder="15" /> 943 + </div> 944 + <div class="b-form-group"> 945 + <label class="b-form-label">Water (g)</label> 946 + <input class="b-form-input" type="text" placeholder="250" /> 947 + </div> 948 + <div class="b-form-group"> 949 + <label class="b-form-label">Tasting Notes</label> 950 + <input class="b-form-input" type="text" placeholder="Bright, citrus, clean..." /> 951 + </div> 952 + <div style="margin-top:16px;"> 953 + <button class="b-btn-primary" style="width:100%;">Save Brew</button> 954 + </div> 955 + </div> 956 + </div> 957 + 958 + <!-- Section Title --> 959 + <div class="component-group"> 960 + <h3>Section Title</h3> 961 + <div class="b-section-title">Recent Brews</div> 962 + <div style="font-size:12px;color:#C4A898;">Section content would appear here...</div> 963 + </div> 964 + 965 + <!-- Table --> 966 + <div class="component-group"> 967 + <h3>Table</h3> 968 + <div class="b-table-wrap"> 969 + <table class="b-table"> 970 + <thead> 971 + <tr><th>Bean</th><th>Roaster</th><th>Origin</th><th>Roast</th></tr> 972 + </thead> 973 + <tbody> 974 + <tr><td>Ethiopian Sidamo</td><td>Sweet Bloom</td><td>Ethiopia</td><td>Light</td></tr> 975 + <tr><td>Guatemala Huehue</td><td>Onyx</td><td>Guatemala</td><td>Medium</td></tr> 976 + <tr><td>Kenya Nyeri AA</td><td>Counter Culture</td><td>Kenya</td><td>Light</td></tr> 977 + </tbody> 978 + </table> 979 + </div> 980 + </div> 981 + 982 + </div> 983 + 984 + </div> 985 + 986 + </body> 987 + </html>
+232
docs/ui-overhaul-option-a.md
··· 1 + # Option A: "Clean Craft" — Modern Minimal with Warmth 2 + 3 + **Vibe:** A well-designed tool for coffee people. Clean surfaces, generous whitespace, subtle depth. Feels professional without losing the coffee identity. 4 + 5 + **Core principle:** Remove visual noise so the *data* becomes the design. 6 + 7 + ## Design Direction 8 + 9 + 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. 10 + 11 + 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. 12 + 13 + ## Color Changes 14 + 15 + | Token | Current | Proposed | Reason | 16 + |-------|---------|----------|--------| 17 + | Page background | `brown-50` (#fdf8f6) | `#FAF7F5` (new `cream`) | Warmer, less pink-tinted | 18 + | Card background | gradient brown-100→200 | `#FFFFFF` flat white | Cards pop via contrast with cream bg | 19 + | Card border | `brown-300` | `brown-200` | Lighter border, less "boxed in" | 20 + | Card shadow | `shadow-xl` | `shadow-sm`, `shadow-md` on hover | Quieter at rest, responsive on interaction | 21 + | Feed card bg | gradient brown-50→100 | `#FFFFFF` flat white | Same as primary cards | 22 + | Table bg | gradient brown-100→200 | `#FFFFFF` with `brown-100` header | Clean, scannable | 23 + | Modal bg | gradient brown-100→200 | `#FFFFFF` | Consistent with card treatment | 24 + 25 + 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. 26 + 27 + ## Typography Changes 28 + 29 + | Element | Current | Proposed | 30 + |---------|---------|----------| 31 + | Page title | `text-3xl font-bold` | `text-2xl font-semibold` | 32 + | Section title | `text-2xl font-bold` | `text-base font-semibold uppercase tracking-wider text-brown-500` | 33 + | Card heading | `text-xl font-bold` | `text-base font-semibold` | 34 + | Body text | `text-base` | `text-sm` | 35 + | Meta/labels | `text-xs` | `text-xs font-medium text-brown-500` | 36 + 37 + 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. 38 + 39 + Section titles become small uppercase labels — a common pattern in tools like Linear, Notion, and GitHub that creates clear sections without shouting. 40 + 41 + ## Card System 42 + 43 + ### Current 44 + ``` 45 + .card = gradient bg + rounded-xl + shadow-xl + border-brown-300 46 + .feed-card = gradient bg + rounded-lg + shadow-md + border-brown-200 47 + .section-box = bg-brown-50 + rounded-lg + border-brown-200 48 + ``` 49 + 50 + Three card types, all slightly different. Feed cards have a nested `.feed-content-box` inside them creating card-in-card. 51 + 52 + ### Proposed 53 + ``` 54 + .card = bg-white rounded-xl border border-brown-200 shadow-sm 55 + hover:shadow-md transition-shadow 56 + .card-sm = bg-white rounded-lg border border-brown-200 shadow-sm 57 + .feed-card = bg-white rounded-lg border border-brown-200 shadow-sm 58 + hover:shadow-md transition-shadow 59 + ``` 60 + 61 + Key changes: 62 + - **Kill the nested content box.** Feed card content lives directly in the card. No `.feed-content-box` wrapper. 63 + - **One visual language.** All cards are white, rounded, with thin borders and minimal shadow. 64 + - **Type indicator via left border.** Feed cards get a `border-l-3` colored by record type: 65 + - Brew: `brown-700` 66 + - Bean: `amber-600` 67 + - Recipe: `brown-500` 68 + - Roaster/Grinder/Brewer: `brown-400` 69 + 70 + This replaces the need for emoji or labels to distinguish record types at a glance. 71 + 72 + ### Section box 73 + ``` 74 + .section-box = bg-brown-50/50 rounded-lg p-4 75 + (no border — just the subtle background tint) 76 + ``` 77 + 78 + Used inside detail views for grouping related data. Lighter treatment than a card. 79 + 80 + ## Button System 81 + 82 + ### Current (5 variants) 83 + ``` 84 + .btn-primary = gradient brown-700→900 + shadow-md 85 + .btn-secondary = bg-brown-300 86 + .btn-tertiary = gradient brown-500→600 87 + .btn-link = text underline 88 + .btn-danger = red text underline 89 + ``` 90 + 91 + ### Proposed (3 variants) 92 + ``` 93 + .btn-primary = bg-brown-800 text-white rounded-lg 94 + hover:bg-brown-900 transition-colors 95 + .btn-secondary = bg-white border border-brown-300 text-brown-700 rounded-lg 96 + hover:bg-brown-50 transition-colors 97 + .btn-danger = text-red-600 hover:text-red-800 underline 98 + ``` 99 + 100 + 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. 101 + 102 + ## Form System 103 + 104 + ### Current 105 + ``` 106 + .form-input = border-2 border-brown-300 rounded-lg 107 + focus:border-brown-600 focus:ring-brown-600 108 + ``` 109 + 110 + ### Proposed 111 + ``` 112 + .form-input = border border-brown-300 rounded-lg bg-white 113 + focus:border-brown-600 focus:ring-1 focus:ring-brown-600 114 + focus:bg-brown-50/30 115 + placeholder:text-brown-400 116 + ``` 117 + 118 + Changes: 119 + - Border from 2px to 1px (less heavy) 120 + - Subtle background tint on focus (instead of just border change) 121 + - Keep the focus `translateY(-1px)` lift — it's a nice touch 122 + - Ring reduced to `ring-1` (thinner, more refined) 123 + 124 + ## Shadow Depth Scale 125 + 126 + | Level | Class | Usage | 127 + |-------|-------|-------| 128 + | 0 | `shadow-none` | Flat surfaces, inline elements | 129 + | 1 | `shadow-sm` | Cards at rest, tables, section boxes | 130 + | 2 | `shadow-md` | Hovered cards, dropdowns, action menus | 131 + | 3 | `shadow-lg` | Modals, popovers, floating UI | 132 + 133 + Current uses shadow-sm through shadow-2xl inconsistently. This simplifies to 3 levels with clear rules. 134 + 135 + ## Navigation 136 + 137 + **Keep** the dark brown gradient header — it's a strong anchor that works well. 138 + 139 + **Refine:** 140 + - Reduce ALPHA badge prominence (smaller, `text-[10px]`) 141 + - Replace hard `border-b` with `shadow-sm` for softer separation 142 + - Shrink header height slightly (48px instead of ~56px) 143 + 144 + ## Feed Cards — Detailed Layout 145 + 146 + ``` 147 + ┌─ border-l-3 brown-700 ─────────────────┐ 148 + │ │ 149 + │ ○ Display Name · @handle · 2h │ 150 + │ brewed with Ethiopian Sidamo │ 151 + │ │ 152 + │ ┌─ bg-brown-50/50 ──────────────────┐ │ 153 + │ │ Sweet Bloom · V60 │ │ 154 + │ │ 15g → 250g · 1:16.7 · ⭐ 8.5 │ │ 155 + │ │ │ │ 156 + │ │ "Bright citrus, chocolate finish" │ │ 157 + │ └────────────────────────────────────┘ │ 158 + │ │ 159 + │ 💬 3 ♡ 12 ↗ Share │ 160 + └──────────────────────────────────────────┘ 161 + ``` 162 + 163 + 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. 164 + 165 + ## Table Styling 166 + 167 + ### Current 168 + ``` 169 + .table-container = gradient bg + shadow-md + border 170 + .table-header = bg-brown-200 171 + .table-body = bg-brown-100 172 + ``` 173 + 174 + ### Proposed 175 + ``` 176 + .table-container = bg-white rounded-lg border border-brown-200 shadow-sm overflow-hidden 177 + .table-header = bg-brown-50 178 + .table-body = bg-white divide-y divide-brown-100 179 + .table-row = hover:bg-brown-50 transition-colors 180 + ``` 181 + 182 + Clean, standard table styling. Header is barely tinted. Rows divide with thin lines. Hover highlights the row. 183 + 184 + ## Animations 185 + 186 + **Keep:** Staggered feed card entry, modal transitions, like pop/shrink, form focus lift. 187 + 188 + **Remove:** Table row stagger (too much motion for data tables — feels gimmicky). 189 + 190 + **Add:** Subtle `opacity` transition on card border-left color when filtering feed by type. 191 + 192 + ## Implementation Phases 193 + 194 + ### Phase 1: Foundation (CSS-only, no template changes) 195 + 1. Update `tailwind.config.js` — add `cream` color 196 + 2. Rewrite card/button/form/table classes in `app.css` 197 + 3. Update `layout.templ` body background 198 + 4. Bump CSS cache version 199 + 200 + ### Phase 2: Template Cleanup 201 + 1. Remove `.feed-content-box` wrappers from feed templates 202 + 2. Add `border-l-3` type indicators to feed cards 203 + 3. Simplify section titles (uppercase label pattern) 204 + 4. Remove `.btn-tertiary` uses 205 + 206 + ### Phase 3: Detail Polish 207 + 1. Refine form layouts 208 + 2. Update modal content styling 209 + 3. Audit shadow usage across all templates 210 + 4. Test responsive behavior 211 + 212 + ## Tradeoffs 213 + 214 + | Pro | Con | 215 + |-----|-----| 216 + | Immediately more professional | Less personality than current | 217 + | Easier to maintain (fewer special cases) | Could feel generic if not careful | 218 + | Better feed scannability at scale | Left-border type system is a new concept to learn | 219 + | Lighter page weight (no gradients) | White cards on cream is a common pattern | 220 + | Clear visual hierarchy | Less "cozy coffee shop" feeling | 221 + | Works well with future dark mode | Requires discipline to not drift back to decoration | 222 + 223 + ## Risk: Becoming Generic 224 + 225 + The biggest risk with clean minimal is looking like every other SaaS tool. Mitigations: 226 + - **Monospace font** is the primary differentiator — keep it everywhere 227 + - **Brown palette** prevents blue/gray sameness 228 + - **Grain overlay** (keep at current opacity) adds tactile quality 229 + - **Left-border accents** give the feed a distinctive pattern 230 + - **Dark nav** provides a strong visual anchor 231 + 232 + The identity comes from the font + color combination, not from gradients and shadows.
+246
docs/ui-overhaul-option-b.md
··· 1 + # Option B: "Roasted" — Bold, Dark, Editorial 2 + 3 + **Vibe:** Specialty coffee packaging meets editorial magazine. Dark surfaces with warm highlights. The app *feels* like coffee — like opening a bag of fresh beans. 4 + 5 + **Core principle:** High contrast and bold typography make data dramatic. 6 + 7 + ## Design Direction 8 + 9 + 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. 10 + 11 + The monospace font becomes a statement rather than a quirk. At large sizes against dark backgrounds, monospace reads as intentional and editorial. 12 + 13 + ## Color System 14 + 15 + ### New Dark Palette 16 + 17 + | Token | Hex | Usage | 18 + |-------|-----|-------| 19 + | `espresso-950` | `#0F0A08` | Deepest background (page bg) | 20 + | `espresso-900` | `#1C1210` | Card backgrounds, primary surface | 21 + | `espresso-850` | `#241A16` | Elevated surfaces, hover states | 22 + | `espresso-800` | `#2E211B` | Borders, dividers | 23 + | `espresso-700` | `#3D2D24` | Secondary borders, subtle elements | 24 + | `cream-50` | `#FAF7F5` | Primary text on dark | 25 + | `cream-100` | `#F2E8E0` | Secondary text on dark | 26 + | `cream-200` | `#E0CEC4` | Muted text, labels | 27 + | `cream-300` | `#C4A898` | Placeholder text, disabled | 28 + | `amber-400` | `#FBBF24` | Primary accent — ratings, highlights | 29 + | `ember-500` | `#C4553A` | Secondary accent — actions, CTAs | 30 + | `ember-600` | `#A3412D` | Hover state for ember accent | 31 + 32 + ### Usage Rules 33 + 34 + - **Dark surfaces layered:** Page (`950`) → Section (`900`) → Card (`850`) creates depth without shadows 35 + - **Warm borders:** `espresso-800` borders, never gray 36 + - **Text contrast:** `cream-50` for headings (≥7:1 ratio), `cream-100` for body (≥4.5:1), `cream-200` for meta 37 + - **Accent restraint:** Amber for data (ratings, stats). Ember for interactive (buttons, links). Never both in the same element. 38 + - **No gradients on surfaces.** Flat dark colors. Gradients only on accent elements (primary button, hero treatments). 39 + 40 + ## Typography Changes 41 + 42 + | Element | Current | Proposed | 43 + |---------|---------|----------| 44 + | Page title | `text-3xl font-bold` | `text-3xl font-semibold text-cream-50 tracking-tight` | 45 + | Section title | `text-2xl font-bold` | `text-lg font-semibold text-amber-400 uppercase tracking-widest` | 46 + | Card heading | `text-xl font-bold` | `text-lg font-semibold text-cream-50` | 47 + | Body text | `text-base` | `text-sm text-cream-100` | 48 + | Meta/labels | `text-xs` | `text-xs font-medium text-cream-300 uppercase tracking-wider` | 49 + | Data values | same as body | `text-sm font-medium text-cream-50 tabular-nums` | 50 + 51 + 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. 52 + 53 + ## Card System 54 + 55 + ### Proposed 56 + ``` 57 + .card = bg-espresso-900 rounded-xl border border-espresso-800 58 + (no shadow — depth comes from surface layering) 59 + .card-hover = hover:bg-espresso-850 hover:border-espresso-700 transition-colors 60 + .feed-card = bg-espresso-900 rounded-lg border border-espresso-800 61 + hover:bg-espresso-850 transition-colors 62 + ``` 63 + 64 + **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. 65 + 66 + **Content areas** inside cards use a slightly lighter surface: 67 + ``` 68 + .card-inset = bg-espresso-850 rounded-lg p-3 69 + (no border — just the shade difference) 70 + ``` 71 + 72 + This replaces both `.section-box` and `.feed-content-box` — a single "recessed area" concept. 73 + 74 + ### Type indicator 75 + Instead of left-border (which can get lost on dark), use a **small colored dot** before the action text: 76 + - Brew: `amber-400` dot 77 + - Bean: `cream-200` dot 78 + - Recipe: `ember-500` dot 79 + 80 + Or a subtle top-border accent (2px) on the card — visible but not dominant. 81 + 82 + ## Button System 83 + 84 + ### Proposed 85 + ``` 86 + .btn-primary = bg-gradient-to-r from-ember-500 to-ember-600 text-cream-50 rounded-lg 87 + hover:from-ember-600 hover:to-ember-600 transition-all 88 + .btn-secondary = bg-espresso-850 border border-espresso-700 text-cream-100 rounded-lg 89 + hover:bg-espresso-800 hover:border-espresso-700 transition-colors 90 + .btn-danger = text-red-400 hover:text-red-300 underline 91 + ``` 92 + 93 + 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). 94 + 95 + ## Form System 96 + 97 + ``` 98 + .form-input = bg-espresso-850 border border-espresso-700 rounded-lg 99 + text-cream-50 placeholder:text-cream-300 100 + focus:border-amber-400 focus:ring-1 focus:ring-amber-400 101 + ``` 102 + 103 + Dark inputs with amber focus ring. The focus state is dramatic and clear — amber on dark brown is high contrast without being harsh. 104 + 105 + **Form labels:** `text-cream-200 text-xs font-medium uppercase tracking-wider` — small, quiet, functional. 106 + 107 + ## Navigation 108 + 109 + ### Proposed 110 + The current dark header is already close to the right direction. Refinements: 111 + 112 + ``` 113 + Header bg: espresso-950 (deepest dark, matches page) 114 + with a subtle bottom border in espresso-800 115 + ``` 116 + 117 + 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. 118 + 119 + **ALPHA badge:** `bg-amber-400 text-espresso-950` (inverted — amber background, dark text). Small, punchy. 120 + 121 + **User dropdown:** `bg-espresso-900 border border-espresso-800` — matches card styling. 122 + 123 + ## Feed Cards — Detailed Layout 124 + 125 + ``` 126 + ┌─ bg-espresso-900 border-espresso-800 ───┐ 127 + │ │ 128 + │ ○ Display Name · @handle · 2h │ 129 + │ ● brewed with Ethiopian Sidamo │ ← amber dot for brew type 130 + │ │ 131 + │ ┌─ bg-espresso-850 ─────────────────┐ │ 132 + │ │ Sweet Bloom · V60 │ │ ← cream-100 text 133 + │ │ 15g → 250g · 1:16.7 │ │ ← cream-50 data values 134 + │ │ │ │ 135 + │ │ ⭐ 8.5 │ │ ← amber badge 136 + │ │ │ │ 137 + │ │ "Bright citrus, chocolate finish" │ │ ← cream-200 italic 138 + │ └────────────────────────────────────┘ │ 139 + │ │ 140 + │ 💬 3 ♡ 12 ↗ Share │ ← cream-300, hover cream-50 141 + └──────────────────────────────────────────┘ 142 + ``` 143 + 144 + The inset area (`.card-inset`) provides visual grouping without borders. On dark backgrounds, even a small shade difference reads clearly. 145 + 146 + ## Table Styling 147 + 148 + ``` 149 + .table-container = bg-espresso-900 rounded-lg border border-espresso-800 overflow-hidden 150 + .table-header = bg-espresso-850 border-b border-espresso-800 151 + .table-th = text-cream-300 text-xs font-medium uppercase tracking-wider 152 + .table-body = divide-y divide-espresso-800 153 + .table-row = hover:bg-espresso-850 transition-colors 154 + .table-td = text-cream-100 text-sm 155 + ``` 156 + 157 + Clean, dark table. Header row is barely differentiated. Dividers between rows. On hover, rows lighten slightly. 158 + 159 + ## Animations 160 + 161 + **Keep:** Staggered feed card entry, modal transitions, like pop/shrink. 162 + 163 + **Modify:** 164 + - Feed card entry: Use `opacity` + `translateY(6px)` (shorter travel on dark — movement reads more clearly against dark backgrounds) 165 + - Modal backdrop: `bg-black/60` (needs to be darker since the page is already dark) 166 + 167 + **Add:** 168 + - Subtle `glow` on amber accent elements: `box-shadow: 0 0 20px rgba(251,191,36,0.1)` — very subtle warm halo 169 + - Card hover: border transitions from `espresso-800` to `espresso-700` (warm reveal) 170 + 171 + **Remove:** Table row stagger, form focus lift (feels odd on dark). 172 + 173 + ## Texture & Atmosphere 174 + 175 + **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. 176 + 177 + **Optional:** Subtle warm vignette on the page body: 178 + ```css 179 + body::after { 180 + content: ""; 181 + position: fixed; 182 + inset: 0; 183 + pointer-events: none; 184 + background: radial-gradient(ellipse at center, transparent 50%, rgba(15,10,8,0.3) 100%); 185 + } 186 + ``` 187 + 188 + This darkens the edges slightly, creating a cozy, focused feel. Can be skipped if it feels heavy. 189 + 190 + ## Implementation Phases 191 + 192 + ### Phase 1: Foundation 193 + 1. Extend `tailwind.config.js` with `espresso` and `cream` color scales 194 + 2. Rewrite `app.css` component classes for dark surfaces 195 + 3. Update `layout.templ` body background + text colors 196 + 4. Update `header.templ` to match dark theme 197 + 5. Bump CSS cache version 198 + 199 + ### Phase 2: Template Updates 200 + 1. Update all page templates — swap brown-* text utilities to cream-* 201 + 2. Replace `.feed-content-box` with `.card-inset` 202 + 3. Update form styling (dark inputs, amber focus) 203 + 4. Update modal styling 204 + 205 + ### Phase 3: Polish 206 + 1. Audit contrast ratios (WCAG AA minimum) 207 + 2. Add subtle glow effects on accent elements 208 + 3. Tune grain overlay opacity 209 + 4. Test all states (hover, focus, active, disabled) on dark 210 + 211 + ### Phase 4: Accessibility Audit 212 + Dark themes have higher risk of contrast failures. Must verify: 213 + - All text meets WCAG AA (4.5:1 for body, 3:1 for large text) 214 + - Focus indicators are visible 215 + - Disabled states are distinguishable 216 + - Form validation errors are readable 217 + 218 + ## Tradeoffs 219 + 220 + | Pro | Con | 221 + |-----|-----| 222 + | Extremely distinctive — memorable identity | Harder to implement correctly (contrast, accessibility) | 223 + | Dark mode is practical (morning/evening brew logging) | More CSS to maintain (dark needs different strategies) | 224 + | High contrast makes data pop | Polarizing — some users hate dark UIs | 225 + | Grain texture reads beautifully on dark | Heavier visual treatment, more code for atmosphere | 226 + | Feels premium, like specialty coffee packaging | Template changes are more extensive (every text color) | 227 + | Monospace font becomes a bold statement | No easy "light mode" toggle without a full second theme | 228 + | Natural depth from surface layering (no shadows needed) | Photos/avatars need extra treatment to not look jarring | 229 + 230 + ## Risk: Too Dark / Oppressive 231 + 232 + The biggest risk is the UI feeling heavy or hard to read. Mitigations: 233 + - **Cream text, not white.** Pure white (#fff) on dark brown is harsh. Warm cream (#FAF7F5) reduces eye strain. 234 + - **Layered surfaces** prevent "black void" feeling — there's always subtle differentiation. 235 + - **Amber accents** add warmth and break up the dark expanse. 236 + - **Generous spacing** — dark UIs need more whitespace (darkspace?) to breathe than light ones. 237 + - **Grain overlay** prevents "screen" feeling, adds organic quality. 238 + 239 + ## Risk: Light Mode Demand 240 + 241 + If users request light mode later, you'd need to: 242 + 1. Define all component colors via CSS custom properties (not Tailwind classes directly) 243 + 2. Create a parallel set of light-theme values 244 + 3. Use `prefers-color-scheme` or a toggle 245 + 246 + 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
··· 27 27 28 28 // BeanCard renders a single bean as a compact card 29 29 templ BeanCard(bean *models.Bean, showActions bool, ownerHandle string) { 30 - <div class="feed-card"> 30 + <div class="feed-card feed-card-bean"> 31 31 <div class="feed-content-box-sm"> 32 32 <div class="flex items-start justify-between gap-2 mb-2"> 33 33 <div class="min-w-0">
+1 -1
internal/web/components/footer.templ
··· 1 1 package components 2 2 3 3 templ Footer() { 4 - <footer class="mt-auto border-t border-brown-200 bg-brown-50"> 4 + <footer class="mt-auto" style="background: var(--footer-bg); border-top: 1px solid var(--footer-border);"> 5 5 <div class="container mx-auto px-4 py-8"> 6 6 <div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"> 7 7 <div class="text-sm text-brown-700">
+3 -3
internal/web/components/header.templ
··· 23 23 } 24 24 25 25 templ HeaderWithProps(props HeaderProps) { 26 - <nav 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" style="view-transition-name: header-nav;"> 27 - <div class="container mx-auto px-4 py-4"> 26 + <nav class="sticky top-0 z-50 text-white" style="background: linear-gradient(135deg, var(--header-bg-from), var(--header-bg-to)); border-bottom: 1px solid var(--header-border); box-shadow: var(--shadow-sm); view-transition-name: header-nav;"> 27 + <div class="container mx-auto px-4 py-3"> 28 28 <div class="flex items-center justify-between"> 29 29 <!-- Logo - always visible --> 30 30 <a href="/" class="flex items-center gap-2 hover:opacity-80 transition"> 31 31 <h1 class="text-2xl font-bold">☕ Arabica</h1> 32 - <span class="text-xs bg-amber-400 text-brown-900 px-2 py-1 rounded-md font-semibold shadow-sm">ALPHA</span> 32 + <span class="text-[10px] bg-amber-400 text-brown-900 px-1.5 py-0.5 rounded font-semibold">ALPHA</span> 33 33 </a> 34 34 <!-- Navigation links --> 35 35 <div class="flex items-center gap-4">
+4 -4
internal/web/components/layout.templ
··· 46 46 47 47 templ Layout(data *LayoutData, content templ.Component) { 48 48 <!DOCTYPE html> 49 - <html lang="en" class="h-full" style="background-color: #fdf8f6;"> 49 + <html lang="en" class="h-full" style="background-color: #FAF7F5;"> 50 50 <head> 51 51 <meta charset="UTF-8"/> 52 52 <meta name="viewport" content="width=device-width, initial-scale=1.0"/> ··· 75 75 <link rel="icon" href="/static/favicon.svg" type="image/svg+xml"/> 76 76 <link rel="icon" href="/static/favicon-32.svg" type="image/svg+xml" sizes="32x32"/> 77 77 <link rel="apple-touch-icon" href="/static/icon-192.svg"/> 78 - <link rel="stylesheet" href="/static/css/output.css?v=0.6.1"/> 78 + <link rel="stylesheet" href="/static/css/output.css?v=0.7.0"/> 79 79 <style> 80 80 [x-cloak] { display: none !important; } 81 81 </style> ··· 256 256 <script src="/static/js/sw-register.js?v=0.2.0"></script> 257 257 </head> 258 258 <body 259 - class="bg-brown-50 min-h-full flex flex-col" 260 - style="background-color: #fdf8f6;" 259 + class="min-h-full flex flex-col" 260 + style="background-color: var(--page-bg); color: var(--page-text);" 261 261 if data.UserDID != "" { 262 262 data-user-did={ data.UserDID } 263 263 }
+1 -1
internal/web/components/profile_brew_card.templ
··· 22 22 23 23 // ProfileBrewCard renders a single brew as a feed-style card 24 24 templ ProfileBrewCard(props ProfileBrewCardProps) { 25 - <div class="feed-card"> 25 + <div class="feed-card feed-card-brew"> 26 26 <!-- Author row --> 27 27 <div class="mb-3"> 28 28 @UserBadge(UserBadgeProps{
+11 -11
internal/web/components/shared.templ
··· 69 69 if props.ActionURL != "" && props.ActionText != "" { 70 70 <a 71 71 href={ templ.SafeURL(props.ActionURL) } 72 - class="inline-block btn-primary py-3 px-6 rounded-lg shadow-lg hover:shadow-xl" 72 + class="inline-block btn-primary py-3 px-6 rounded-lg" 73 73 > 74 74 { props.ActionText } 75 75 </a> ··· 111 111 if props.BackURL != "" { 112 112 @BackButton() 113 113 } 114 - <h2 class="text-3xl font-bold text-brown-900">{ props.Title }</h2> 114 + <h2 class="page-title">{ props.Title }</h2> 115 115 </div> 116 116 if props.ActionURL != "" && props.ActionText != "" { 117 117 <a 118 118 href={ templ.SafeURL(props.ActionURL) } 119 119 class={ templ.Classes( 120 - templ.KV("btn-primary shadow-lg hover:shadow-xl", props.ActionClass == ""), 120 + templ.KV("btn-primary", props.ActionClass == ""), 121 121 templ.KV(props.ActionClass, props.ActionClass != ""), 122 122 ) } 123 123 > ··· 169 169 templ WelcomeCard(props WelcomeCardProps) { 170 170 <div class="card p-8 mb-8"> 171 171 <div class="flex items-center gap-3 mb-4"> 172 - <h2 class="text-3xl font-bold text-brown-900">Welcome to Arabica</h2> 172 + <h2 class="text-2xl font-semibold text-brown-900">Welcome to Arabica</h2> 173 173 <span class="text-xs bg-amber-400 text-brown-900 px-2 py-1 rounded-md font-semibold shadow-sm">ALPHA</span> 174 174 </div> 175 175 <p class="text-brown-800 mb-2 text-lg">Track your coffee brewing journey with detailed logs of every cup.</p> ··· 192 192 <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> 193 193 <a 194 194 href="/brews/new" 195 - class="btn-primary block text-center py-4 px-6 rounded-xl shadow-lg hover:shadow-xl" 195 + class="btn-primary block text-center py-4 px-6 rounded-xl" 196 196 hx-get="/brews/new" 197 197 hx-target="main" 198 198 hx-swap="innerHTML show:top" ··· 203 203 </a> 204 204 <a 205 205 href="/brews" 206 - class="btn-tertiary block text-center py-4 px-6 rounded-xl shadow-lg hover:shadow-xl" 206 + class="btn-tertiary block text-center py-4 px-6 rounded-xl" 207 207 hx-get="/brews" 208 208 hx-target="main" 209 209 hx-swap="innerHTML show:top" ··· 235 235 placeholder="alice.arabica.systems" 236 236 autocomplete="off" 237 237 required 238 - class="w-full px-4 py-3 border-2 border-brown-300 rounded-lg focus:ring-2 focus:ring-brown-600 focus:border-brown-600 bg-white" 238 + class="w-full px-4 py-3 border border-brown-300 rounded-lg focus:ring-2 focus:ring-brown-600 focus:border-brown-600 bg-white" 239 239 /> 240 - <div id="autocomplete-results" class="hidden absolute z-10 w-full mt-1 bg-brown-50 border-2 border-brown-300 rounded-lg shadow-lg max-h-60 overflow-y-auto"></div> 240 + <div id="autocomplete-results" class="hidden absolute z-10 w-full mt-1 bg-brown-50 border border-brown-300 rounded-lg shadow-lg max-h-60 overflow-y-auto"></div> 241 241 </div> 242 242 <button 243 243 type="submit" 244 - class="btn-primary w-full mt-4 py-3 px-8 text-lg font-semibold shadow-lg hover:shadow-xl" 244 + class="btn-primary w-full mt-4 py-3 px-8 text-lg font-semibold" 245 245 > 246 246 Log In 247 247 </button> ··· 252 252 253 253 templ AboutInfoCard() { 254 254 // TODO: only show this at the bottom of the page when authenticated already? 255 - <div class="bg-gradient-to-br from-amber-50 to-brown-100 rounded-xl p-6 border-2 border-brown-300 shadow-lg mb-6"> 255 + <div class="card p-6 mb-6"> 256 256 <h3 class="text-lg font-bold text-brown-900 mb-3">✨ About Arabica</h3> 257 257 <ul class="text-brown-800 space-y-2 leading-relaxed mb-3"> 258 258 <li class="flex items-start"><span class="mr-2">📝</span><span>Add tasting notes and ratings to each brew</span></li> ··· 394 394 // RecipeAlphaWarning displays a warning banner about recipes being in early alpha. 395 395 // TODO: remove this later once recipes are stable 396 396 templ RecipeAlphaWarning() { 397 - <div class="rounded-lg border-2 border-amber-400 bg-amber-50 p-4 mb-6"> 397 + <div class="rounded-lg border border-amber-400 bg-amber-50 p-4 mb-6"> 398 398 <div class="flex items-start gap-3"> 399 399 <span class="text-xl leading-none mt-0.5">&#9888;&#65039;</span> 400 400 <div class="text-sm text-amber-900">
+9 -9
internal/web/pages/about.templ
··· 23 23 coffee beans, and equipment information in <strong>your own Personal Data Server (PDS)</strong>. 24 24 </p> 25 25 </section> 26 - <section class="bg-gradient-to-br from-brown-100 to-brown-200 p-6 rounded-xl border border-brown-300"> 26 + <section class="card p-6"> 27 27 <h3 class="text-xl font-semibold text-brown-900 mb-3">What Makes Arabica Different?</h3> 28 28 <ul class="list-disc list-inside space-y-2 text-brown-800"> 29 29 <li><strong>You own your data</strong> - All your brew logs live in your PDS</li> ··· 34 34 <section> 35 35 <h2 class="text-2xl font-semibold text-brown-900 mb-4">Features</h2> 36 36 <div class="grid md:grid-cols-2 gap-4"> 37 - <div class="bg-gradient-to-br from-brown-50 to-brown-100 border border-brown-200 p-4 rounded-lg shadow-md"> 37 + <div class="card p-4"> 38 38 <h4 class="font-semibold text-brown-900 mb-2">Track Your Brews</h4> 39 39 <p class="text-brown-700 text-sm">Log every detail: beans, grind size, water temp, brew time, and tasting notes</p> 40 40 </div> 41 - <div class="bg-gradient-to-br from-brown-50 to-brown-100 border border-brown-200 p-4 rounded-lg shadow-md"> 41 + <div class="card p-4"> 42 42 <h4 class="font-semibold text-brown-900 mb-2">Manage Equipment</h4> 43 43 <p class="text-brown-700 text-sm">Keep track of your grinders, brewers, beans, and roasters</p> 44 44 </div> 45 - <div class="bg-gradient-to-br from-brown-50 to-brown-100 border border-brown-200 p-4 rounded-lg shadow-md"> 45 + <div class="card p-4"> 46 46 <h4 class="font-semibold text-brown-900 mb-2">Community Feed</h4> 47 47 <p class="text-brown-700 text-sm">Share your best brews with the community (coming soon: likes and comments!)</p> 48 48 </div> ··· 65 65 <li>Your identity is portable - change PDS providers without losing your account</li> 66 66 </ul> 67 67 </section> 68 - <section class="bg-gradient-to-br from-amber-50 to-brown-100 border-2 border-brown-300 p-6 rounded-xl shadow-lg"> 68 + <section class="card p-6"> 69 69 <h3 class="text-xl font-semibold text-brown-900 mb-3">Getting Started</h3> 70 70 <p class="text-brown-800 mb-4"> 71 71 To use Arabica, you'll need an account on a PDS that supports the AT Protocol. ··· 82 82 Visit our <a href="https://github.com/arabica-social/arabica" class="text-brown-700 hover:underline font-medium" target="_blank" rel="noopener noreferrer">GitHub repository</a> to learn more. 83 83 </p> 84 84 </section> 85 - <section id="support" class="bg-gradient-to-br from-brown-100 to-brown-200 p-6 rounded-xl border border-brown-300"> 85 + <section id="support" class="card p-6"> 86 86 <h3 class="text-xl font-semibold text-brown-900 mb-3">Support Arabica</h3> 87 87 <p class="text-brown-800 leading-relaxed"> 88 88 Buy me a coffee, or help cover infra costs (and Claude subscription). ··· 99 99 </div> 100 100 <div class="mt-12 text-center"> 101 101 if !isAuthenticated { 102 - <a href="/login" class="btn-primary px-8 py-3 font-semibold shadow-lg hover:shadow-xl"> 102 + <a href="/login" class="btn-primary px-8 py-3 font-semibold"> 103 103 Get Started 104 104 </a> 105 105 } else { 106 - // <a href="/brews/new" class="btn-primary px-8 py-3 font-semibold shadow-lg hover:shadow-xl"> 106 + // <a href="/brews/new" class="btn-primary px-8 py-3 font-semibold"> 107 107 // Log Your Next Brew 108 108 // </a> 109 - <a href="/" class="btn-primary px-8 py-3 font-semibold shadow-lg hover:shadow-xl"> 109 + <a href="/" class="btn-primary px-8 py-3 font-semibold"> 110 110 Back to Home 111 111 </a> 112 112 }
+11 -11
internal/web/pages/atproto.templ
··· 15 15 <h1 class="text-4xl font-bold text-brown-900">The AT Protocol</h1> 16 16 </div> 17 17 <div class="prose prose-lg max-w-none space-y-6"> 18 - <section class="bg-gradient-to-br from-brown-100 to-brown-200 p-6 rounded-xl border border-brown-300"> 18 + <section class="card p-6"> 19 19 <h2 class="text-2xl font-semibold text-brown-900 mb-4">What is the AT Protocol?</h2> 20 20 <p class="text-brown-800 leading-relaxed mb-3"> 21 21 Arabica uses the <strong>AT Protocol</strong> (Authenticated Transfer Protocol) to power many of its social features, ··· 30 30 <section> 31 31 <h2 class="text-2xl font-semibold text-brown-900 mb-4">Key Concepts</h2> 32 32 <div class="space-y-4"> 33 - <div class="bg-gradient-to-br from-brown-50 to-brown-100 border border-brown-200 p-4 rounded-lg shadow-md"> 33 + <div class="card p-4"> 34 34 <h3 class="font-semibold text-brown-900 mb-2">Personal Data Server (PDS)</h3> 35 35 <p class="text-brown-700 text-sm"> 36 36 Your PDS is where all your data lives. Think of it as your personal database in the cloud. ··· 38 38 You can switch PDS providers anytime without losing your data. 39 39 </p> 40 40 </div> 41 - <div class="bg-gradient-to-br from-brown-50 to-brown-100 border border-brown-200 p-4 rounded-lg shadow-md"> 41 + <div class="card p-4"> 42 42 <h3 class="font-semibold text-brown-900 mb-2">Decentralized Identity (DID)</h3> 43 43 <p class="text-brown-700 text-sm"> 44 44 Your identity is represented by a DID (Decentralized Identifier) that you own. ··· 46 46 (e.g. yourname.arabica.systems) is just a human-friendly alias { "for" } your DID. 47 47 </p> 48 48 </div> 49 - <div class="bg-gradient-to-br from-brown-50 to-brown-100 border border-brown-200 p-4 rounded-lg shadow-md"> 49 + <div class="card p-4"> 50 50 <h3 class="font-semibold text-brown-900 mb-2">Lexicons</h3> 51 51 <p class="text-brown-700 text-sm"> 52 52 Lexicons define the structure of data in the AT Protocol. Arabica uses custom lexicons ··· 54 54 ensure data consistency and enable interoperability with other coffee apps. 55 55 </p> 56 56 </div> 57 - <div class="bg-gradient-to-br from-brown-50 to-brown-100 border border-brown-200 p-4 rounded-lg shadow-md"> 57 + <div class="card p-4"> 58 58 <h3 class="font-semibold text-brown-900 mb-2">AT-URIs</h3> 59 59 <p class="text-brown-700 text-sm"> 60 60 Records reference each other using AT-URIs, which look like: ··· 91 91 <section> 92 92 <h2 class="text-2xl font-semibold text-brown-900 mb-4">Why This Matters</h2> 93 93 <div class="grid md:grid-cols-2 gap-4"> 94 - <div class="bg-gradient-to-br from-green-50 to-green-100 border border-green-200 p-4 rounded-lg"> 94 + <div class="bg-green-50 border border-green-200 p-4 rounded-lg"> 95 95 <h4 class="font-semibold text-green-900 mb-2">Data Ownership</h4> 96 96 <p class="text-green-800 text-sm"> 97 97 Your coffee data is yours. If Arabica shuts down, your data remains safe in your PDS. 98 98 </p> 99 99 </div> 100 - <div class="bg-gradient-to-br from-green-50 to-green-100 border border-green-200 p-4 rounded-lg"> 100 + <div class="bg-green-50 border border-green-200 p-4 rounded-lg"> 101 101 <h4 class="font-semibold text-green-900 mb-2">Portability</h4> 102 102 <p class="text-green-800 text-sm"> 103 103 Switch PDS providers without losing anything. Your identity and data move with you. 104 104 </p> 105 105 </div> 106 - <div class="bg-gradient-to-br from-green-50 to-green-100 border border-green-200 p-4 rounded-lg"> 106 + <div class="bg-green-50 border border-green-200 p-4 rounded-lg"> 107 107 <h4 class="font-semibold text-green-900 mb-2">Interoperability</h4> 108 108 <p class="text-green-800 text-sm"> 109 109 Other apps can build on the same data. Future coffee apps could read your Arabica brews. 110 110 </p> 111 111 </div> 112 - <div class="bg-gradient-to-br from-green-50 to-green-100 border border-green-200 p-4 rounded-lg"> 112 + <div class="bg-green-50 border border-green-200 p-4 rounded-lg"> 113 113 <h4 class="font-semibold text-green-900 mb-2">Transparency</h4> 114 114 <p class="text-green-800 text-sm"> 115 115 Open protocol means no hidden algorithms. You can see exactly how your data is handled. ··· 117 117 </div> 118 118 </div> 119 119 </section> 120 - <section class="bg-gradient-to-br from-amber-50 to-brown-100 border-2 border-brown-300 p-6 rounded-xl shadow-lg"> 120 + <section class="card p-6"> 121 121 <h3 class="text-xl font-semibold text-brown-900 mb-3">Learn More</h3> 122 122 <p class="text-brown-800 mb-4"> 123 123 The AT Protocol is an open standard with comprehensive documentation. To dive deeper: ··· 139 139 </section> 140 140 </div> 141 141 <div class="mt-12 text-center"> 142 - <a href="/" class="btn-primary px-8 py-3 font-semibold shadow-lg hover:shadow-xl"> 142 + <a href="/" class="btn-primary px-8 py-3 font-semibold"> 143 143 Back to Home 144 144 </a> 145 145 </div>
+1 -1
internal/web/pages/bean_view.templ
··· 119 119 120 120 templ BeanViewHeader(props BeanViewProps) { 121 121 <div class="mb-6"> 122 - <h2 class="text-3xl font-bold text-brown-900"> 122 + <h2 class="text-2xl font-semibold text-brown-900"> 123 123 if props.Bean.Name != "" { 124 124 { props.Bean.Name } 125 125 } else {
+1 -1
internal/web/pages/brew_form.templ
··· 50 50 templ BrewFormHeader(props BrewFormProps) { 51 51 <div class="flex items-center gap-3 mb-6"> 52 52 @components.BackButton() 53 - <h2 class="text-3xl font-bold text-brown-900"> 53 + <h2 class="text-2xl font-semibold text-brown-900"> 54 54 if props.Brew != nil { 55 55 Edit Brew 56 56 } else {
+1 -1
internal/web/pages/brew_view.templ
··· 107 107 // BrewViewHeader renders the header with title and timestamp 108 108 templ BrewViewHeader(props BrewViewProps) { 109 109 <div class="mb-6"> 110 - <h2 class="text-3xl font-bold text-brown-900">Brew Details</h2> 110 + <h2 class="text-2xl font-semibold text-brown-900">Brew Details</h2> 111 111 <p class="text-sm text-brown-600 mt-1"><time datetime={ bff.FormatISO(props.Brew.CreatedAt) } data-local="long">{ props.Brew.CreatedAt.Format("January 2, 2006 at 3:04 PM") }</time></p> 112 112 </div> 113 113 }
+1 -1
internal/web/pages/brewer_view.templ
··· 90 90 91 91 templ BrewerViewHeader(props BrewerViewProps) { 92 92 <div class="mb-6"> 93 - <h2 class="text-3xl font-bold text-brown-900">{ props.Brewer.Name }</h2> 93 + <h2 class="text-2xl font-semibold text-brown-900">{ props.Brewer.Name }</h2> 94 94 <p class="text-sm text-brown-600 mt-1"><time datetime={ bff.FormatISO(props.Brewer.CreatedAt) } data-local="long">{ props.Brewer.CreatedAt.Format("January 2, 2006 at 3:04 PM") }</time></p> 95 95 </div> 96 96 }
+9 -1
internal/web/pages/feed.templ
··· 174 174 175 175 // FeedCardWithModeration renders a single feed item card with moderation context 176 176 templ FeedCardWithModeration(item *feed.FeedItem, isAuthenticated bool, modCtx FeedModerationContext) { 177 - <div class="feed-card"> 177 + <div class={ templ.Classes( 178 + "feed-card", 179 + templ.KV("feed-card-brew", item.RecordType == lexicons.RecordTypeBrew), 180 + templ.KV("feed-card-bean", item.RecordType == lexicons.RecordTypeBean), 181 + templ.KV("feed-card-recipe", item.RecordType == lexicons.RecordTypeRecipe), 182 + templ.KV("feed-card-roaster", item.RecordType == lexicons.RecordTypeRoaster), 183 + templ.KV("feed-card-grinder", item.RecordType == lexicons.RecordTypeGrinder), 184 + templ.KV("feed-card-brewer", item.RecordType == lexicons.RecordTypeBrewer), 185 + ) }> 178 186 <!-- Author row --> 179 187 <div class="mb-3"> 180 188 @components.UserBadge(components.UserBadgeProps{
+1 -1
internal/web/pages/grinder_view.templ
··· 93 93 94 94 templ GrinderViewHeader(props GrinderViewProps) { 95 95 <div class="mb-6"> 96 - <h2 class="text-3xl font-bold text-brown-900">{ props.Grinder.Name }</h2> 96 + <h2 class="text-2xl font-semibold text-brown-900">{ props.Grinder.Name }</h2> 97 97 <p class="text-sm text-brown-600 mt-1"><time datetime={ bff.FormatISO(props.Grinder.CreatedAt) } data-local="long">{ props.Grinder.CreatedAt.Format("January 2, 2006 at 3:04 PM") }</time></p> 98 98 </div> 99 99 }
+1 -1
internal/web/pages/manage.templ
··· 16 16 <div class="page-container-xl" x-data="managePage()"> 17 17 <div class="flex items-center gap-3 mb-6"> 18 18 @components.BackButton() 19 - <h2 class="text-3xl font-bold text-brown-900">Manage</h2> 19 + <h2 class="text-2xl font-semibold text-brown-900">Manage</h2> 20 20 </div> 21 21 @ManageTabs() 22 22 @ManageContentLoader()
+1 -1
internal/web/pages/notifications.templ
··· 30 30 <div class="flex items-center justify-between mb-8"> 31 31 <div class="flex items-center gap-3"> 32 32 @components.BackButton() 33 - <h1 class="text-3xl font-bold text-brown-900">Notifications</h1> 33 + <h1 class="text-2xl font-semibold text-brown-900">Notifications</h1> 34 34 </div> 35 35 if len(props.Notifications) > 0 { 36 36 <form method="POST" action="/api/notifications/read">
+2 -2
internal/web/pages/recipe_explore.templ
··· 26 26 > 27 27 <div class="flex items-center gap-3 mb-6"> 28 28 @components.BackButton() 29 - <h2 class="text-3xl font-bold text-brown-900">Explore Recipes</h2> 29 + <h2 class="text-2xl font-semibold text-brown-900">Explore Recipes</h2> 30 30 </div> 31 31 @components.RecipeAlphaWarning() 32 32 <!-- Search and filters --> ··· 172 172 <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> 173 173 <template x-for="recipe in recipes" :key="recipe.rkey"> 174 174 <div 175 - class="feed-card cursor-pointer" 175 + class="feed-card feed-card-recipe cursor-pointer" 176 176 @click="selectRecipe(recipe)" 177 177 > 178 178 <!-- Author row -->
+1 -1
internal/web/pages/recipe_view.templ
··· 160 160 161 161 templ RecipeViewHeader(props RecipeViewProps) { 162 162 <div class="mb-6"> 163 - <h2 class="text-3xl font-bold text-brown-900">{ props.Recipe.Name }</h2> 163 + <h2 class="text-2xl font-semibold text-brown-900">{ props.Recipe.Name }</h2> 164 164 <p class="text-sm text-brown-600 mt-1"><time datetime={ bff.FormatISO(props.Recipe.CreatedAt) } data-local="long">{ props.Recipe.CreatedAt.Format("January 2, 2006 at 3:04 PM") }</time></p> 165 165 if props.SourceRecipeURL != "" { 166 166 <p class="text-sm text-brown-500 mt-1">
+1 -1
internal/web/pages/roaster_view.templ
··· 98 98 99 99 templ RoasterViewHeader(props RoasterViewProps) { 100 100 <div class="mb-6"> 101 - <h2 class="text-3xl font-bold text-brown-900">{ props.Roaster.Name }</h2> 101 + <h2 class="text-2xl font-semibold text-brown-900">{ props.Roaster.Name }</h2> 102 102 <p class="text-sm text-brown-600 mt-1"><time datetime={ bff.FormatISO(props.Roaster.CreatedAt) } data-local="long">{ props.Roaster.CreatedAt.Format("January 2, 2006 at 3:04 PM") }</time></p> 103 103 </div> 104 104 }
+378 -103
static/css/app.css
··· 22 22 font-display: swap; 23 23 } 24 24 25 + /* ======================================== 26 + Design Tokens (CSS Custom Properties) 27 + Light theme (default) 28 + ======================================== */ 29 + :root { 30 + /* Page */ 31 + --page-bg: #FAF7F5; 32 + --page-text: #3d2319; 33 + 34 + /* Cards */ 35 + --card-bg: #FFFFFF; 36 + --card-border: #eaddd7; 37 + --card-shadow: rgba(61, 35, 25, 0.06); 38 + --card-shadow-hover: rgba(61, 35, 25, 0.10); 39 + 40 + /* Surfaces (inset areas inside cards) */ 41 + --surface-bg: rgba(250, 247, 245, 0.5); 42 + --surface-border: #f2e8e5; 43 + 44 + /* Header */ 45 + --header-bg-from: #4a2c2a; 46 + --header-bg-to: #3d2319; 47 + --header-border: #7f5539; 48 + --header-text: #FAF7F5; 49 + 50 + /* Text hierarchy */ 51 + --text-primary: #3d2319; 52 + --text-secondary: #4a2c2a; 53 + --text-muted: #7f5539; 54 + --text-faint: #bfa094; 55 + --text-placeholder: #d2bab0; 56 + 57 + /* Interactive */ 58 + --btn-primary-bg: #4a2c2a; 59 + --btn-primary-bg-hover: #3d2319; 60 + --btn-primary-text: #FAF7F5; 61 + --btn-secondary-bg: #FFFFFF; 62 + --btn-secondary-border: #e0cec7; 63 + --btn-secondary-text: #6b4423; 64 + --btn-secondary-bg-hover: #FAF7F5; 65 + 66 + /* Forms */ 67 + --input-bg: #FFFFFF; 68 + --input-border: #e0cec7; 69 + --input-border-focus: #7f5539; 70 + --input-ring-focus: rgba(127, 85, 57, 0.15); 71 + --input-bg-focus: rgba(250, 247, 245, 0.3); 72 + 73 + /* Tables */ 74 + --table-bg: #FFFFFF; 75 + --table-header-bg: #FAF7F5; 76 + --table-border: #eaddd7; 77 + --table-row-hover: #FAF7F5; 78 + --table-divider: #f2e8e5; 79 + 80 + /* Modals */ 81 + --modal-bg: #FFFFFF; 82 + --modal-border: #eaddd7; 83 + --modal-backdrop: rgba(0, 0, 0, 0.4); 84 + 85 + /* Feed type indicators (left border) */ 86 + --type-brew: #6b4423; 87 + --type-bean: #d97706; 88 + --type-recipe: #bfa094; 89 + --type-roaster: #d2bab0; 90 + --type-grinder: #d2bab0; 91 + --type-brewer: #d2bab0; 92 + 93 + /* Shadows */ 94 + --shadow-sm: 0 1px 3px var(--card-shadow); 95 + --shadow-md: 0 4px 12px var(--card-shadow-hover); 96 + --shadow-lg: 0 10px 25px var(--card-shadow-hover); 97 + 98 + /* Footer */ 99 + --footer-bg: #FAF7F5; 100 + --footer-border: #eaddd7; 101 + } 102 + 25 103 @tailwind base; 26 104 @tailwind components; 27 105 @tailwind utilities; ··· 73 151 74 152 /* Cards and Containers */ 75 153 .card { 76 - @apply bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-xl border border-brown-300; 154 + background: var(--card-bg); 155 + border: 1px solid var(--card-border); 156 + @apply rounded-xl; 157 + box-shadow: var(--shadow-sm); 158 + transition: box-shadow 200ms ease; 159 + } 160 + 161 + .card:hover { 162 + box-shadow: var(--shadow-md); 77 163 } 78 164 79 165 .card-inner { ··· 81 167 } 82 168 83 169 .card-sm { 84 - @apply bg-gradient-to-br from-brown-100 to-brown-200 rounded-lg shadow-md border border-brown-300; 170 + background: var(--card-bg); 171 + border: 1px solid var(--card-border); 172 + @apply rounded-lg; 173 + box-shadow: var(--shadow-sm); 85 174 } 86 175 87 176 /* Section box for lighter content areas */ 88 177 .section-box { 89 - @apply bg-brown-50 rounded-lg p-4 border border-brown-200; 178 + background: var(--surface-bg); 179 + @apply rounded-lg p-4; 90 180 } 91 181 92 182 /* Buttons */ ··· 95 185 } 96 186 97 187 .btn-primary { 98 - @apply btn bg-gradient-to-br from-brown-700 to-brown-900 text-white hover:from-brown-800 hover:to-brown-900 shadow-md; 188 + @apply btn text-white; 189 + background: var(--btn-primary-bg); 190 + } 191 + 192 + .btn-primary:hover { 193 + background: var(--btn-primary-bg-hover); 99 194 } 100 195 101 196 .btn-secondary { 102 - @apply btn bg-brown-300 text-brown-900 hover:bg-brown-400; 197 + @apply btn; 198 + background: var(--btn-secondary-bg); 199 + color: var(--btn-secondary-text); 200 + border: 1px solid var(--btn-secondary-border); 201 + } 202 + 203 + .btn-secondary:hover { 204 + background: var(--btn-secondary-bg-hover); 103 205 } 104 206 105 207 .btn-tertiary { 106 - @apply btn bg-gradient-to-br from-brown-500 to-brown-600 text-white hover:from-brown-600 hover:to-brown-700; 208 + @apply btn text-white; 209 + background: var(--btn-primary-bg); 210 + } 211 + 212 + .btn-tertiary:hover { 213 + background: var(--btn-primary-bg-hover); 107 214 } 108 215 109 216 .btn-link { 110 - @apply text-brown-700 hover:text-brown-900 font-medium underline transition-colors cursor-pointer; 217 + color: var(--text-muted); 218 + @apply font-medium underline transition-colors cursor-pointer; 219 + } 220 + 221 + .btn-link:hover { 222 + color: var(--text-primary); 111 223 } 112 224 113 225 .btn-danger { ··· 116 228 117 229 /* Forms */ 118 230 .form-label { 119 - @apply block text-sm font-medium text-brown-900 mb-2; 231 + @apply block text-sm font-medium mb-2; 232 + color: var(--text-primary); 120 233 } 121 234 122 235 .form-input { 123 - @apply rounded-lg border-2 border-brown-300 shadow-sm focus:border-brown-600 focus:ring-brown-600 text-base py-2 px-3 bg-white; 236 + @apply rounded-lg text-base py-2 px-3; 237 + background: var(--input-bg); 238 + border: 1px solid var(--input-border); 239 + color: var(--text-primary); 240 + transition: border-color 150ms ease, box-shadow 150ms ease, background-color 150ms ease; 241 + } 242 + 243 + .form-input:focus { 244 + border-color: var(--input-border-focus); 245 + box-shadow: 0 0 0 2px var(--input-ring-focus); 246 + background: var(--input-bg-focus); 247 + outline: none; 248 + } 249 + 250 + .form-input::placeholder { 251 + color: var(--text-placeholder); 124 252 } 125 253 126 254 .form-input-lg { ··· 137 265 138 266 /* Tables */ 139 267 .table-container { 140 - @apply bg-gradient-to-br from-brown-100 to-brown-200 rounded-lg shadow-md overflow-hidden border border-brown-300; 268 + background: var(--table-bg); 269 + border: 1px solid var(--table-border); 270 + @apply rounded-lg overflow-hidden; 271 + box-shadow: var(--shadow-sm); 141 272 } 142 273 143 274 .table { 144 - @apply min-w-full divide-y divide-brown-300; 275 + @apply min-w-full; 276 + border-collapse: collapse; 145 277 } 146 278 147 279 .table-header { 148 - @apply bg-brown-200; 280 + background: var(--table-header-bg); 281 + border-bottom: 1px solid var(--table-border); 149 282 } 150 283 151 284 .table-th { 152 - @apply px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider; 285 + @apply px-6 py-3 text-left text-xs font-medium uppercase tracking-wider; 286 + color: var(--text-muted); 153 287 } 154 288 155 289 .table-body { 156 - @apply bg-brown-100 divide-y divide-brown-200; 290 + background: var(--table-bg); 291 + } 292 + 293 + .table-body tr { 294 + border-bottom: 1px solid var(--table-divider); 295 + } 296 + 297 + .table-body tr:last-child { 298 + border-bottom: none; 157 299 } 158 300 159 301 .table-row { 160 - @apply hover:bg-brown-100 transition-colors; 302 + transition: background-color 150ms ease; 303 + } 304 + 305 + .table-row:hover { 306 + background: var(--table-row-hover); 161 307 } 162 308 163 309 .table-td { 164 - @apply px-6 py-4 whitespace-nowrap text-sm text-brown-800; 310 + @apply px-6 py-4 whitespace-nowrap text-sm; 311 + color: var(--text-secondary); 165 312 } 166 313 167 314 /* Modals */ 168 315 .modal-backdrop { 169 - @apply fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-50 p-4; 316 + @apply fixed inset-0 flex items-center justify-center z-50 p-4; 317 + background: var(--modal-backdrop); 318 + backdrop-filter: blur(4px); 170 319 } 171 320 172 321 .modal-content { 173 - @apply bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-2xl p-6 max-w-md w-full border border-brown-300 max-h-[90vh] overflow-y-auto; 322 + background: var(--modal-bg); 323 + border: 1px solid var(--modal-border); 324 + @apply rounded-xl p-6 max-w-md w-full max-h-[90vh] overflow-y-auto; 325 + box-shadow: var(--shadow-lg); 174 326 } 175 327 176 328 .modal-title { 177 - @apply text-xl font-semibold text-brown-900 mb-4; 329 + @apply text-xl font-semibold mb-4; 330 + color: var(--text-primary); 178 331 } 179 332 180 333 /* Native Dialog Element */ ··· 183 336 } 184 337 185 338 .modal-dialog::backdrop { 186 - @apply bg-black/40 backdrop-blur-sm; 339 + background: var(--modal-backdrop); 340 + backdrop-filter: blur(4px); 187 341 } 188 342 189 - /* Dialog content wrapper (nested inside dialog) */ 190 343 .modal-dialog .modal-content { 191 - @apply bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-2xl p-6 w-full border border-brown-300 max-h-[90vh] overflow-y-auto; 344 + background: var(--modal-bg); 345 + border: 1px solid var(--modal-border); 346 + @apply rounded-xl p-6 w-full max-h-[90vh] overflow-y-auto; 347 + box-shadow: var(--shadow-lg); 192 348 } 193 349 194 350 /* Entity suggestion typeahead dropdown */ 195 351 .suggestions-dropdown { 196 - @apply absolute z-50 left-0 right-0 mt-1 bg-white rounded-lg shadow-lg border border-brown-200 max-h-48 overflow-y-auto; 352 + @apply absolute z-50 left-0 right-0 mt-1 rounded-lg max-h-48 overflow-y-auto; 353 + background: var(--card-bg); 354 + border: 1px solid var(--card-border); 355 + box-shadow: var(--shadow-md); 197 356 } 198 357 199 358 .suggestions-item { 200 - @apply w-full text-left px-3 py-2 flex items-center gap-2 hover:bg-brown-50 transition-colors cursor-pointer border-b border-brown-100 last:border-b-0; 359 + @apply w-full text-left px-3 py-2 flex items-center gap-2 transition-colors cursor-pointer last:border-b-0; 360 + border-bottom: 1px solid var(--surface-border); 361 + } 362 + 363 + .suggestions-item:hover { 364 + background: var(--surface-bg); 201 365 } 202 366 203 367 /* Typography */ 204 368 .section-title { 205 - @apply text-2xl font-bold text-brown-900 mb-4; 369 + @apply text-xs font-semibold uppercase tracking-widest mb-4; 370 + color: var(--text-faint); 206 371 } 207 372 208 373 .page-title { 209 - @apply text-3xl font-bold text-brown-900; 374 + @apply text-2xl font-semibold; 375 + color: var(--text-primary); 210 376 } 211 377 212 378 /* Feed Components */ 213 379 .feed-card { 214 - @apply bg-gradient-to-br from-brown-50 to-brown-100 rounded-lg shadow-md border border-brown-200 p-3 sm:p-4 hover:shadow-lg transition-shadow; 380 + background: var(--card-bg); 381 + border: 1px solid var(--card-border); 382 + @apply rounded-lg p-3 sm:p-4 transition-shadow; 383 + box-shadow: var(--shadow-sm); 384 + } 385 + 386 + .feed-card:hover { 387 + box-shadow: var(--shadow-md); 388 + } 389 + 390 + /* Feed card type indicators */ 391 + .feed-card-brew { 392 + border-left: 3px solid var(--type-brew); 393 + } 394 + 395 + .feed-card-bean { 396 + border-left: 3px solid var(--type-bean); 397 + } 398 + 399 + .feed-card-recipe { 400 + border-left: 3px solid var(--type-recipe); 401 + } 402 + 403 + .feed-card-roaster { 404 + border-left: 3px solid var(--type-roaster); 405 + } 406 + 407 + .feed-card-grinder { 408 + border-left: 3px solid var(--type-grinder); 409 + } 410 + 411 + .feed-card-brewer { 412 + border-left: 3px solid var(--type-brewer); 215 413 } 216 414 217 415 .feed-content-box { 218 - @apply bg-white/60 backdrop-blur rounded-lg p-3 sm:p-4 border border-brown-200; 416 + background: var(--surface-bg); 417 + @apply rounded-lg p-3 sm:p-4; 219 418 } 220 419 221 420 .feed-content-box-sm { 222 - @apply bg-white/60 backdrop-blur rounded-lg p-2 sm:p-3 border border-brown-200; 421 + background: var(--surface-bg); 422 + @apply rounded-lg p-2 sm:p-3; 223 423 } 224 424 225 425 /* Avatar - base styles */ ··· 271 471 272 472 /* Text Utilities */ 273 473 .text-helper { 274 - @apply text-sm text-brown-700 mt-1; 474 + @apply text-sm mt-1; 475 + color: var(--text-muted); 275 476 } 276 477 277 478 .text-meta { 278 - @apply text-xs text-brown-600; 479 + @apply text-xs; 480 + color: var(--text-muted); 279 481 } 280 482 281 483 .text-meta-sm { 282 - @apply text-sm text-brown-600; 484 + @apply text-sm; 485 + color: var(--text-muted); 283 486 } 284 487 285 488 .text-label { 286 - @apply text-brown-600; 489 + color: var(--text-muted); 287 490 } 288 491 289 492 /* Badges */ ··· 297 500 298 501 /* Links */ 299 502 .link { 300 - @apply text-brown-700 hover:text-brown-900 hover:underline transition-colors; 503 + color: var(--text-muted); 504 + @apply hover:underline transition-colors; 505 + } 506 + 507 + .link:hover { 508 + color: var(--text-primary); 301 509 } 302 510 303 511 .link-bold { 304 - @apply font-medium text-brown-700 hover:text-brown-900 hover:underline transition-colors; 512 + color: var(--text-muted); 513 + @apply font-medium hover:underline transition-colors; 514 + } 515 + 516 + .link-bold:hover { 517 + color: var(--text-primary); 305 518 } 306 519 307 520 /* Like Button */ ··· 310 523 } 311 524 312 525 .like-btn-liked { 313 - @apply like-btn bg-brown-100 text-red-600 hover:bg-brown-200; 526 + @apply like-btn text-red-600; 527 + background: transparent; 314 528 animation: like-pop 400ms ease-out; 315 529 } 316 530 531 + .like-btn-liked:hover { 532 + background: var(--surface-bg); 533 + } 534 + 317 535 .like-btn-unliked { 318 - @apply like-btn bg-brown-100 text-brown-600 hover:bg-brown-200; 536 + @apply like-btn; 537 + color: var(--text-muted); 538 + background: transparent; 319 539 animation: like-shrink 200ms ease-out; 320 540 } 321 541 542 + .like-btn-unliked:hover { 543 + background: var(--surface-bg); 544 + } 545 + 322 546 /* Share Button */ 323 547 .share-btn { 324 - @apply inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors bg-brown-100 text-brown-600 hover:bg-brown-200 min-h-[44px]; 548 + @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]; 549 + color: var(--text-muted); 550 + background: transparent; 551 + } 552 + 553 + .share-btn:hover { 554 + background: var(--surface-bg); 325 555 } 326 556 327 557 /* Comment Button */ 328 558 .comment-btn { 329 - @apply inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors bg-brown-100 text-brown-600 hover:bg-brown-200 min-h-[44px]; 559 + @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]; 560 + color: var(--text-muted); 561 + background: transparent; 562 + } 563 + 564 + .comment-btn:hover { 565 + background: var(--surface-bg); 330 566 } 331 567 332 568 /* Comment Section */ 333 569 .comment-section { 334 - @apply mt-8 pt-6 border-t-2 border-brown-200; 570 + @apply mt-8 pt-6; 571 + border-top: 2px solid var(--card-border); 335 572 } 336 573 337 574 .comment-section-header { ··· 343 580 } 344 581 345 582 .comment-login-prompt { 346 - @apply flex items-center gap-3 bg-brown-50 rounded-lg p-4 mb-5 border border-dashed border-brown-300; 583 + @apply flex items-center gap-3 rounded-lg p-4 mb-5 border border-dashed; 584 + background: var(--surface-bg); 585 + border-color: var(--card-border); 347 586 } 348 587 349 588 .comment-compose { 350 - @apply bg-brown-50 rounded-lg p-4 mb-5 border border-brown-200 flex flex-col gap-2; 589 + @apply rounded-lg p-4 mb-5 flex flex-col gap-2; 590 + background: var(--surface-bg); 591 + border: 1px solid var(--card-border); 351 592 } 352 593 353 594 .comment-textarea { 354 - @apply w-full rounded-lg border-2 border-brown-200 bg-white px-3 py-2.5 text-base text-brown-900 placeholder-brown-400 resize-none transition-colors focus:border-brown-500 focus:ring-0 focus:outline-none; 595 + @apply w-full rounded-lg px-3 py-2.5 text-base resize-none transition-colors focus:ring-0 focus:outline-none; 596 + background: var(--card-bg); 597 + border: 1px solid var(--card-border); 598 + color: var(--text-primary); 599 + } 600 + 601 + .comment-textarea::placeholder { 602 + color: var(--text-placeholder); 603 + } 604 + 605 + .comment-textarea:focus { 606 + border-color: var(--input-border-focus); 355 607 } 356 608 357 609 .comment-list { ··· 363 615 } 364 616 365 617 .comment-item { 366 - @apply relative rounded-lg p-3 transition-colors hover:bg-brown-50/60; 618 + @apply relative rounded-lg p-3 transition-colors; 619 + } 620 + 621 + .comment-item:hover { 622 + background: var(--surface-bg); 367 623 } 368 624 369 625 .comment-item-inner { ··· 379 635 } 380 636 381 637 .comment-thread-line { 382 - @apply absolute left-0 top-3 bottom-3 w-0.5 bg-brown-200 rounded-full; 638 + @apply absolute left-0 top-3 bottom-3 w-0.5 rounded-full; 639 + background: var(--card-border); 383 640 } 384 641 385 642 .comment-reply-btn { 386 - @apply inline-flex items-center gap-1 text-brown-400 hover:text-brown-700 transition-colors text-xs font-medium; 643 + @apply inline-flex items-center gap-1 transition-colors text-xs font-medium; 644 + color: var(--text-placeholder); 645 + } 646 + 647 + .comment-reply-btn:hover { 648 + color: var(--text-muted); 387 649 } 388 650 389 651 .comment-delete-btn { 390 - @apply text-brown-300 hover:text-brown-600 transition-colors; 652 + @apply transition-colors; 653 + color: var(--text-placeholder); 654 + } 655 + 656 + .comment-delete-btn:hover { 657 + color: var(--text-muted); 391 658 } 392 659 393 660 .comment-reply-form { 394 - @apply flex flex-col gap-2 bg-brown-50 rounded-lg p-3 border border-brown-200; 661 + @apply flex flex-col gap-2 rounded-lg p-3; 662 + background: var(--surface-bg); 663 + border: 1px solid var(--card-border); 395 664 } 396 665 397 666 /* Action Bar */ 398 667 .action-bar { 399 - @apply flex items-center gap-2 mt-3 pt-3 border-t border-brown-200; 668 + @apply flex items-center gap-2 mt-3 pt-3; 669 + border-top: 1px solid var(--surface-border); 400 670 } 401 671 402 672 /* Action bar inside brew view container - no separator needed */ ··· 406 676 407 677 /* Compact action bar variant for comments */ 408 678 .comment-item .action-bar { 409 - @apply mt-1 border-t-0 gap-1 bg-brown-100 rounded-lg px-1.5 py-1 inline-flex items-center; 679 + @apply mt-1 border-t-0 gap-1 rounded-lg px-1.5 py-1 inline-flex items-center; 680 + background: var(--surface-bg); 410 681 } 411 682 412 683 .comment-item .action-btn, ··· 415 686 } 416 687 417 688 .action-btn { 418 - @apply inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors bg-brown-100 text-brown-600 hover:bg-brown-200 cursor-pointer min-h-[44px]; 689 + @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]; 690 + color: var(--text-muted); 691 + background: transparent; 692 + } 693 + 694 + .action-btn:hover { 695 + background: var(--surface-bg); 696 + color: var(--text-secondary); 419 697 } 420 698 421 699 .action-btn-liked { ··· 424 702 } 425 703 426 704 .action-btn-disabled { 427 - @apply opacity-50 cursor-not-allowed hover:bg-brown-100; 705 + @apply opacity-50 cursor-not-allowed; 706 + } 707 + 708 + .action-btn-disabled:hover { 709 + background: transparent; 428 710 } 429 711 430 712 /* Action Menu (More dropdown) */ 431 713 .action-menu { 432 - @apply absolute left-1/2 -translate-x-1/2 w-36 bg-white rounded-lg shadow-lg border border-brown-200 py-1 z-50; 714 + @apply absolute left-1/2 -translate-x-1/2 w-36 rounded-lg py-1 z-50; 715 + background: var(--card-bg); 716 + border: 1px solid var(--card-border); 717 + box-shadow: var(--shadow-md); 433 718 } 434 719 435 720 /* Dropdown menu (top-positioned, for headers/nav) */ 436 721 .dropdown-menu { 437 - @apply absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-brown-200 py-1 z-50; 722 + @apply absolute right-0 mt-2 w-48 rounded-lg py-1 z-50; 723 + background: var(--card-bg); 724 + border: 1px solid var(--card-border); 725 + box-shadow: var(--shadow-md); 438 726 } 439 727 440 728 .dropdown-item { 441 - @apply block px-4 py-2 text-sm text-brown-700 hover:bg-brown-50 transition-colors; 729 + @apply block px-4 py-2 text-sm transition-colors; 730 + color: var(--text-muted); 731 + } 732 + 733 + .dropdown-item:hover { 734 + background: var(--surface-bg); 442 735 } 443 736 444 737 .dropdown-item-disabled { 445 - @apply block px-4 py-2 text-sm text-brown-400 cursor-not-allowed; 738 + @apply block px-4 py-2 text-sm cursor-not-allowed; 739 + color: var(--text-placeholder); 446 740 } 447 741 448 742 .dropdown-item-mod { 449 - @apply text-amber-700 hover:bg-amber-50; 743 + @apply text-amber-700; 744 + } 745 + 746 + .dropdown-item-mod:hover { 747 + @apply bg-amber-50; 450 748 } 451 749 452 750 .dropdown-header { 453 - @apply px-4 py-2 border-b border-brown-100; 751 + @apply px-4 py-2; 752 + border-bottom: 1px solid var(--surface-border); 454 753 } 455 754 456 755 .dropdown-divider { 457 - @apply border-t border-brown-100 mt-1 pt-1; 756 + margin-top: 4px; 757 + padding-top: 4px; 758 + border-top: 1px solid var(--surface-border); 458 759 } 459 760 460 761 .action-menu-item { 461 - @apply flex items-center gap-2 w-full px-3 py-2 text-sm text-brown-700 hover:bg-brown-100 transition-colors cursor-pointer text-left; 762 + @apply flex items-center gap-2 w-full px-3 py-2 text-sm transition-colors cursor-pointer text-left; 763 + color: var(--text-muted); 764 + } 765 + 766 + .action-menu-item:hover { 767 + background: var(--surface-bg); 462 768 } 463 769 464 770 .action-menu-item-danger { 465 - @apply text-red-600 hover:bg-red-50; 771 + @apply text-red-600; 772 + } 773 + 774 + .action-menu-item-danger:hover { 775 + @apply bg-red-50; 466 776 } 467 777 468 778 .action-menu-item-warning { 469 - @apply text-amber-600 hover:bg-amber-50; 779 + @apply text-amber-600; 780 + } 781 + 782 + .action-menu-item-warning:hover { 783 + @apply bg-amber-50; 470 784 } 471 785 472 786 .action-menu-divider { 473 - @apply border-t border-brown-200 my-1; 787 + @apply my-1; 788 + border-top: 1px solid var(--surface-border); 474 789 } 475 790 476 791 /* Hidden record indicator badge */ ··· 552 867 animation-delay: 250ms; 553 868 } 554 869 555 - /* Table rows slide in with stagger effect (dynamic content) */ 556 - .table-body tr { 557 - animation: fade-in-slide-up 300ms ease-out backwards; 558 - } 559 - 560 - .table-body tr:nth-child(1) { 561 - animation-delay: 0ms; 562 - } 563 - .table-body tr:nth-child(2) { 564 - animation-delay: 30ms; 565 - } 566 - .table-body tr:nth-child(3) { 567 - animation-delay: 60ms; 568 - } 569 - .table-body tr:nth-child(4) { 570 - animation-delay: 90ms; 571 - } 572 - .table-body tr:nth-child(5) { 573 - animation-delay: 120ms; 574 - } 575 - .table-body tr:nth-child(n + 6) { 576 - animation-delay: 150ms; 577 - } 578 - 579 870 /* Modal transitions (enhanced) */ 580 871 .modal-backdrop { 581 872 animation: fade-in 100ms ease-out; ··· 642 933 643 934 /* Modal content inside dialog - no separate animation needed */ 644 935 /* The dialog element itself handles the transition */ 645 - 646 - /* Form elements slide in */ 647 - .form-input, 648 - .form-select, 649 - .form-textarea { 650 - transition: 651 - border-color 100ms ease, 652 - box-shadow 100ms ease, 653 - transform 50ms ease; 654 - } 655 - 656 - .form-input:focus, 657 - .form-select:focus, 658 - .form-textarea:focus { 659 - transform: translateY(-1px); 660 - } 661 936 662 937 /* ======================================== 663 938 Keyframes
+3
tailwind.config.js
··· 11 11 mono: ['"Iosevka Patrick"', 'ui-monospace', 'monospace'], 12 12 }, 13 13 colors: { 14 + cream: { 15 + 50: "#FAF7F5", 16 + }, 14 17 brown: { 15 18 50: "#fdf8f6", 16 19 100: "#f2e8e5",