style warm high-contrast palette, flat surfaces, workout table

Rework the visual language: pure black on a warm white with an oxblood primary, warm bronze secondary, and evergreen tertiary (all WCAG AA+); remove the page-layout card metaphor in favour of flat surfaces; move the oxblood accent to the masthead; a golden-ratio heading scale; and the workout logging styles (golden-ratio set table, centered Courier timer, single-line ending block, crossed-off completed slots, outline Save correction). Remove the toast styles.

Commit
5e0df23e4903f662797355ca06233344fc4eb427
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
lib/web/assets/hito.css
index ebc9d0a2..83a36e1f 100644..100644
@@ -2,30 +2,42 @@
2 2
3 3 :root {
4 4 color-scheme: light;
5 Removed: --paper: #f7f2e8;
6 Removed: --paper-raised: #fffdf8;
7 Removed: --on-primary: #fffdf8;
8 Removed: --on-brass: #fffdf8;
9 Removed: --ink: #1a1a16;
10 Removed: --muted-ink: #5b584f;
11 Removed: --rule: #b9b1a2;
12 Removed: --rule-strong: #756f63;
5 Added: /* Uncompromising legibility: pure black text on a very slightly warm white.
6 Added: The palette stays warm throughout — oxblood primary and a warm bronze
7 Added: secondary carry the blood-and-effort register — with evergreen reserved for
8 Added: success. Contrast is the constraint the warmth must respect, not the other
9 Added: way round. */
10 Added: --paper: #fdfcfa;
11 Added: --paper-raised: #ffffff;
12 Added: --on-primary: #ffffff;
13 Added: --on-brass: #ffffff;
14 Added: --ink: #000000;
15 Added: --muted-ink: #3f3f3f;
16 Added: --rule: #c9c6c0;
17 Added: --rule-strong: #6f6c66;
13 18 --oxblood: #8c201b;
14 Removed: --oxblood-dark: #65130f;
15 Removed: --oxblood-hover: #65130f;
16 Removed: --link-hover: #8c201b;
17 Removed: --brass: #7b5714;
18 Removed: --brass-wash: #f4e7c5;
19 Removed: --hover-surface: #ece4d5;
20 Removed: --disabled-surface: #ded8cc;
19 Added: --oxblood-dark: #6d1713;
20 Added: --oxblood-hover: #6d1713;
21 Added: --link-hover: #6d1713;
22 Added: --brass: #8a5a12;
23 Added: --brass-wash: #f3e6c8;
24 Added: --hover-surface: #eeeae3;
25 Added: --disabled-surface: #ddd9d1;
21 26 --evergreen: #1f5b45;
22 27 --evergreen-wash: #dcebe2;
23 Removed: --focus: #65130f;
28 Added: --focus: #8c201b;
24 29 --error: #8c201b;
25 30 --font-size-small: 0.875rem;
26 31 --font-size-base: 1rem;
27 32 --font-size-lead: 1.125rem;
28 Removed: --font-size-heading: 2rem;
33 Added: /* Heading scale anchored on the golden ratio. Body is 1rem; each heading
34 Added: tier steps by √φ (≈1.272), so h2 sits a full φ (≈1.618) above body and h1
35 Added: a further √φ above h2. A ratio-based hierarchy a reader perceives, in place
36 Added: of ad-hoc sizes. */
37 Added: --font-size-h3: 1.272rem;
38 Added: --font-size-h2: 1.618rem;
39 Added: --font-size-h1: 2.058rem;
40 Added: --font-size-heading: 2.058rem;
29 41 --radius: 0.4em;
30 42 --sans: "Work Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
31 43 --mono: "Courier Prime", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
@@ -35,25 +47,28 @@
35 47
36 48 html[data-theme="dark"] {
37 49 color-scheme: dark;
38 Removed: --paper: #171614;
39 Removed: --paper-raised: #24211d;
40 Removed: --on-primary: #f3eee3;
41 Removed: --on-brass: #171614;
42 Removed: --ink: #f3eee3;
43 Removed: --muted-ink: #c2b9aa;
44 Removed: --rule: #655e54;
45 Removed: --rule-strong: #918778;
46 Removed: --oxblood: #ae453e;
50 Added: /* The dark inversion of the same warm system: near-white text on a warm
51 Added: near-black, with the oxblood primary and a warm amber secondary lightened
52 Added: for AA+ contrast on the dark surface. */
53 Added: --paper: #141312;
54 Added: --paper-raised: #201e1b;
55 Added: --on-primary: #ffffff;
56 Added: --on-brass: #201607;
57 Added: --ink: #f5f2ec;
58 Added: --muted-ink: #c6bfb4;
59 Added: --rule: #5f584f;
60 Added: --rule-strong: #938a7c;
61 Added: --oxblood: #d8564d;
47 62 --oxblood-dark: #f08073;
48 Removed: --oxblood-hover: #96332f;
49 Removed: --link-hover: #d4a94f;
50 Removed: --brass: #d4a94f;
51 Removed: --brass-wash: #40351f;
52 Removed: --hover-surface: #40351f;
53 Removed: --disabled-surface: #4a443c;
63 Added: --oxblood-hover: #b8433b;
64 Added: --link-hover: #e0b256;
65 Added: --brass: #e0b256;
66 Added: --brass-wash: #3a2c12;
67 Added: --hover-surface: #2b2824;
68 Added: --disabled-surface: #423d36;
54 69 --evergreen: #74c49a;
55 Removed: --evergreen-wash: #244334;
56 Removed: --focus: #f08073;
70 Added: --evergreen-wash: #17362a;
71 Added: --focus: #d8564d;
57 72 --error: #f08073;
58 73 }
59 74
@@ -105,22 +120,22 @@
105 120 padding: 0 1rem 2.5rem;
106 121 }
107 122
123 Added: /* The oxblood bar is the header's base edge — the brand marker — so it belongs
124 Added: to the masthead, not the content. */
108 125 .masthead {
109 126 display: flex;
110 127 align-items: center;
111 128 gap: 1rem;
112 129 padding: 0.75rem 0;
130 Added: border-bottom: 6px solid var(--oxblood);
113 131 }
114 132
115 Removed: /* The footer is a small card beneath the page surface: same raised paper,
116 Removed: border, and radius, without the surface's oxblood accent. */
133 Added: /* The footer is a flat divider: a quiet line of text separated by a hairline
134 Added: rule, at every width. */
117 135 .site-footer {
118 136 margin: 1.25rem 0 0;
119 Removed: padding: 0.85rem 1rem;
120 Removed: border: 1px solid var(--rule-strong);
121 Removed: border-radius: var(--radius);
122 Removed: background: var(--paper-raised);
123 Removed: box-shadow: 0 0.4rem 1.5rem rgba(26, 26, 22, 0.09);
137 Added: padding: 0.85rem 0;
138 Added: border-top: 1px solid var(--rule);
124 139 color: var(--muted-ink);
125 140 font-size: var(--font-size-small);
126 141 text-align: center;
@@ -139,7 +154,7 @@
139 154 display: inline-flex;
140 155 align-items: center;
141 156 color: var(--ink);
142 Removed: font: 800 2rem/1 var(--sans);
157 Added: font: 800 var(--font-size-h1)/1 var(--sans);
143 158 letter-spacing: -0.03em;
144 159 text-decoration: none;
145 160 }
@@ -168,8 +183,7 @@
168 183 border-color: var(--oxblood-dark);
169 184 }
170 185
171 Removed: .eyebrow,
172 Removed: .ledger-meta {
186 Added: .eyebrow {
173 187 font: 700 var(--font-size-small)/1.35 var(--sans);
174 188 letter-spacing: 0.09em;
175 189 text-transform: uppercase;
@@ -302,14 +316,12 @@
302 316
303 317 .bottom-nav { display: none; }
304 318
319 Added: /* Flat and native: the content sits directly on the page, no card chrome. The
320 Added: oxblood brand marker now lives on the masthead, so the content starts clean.
321 Added: The desktop breakpoint only constrains the reading width. */
305 322 .page-surface {
306 323 min-height: 26rem;
307 Removed: border: 1px solid var(--rule-strong);
308 Removed: border-top: 6px solid var(--oxblood);
309 Removed: background: var(--paper-raised);
310 Removed: border-radius: var(--radius);
311 Removed: box-shadow: 0 0.4rem 1.5rem rgba(26, 26, 22, 0.09);
312 Removed: padding: clamp(1.25rem, 3vw, 2rem);
324 Added: padding: 1.25rem 0 0;
313 325 }
314 326
315 327 /* Page transitions. The SPA client swaps the content node on navigation
@@ -326,10 +338,17 @@
326 338 }
327 339
328 340 /* Keep the desktop masthead and navigation wide, while giving reading content
329 Removed: a shorter line length. Mobile keeps the full page-surface width below. */
341 Added: a shorter line length. The raised card returns at this width; mobile stays
342 Added: flat and native above. */
343 Added: /* On desktop the shell and content settle at a compact, readable ~50rem, rather
344 Added: than stretching with the viewport. The shell frames the masthead, content,
345 Added: and footer; the content fills it. */
330 346 @media (min-width: 42.0625rem) {
347 Added: .app-shell {
348 Added: width: min(100%, 50rem);
349 Added: }
331 350 .page-surface {
332 Removed: max-width: 52rem;
351 Added: width: 100%;
333 352 margin-inline: auto;
334 353 }
335 354 }
@@ -345,20 +364,25 @@
345 364 margin: 0 0 0.45rem;
346 365 color: var(--ink);
347 366 font-weight: 700;
348 Removed: line-height: 1.2;
349 367 letter-spacing: -0.03em;
350 368 }
351 Removed: h1 { font-size: clamp(1.75rem, 4vw, var(--font-size-heading)); }
352 Removed: h2 { font-size: 1.35rem; }
353 Removed: h3 { font-size: 1.1rem; }
369 Added: /* Line-height tightens as size grows, paired to the golden scale. */
370 Added: h1 {
371 Added: font-size: clamp(1.75rem, 4vw, var(--font-size-h1));
372 Added: line-height: 1.15;
373 Added: }
374 Added: h2 {
375 Added: font-size: var(--font-size-h2);
376 Added: line-height: 1.25;
377 Added: }
378 Added: h3 {
379 Added: font-size: var(--font-size-h3);
380 Added: line-height: 1.35;
381 Added: }
354 382
355 383 p { max-width: 46rem; margin: 0.65rem 0; color: var(--muted-ink); }
356 384 h2 + p { font-size: var(--font-size-lead); }
357 385
358 Removed: .ledger-meta {
359 Removed: color: var(--muted-ink);
360 Removed: font-variant-numeric: tabular-nums;
361 Removed: }
362 386
363 387 /* The upcoming workout's exercises, previewed on the Home card. A tight ledger
364 388 of what is coming, so the next session reads at a glance. */
@@ -371,19 +395,10 @@
371 395
372 396 fieldset {
373 397 margin: 1rem 0 0;
374 Removed: padding: 0.875rem;
375 Removed: border: 1px solid var(--rule);
376 Removed: border-top: 3px solid var(--ink);
377 Removed: background: var(--paper);
378 Removed: border-radius: var(--radius);
398 Added: padding: 0;
399 Added: border: 0;
379 400 }
380 401
381 Removed: /* The exercise logging fieldset drops the heavy ink top border: the card reads
382 Removed: as a plain bordered panel so the exercise-name heading leads it, not a rule. */
383 Removed: .logging-fieldset {
384 Removed: border-top: 1px solid var(--rule);
385 Removed: }
386 Removed:
387 402 legend {
388 403 max-width: 100%;
389 404 padding: 0 0.35rem;
@@ -392,16 +407,6 @@
392 407 letter-spacing: 0.025em;
393 408 }
394 409
395 Removed: /* The exercise name inside a fieldset reads as a heading: black Work Sans, so
396 Removed: the movement being logged stands out from the numeric fields below it. */
397 Removed: .exercise-name {
398 Removed: margin: 1rem 0 0;
399 Removed: color: var(--ink);
400 Removed: font: 700 var(--font-size-lead)/1.2 var(--sans);
401 Removed: letter-spacing: -0.01em;
402 Removed: }
403 Removed:
404 Removed: .field { margin-top: 1rem; }
405 410 label {
406 411 display: block;
407 412 margin: 0 0 0.35rem;
@@ -467,6 +472,55 @@
467 472 cursor: not-allowed;
468 473 }
469 474
475 Added: /* Correction actions (Save correction + Reset) appear only once a recorded
476 Added: value has changed. With JavaScript, the pair is hidden until the client marks
477 Added: the form dirty; the reset button then restores the recorded values, which
478 Added: clears the dirty state and hides the pair again. Without JavaScript the pair
479 Added: stays visible, so a correction is always possible. The buttons share the row
480 Added: at equal width. */
481 Added: .correction-actions {
482 Added: display: grid;
483 Added: grid-template-columns: 1fr 1fr;
484 Added: gap: 0.6rem;
485 Added: }
486 Added: [data-hito-js="on"] .correction-actions[data-hito-correction-actions] {
487 Added: display: none;
488 Added: }
489 Added: [data-hito-js="on"]
490 Added: form[data-hito-dirty]
491 Added: .correction-actions[data-hito-correction-actions] {
492 Added: display: grid;
493 Added: }
494 Added: /* Save correction reads as a primary outline: oxblood border and text on a
495 Added: transparent field, filling oxblood on hover. It pairs with the Reset button
496 Added: beside it without competing as a solid fill. */
497 Added: .correction-actions input[type="submit"] {
498 Added: color: var(--oxblood);
499 Added: background: transparent;
500 Added: border: 2px solid var(--oxblood-dark);
501 Added: }
502 Added: .correction-actions input[type="submit"]:hover {
503 Added: color: var(--on-primary);
504 Added: background: var(--oxblood);
505 Added: }
506 Added: /* A reset button styled to match a secondary submit. */
507 Added: button[type="reset"].secondary {
508 Added: min-height: 3rem;
509 Added: margin-top: 1rem;
510 Added: border: 2px solid var(--brass);
511 Added: border-radius: var(--radius);
512 Added: color: var(--brass);
513 Added: background: transparent;
514 Added: cursor: pointer;
515 Added: font: 700 var(--font-size-small)/1 var(--sans);
516 Added: transition: background-color 150ms ease, transform 150ms ease;
517 Added: }
518 Added: button[type="reset"].secondary:hover {
519 Added: color: var(--on-brass);
520 Added: background: var(--brass);
521 Added: transform: translateY(-2px);
522 Added: }
523 Added:
470 524 /* A secondary submit: a lower-emphasis action next to the primary one. It reads
471 525 as a receding, brass-outlined control so the primary submit stays the obvious
472 526 choice. Used for "Cancel logging" beside "Finish workout". */
@@ -572,6 +626,7 @@
572 626 width: 100%;
573 627 margin-top: 1.5rem;
574 628 border-collapse: collapse;
629 Added: table-layout: fixed;
575 630 }
576 631 .logbook-table th,
577 632 .logbook-table td {
@@ -605,7 +660,23 @@
605 660 font-family: var(--mono);
606 661 color: var(--muted-ink);
607 662 white-space: nowrap;
663 Added:
664 Added: @media (max-width: 30rem) {
665 Added: .logbook-table th,
666 Added: .logbook-table td {
667 Added: padding-inline: 0.25rem;
668 Added: }
669 Added:
670 Added: .logbook-actions-heading,
671 Added: .logbook-actions {
672 Added: width: 2.5rem;
673 Added: }
674 Added:
675 Added: .logbook-row-menu > summary {
676 Added: width: 2.5rem;
677 Added: }
608 678 }
679 Added: }
609 680 .logbook-duration {
610 681 white-space: nowrap;
611 682 font-family: var(--mono);
@@ -731,17 +802,14 @@
731 802 /* The action bar under the logbook heading. */
732 803 .logbook-actions-bar { margin: 0.5rem 0 1rem; }
733 804
734 Removed: /* The month calendar above the logbook: a self-contained widget. A bordered
735 Removed: card holds a header row (prev arrow, month/year picker, next arrow), a Today
736 Removed: link, and the seven-column day grid. Days with a workout are filled oxblood;
737 Removed: today is ringed. */
805 Added: /* The month calendar above the logbook: a self-contained widget, laid out flat
806 Added: on the page. A header row (prev arrow, month/year picker, next arrow), a
807 Added: Today link, and the seven-column day grid. Days with a workout are filled
808 Added: oxblood; today is ringed. */
738 809 .calendar {
739 810 max-width: 22rem;
740 811 margin: 0 auto 1.5rem;
741 Removed: padding: 0.6rem 0.75rem 0.75rem;
742 Removed: border: 1px solid var(--rule-strong);
743 Removed: border-radius: var(--radius);
744 Removed: background: var(--paper-raised);
812 Added: padding: 0.6rem 0 0.75rem;
745 813 }
746 814 .calendar-header {
747 815 display: flex;
@@ -763,16 +831,27 @@
763 831 }
764 832 .calendar-arrow:hover { background: var(--hover-surface); }
765 833 .calendar-today-link {
834 Added: display: inline-flex;
835 Added: align-items: center;
836 Added: justify-content: center;
837 Added: min-height: 2.25rem;
838 Added: padding: 0.35rem 0.75rem;
839 Added: border: 1px solid var(--oxblood-dark);
840 Added: border-radius: var(--radius);
766 841 flex: 1 1 auto;
767 Removed: text-align: center;
768 842 color: var(--oxblood-dark);
843 Added: background: transparent;
769 844 font-weight: 700;
770 845 text-decoration: none;
771 846 }
772 Removed: .calendar-today-link:hover { text-decoration: underline; }
847 Added: .calendar-today-link:hover {
848 Added: color: var(--oxblood-dark);
849 Added: background: var(--hover-surface);
850 Added: }
773 851 .calendar-grid {
774 852 width: 100%;
775 Removed: border-collapse: collapse;
853 Added: border-collapse: separate;
854 Added: border-spacing: 0.2rem 0.2rem;
776 855 table-layout: fixed;
777 856 }
778 857 .calendar-grid th {
@@ -849,8 +928,12 @@
849 928 }
850 929 .chart-axis { stroke: var(--rule-strong); stroke-width: 1; }
851 930 .chart-grid { stroke: var(--rule); stroke-width: 1; opacity: 0.5; }
852 Removed: .chart-tick { font: 500 0.7rem var(--mono); fill: var(--muted-ink); }
853 Removed: .chart-axis-title { font: 700 0.72rem var(--sans); }
931 Added: /* Chart text is sized in viewBox user units, not rem: the SVG scales to the
932 Added: container, so rem labels shrank to ~half size on a phone. User-unit sizes
933 Added: track the 640-unit viewBox, staying legible when the chart is narrow. At the
934 Added: 40rem cap they render ~20px; on a ~375px phone (≈0.59 scale) ~12px. */
935 Added: .chart-tick { font: 500 20px var(--mono); fill: var(--muted-ink); }
936 Added: .chart-axis-title { font: 700 22px var(--sans); }
854 937 .chart-axis-title.weight { fill: var(--oxblood-dark); }
855 938 .chart-axis-title.reps { fill: var(--brass); }
856 939 /* Weight is the primary series: a solid oxblood line leads. Reps is secondary:
@@ -859,31 +942,89 @@
859 942 .chart-series.weight { stroke: var(--oxblood); }
860 943 .chart-series.reps { stroke: var(--brass); stroke-dasharray: 5 3; }
861 944
862 Removed: /* One responsive grid for the whole logging fieldset. Every movement's name,
863 Removed: load, and reps line up in the same three columns — the name takes the
864 Removed: remaining space, the two numeric inputs take fixed, tap-friendly columns — so
865 Removed: a set fits on one line without scrolling. A [.movement] wrapper groups a
866 Removed: movement's cells for the markup but opens no nested grid ([display:
867 Removed: contents]), so all rows share this one grid. The [.field] top margin is
868 Removed: dropped here; the grid gap handles spacing. */
869 Removed: .logging-grid {
945 Added: /* --- Workout logging fieldset ---
946 Added: The set is a semantic table laid out as a grid so the three columns hold the
947 Added: golden ratio: the exercise name is the smaller part (1fr); load and reps take
948 Added: the wider φ portion, split evenly (0.809fr each). thead/tbody/tr are
949 Added: display:contents so their cells fall on the grid. The set ending (extension
950 Added: select + optional amount) is a separate flex row below the table. *)
951 Added: .logging-fieldset {
952 Added: border-top: 1px solid var(--rule);
953 Added: }
954 Added:
955 Added: .exercise-name-label {
956 Added: display: block;
957 Added: color: var(--ink);
958 Added: font: 700 var(--font-size-lead)/1.2 var(--sans);
959 Added: letter-spacing: -0.01em;
960 Added: }
961 Added: /* The record to beat: the exercise's most recent prior effort, an observed fact
962 Added: under its name, muted so it does not compete with the inputs. */
963 Added: .exercise-previous {
964 Added: display: block;
965 Added: margin: 0.15rem 0 0;
966 Added: color: var(--muted-ink);
967 Added: font: 500 var(--font-size-small)/1.2 var(--mono);
968 Added: font-variant-numeric: tabular-nums;
969 Added: }
970 Added:
971 Added: .logging-table {
870 972 display: grid;
871 Removed: grid-template-columns: minmax(0, 1fr) 5.5rem 5.5rem;
872 Removed: align-items: end;
873 Removed: gap: 0.6rem 0.6rem;
973 Added: /* Golden ratio between the name column and the two number columns: name is
974 Added: the smaller part (1), load and reps together are the larger part (φ ≈
975 Added: 1.618), split evenly (0.809fr each). So name : (load + reps) = 1 : φ. */
976 Added: grid-template-columns: minmax(0, 1fr) minmax(0, 0.809fr) minmax(0, 0.809fr);
977 Added: align-items: center;
978 Added: gap: 0.35rem 0.6rem;
874 979 margin-top: 1rem;
875 980 }
876 Removed: .movement { display: contents; }
877 Removed: .logging-grid .exercise-name { margin: 0; align-self: center; }
878 Removed: .logging-grid .field { margin-top: 0; }
879 Removed: /* The Ending control spans the full width below the movement rows. */
880 Removed: .logging-grid .field-ending { grid-column: 1 / -1; margin-top: 0.4rem; }
981 Added: .logging-table thead,
982 Added: .logging-table tbody,
983 Added: .logging-table tr {
984 Added: display: contents;
985 Added: }
986 Added: .logging-table th {
987 Added: margin: 0;
988 Added: padding: 0;
989 Added: text-align: left;
990 Added: }
991 Added: .logging-table thead th {
992 Added: padding-bottom: 0.35rem;
993 Added: color: var(--muted-ink);
994 Added: font: 700 var(--font-size-small)/1 var(--sans);
995 Added: align-self: end;
996 Added: }
997 Added: .logging-table td { padding: 0; }
998 Added: .logging-table input { width: 100%; }
881 999
882 Removed: /* The extension amount follows the ending select without script: option:checked
883 Removed: tracks the live selection and :has() lets the fieldset react to it. At
884 Removed: positive failure there is nothing to count, so the field hides; forced reps
885 Removed: and negatives count reps, a static hold counts seconds. Without :has()
886 Removed: support the field stays and the label reads as the combined form. */
1000 Added: /* The set ending: the extension select and its optional amount on one line.
1001 Added: The select flexes to fill; the amount is a compact fixed width followed by a
1002 Added: muted unit. The amount hides at positive failure (nothing to count). *)
1003 Added: /* The set ending: the extension select and its optional amount on one line, as
1004 Added: a two-column grid (select fills, amount sizes to content). A grid is
1005 Added: deterministic here where flex min-content sizing proved fragile. *)
1006 Added: .ending-block {
1007 Added: display: grid;
1008 Added: grid-template-columns: 1fr auto;
1009 Added: align-items: end;
1010 Added: gap: 0.6rem;
1011 Added: margin-top: 0.9rem;
1012 Added: }
1013 Added: .ending-block .field { margin: 0; min-width: 0; }
1014 Added: .ending-block .field-extension-amount {
1015 Added: display: flex;
1016 Added: align-items: baseline;
1017 Added: gap: 0.35rem;
1018 Added: }
1019 Added: .ending-block .field-extension-amount input { width: 4.5rem; }
1020 Added: .ending-block .field-unit {
1021 Added: color: var(--muted-ink);
1022 Added: font: 500 var(--font-size-small)/1 var(--sans);
1023 Added: }
1024 Added:
1025 Added: /* The extension amount follows the live selection with no script: :has() reads
1026 Added: the checked option. Positive failure hides the amount; a static hold counts
1027 Added: seconds, everything else counts reps. */
887 1028 @supports selector(:has(*)) {
888 1029 .logging-fieldset .amount-label-sep { display: none; }
889 1030 .logging-fieldset:has(select[name="extension"] option[value=""]:checked)
@@ -914,10 +1055,10 @@
914 1055 border: 0;
915 1056 }
916 1057
917 Removed: /* The Home card shown while a workout is in progress. A full-width pressable
918 Removed: panel that returns the user to the current workout. It reads as a raised
919 Removed: surface with an oxblood accent, echoing the page surface. */
920 Removed: .in-progress-card {
1058 Added: /* The Home affordance shown while a workout is in progress: a full-width
1059 Added: pressable panel that returns the user to the current workout. A left oxblood
1060 Added: accent marks it; it is not the page-surface card metaphor. */
1061 Added: .in-progress {
921 1062 display: block;
922 1063 margin: 1.25rem 0 0;
923 1064 border: 1px solid var(--rule-strong);
@@ -928,117 +1069,58 @@
928 1069 color: var(--ink);
929 1070 text-decoration: none;
930 1071 }
931 Removed: .in-progress-card:hover {
1072 Added: .in-progress:hover {
932 1073 background: var(--hover-surface);
933 1074 }
934 Removed: .in-progress-card h1 { margin: 0.35rem 0; }
935 Removed: .in-progress-card p { margin: 0; }
1075 Added: .in-progress h1 { margin: 0.35rem 0; }
1076 Added: .in-progress p { margin: 0; }
936 1077
937 Removed: /* The exercise accordion: one <details> per prescription slot. They share a
938 Removed: [name], so the browser keeps exactly one open — no script. The summary shows
939 Removed: the slot number, the exercise, and a done mark once recorded. It borrows the
940 Removed: routine accordion's shape so the two read as the same control. */
941 Removed: .slot-accordions {
942 Removed: display: grid;
943 Removed: gap: 0.65rem;
1078 Added: /* The prescription slots as a flat, always-visible list. Each slot is a section
1079 Added: with a heading (number + exercise label, struck through once recorded) and its
1080 Added: record or correction form directly below. With few slots per session, every
1081 Added: slot's state is visible at once, no click needed. Vertical spacing alone
1082 Added: separates slots — no rules between them. */
1083 Added: .slot-sections {
944 1084 margin: 1.25rem 0 0;
945 1085 }
946 Removed: .slot-accordion {
947 Removed: border: 1px solid var(--rule);
948 Removed: border-radius: var(--radius);
949 Removed: background: var(--paper-raised);
950 Removed: overflow: visible;
1086 Added: .slot-section {
1087 Added: padding: 1rem 0;
951 1088 }
952 Removed: .slot-accordion > summary {
1089 Added: .slot-heading {
953 1090 display: flex;
954 1091 align-items: baseline;
955 1092 gap: 0.6rem;
956 Removed: padding: 0.8rem 1rem;
957 Removed: color: var(--ink);
958 Removed: font-weight: 700;
959 Removed: cursor: pointer;
960 Removed: list-style: none;
1093 Added: margin: 0 0 0.5rem;
961 1094 }
962 Removed: .slot-accordion > summary::-webkit-details-marker { display: none; }
963 Removed: .slot-accordion > summary::marker { content: ""; }
964 Removed: .slot-accordion > summary::after {
965 Removed: margin-left: auto;
966 Removed: content: "+";
1095 Added: .slot-heading .slot-number { color: var(--muted-ink); }
1096 Added: .slot-heading .slot-label-done {
967 1097 color: var(--muted-ink);
1098 Added: text-decoration: line-through;
968 1099 }
969 Removed: .slot-accordion[open] > summary::after { content: "−"; }
970 Removed: .slot-accordion > summary .slot-number { color: var(--muted-ink); }
971 Removed: .slot-accordion > summary .slot-done {
972 Removed: font: 700 var(--font-size-small)/1 var(--sans);
973 Removed: letter-spacing: 0.055em;
974 Removed: text-transform: uppercase;
975 Removed: color: var(--brass);
976 Removed: }
977 Removed: .slot-accordion[data-done="true"] > summary { border-left: 4px solid var(--brass); }
978 1100
979 Removed: /* Unfold and collapse. ::details-content is the box the browser toggles for a
980 Removed: <details>; keep it rendered when closed and drive a grid row from 0fr to 1fr
981 Removed: off the [open] attribute, so both opening and closing ease in every browser
982 Removed: that has ::details-content. A first-render starting style would not do: a
983 Removed: closed panel stays in the tree with content-visibility hidden, so a toggle is
984 Removed: not a first render and @starting-style never fires. visibility keeps a closed
985 Removed: panel out of the tab order and the accessibility tree; it flips to visible at
986 Removed: the start of an open and to hidden at the end of a close. Browsers without
987 Removed: ::details-content ignore all of this and snap. Reduced motion snaps too. */
988 Removed: .slot-accordion::details-content {
989 Removed: content-visibility: visible;
990 Removed: display: grid;
991 Removed: grid-template-rows: 0fr;
992 Removed: visibility: hidden;
993 Removed: transition:
994 Removed: grid-template-rows 220ms ease,
995 Removed: visibility 220ms;
996 Removed: }
997 Removed: .slot-accordion[open]::details-content {
998 Removed: grid-template-rows: 1fr;
999 Removed: visibility: visible;
1000 Removed: }
1001 Removed: .slot-accordion > .slot-panel {
1002 Removed: min-height: 0;
1003 Removed: overflow: clip;
1004 Removed: }
1005 Removed: @media (prefers-reduced-motion: reduce) {
1006 Removed: .slot-accordion::details-content { transition: none; }
1007 Removed: }
1008 Removed:
1009 1101 /* The sticky workout timer bar. It pins to the top of the scroll area while a
1010 Removed: workout is being logged and shows the elapsed time. The client ticks the
1011 Removed: value from the workout's start time; server-rendered markup shows a static
1012 Removed: start so the bar is present without script. */
1102 Added: workout is being logged and shows the elapsed time as a centered Courier
1103 Added: Prime clock. The client ticks the value from the workout's start time;
1104 Added: server-rendered markup shows a static start so the bar is present without
1105 Added: script. */
1013 1106 .workout-timer {
1014 1107 position: sticky;
1015 1108 z-index: 10;
1016 1109 top: 0;
1017 1110 display: flex;
1018 Removed: align-items: baseline;
1019 Removed: justify-content: space-between;
1020 Removed: gap: 0.75rem;
1111 Added: align-items: center;
1112 Added: justify-content: center;
1021 1113 margin: 0 0 1rem;
1022 Removed: border: 1px solid var(--rule-strong);
1023 Removed: border-radius: var(--radius);
1024 Removed: background: var(--paper-raised);
1025 Removed: box-shadow: 0 0.2rem 0.6rem rgba(26, 26, 22, 0.08);
1026 Removed: padding: 0.55rem 0.9rem;
1114 Added: border-bottom: 2px solid var(--rule-strong);
1115 Added: background: var(--paper);
1116 Added: padding: 0.7rem 0;
1027 1117 }
1028 Removed: .workout-timer-label {
1029 Removed: color: var(--muted-ink);
1030 Removed: font: 700 var(--font-size-small)/1 var(--sans);
1031 Removed: letter-spacing: 0.08em;
1032 Removed: text-transform: uppercase;
1033 Removed: }
1034 1118 .workout-timer-value {
1035 1119 color: var(--ink);
1036 Removed: font: 700 var(--font-size-lead)/1 var(--mono);
1120 Added: font: 700 var(--font-size-h3)/1 var(--mono);
1037 1121 font-variant-numeric: tabular-nums;
1038 1122 }
1039 1123
1040 Removed: .slot-panel-body { padding: 0 1rem 1rem; }
1041 Removed:
1042 1124 /* The cancel confirmation modal. A native dialog styled as a raised paper
1043 1125 surface: the oxblood top accent, radius, border, and shadow echo the page
1044 1126 surface, so the modal reads as part of the same interface. The actions reuse
@@ -1109,18 +1191,11 @@
1109 1191 .feedback-modal-content { padding: 1.25rem; }
1110 1192 .feedback-modal-content h2 { margin-top: 0; text-align: center; }
1111 1193
1112 Removed: .feedback-list {
1113 Removed: margin-top: 1rem;
1114 Removed: list-style: disc;
1115 Removed: }
1116 Removed:
1117 1194 .settings-section {
1118 1195 max-width: 32rem;
1119 1196 margin-top: 1.25rem;
1120 Removed: padding: 1rem;
1121 Removed: border: 1px solid var(--rule);
1122 Removed: border-radius: var(--radius);
1123 Removed: background: var(--paper-raised);
1197 Added: padding: 0 0 1rem;
1198 Added: border-bottom: 1px solid var(--rule);
1124 1199 }
1125 1200 .settings-form { margin-top: 1rem; }
1126 1201
@@ -1139,10 +1214,10 @@
1139 1214 .notice {
1140 1215 max-width: 47rem;
1141 1216 margin: 1rem 0;
1142 Removed: border: 1px solid var(--rule-strong);
1143 Removed: border-left: 5px solid var(--oxblood);
1217 Added: border: 1px solid var(--evergreen);
1218 Added: border-left: 5px solid var(--evergreen);
1144 1219 color: var(--ink);
1145 Removed: background: var(--paper-raised);
1220 Added: background: var(--evergreen-wash);
1146 1221 padding: 0.9rem 1rem;
1147 1222 }
1148 1223
@@ -1250,6 +1325,18 @@
1250 1325 }
1251 1326
1252 1327 .page-surface { min-height: calc(100vh - 9rem); padding: 1.25rem; }
1328 Added:
1329 Added: /* On mobile the elapsed bar mirrors the bottom navigation visually: it spans
1330 Added: the full viewport width (breaking out of the page-surface 1.25rem and
1331 Added: app-shell 0.75rem side padding, then re-padding by the same 2rem), and
1332 Added: carries the bottom-nav's paper background, hairline border, and elevation
1333 Added: shadow — flipped to the top edge. It stays sticky, not fixed. */
1334 Added: .workout-timer {
1335 Added: margin-inline: -2rem;
1336 Added: padding-inline: 2rem;
1337 Added: border-bottom: 1px solid var(--rule);
1338 Added: box-shadow: 0 0.35rem 1.2rem rgba(26, 26, 22, 0.15);
1339 Added: }
1253 1340 }
1254 1341
1255 1342 @media (max-width: 26rem) {
@@ -1393,61 +1480,6 @@
1393 1480 .app-feedback-message {
1394 1481 white-space: pre-wrap;
1395 1482 }
1396 Removed:
1397 Removed: /* The early-workout toast. It reports that the workout began before recovery
1398 Removed: finished. The modal already confirmed the override, so on the workout screen
1399 Removed: this is informational only: a fixed notice above the content rather than a
1400 Removed: block in the flow. Rendered visible so a no-JS visitor reads it; the client
1401 Removed: marks it done and removes it after three seconds. The brass wash keeps it
1402 Removed: distinct from an oxblood error. */
1403 Removed: /* The transient notice. Served from the top edge: it slides down into view on
1404 Removed: arrival and, once the client marks it done — after five seconds or on
1405 Removed: Dismiss — slides back up out of view before it is removed. Without script it
1406 Removed: simply stays. */
1407 Removed: .toast {
1408 Removed: position: fixed;
1409 Removed: top: 0;
1410 Removed: left: 50%;
1411 Removed: z-index: 20;
1412 Removed: display: flex;
1413 Removed: align-items: center;
1414 Removed: gap: 0.75rem;
1415 Removed: max-width: min(30rem, calc(100% - 2rem));
1416 Removed: padding: 0.7rem 0.6rem 0.7rem 1rem;
1417 Removed: border: 1px solid var(--brass);
1418 Removed: border-top: none;
1419 Removed: border-radius: 0 0 var(--radius) var(--radius);
1420 Removed: color: var(--ink);
1421 Removed: background: var(--brass-wash);
1422 Removed: box-shadow: 0 6px 18px rgba(26, 26, 22, 0.18);
1423 Removed: font-weight: 600;
1424 Removed: transform: translate(-50%, 0);
1425 Removed: animation: toast-in 260ms ease both;
1426 Removed: transition: transform 320ms ease, opacity 320ms ease;
1427 Removed: }
1428 Removed: @keyframes toast-in {
1429 Removed: from { transform: translate(-50%, -100%); }
1430 Removed: to { transform: translate(-50%, 0); }
1431 Removed: }
1432 Removed: .toast[data-hito-toast-done] {
1433 Removed: transform: translate(-50%, -100%);
1434 Removed: opacity: 0;
1435 Removed: }
1436 Removed: .toast-message { flex: 1 1 auto; }
1437 Removed: .toast-dismiss {
1438 Removed: flex: none;
1439 Removed: width: 2rem;
1440 Removed: height: 2rem;
1441 Removed: padding: 0;
1442 Removed: border: none;
1443 Removed: border-radius: 50%;
1444 Removed: color: var(--ink);
1445 Removed: background: transparent;
1446 Removed: font-size: 1.25rem;
1447 Removed: line-height: 1;
1448 Removed: cursor: pointer;
1449 Removed: }
1450 Removed: .toast-dismiss:hover { background: rgba(26, 26, 22, 0.08); }
1451 1483
1452 1484 /* Subjective feedback uses one inline three-choice group per signal. The radio
1453 1485 input keeps native keyboard and form behavior; its adjacent span is the