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

Configure Feed

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

fix: design audit #1

open opened by pdewey.com targeting main from push-wyqtrtsvtyyy
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:hm5f3dnm6jdhrc55qp2npdja/sh.tangled.repo.pull/3mkm2vnrhkl22
+80 -33
Diff #0
+1
.gitignore
··· 30 30 moderators.json 31 31 roles.json 32 32 pds-config.json 33 + .superpowers/
+2 -2
internal/web/components/action_bar.templ
··· 288 288 289 289 // ReportModal renders an inline report modal for the action bar 290 290 templ ReportModal(props ReportModalProps) { 291 - <dialog id={ props.ID } class="modal-dialog" x-data="{ reason: '', charCount: 0, submitting: false, error: '', success: false }"> 291 + <dialog id={ props.ID } class="modal-dialog" aria-labelledby={ props.ID + "-title" } x-data="{ reason: '', charCount: 0, submitting: false, error: '', success: false }"> 292 292 <div class="modal-content"> 293 - <h3 class="modal-title">Report Content</h3> 293 + <h3 id={ props.ID + "-title" } class="modal-title">Report Content</h3> 294 294 <template x-if="!success"> 295 295 <form 296 296 @submit.prevent={ fmt.Sprintf(`
+1 -1
internal/web/components/layout.templ
··· 115 115 <link rel="icon" href="/static/favicon.svg" type="image/svg+xml"/> 116 116 <link rel="icon" href="/static/favicon-32.svg" type="image/svg+xml" sizes="32x32"/> 117 117 <link rel="apple-touch-icon" href="/static/icon-192.svg"/> 118 - <link rel="stylesheet" href="/static/css/output.css?v=0.11.0"/> 118 + <link rel="stylesheet" href="/static/css/output.css?v=0.12.0"/> 119 119 <style> 120 120 [x-cloak] { display: none !important; } 121 121 </style>
+10 -7
internal/web/pages/bean_view.templ
··· 44 44 { props.Bean.Roaster.Name } 45 45 </a> 46 46 if props.Bean.Roaster.Location != "" { 47 - <span style="color: var(--text-faint)">·</span> 47 + <span class="text-faint">·</span> 48 48 <span class="inline-flex items-center gap-1"> 49 49 @components.IconMapPin() 50 50 { props.Bean.Roaster.Location } ··· 194 194 // BeanCloseBagConfirm renders a styled confirmation dialog for closing a bag 195 195 templ BeanCloseBagConfirm(props BeanViewProps) { 196 196 <dialog 197 - id="close-bag-confirm" 197 + id={ "close-bag-confirm-" + props.Bean.RKey } 198 198 class="modal-dialog" 199 199 x-ref="closeDialog" 200 200 x-init="$watch('showConfirm', v => { if (v) $refs.closeDialog.showModal() })" 201 201 @close="showConfirm = false" 202 + aria-labelledby={ "close-bag-title-" + props.Bean.RKey } 202 203 > 203 204 <div class="modal-content"> 204 - <h3 class="modal-title">Close Bag</h3> 205 + <h3 id={ "close-bag-title-" + props.Bean.RKey } class="modal-title">Close Bag</h3> 205 206 <p class="text-brown-700 text-sm mb-4"> 206 207 Mark this bag as finished? You can reopen it later from the edit menu. 207 208 </p> ··· 230 231 // BeanRateModal renders a dialog for rating or editing a bean's rating 231 232 templ BeanRateModal(props BeanViewProps) { 232 233 <dialog 233 - id="rate-bag-modal" 234 + id={ "rate-bag-modal-" + props.Bean.RKey } 234 235 class="modal-dialog" 235 236 x-ref="rateDialog" 237 + aria-labelledby={ "rate-bag-title-" + props.Bean.RKey } 236 238 > 237 239 <div class="modal-content"> 238 - <h3 class="modal-title"> 240 + <h3 id={ "rate-bag-title-" + props.Bean.RKey } class="modal-title"> 239 241 if props.Bean.Rating != nil { 240 242 Edit Rating 241 243 } else { ··· 249 251 max="10" 250 252 x-model.number="rating" 251 253 class="w-full accent-brown-700" 254 + aria-label="Bag rating, 1 to 10" 252 255 /> 253 - <div class="text-center text-3xl font-bold text-brown-800"> 256 + <div class="text-center text-3xl font-bold text-brown-800" aria-hidden="true"> 254 257 <span x-text="rating"></span>/10 255 258 </div> 256 259 </div> ··· 268 271 <button 269 272 type="button" 270 273 @click={ beanPatchAction(props.Bean, `{ "rating": null }`, "Failed to remove rating") } 271 - class="flex-1 btn-secondary text-red-700" 274 + class="flex-1 btn-secondary text-danger" 272 275 :disabled="_saving" 273 276 > 274 277 <span x-show="!_saving">Remove</span>
+14 -10
internal/web/pages/brew_view.templ
··· 221 221 } 222 222 </a> 223 223 if brew.Bean.Roaster != nil && brew.Bean.Roaster.Name != "" { 224 - <div class="text-sm mt-1" style="color: var(--text-secondary)"> 224 + <div class="text-sm mt-1 text-secondary"> 225 225 <span class="inline-flex items-center gap-1"> 226 226 @components.IconStore() 227 227 <a href={ templ.SafeURL(fmt.Sprintf("/roasters/%s?owner=%s", brew.Bean.Roaster.RKey, owner)) } class="hover:underline"> ··· 230 230 </span> 231 231 </div> 232 232 } 233 - <div class="flex flex-wrap gap-3 mt-2 text-sm" style="color: var(--text-muted)"> 233 + <div class="flex flex-wrap gap-3 mt-2 text-sm text-muted"> 234 234 if brew.Bean.Origin != "" { 235 235 <span class="inline-flex items-center gap-1"> 236 236 @components.IconMapPin() ··· 364 364 </template> 365 365 <template x-if="showForm && !success"> 366 366 <div class="space-y-3"> 367 - <h3 class="text-sm font-medium text-brown-600 uppercase tracking-wider">Save as Recipe</h3> 367 + <h3 class="text-sm font-medium text-muted uppercase tracking-wider">Save as Recipe</h3> 368 + <label for={ "save-recipe-name-" + brewRKey } class="sr-only">Recipe name</label> 368 369 <input 370 + id={ "save-recipe-name-" + brewRKey } 369 371 type="text" 370 372 x-model="name" 371 - placeholder="Recipe name *" 373 + placeholder="Recipe name" 374 + required 375 + aria-required="true" 372 376 class="w-full form-input" 373 377 /> 374 378 <template x-if="error"> 375 - <div class="text-red-600 text-sm" x-text="error"></div> 379 + <div class="text-danger text-sm" x-text="error"></div> 376 380 </template> 377 381 <div class="flex gap-2"> 378 382 <button ··· 393 397 </div> 394 398 </template> 395 399 <template x-if="success"> 396 - <div class="text-center text-green-700 text-sm font-medium py-2"> 400 + <div class="text-center text-success text-sm font-medium py-2"> 397 401 Recipe saved! 398 402 </div> 399 403 </template> ··· 409 413 <div> 410 414 <span class="detail-label mb-2 block">Recipe</span> 411 415 <a href={ templ.SafeURL(fmt.Sprintf("/recipes/%s?owner=%s", recipe.RKey, owner)) } class="detail-value-lg hover:underline">{ recipe.Name }</a> 412 - <div class="flex flex-wrap gap-3 mt-2 text-sm" style="color: var(--text-muted)"> 416 + <div class="flex flex-wrap gap-3 mt-2 text-sm text-muted"> 413 417 if recipe.CoffeeAmount > 0 { 414 418 <span class="inline-flex items-center gap-1"> 415 419 @components.IconCoffee() ··· 435 439 } 436 440 </div> 437 441 if recipe.Notes != "" { 438 - <div class="mt-2 text-sm italic" style="color: var(--text-secondary)">"{ recipe.Notes }"</div> 442 + <div class="mt-2 text-sm italic text-secondary">"{ recipe.Notes }"</div> 439 443 } 440 444 </div> 441 445 } ··· 451 455 </span> 452 456 <div class="space-y-2"> 453 457 for _, pour := range pours { 454 - <div class="flex gap-4 text-sm py-2" style="border-bottom: 1px solid var(--surface-border)"> 458 + <div class="pour-row"> 455 459 <span class="detail-value">{ fmt.Sprintf("%dg", pour.WaterAmount) }</span> 456 460 // TODO: add a setting to allow users to configure "at" vs "for" in pours display here 457 - <span style="color: var(--text-muted)">{ "for " + bff.FormatTime(pour.TimeSeconds) }</span> 461 + <span class="text-muted">{ "for " + bff.FormatTime(pour.TimeSeconds) }</span> 458 462 </div> 459 463 } 460 464 </div>
+2 -2
internal/web/pages/recipe_view.templ
··· 99 99 </span> 100 100 <div> 101 101 for _, pour := range props.Recipe.Pours { 102 - <div class="flex gap-4 text-sm py-2" style="border-bottom: 1px solid var(--surface-border)"> 102 + <div class="pour-row"> 103 103 <span class="detail-value">{ fmt.Sprintf("%dg", pour.WaterAmount) }</span> 104 - <span style="color: var(--text-muted)">{ "for " + bff.FormatTime(pour.TimeSeconds) }</span> 104 + <span class="text-muted">{ "for " + bff.FormatTime(pour.TimeSeconds) }</span> 105 105 </div> 106 106 } 107 107 </div>
+2 -2
internal/web/pages/roaster_view.templ
··· 44 44 </span> 45 45 </span> 46 46 if props.Roaster.Location != "" { 47 - <span class="label-origin-hero" style="font-size: 1.5rem">{ props.Roaster.Location }</span> 47 + <span class="detail-value-lg">{ props.Roaster.Location }</span> 48 48 } else { 49 - <span class="text-sm" style="color: var(--text-faint)">—</span> 49 + <span class="text-sm text-faint">—</span> 50 50 } 51 51 </div> 52 52 if props.Roaster.Website != "" {
+48 -9
static/css/app.css
··· 34 34 --feed-board-border: #B09470; 35 35 /* Sticky note base — warm off-white */ 36 36 --feed-card-bg: #FFFDF5; 37 - /* Dot-grid — faint dots on warm paper, for journal pages */ 38 - --texture-dotgrid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='0.7' fill='%23d2bab0' fill-opacity='0.35'/%3E%3C/svg%3E"); 39 - /* Kraft grain — subtle fiber texture for label pages */ 40 - --texture-kraft: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='k'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.15 0 0 0 0.1 0.1 0 0 0 0.06 0.05 0 0 0 0.02 0 0 0 0.07 0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23k)'/%3E%3C/svg%3E"); 37 + /* Dot-grid + kraft — defined once per theme, theme blocks just swap the active alias */ 38 + --texture-dotgrid-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='0.7' fill='%23d2bab0' fill-opacity='0.35'/%3E%3C/svg%3E"); 39 + --texture-dotgrid-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='0.7' fill='%235A4A40' fill-opacity='0.4'/%3E%3C/svg%3E"); 40 + --texture-kraft-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='k'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.15 0 0 0 0.1 0.1 0 0 0 0.06 0.05 0 0 0 0.02 0 0 0 0.07 0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23k)'/%3E%3C/svg%3E"); 41 + --texture-kraft-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='k'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.08 0 0 0 0.04 0.06 0 0 0 0.03 0.03 0 0 0 0.01 0 0 0 0.06 0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23k)'/%3E%3C/svg%3E"); 42 + --texture-dotgrid: var(--texture-dotgrid-light); 43 + --texture-kraft: var(--texture-kraft-light); 41 44 /* Journal paper — warm cream */ 42 45 --journal-bg: #FFFDF5; 43 46 /* Kraft — slightly warmer/darker than card bg */ ··· 124 127 --rating-bg: #fef3c7; 125 128 --rating-text: #78350f; 126 129 130 + /* Status text — danger / success */ 131 + --text-danger: #b91c1c; 132 + --text-success: #15803d; 133 + 127 134 /* Alerts/warnings */ 128 135 --alert-warning-bg: #fffbeb; 129 136 --alert-warning-border: #fbbf24; ··· 162 169 --surface-border: #2E211B; 163 170 164 171 /* Journal/label page backgrounds */ 165 - --texture-dotgrid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='0.7' fill='%235A4A40' fill-opacity='0.4'/%3E%3C/svg%3E"); 166 - --texture-kraft: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='k'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.08 0 0 0 0.04 0.06 0 0 0 0.03 0.03 0 0 0 0.01 0 0 0 0.06 0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23k)'/%3E%3C/svg%3E"); 172 + --texture-dotgrid: var(--texture-dotgrid-dark); 173 + --texture-kraft: var(--texture-kraft-dark); 167 174 --journal-bg: #1A1210; 168 175 --kraft-bg: #201814; 169 176 ··· 234 241 --rating-bg: rgba(251, 191, 36, 0.15); 235 242 --rating-text: #fbbf24; 236 243 244 + /* Status text — danger / success */ 245 + --text-danger: #fca5a5; 246 + --text-success: #86efac; 247 + 237 248 /* Alerts/warnings */ 238 249 --alert-warning-bg: rgba(251, 191, 36, 0.08); 239 250 --alert-warning-border: rgba(251, 191, 36, 0.3); ··· 329 340 --feed-board-bg: #3D2D22; 330 341 --feed-board-border: #4A3828; 331 342 --feed-card-bg: #1A1210; 332 - --texture-dotgrid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='0.7' fill='%235A4A40' fill-opacity='0.4'/%3E%3C/svg%3E"); 333 - --texture-kraft: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='k'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.08 0 0 0 0.04 0.06 0 0 0 0.03 0.03 0 0 0 0.01 0 0 0 0.06 0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23k)'/%3E%3C/svg%3E"); 343 + --texture-dotgrid: var(--texture-dotgrid-dark); 344 + --texture-kraft: var(--texture-kraft-dark); 334 345 --journal-bg: #1A1210; 335 346 --kraft-bg: #201814; 336 347 --header-bg-from: #0F0A08; ··· 376 387 --type-brewer-tint: rgba(139, 163, 122, 0.15); 377 388 --rating-bg: rgba(251, 191, 36, 0.15); 378 389 --rating-text: #fbbf24; 390 + --text-danger: #fca5a5; 391 + --text-success: #86efac; 379 392 --alert-warning-bg: rgba(251, 191, 36, 0.08); 380 393 --alert-warning-border: rgba(251, 191, 36, 0.3); 381 394 --alert-warning-text: #fde68a; ··· 449 462 min-width: auto; 450 463 } 451 464 465 + /* Global keyboard focus ring — visible only for keyboard users (not click) */ 466 + :focus-visible { 467 + outline: 2px solid var(--input-border-focus); 468 + outline-offset: 2px; 469 + border-radius: 2px; 470 + } 471 + /* Clear native outline when focus-visible is in play (we draw our own above) */ 472 + :focus:not(:focus-visible) { 473 + outline: none; 474 + } 475 + 452 476 /* Prevent iOS zoom on input focus */ 453 477 @media (max-width: 768px) { 454 478 input, ··· 606 630 } 607 631 608 632 .record-view-footer { 609 - @apply flex justify-between items-center pt-4 mt-6; 633 + @apply flex flex-wrap justify-between items-center gap-3 pt-4 mt-6; 610 634 } 611 635 612 636 .record-stat-line { ··· 698 722 } 699 723 } 700 724 725 + /* Pour rows: shared by brew and recipe views */ 726 + .pour-row { 727 + @apply flex gap-4 text-sm py-2; 728 + border-bottom: 1px solid var(--surface-border); 729 + } 730 + .pour-row:last-child { 731 + border-bottom: none; 732 + } 733 + 701 734 /* Prose section in journal context */ 702 735 .journal-prose { 703 736 @apply whitespace-pre-wrap leading-relaxed; ··· 1335 1368 color: var(--text-muted); 1336 1369 } 1337 1370 1371 + .text-secondary { color: var(--text-secondary); } 1372 + .text-muted { color: var(--text-muted); } 1373 + .text-faint { color: var(--text-faint); } 1374 + .text-danger { color: var(--text-danger); } 1375 + .text-success { color: var(--text-success); } 1376 + 1338 1377 /* Badges */ 1339 1378 .badge-rating { 1340 1379 @apply inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium flex-shrink-0;

History

1 round 0 comments
sign up or login to add to the discussion
pdewey.com submitted #0
1 commit
expand
fix: design audit
merge conflicts detected
expand
  • internal/web/components/layout.templ:115
  • internal/web/components/shared.templ:218
  • internal/web/pages/bean_view.templ:53
  • internal/web/pages/brew_view.templ:56
  • internal/web/pages/brewer_view.templ:53
  • internal/web/pages/grinder_view.templ:53
  • internal/web/pages/recipe_view.templ:66
  • internal/web/pages/roaster_view.templ:53
  • static/css/app.css:561
expand 0 comments