@usableapp/cardds 0.7.16 → 0.7.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +23 -20
  3. package/cardds.css +3 -3
  4. package/css/actions.css +60 -38
  5. package/css/base.css +43 -17
  6. package/css/card.css +18 -13
  7. package/css/choice.css +21 -33
  8. package/css/forms.css +0 -3
  9. package/css/journey.css +0 -2
  10. package/css/layover.css +14 -24
  11. package/css/lists.css +1 -6
  12. package/css/media.css +7 -15
  13. package/css/numbers.css +5 -21
  14. package/css/people.css +1 -8
  15. package/css/sheet.css +12 -14
  16. package/css/stack.css +17 -21
  17. package/css/step.css +5 -8
  18. package/css/tokens.css +19 -4
  19. package/dist/actions/Btn.d.ts +1 -1
  20. package/dist/actions/Dropdown.js +1 -6
  21. package/dist/actions/Fab.d.ts +1 -1
  22. package/dist/actions/Fab.js +3 -2
  23. package/dist/cardds.css +228 -232
  24. package/dist/cards/Card.d.ts +23 -7
  25. package/dist/cards/Card.js +23 -8
  26. package/dist/cards/CardHead.d.ts +0 -1
  27. package/dist/cards/CardHead.js +1 -1
  28. package/dist/choice/Calendar.js +1 -2
  29. package/dist/choice/Mood.js +2 -2
  30. package/dist/forms/Field.js +2 -3
  31. package/dist/forms/FileBtn.js +1 -1
  32. package/dist/forms/Pin.js +6 -10
  33. package/dist/index.js +2 -2
  34. package/dist/layover/Banner.d.ts +10 -3
  35. package/dist/layover/Banner.js +6 -3
  36. package/dist/lists/Row.js +3 -3
  37. package/dist/media/Postcard.js +2 -2
  38. package/dist/numbers/Band.d.ts +3 -1
  39. package/dist/numbers/Picker.js +1 -7
  40. package/dist/scaffold/ActionBar.d.ts +3 -3
  41. package/dist/scaffold/ActionBar.js +2 -2
  42. package/dist/scaffold/Centre.d.ts +1 -1
  43. package/dist/scaffold/Centre.js +1 -1
  44. package/dist/scaffold/PagerAt.d.ts +1 -1
  45. package/dist/scaffold/PagerAt.js +1 -1
  46. package/dist/scaffold/Screen.js +2 -4
  47. package/dist/sheets/Drawer.js +3 -4
  48. package/dist/sheets/Sheet.js +8 -19
  49. package/dist/sheets/SheetStack.d.ts +2 -2
  50. package/dist/sheets/SheetStack.js +18 -10
  51. package/dist/sheets/SheetSteps.js +5 -7
  52. package/dist/sheets/StackBack.js +2 -1
  53. package/dist/sheets/StackSheet.d.ts +3 -2
  54. package/dist/sheets/StackSheet.js +6 -9
  55. package/dist/state.d.ts +19 -2
  56. package/dist/state.js +47 -7
  57. package/dist/templates/CallSheet.js +1 -2
  58. package/dist/templates/Home.d.ts +3 -2
  59. package/dist/templates/Home.js +1 -2
  60. package/dist/templates/Splash.js +2 -3
  61. package/dist/templates/Walkthrough.js +2 -2
  62. package/dist/type/Text.d.ts +1 -1
  63. package/package.json +7 -4
package/dist/cardds.css CHANGED
@@ -134,11 +134,26 @@
134
134
 
135
135
  /* ---- motion ---- */
136
136
  --motion-sheet: 0.35s;
137
+ --motion-quick: 0.2s; /* a small state flip that isn't a press: a chevron turning, a popover/banner opening */
137
138
  --motion-slide: 0.22s; /* a centred card sliding out and the next in (card.css) — quick, a page turn (Lh, 2026-09-12) */
138
139
  --motion-flip: 0.45s; /* a postcard turning over (media.css) — slow enough to read as a card in the hand */
139
140
  --dim: 0.7; /* secondary words dimmed on a ground whose muted ink does not fit (the dark card --3, a drawer, a tile's sub line): ONE share for all */
141
+ --rest: 0.6; /* an unpicked choice at rest: .day, .segment__item, .float-bar__item */
142
+ --faint: 0.3; /* further still: rails, far dots, unselected picker items */
140
143
  --motion-press: 0.12s; /* a control giving under the finger and springing back (actions.css PRESS) */
141
- --press-scale: 0.96; /* how far it gives — a share of its size */
144
+ --press-scale: 0.96; /* how far a finger-sized control gives — a share of its size */
145
+ --press-dim: 0.6; /* how far a card-wide one (a row, a summary line, a card) fades under the finger */
146
+ --press-shade: 0.93; /* how far something with a thing behind it (a peeked sheet, a postcard) darkens under the finger */
147
+ --off: 0.4; /* a disabled control's fade */
148
+
149
+ /* ---- z-index: the few layers that stack ABOVE ordinary content, low to high ---- */
150
+ --z-bar: 30; /* .action-bar, .float-bar — above .sheet-stack sheets (--level 1-5) */
151
+ --z-fab: 31; /* .fab — above the bars it floats near */
152
+ --z-top: 40; /* .appbar, a stage laid OVER its screen (.sheet-stage--over) — topmost, card animations never cover it */
153
+
154
+ /* ---- shared private sizes (not a knob: two components share the exact same
155
+ literal by design and must not drift apart) ---- */
156
+ --_cover-min-h: 22.5rem; /* .card--cover's min-height; --postcard-min-h falls back to it (media.css) */
142
157
 
143
158
  /* ---- typography: one scale, rem so it follows the root ---- */
144
159
  --font-ui: "PK Nonthaburi", -apple-system, system-ui, sans-serif; /* fonts.css ships it; the rest is the fallback while it loads */
@@ -253,7 +268,7 @@
253
268
 
254
269
  /* ============================================================
255
270
  REGISTERED TOKENS — the two sheet lengths are typed, so that
256
- cardds.js can read them back RESOLVED: getComputedStyle returns a
271
+ the Sheet component can read them back RESOLVED: getComputedStyle returns a
257
272
  registered <length> in px and a <number> as a number, where an
258
273
  unregistered custom property only ever reads back as its text
259
274
  ("calc(1.25rem + …)"). Drag-to-snap needs to know where the states
@@ -265,8 +280,8 @@
265
280
  falls back to when its own declaration cannot resolve.
266
281
  ============================================================ */
267
282
  @property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
268
- @property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; }
269
- @property --_lead { syntax: "<length>"; inherits: true; initial-value: 0px; } /* the room above a step's words: registered so a cqh share (a no-bar step, 15% of the SCREEN) resolves where it is declared — on the step, against the screen — not on the words, against the step */ /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
283
+ @property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; } /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
284
+ @property --_lead { syntax: "<length>"; inherits: true; initial-value: 0px; } /* the room above a step's words: registered so a cqh share (a no-bar step, 15% of the SCREEN) resolves where it is declared — on the step, against the screen — not on the words, against the step */
270
285
  @property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
271
286
  @property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
272
287
 
@@ -430,9 +445,9 @@
430
445
  /* …but controls keep a faint edge so they still read as controls */
431
446
  [data-palette] { --outline: color-mix(in srgb, currentColor 25%, transparent); }
432
447
 
433
- /* ---- css/base.css — reset, surface, type levels, section ---- */
448
+ /* ---- css/base.css — reset, surface, type levels, page ---- */
434
449
  /* ============================================================
435
- cardds/base.css — reset, surface, typography levels, section
450
+ cardds/base.css — reset, surface, typography levels, page
436
451
  ============================================================ */
437
452
 
438
453
  *, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
@@ -454,9 +469,8 @@ button, [role="button"], [role="tab"], [role="option"], [role="menuitem"], a.btn
454
469
  An app's OWN text-size setting is NOT here (Lh 2026-09-22: --text-scale, which
455
470
  multiplied the root, zoomed the UI too — retired): it is --fs-base on <html>,
456
471
  the type base, so the words grow and the things keep their size (tokens.css). */
457
- html { font-size: 100%; scrollbar-width: none; }
472
+ html { font-size: 100%; }
458
473
  @media (max-width: 22.5em) { html { font-size: 93.75%; } }
459
- html::-webkit-scrollbar { display: none; } /* Chromium/Safari */
460
474
 
461
475
  body {
462
476
  margin: 0;
@@ -472,7 +486,30 @@ body {
472
486
  [hidden] { display: none !important; }
473
487
 
474
488
  img, svg { display: block; max-width: 100%; }
475
- button { font: inherit; cursor: pointer; }
489
+ button { font: inherit; }
490
+ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
491
+
492
+ /* zero-specificity reset for controls that draw nothing of their own (2026-09-27 review):
493
+ a component's own rule always wins, at any specificity, so this never fights it — it only
494
+ fills in for a selector that never says otherwise. Not everything below shares every
495
+ property: two exceptions read as they always have — .calendar__day has never set its own
496
+ padding (the UA button default), and .sheet-step__go has never set its own color (the UA
497
+ button default too) — giving either one now would be a look change, so neither is here. */
498
+ :where(.appbar__who, .segment__item, .day, .calendar__day, .menu__item, .float-bar__item, .link,
499
+ .add-row, .avatar-pick__item, .picker__item, .postcard__turn, .sheet-step__go) {
500
+ border: 0;
501
+ background: transparent;
502
+ font: inherit;
503
+ }
504
+ :where(.appbar__who, .segment__item, .day, .calendar__day, .menu__item, .float-bar__item, .link,
505
+ .add-row, .avatar-pick__item, .picker__item, .postcard__turn) { color: inherit; }
506
+ :where(.appbar__who, .segment__item, .day, .menu__item, .float-bar__item, .link, .add-row,
507
+ .avatar-pick__item, .picker__item, .postcard__turn, .sheet-step__go) { padding: 0; }
508
+
509
+ /* scrollbar hidden, scroll kept: the track scrolls by touch/drag, never by a visible bar —
510
+ .bleed (card.css) is the one exception, its bar is designed, not hidden */
511
+ :where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick) { scrollbar-width: none; }
512
+ :where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick)::-webkit-scrollbar { display: none; }
476
513
 
477
514
  /* icon from icons.svg sprite: <svg class="icon"><use href="icons.svg#bell"/></svg> */
478
515
  .icon { width: var(--icon-size, var(--icon)); height: var(--icon-size, var(--icon)); flex: none; }
@@ -481,10 +518,16 @@ button { font: inherit; cursor: pointer; }
481
518
  .icon--lg { width: var(--icon-lg-size, var(--icon-lg)); height: var(--icon-lg-size, var(--icon-lg)); } /* empty states */
482
519
 
483
520
  /* mobile screen column — the standard horizontal inset, and nothing vertical.
484
- Top and bottom are zero on purpose: vertical room is only ever a RESERVE for
485
- something fixed parked over the screen, so each bar adds its own and a page
486
- without bars needs no override. .appbar reserves the top (actions.css),
487
- .action-bar (actions.css) and .float-bar (layover.css) the bottom. */
521
+ Top is zero on purpose: room there is only ever a RESERVE for something
522
+ FIXED parked over the screen — .appbar reserves it (actions.css). A bottom
523
+ bar is not fixed (0.7.1): it is the Screen's own last child, in flow on a
524
+ scrolling screen (a sticky row, actions.css) or, in a fill screen, a layer
525
+ over the content's foot (step.css) — nothing here reserves room for it.
526
+ It also reads like a native app (Lh 2026-09-25): words are not highlighted
527
+ by a long press or a drag, no copy / web-search callout — only what is
528
+ typed into, and words marked selectable (`Text selectable` → .t-select: a
529
+ code, an address). Knob --screen-select: `text` = the web page's old way
530
+ everywhere. */
488
531
  .screen {
489
532
  --screen-gap: var(--sp-5); /* published: a .step cancels it to touch the bar it follows (step.css) */
490
533
  max-width: var(--screen-w);
@@ -493,6 +536,7 @@ button { font: inherit; cursor: pointer; }
493
536
  display: grid;
494
537
  gap: var(--screen-gap);
495
538
  align-content: start; /* a screen taller than its content (a fixed phone) never spreads its rows — the top bar stays at the top, a row keeps its height */
539
+ -webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); -webkit-touch-callout: none;
496
540
  }
497
541
  .screen > * { min-width: 0; } /* content never widens the column past the viewport */
498
542
  /* a screen that carries its own palette (data-palette on the element, not on <html>)
@@ -535,21 +579,18 @@ button { font: inherit; cursor: pointer; }
535
579
 
536
580
  .t-muted { color: var(--on-surface-muted); }
537
581
  .card .t-muted { color: var(--card-muted); } /* muted follows the card's ink, not the surface's */
538
- .card--3 .t-muted { color: inherit; opacity: var(--dim); } /* card-muted is tuned for light cards; dim instead on the dark slot */
582
+ /* .card--3 .t-muted (dim instead of --card-muted on the dark slot) is declared once in card.css, with the rest of that family */
539
583
  .t-caps { text-transform: uppercase; }
540
584
  .grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
541
585
  /* read, not shown (Lh 2026-09-23): words a screen reader needs — a page's h1 the design leaves out — kept in the page, taking no room.
542
586
  The 1px is the one the platform needs to keep it in the accessibility tree, not a length of the design. */
543
- /* a screen reads like a native app (Lh 2026-09-25): words are not highlighted by a long press or a drag, no copy / web-search
544
- callout — only what is typed into, and words marked selectable (`Text selectable` → .t-select: a code, an address).
545
- Knob --screen-select: `text` = the web page's old way everywhere. */
546
- .screen { -webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); -webkit-touch-callout: none; }
547
587
  .screen :is(input, textarea, [contenteditable="true"]), .screen .t-select { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
548
588
  .t-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
549
589
 
550
- /* ---- base content: words straight on the base, no card around them — an
551
- a heading, a paragraph, maybe a .cta-pack. Was .section until
552
- 2026-09-16 (Lh: the name said nothing about WHERE it lives; this one does).
590
+ /* ---- page: words straight on the base, no card around them — a
591
+ heading, a paragraph, maybe a .cta-pack. Was .section until 2026-09-16
592
+ (Lh: the name said nothing about WHERE it lives), then .base-content
593
+ until 2026-09-23 (Lh: the content is a BOX; what's in it is .page).
553
594
  In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
554
595
  .page {
555
596
  display: grid;
@@ -583,14 +624,15 @@ button { font: inherit; cursor: pointer; }
583
624
  color: var(--empty-state-ink, var(--on-surface)); /* full ink, icon and words alike: muted read as "disabled" at the middle of an empty screen (Lh 2026-09-16) — muted is for meta, not a sentence */
584
625
  }
585
626
  .empty-state__action { margin-top: var(--sp-2); }
586
- /* inside a card (a section's "nothing yet"): the card's ink */
627
+ /* inside a card (a card's own "nothing yet"): the card's ink */
587
628
  .card .empty-state { color: var(--card-ink); padding-block: var(--sp-6); }
588
629
  .card--3 .empty-state { color: inherit; }
589
630
 
590
631
  /* ---- css/card.css — card boundary + list variants ---- */
591
632
  /* ============================================================
592
633
  cardds/card.css — the card boundary + list variants
593
- One card = one boundary. Tone via .card--1/2/3 (role slots).
634
+ One card = one boundary. The base .card is tone 1; further tones
635
+ via .card--2/3/4/5 (role slots).
594
636
  ============================================================ */
595
637
 
596
638
  .card {
@@ -615,14 +657,18 @@ button { font: inherit; cursor: pointer; }
615
657
  .card > * { min-width: 0; }
616
658
 
617
659
  .card--2 { --card-bg: var(--card-2-bg); --card-ink: var(--card-2-ink); --card-muted: var(--card-2-muted); }
618
- .card--3 { --card-bg: var(--card-3-bg); --card-ink: var(--card-3-ink); }
619
- .card--4 { --card-bg: var(--card-4-bg); --card-ink: var(--card-4-ink); --card-muted: var(--card-4-muted); }
620
- .card--5 { --card-bg: var(--card-5-bg); --card-ink: var(--card-5-ink); --card-muted: var(--card-5-muted); }
621
-
622
660
  /* dark/accent slot: accent-toned controls would vanish — the slot re-points
623
661
  the accent tokens, so every accent consumer (btn--primary, chip--on,
624
662
  check, toggle, ring, day…) inverts without per-component rules */
625
- .card--3 { --accent: var(--card-3-ink); --on-accent: var(--card-3-bg); }
663
+ .card--3 { --card-bg: var(--card-3-bg); --card-ink: var(--card-3-ink); --accent: var(--card-3-ink); --on-accent: var(--card-3-bg); }
664
+ .card--4 { --card-bg: var(--card-4-bg); --card-ink: var(--card-4-ink); --card-muted: var(--card-4-muted); }
665
+ .card--5 { --card-bg: var(--card-5-bg); --card-ink: var(--card-5-ink); --card-muted: var(--card-5-muted); }
666
+ /* the dark slot's captions/hints/footnotes: --card-muted is tuned for a light card, so these dim the full ink
667
+ instead (one rule, matching specificity to what it replaces — .card--3 <space> <one class>) — merged from
668
+ base.css/choice.css/media.css/numbers.css/journey.css/people.css (2026-09-27 review). lists.css's .legend
669
+ differs (opacity 0.8, not --dim) and stays there. */
670
+ .card--3 :is(.t-muted, .calendar__wd, .composer__hint, .quote__by, .ring__label, .band__foot, .picker__unit,
671
+ .route__place, .note-row, .avatar-pick__role) { color: inherit; opacity: var(--dim); }
626
672
 
627
673
  /* header row: title left, floating action right — centered, since the
628
674
  trailing control (chip/badge/icon-btn/link) is almost always taller than
@@ -725,7 +771,7 @@ button { font: inherit; cursor: pointer; }
725
771
  linear-gradient(to top left, transparent calc(50% - 1px), currentColor calc(50% - 1px), currentColor calc(50% + 1px), transparent calc(50% + 1px));
726
772
  border: var(--border-w) solid currentColor;
727
773
  border-radius: var(--r-media);
728
- opacity: 0.4;
774
+ opacity: var(--off);
729
775
  min-height: 10rem;
730
776
  }
731
777
  .ph--sm { min-height: 7.5rem; }
@@ -781,9 +827,9 @@ button { font: inherit; cursor: pointer; }
781
827
  likes when not (Lh, 2026-09-12) ----
782
828
  The member sees one content area — whatever is between the top bar and the
783
829
  bar — and reads it as a whole page: a short card sits at its middle, a long
784
- one simply makes the page longer, runs under the bar (the bar's reserve is
785
- already the screen's bottom padding, actions.css) and scrolls up like any
786
- page — nothing is capped, nothing shrinks, no scroll box inside a box.
830
+ one simply makes the page longer, runs under the bar (a sticky last row,
831
+ actions.css) and scrolls up like any page — nothing is capped, nothing
832
+ shrinks, no scroll box inside a box.
787
833
  So on a .screen that is NOT --fill the group is in flow: the column is at
788
834
  least one phone tall, the group takes the room after the top bar and
789
835
  centres its children in it. The screen's own inset stays the inset.
@@ -801,9 +847,9 @@ button { font: inherit; cursor: pointer; }
801
847
  the React adapter's slideTo() sets data-slide on :root, starts the transition
802
848
  and updates inside it; a browser without them (the floor: Chrome 105 / iOS 16)
803
849
  simply switches. Only the card is named; the rest of the page must not
804
- cross-fade. */
805
- /* the name is set by slideTo() on the moving element for the move only (2026-09-22): a static name on every walk's current
806
- page made two walks on one page — the gallery — share it, and the browser aborts a transition with a duplicate name */
850
+ cross-fade. The name is set by slideTo() on the moving element for the move only (2026-09-22): a static name on
851
+ every walk's current page made two walks on one page — the gallery — share it, and the browser aborts a
852
+ transition with a duplicate name. */
807
853
 
808
854
  /* ---- pages: the cards of a walk, all one height ----
809
855
  A sequence the member walks (an introduction) reads steadier when every card
@@ -935,7 +981,7 @@ button { font: inherit; cursor: pointer; }
935
981
  bottom, the headroom above is the base's (block below).
936
982
  Direct children only, five at most (slots are :nth-child below):
937
983
  first = the back of the stack, last = the front. README "Sheet stack
938
- markup". cardds.js opens and closes a --tap stack; without it the
984
+ markup". The SheetStack component opens and closes a --tap stack; without it the
939
985
  stack still renders, it just won't open.
940
986
  ============================================================ */
941
987
 
@@ -948,14 +994,14 @@ button { font: inherit; cursor: pointer; }
948
994
  track is being sized), so a long sheet grew the track past the screen and
949
995
  every sheet's 100% then resolved against that — the bottom of every open
950
996
  sheet sat below the screen, unreachable (timebank home, 2026-09-13). */
951
- .sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step: 5.5rem; }
952
-
953
997
  /* the stack breaks out of the screen inset, edge to edge, always: the peek
954
998
  strips and the opened sheet span the screen's full width whatever the
955
999
  viewport (it used to bleed only under a 27em media query — a stack in a
956
1000
  phone frame on a desktop kept the inset, and read as a narrower card;
957
- Lh, 2026-09-11) */
958
- .sheet-stack { margin-inline: calc(-1 * var(--screen-pad)); }
1001
+ Lh, 2026-09-11). --_n: how many sheets the stack holds, read from the DOM
1002
+ by :has below so the closed geometry needs no script and no per-stack
1003
+ declaration. */
1004
+ .sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step: 5.5rem; margin-inline: calc(-1 * var(--screen-pad)); --_n: 1; }
959
1005
  .sheet-stack > .card {
960
1006
  grid-area: 1 / 1; /* every card shares the stack's cell; later ones offset down */
961
1007
  border-bottom-left-radius: 0;
@@ -972,9 +1018,6 @@ button { font: inherit; cursor: pointer; }
972
1018
  .sheet-stack > .card:not(:has(~ .card)) {
973
1019
  align-self: stretch; /* the front card fills the remaining stack height */
974
1020
  }
975
- /* --_n: how many sheets the stack holds, read from the DOM by :has so the
976
- closed geometry needs no script and no per-stack declaration */
977
- .sheet-stack { --_n: 1; }
978
1021
  .sheet-stack:has(> .card:nth-child(2)) { --_n: 2; }
979
1022
  .sheet-stack:has(> .card:nth-child(3)) { --_n: 3; }
980
1023
  .sheet-stack:has(> .card:nth-child(4)) { --_n: 4; }
@@ -1021,7 +1064,6 @@ button { font: inherit; cursor: pointer; }
1021
1064
  /* fanned stack: a toggle opens the stack wider so every peek shows more */
1022
1065
  .sheet-stack--fanned { --stack-step: 9.5rem; }
1023
1066
 
1024
- .sheet-stack > .card:has(~ .card) { cursor: pointer; }
1025
1067
  /* a peeked card in a plain stack hides its own body. A --tap stack does
1026
1068
  NOT: there, every card is full height and full content always, and a
1027
1069
  peek is simply the strip of it the next card doesn't cover — the way a
@@ -1067,9 +1109,10 @@ button { font: inherit; cursor: pointer; }
1067
1109
  by n steps (clipped by .screen--fill), so translating it up by exactly
1068
1110
  that much lands it filling the stack precisely. */
1069
1111
  overflow: clip;
1070
- transition: translate 0.35s ease; /* the only animated property here */
1112
+ transition: translate var(--motion-sheet) ease; /* the only animated property here */
1071
1113
  transition-delay: calc(var(--stagger, 0) * 70ms); /* 0 for everything
1072
1114
  except a staggered is-before card, set in JS as it tucks or un-tucks */
1115
+ --_back-size: var(--stack-back-size, calc(var(--tap-base) * 2 / 3)); /* read once, shared by the back button and the head's padding below */
1073
1116
  }
1074
1117
  .sheet-stack--tap > .card.is-open {
1075
1118
  /* up by its own resting offset — pure movement, nothing resizes */
@@ -1091,9 +1134,8 @@ button { font: inherit; cursor: pointer; }
1091
1134
  overscroll-behavior: contain;
1092
1135
  }
1093
1136
  /* a scrolling sheet shows no scrollbar (a desktop with a mouse would paint one
1094
- down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself */
1095
- .sheet-stack--tap > .card { scrollbar-width: none; }
1096
- .sheet-stack--tap > .card::-webkit-scrollbar { display: none; }
1137
+ down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself
1138
+ (the hiding rule is base.css's, shared with every other scrolling track) */
1097
1139
  .sheet-stack--tap > .card.is-before { translate: 0 var(--stack-step); }
1098
1140
  .sheet-stack--tap > .card.is-after { translate: 0 100vh; }
1099
1141
 
@@ -1112,7 +1154,7 @@ button { font: inherit; cursor: pointer; }
1112
1154
  lands flush at the top. is-before tucks one step below its resting place.
1113
1155
  ============================================================ */
1114
1156
  .sheet-stack--closed { --_headroom: calc(100% - var(--_n) * var(--stack-step)); }
1115
- .sheet-stack--closed > .card { translate: 0 var(--_headroom); cursor: pointer; }
1157
+ .sheet-stack--closed > .card { translate: 0 var(--_headroom); }
1116
1158
  .sheet-stack--closed > .card.is-before { translate: 0 calc(var(--_headroom) + var(--stack-step)); }
1117
1159
  .sheet-stack--closed > .card.is-open { translate: 0 calc(-1 * var(--slot, 0) * var(--stack-step)); }
1118
1160
  .sheet-stack--closed > .card.is-after { translate: 0 100vh; }
@@ -1144,7 +1186,7 @@ button { font: inherit; cursor: pointer; }
1144
1186
  .sheet-stack--tap .card__head {
1145
1187
  position: relative;
1146
1188
  padding-left: 0;
1147
- transition: padding-left 0.35s ease;
1189
+ transition: padding-left var(--motion-sheet) ease;
1148
1190
  }
1149
1191
  .sheet-stack--tap .sheet-stack__back {
1150
1192
  position: absolute;
@@ -1153,10 +1195,10 @@ button { font: inherit; cursor: pointer; }
1153
1195
  translate: 0 -50%;
1154
1196
  opacity: 0;
1155
1197
  visibility: hidden;
1156
- transition: opacity 0.35s ease, visibility 0.35s;
1198
+ transition: opacity var(--motion-sheet) ease, visibility var(--motion-sheet);
1157
1199
  /* SMALL (see "the back button, SMALL" below) */
1158
- width: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
1159
- height: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
1200
+ width: var(--_back-size);
1201
+ height: var(--_back-size);
1160
1202
  min-width: 0;
1161
1203
  min-height: 0;
1162
1204
  padding: 0;
@@ -1218,21 +1260,21 @@ button { font: inherit; cursor: pointer; }
1218
1260
  /* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
1219
1261
  tappable"): a SMALL DISC — its ring kept, 32px instead of 44, the chevron in it at 20 — so the head gives it 32px + a gap
1220
1262
  (was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
1221
- .sheet-stack--tap > .card.is-open > .card__head { padding-left: var(--stack-back-room, calc(var(--stack-back-size, calc(var(--tap-base) * 2 / 3)) + var(--sp-2))); }
1263
+ .sheet-stack--tap > .card.is-open > .card__head { padding-left: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
1222
1264
  .sheet-stack--tap .sheet-stack__back > .icon { width: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); height: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); flex: none; } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
1223
- .sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--stack-back-size, calc(var(--tap-base) * 2 / 3)) - var(--tap-sm)) / 2); }
1265
+ .sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--_back-size) - var(--tap-sm)) / 2); }
1224
1266
 
1225
1267
  /* the open sheet's title folds it (SheetStack.tsx): it says so to a pointer */
1226
1268
  .sheet-stack--tap > .card.is-open > .card__head > :is(h1, h2, h3) { cursor: pointer; }
1227
1269
  /* a stacked sheet is a control until it is open — tapped, never read into: no text selection (a drag, a double tap, a long press),
1228
1270
  no grey flash, no iOS callout; its head stays one when open. Only the OPEN sheet's body is text to select (Lh 2026-09-23) */
1229
- .sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; }
1271
+ .sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
1230
1272
  .sheet-stack--tap > .card.is-open > :not(.card__head) { -webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); } /* the open sheet reads as the rest of the screen: not selectable (Lh 2026-09-25; it was text) */
1231
1273
 
1232
1274
  /* ---- css/sheet.css — sheet single: .sheet — one card as a bottom sheet, peek / half / full ---- */
1233
1275
  /* ============================================================
1234
1276
  cardds/sheet.css — sheet single: one card as a bottom sheet.
1235
- Four states: .sheet--peek · .sheet--half · .sheet--3q · .sheet--full
1277
+ Five states: .sheet--peek · .sheet--half · .sheet--3q · .sheet--full · .sheet--away
1236
1278
 
1237
1279
  A sheet is rigid material, exactly like a sheet in .sheet-stack--tap
1238
1280
  (stack.css): it is ALWAYS the full height of the box that holds it —
@@ -1273,8 +1315,8 @@ button { font: inherit; cursor: pointer; }
1273
1315
  the body. The handle and the head stay where they are at every
1274
1316
  state; the body is what scrolls — a sheet is a card whose head is
1275
1317
  pinned, not a scrolling column with a title somewhere in it.
1276
- cardds.js adds the handle's click / drag / keyboard and the focus
1277
- raise; without it every state still renders.
1318
+ The Sheet component adds the handle's click / drag / keyboard and the
1319
+ focus raise; without it every state still renders.
1278
1320
  ============================================================ */
1279
1321
 
1280
1322
  .sheet {
@@ -1302,7 +1344,7 @@ button { font: inherit; cursor: pointer; }
1302
1344
  position: absolute;
1303
1345
  inset: 0;
1304
1346
  top: var(--_top, 0); /* a stage keeps its top bar's line free — see .sheet-stage */
1305
- translate: 0 calc(var(--_y) + var(--_drag, 0rem)); /* --_drag: cardds.js, only while the handle is held */
1347
+ translate: 0 calc(var(--_y) + var(--_drag, 0rem)); /* --_drag: the Sheet component, only while the handle is held */
1306
1348
  transition: translate var(--motion-sheet) ease;
1307
1349
 
1308
1350
  /* handle · head · body: a column, the body takes what is left and scrolls
@@ -1313,6 +1355,7 @@ button { font: inherit; cursor: pointer; }
1313
1355
  border-radius: var(--r-card) var(--r-card) 0 0;
1314
1356
  border-bottom: 0;
1315
1357
  box-shadow: var(--sheet-shadow, var(--shadow-sheet));
1358
+ --_foot: calc(var(--card-gap) + var(--btn-h) + var(--card-pad)); /* the foot strip's height (below) — merged here, one bare .sheet rule */
1316
1359
  }
1317
1360
  .sheet--full { --_y: 0%; --_cover: 0rem; }
1318
1361
  .sheet--half { --_y: calc(var(--sheet-half) * 100%); --_cover: calc(var(--sheet-half) * var(--_box)); }
@@ -1378,9 +1421,8 @@ button { font: inherit; cursor: pointer; }
1378
1421
  takes no taps, its buttons do; the strip behind them is the sheet's ground, --_foot tall, and the body
1379
1422
  stops that far above the sheet's edge. At peek there is only the handle and the title: the foot is gone.
1380
1423
  One row, ≤ 2 buttons. <div class="card__foot"><button class="btn btn--primary btn--block">…</button></div> */
1381
- .sheet { --_foot: calc(var(--card-gap) + var(--btn-h) + var(--card-pad)); }
1382
1424
  /* …and the link row under the button, when there is one (its line + the gap over it) */
1383
- .sheet:has(> .card__foot--link) { --_foot: calc(var(--card-gap) + var(--btn-h) + var(--sp-1) + calc(var(--tap-base) * 7 / 12) + var(--card-pad)); }
1425
+ .sheet:has(> .card__foot--link) { --_foot: calc(var(--card-gap) + var(--btn-h) + var(--sp-1) + var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)) + var(--card-pad)); }
1384
1426
  .sheet > .card__foot {
1385
1427
  position: absolute; inset: calc(-1 * var(--border-w)) 0 0; z-index: 1; /* the sheet's border box: the same height the sheet's own % reads */
1386
1428
  align-items: end;
@@ -1406,7 +1448,7 @@ button { font: inherit; cursor: pointer; }
1406
1448
  /* grab handle — first child of the sheet. Drawn as a small bar, but it is
1407
1449
  the thumb's grab area for the whole strip above the title: a pseudo widens
1408
1450
  its hit box to --tap tall and wider than the bar without changing the
1409
- picture. touch-action: none hands a touch drag to cardds.js instead of to
1451
+ picture. touch-action: none hands a touch drag to the Sheet component instead of to
1410
1452
  the scroller behind it. */
1411
1453
  .sheet__handle {
1412
1454
  position: relative;
@@ -1480,7 +1522,7 @@ button { font: inherit; cursor: pointer; }
1480
1522
  .screen > .sheet-stage { margin-inline: calc(-1 * var(--screen-pad)); width: auto; } /* auto: 100% would keep the inset width */
1481
1523
  /* a stage laid OVER its screen (a Modal a link opened, Lh 2026-09-23): the page shows through, the stage covers it and its bar */
1482
1524
  .sheet-stage--over, .screen > .sheet-stage--over {
1483
- position: absolute; inset: 0; z-index: 40;
1525
+ position: absolute; inset: 0; z-index: var(--z-top);
1484
1526
  width: auto; height: auto; max-width: none; margin: 0;
1485
1527
  background: transparent;
1486
1528
  --_top: 0rem;
@@ -1537,7 +1579,7 @@ button { font: inherit; cursor: pointer; }
1537
1579
  translate: -50% -50%;
1538
1580
  border-radius: 50%;
1539
1581
  border: var(--border-w) solid var(--map-ring-border, var(--accent));
1540
- background: var(--map-ring-bg, color-mix(in srgb, var(--accent) 22%, transparent));
1582
+ background: var(--map-ring-bg, var(--accent-soft));
1541
1583
  box-shadow: var(--map-ring-shadow, var(--shadow-float));
1542
1584
  pointer-events: none;
1543
1585
  z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
@@ -1571,9 +1613,6 @@ button { font: inherit; cursor: pointer; }
1571
1613
  z-index: 1;
1572
1614
  }
1573
1615
 
1574
- /* raised: the sheet floats above a footnote strip on the scrim. Its bottom
1575
- edge shows, so it gets its corners and border back; full still stops at
1576
- the top bar's line, because the top edge never moved. */
1577
1616
  /* EVERY sheet has one shape (Lh, 2026-09-21): the handle and the head stay at the top and never scroll · the
1578
1617
  foot's button stands at the bottom, and ABOVE the keyboard when there is one · everything between them
1579
1618
  scrolls, as far as it needs to. In a .step the keyboard's room is the step's (--kb, step.css); on a stage
@@ -1621,6 +1660,7 @@ button { font: inherit; cursor: pointer; }
1621
1660
  display: grid; grid-template-rows: auto 0fr; align-content: start; /* mid-fold the rows sum to less than the box: the spare room stays BELOW, never spread into the title row (Lh: it looked like a spring) */
1622
1661
  flex: 0 0 auto;
1623
1662
  transition: grid-template-rows var(--motion-sheet) ease, flex-grow var(--motion-sheet) ease;
1663
+ --_row-h: var(--sheet-steps-row-h, calc(var(--tap-base) * 2 / 3)); /* the bar's own floor, read once (__head and __go below share it) */
1624
1664
  }
1625
1665
  /* the open step GROWS into the room left, so the steps not done yet wait in a pile at the bottom — and a move is an ACCORDION:
1626
1666
  in one beat the step that was open folds while the new one unfolds (flex-grow and the 1fr row both ease), the piles slide
@@ -1628,7 +1668,7 @@ button { font: inherit; cursor: pointer; }
1628
1668
  .sheet-step--now { grid-template-rows: auto 1fr; flex-grow: 1; }
1629
1669
  .sheet-step + .sheet-step { margin-top: var(--sheet-steps-gap, var(--sp-1)); } /* the folded bars read as a pile, a little ground between them (Lh 2026-09-25) */
1630
1670
  .sheet-step__head { /* every step's title line is a quiet BAR (a tint of the ink, not the brand — a brand bar read as a button, Lh 2026-09-25), the same height open or folded — opening shows the white room under it, never a taller bar (Lh 2026-09-25; was a thin rule on top) */
1631
- display: flex; align-items: center; gap: var(--sp-3); min-height: var(--sheet-steps-row-h, calc(var(--tap-base) * 2 / 3)); /* THIN: 32px, a summary line, not a button (Lh 2026-09-25) — the tap stays 44, see __go */
1671
+ display: flex; align-items: center; gap: var(--sp-3); min-height: var(--_row-h); /* THIN: 32px, a summary line, not a button (Lh 2026-09-25) — the tap stays 44, see __go */
1632
1672
  padding-inline: var(--sheet-step-bar-pad-x, var(--sp-3)); border-radius: var(--sheet-step-bar-radius, var(--r-chip));
1633
1673
  background: var(--sheet-step-bar-bg, var(--ink-tint)); color: var(--sheet-step-bar-ink, inherit);
1634
1674
  border-top: var(--sheet-step-border-w, 0rem) solid var(--sheet-step-border, currentColor); /* a rule instead of the bar: set this, the bar's bg transparent, radius 0 */
@@ -1646,7 +1686,7 @@ button { font: inherit; cursor: pointer; }
1646
1686
  .sheet-step__inner > * { min-width: 0; }
1647
1687
  .sheet-step__inner > .link { justify-self: center; } /* a link of its own row in a step (skip, "change number", "set PIN again"): centred under the step, as the skip (Lh 2026-09-25) */
1648
1688
  .sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s linear var(--motion-sheet); }
1649
- .sheet-step__go { position: absolute; inset: min(0rem, (var(--sheet-steps-row-h, calc(var(--tap-base) * 2 / 3)) - var(--tap-sm)) / 2) 0 auto; min-height: max(100%, var(--tap-sm)); /* a thin bar still takes a 44px tap */ margin: 0; padding: 0; border: 0; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent; }
1689
+ .sheet-step__go { position: absolute; inset: min(0rem, (var(--_row-h) - var(--tap-sm)) / 2) 0 auto; min-height: max(100%, var(--tap-sm)); /* a thin bar still takes a 44px tap */ margin: 0; cursor: pointer; }
1650
1690
  /* the last step's finish, when the app gives one: under the steps */
1651
1691
  .sheet-steps__finish { display: grid; padding-top: var(--card-gap); }
1652
1692
  @media (prefers-reduced-motion: reduce) { .sheet-step { transition: none; } }
@@ -1715,9 +1755,6 @@ button { font: inherit; cursor: pointer; }
1715
1755
  more air and a short one less; a screen with a bar keeps the bar's own room */
1716
1756
  .screen > .step:first-child, .screen--lead > .step { --_lead: var(--screen-lead, 15cqh); } /* top="lead" says it; a Step first says it too */
1717
1757
 
1718
- /* a walk (the dots float on its foot): the reading block runs right to the bar's top edge — the reserve's breathing gap (--sp-6)
1719
- is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
1720
-
1721
1758
  /* LAYERS (Lh, 2026-09-20): the base, then the sheet over it, then the top bar. A bar is the BASE's — the
1722
1759
  move of a page of words — so a step with a sheet has NO bar: the sheet finishes on its own .card__foot
1723
1760
  (sheet.css), and a form lives whole in ONE sheet, never item by item on the base. That is how cardds's
@@ -1737,8 +1774,7 @@ button { font: inherit; cursor: pointer; }
1737
1774
  --card-gap: var(--sp-4);
1738
1775
  gap: var(--card-gap);
1739
1776
  padding-block: 0; /* --_lead carries the top; the bar's room is the step's */
1740
- align-content: start;
1741
- color: var(--on-surface);
1777
+ color: var(--on-surface); /* align-content: start is .page's own (base.css) — not restated here */
1742
1778
  }
1743
1779
  /* the reading block is the .page — or the .pages that holds several of them, a walk of words
1744
1780
  (card.css). Behind a PEEKED sheet the words keep the strip's room under them, so whatever is down there
@@ -1747,22 +1783,23 @@ button { font: inherit; cursor: pointer; }
1747
1783
  .step > :is(.page, .pages) {
1748
1784
  flex: 0 1 auto;
1749
1785
  min-height: 0;
1750
- margin: var(--_lead, 0) var(--screen-pad) 0; /* the screen's gap, moved in here from above the step; the inset, taken back from the bleed */
1751
1786
  overflow-y: auto;
1752
1787
  overscroll-behavior: contain;
1753
1788
  }
1789
+ /* a lone .page carries the margin a walk's .pages does not (its own .page children do, below) —
1790
+ the screen's gap, moved in here from above the step; the inset, taken back from the bleed */
1791
+ .step > .page { margin: var(--_lead, 0) var(--screen-pad) 0; }
1754
1792
 
1755
1793
  /* a WALK (Pages of Page) — Lh 2026-09-23, third call: no top; the picture and the words are ONE run that scrolls up together
1756
1794
  (the picture is never pinned). Each page starts at the screen's lead and ends with the same room under it — the lead as
1757
1795
  padding above AND below — so the words begin near the middle and, when a big text size runs them long, scroll up clear.
1758
1796
  The walk shows no scrollbar. */
1759
- .step > .pages { margin: 0; scrollbar-width: none; touch-action: pan-y; } /* pan-y: the walk scrolls itself up and down; a sideways swipe is the Walkthrough's (it turns the page) */ /* the scrolling box is the whole content: from the step's top edge, edge to edge — no margin (Lh 2026-09-23) */
1797
+ .step > .pages { touch-action: pan-y; } /* pan-y: the walk scrolls itself up and down; a sideways swipe is the Walkthrough's (it turns the page) */ /* the scrolling box is the whole content: from the step's top edge, edge to edge — no margin, the default (Lh 2026-09-23) */
1760
1798
  .step > .pages > .page {
1761
1799
  padding-inline: var(--screen-pad); /* the words keep the inset INSIDE it */
1762
1800
  margin-top: var(--_lead, 0rem); /* the lead is each PAGE's margin (15% with no top, the screen's gap under a bar): the words start there and scroll up to the box's edge */
1763
1801
  padding-bottom: var(--bottom-h, 0rem); /* exactly the bottom layer (measured by Screen): a page scrolls ONLY when the layer would cover its words, and then just far enough to clear them — at 100% nothing scrolls (Lh 2026-09-23) */
1764
1802
  }
1765
- .step > .pages::-webkit-scrollbar { display: none; }
1766
1803
 
1767
1804
  /* ============================================================
1768
1805
  TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
@@ -1865,9 +1902,6 @@ button { font: inherit; cursor: pointer; }
1865
1902
  display: inline-flex;
1866
1903
  align-items: center;
1867
1904
  gap: var(--add-row-gap, var(--sp-2));
1868
- border: 0;
1869
- background: none;
1870
- color: inherit;
1871
1905
  font: var(--add-row-font, var(--type-label));
1872
1906
  font-size: var(--add-row-font-size, var(--fs-body));
1873
1907
  padding: var(--add-row-pad-y, var(--sp-2)) var(--add-row-pad-x, 0);
@@ -1933,7 +1967,6 @@ button { font: inherit; cursor: pointer; }
1933
1967
  .route__point--end .route__code::after { content: none; }
1934
1968
  .route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
1935
1969
  .route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
1936
- .card--3 .route__place { color: inherit; opacity: var(--dim); }
1937
1970
 
1938
1971
  .route__via { grid-column: 2; grid-row: 2; align-self: center; display: flex; align-items: center; opacity: 0.65; font-size: var(--route-via-font-size, var(--fs-body)); }
1939
1972
 
@@ -1965,7 +1998,6 @@ button { font: inherit; cursor: pointer; }
1965
1998
  font: var(--note-row-font, var(--type-caption));
1966
1999
  color: var(--note-row-ink, var(--card-muted));
1967
2000
  }
1968
- .card--3 .note-row { color: inherit; opacity: var(--dim); }
1969
2001
 
1970
2002
  /* ---- css/actions.css — topbar, chips, buttons, CTA pack ---- */
1971
2003
  /* ============================================================
@@ -2025,12 +2057,12 @@ button { font: inherit; cursor: pointer; }
2025
2057
  max-width: var(--screen-w);
2026
2058
  margin-inline: auto;
2027
2059
  padding-inline: var(--appbar-pad-x, var(--screen-pad)); /* fixed + full-bleed: the screen's own inset, so the bar's contents line up with the title bar under it (Lh 2026-09-15) */
2028
- z-index: 40; /* topmost — card animations never cover it */
2060
+ z-index: var(--z-top); /* topmost — card animations never cover it */
2029
2061
  background: var(--appbar-bg, var(--surface)); /* solid: sliding cards never show through */
2030
2062
  }
2031
2063
  /* fixed, so it covers the top of the screen it sits in — reserve its height
2032
- there, from the same token, the way the bottom bars reserve theirs. The
2033
- breathing under it is published: a .step cancels it to touch the bar. */
2064
+ there, from the same header-h token. The breathing under it is published:
2065
+ a .step cancels it to touch the bar. */
2034
2066
  .screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
2035
2067
  .appbar__brand { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
2036
2068
  .appbar__text { display: grid; min-width: 0; flex: 1; } /* pushes trailing button to the right edge */
@@ -2038,11 +2070,9 @@ button { font: inherit; cursor: pointer; }
2038
2070
  .appbar__who {
2039
2071
  flex: 1; min-width: 0; min-height: var(--tap);
2040
2072
  display: flex; align-items: center; gap: var(--sp-2);
2041
- padding: 0; margin: 0; border: 0; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
2073
+ margin: 0; text-align: start;
2042
2074
  border-radius: var(--appbar-who-radius, var(--r-chip));
2043
- -webkit-tap-highlight-color: transparent;
2044
2075
  }
2045
- .appbar__who:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2046
2076
  .appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.35; } /* clipped lines: room for Thai marks */
2047
2077
  .appbar__title { font: var(--type-label); }
2048
2078
  .appbar__sub { font: var(--type-caption); color: var(--on-surface-muted); }
@@ -2066,7 +2096,7 @@ button { font: inherit; cursor: pointer; }
2066
2096
  .icon-btn--invert { background: var(--icon-btn-invert-bg, var(--accent)); color: var(--icon-btn-invert-ink, var(--on-accent)); border-color: transparent; }
2067
2097
  .icon-btn--sm { width: var(--icon-btn-sm-size, var(--tap-sm)); height: var(--icon-btn-sm-size, var(--tap-sm)); font-size: var(--fs-title); }
2068
2098
  /* fab: the square floating action, bottom-right of a stage or screen */
2069
- .fab { width: var(--fab); height: var(--fab); border-radius: var(--r-card); position: absolute; right: var(--sp-4); bottom: calc(var(--sp-5) + var(--bar-h) + var(--sp-3)); box-shadow: var(--shadow-float); z-index: 31; }
2099
+ .fab { width: var(--fab); height: var(--fab); border-radius: var(--r-card); position: absolute; right: var(--sp-4); bottom: calc(var(--sp-5) + var(--bar-h) + var(--sp-3)); box-shadow: var(--shadow-float); z-index: var(--z-fab); }
2070
2100
 
2071
2101
  /* ---- buttons ---- */
2072
2102
  .btn {
@@ -2099,7 +2129,6 @@ button { font: inherit; cursor: pointer; }
2099
2129
  /* disabled keeps the slot — a control that vanishes moves everything else,
2100
2130
  and a pager whose "previous" disappeared on page one would shift the bar
2101
2131
  under the thumb on every page */
2102
- :is(.btn, .icon-btn):disabled { opacity: 0.4; cursor: default; }
2103
2132
  /* a row of buttons sharing the width (twin CTAs inside a card) */
2104
2133
  .btn-row { display: flex; gap: var(--sp-2); }
2105
2134
  .btn-row > .btn { flex: 1; min-width: 0; }
@@ -2139,7 +2168,7 @@ button { font: inherit; cursor: pointer; }
2139
2168
  /* ---- dropdown: the standard for every select — a pill trigger + .menu popover ----
2140
2169
  <div class="dropdown"><button class="dropdown__btn" popovertarget="f1"><span class="dropdown__label">this week</span><svg class="icon icon--xs"><use href="icons.svg#chevron-down"/></svg></button>
2141
2170
  <div class="menu" id="f1" popover><button class="menu__item" aria-current="true" data-value="week">this week</button>…</div></div>
2142
- cardds.js copies the picked item into the label and fires "change" on .dropdown.
2171
+ The Dropdown component copies the picked item into the label and reports the change.
2143
2172
  .menu (layover.css) draws the panel; anchor positioning pins it under the trigger. */
2144
2173
  .dropdown { position: relative; min-width: 0; display: inline-grid; }
2145
2174
  .dropdown__btn {
@@ -2158,12 +2187,11 @@ button { font: inherit; cursor: pointer; }
2158
2187
  font: var(--type-label);
2159
2188
  font-size: var(--fs-body);
2160
2189
  text-align: left;
2161
- cursor: pointer;
2162
2190
  }
2163
2191
  /* muted: the value is still the default (README 13) */
2164
2192
  .dropdown--muted .dropdown__btn { color: var(--dropdown-muted-ink, var(--card-muted)); }
2165
2193
  .dropdown__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2166
- .dropdown__btn > .icon { transition: rotate 0.2s ease; }
2194
+ .dropdown__btn > .icon { transition: rotate var(--motion-quick) ease; }
2167
2195
  .dropdown:has(.menu:popover-open) .dropdown__btn > .icon { rotate: 180deg; }
2168
2196
  .dropdown:has(.menu.\:popover-open) .dropdown__btn > .icon { rotate: 180deg; } /* the polyfill's open (layover.css) */
2169
2197
  .dropdown--end .menu[popover] { position-area: block-end span-inline-start; } /* right-aligned trigger: panel grows leftwards (= .menu--end, layover.css) */
@@ -2173,10 +2201,7 @@ button { font: inherit; cursor: pointer; }
2173
2201
  /* ---- segment (day picker / tab strip) — items share width, shrink on small screens ---- */
2174
2202
  .segment { display: flex; gap: var(--sp-1); }
2175
2203
  .segment__item {
2176
- border: 0;
2177
- background: transparent;
2178
- color: inherit;
2179
- opacity: 0.6;
2204
+ opacity: var(--rest);
2180
2205
  border-radius: var(--r-chip);
2181
2206
  padding: var(--sp-2) var(--sp-1);
2182
2207
  font: var(--type-label);
@@ -2200,7 +2225,7 @@ button { font: inherit; cursor: pointer; }
2200
2225
  :root { --bar-reserve: calc(var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2201
2226
  .action-bar {
2202
2227
  position: relative;
2203
- z-index: 30; /* above .sheet-stack sheets, which paint at --level 1-5 */
2228
+ z-index: var(--z-bar); /* above .sheet-stack sheets, which paint at --level 1-5 */
2204
2229
  max-width: var(--screen-w);
2205
2230
  margin-inline: auto;
2206
2231
  padding: var(--sp-3) var(--sp-4) var(--sp-5);
@@ -2230,10 +2255,11 @@ button { font: inherit; cursor: pointer; }
2230
2255
  NEVER carry words (Lh 2026-09-16, for good): an arrow is the whole meaning,
2231
2256
  and a word that isn't a direction ("Get started", "Done") is not a pager
2232
2257
  button — that step COMMITS and goes back to the plain .action-bar with one
2233
- wide button. Where you are is said by the .pager__at dots UNDER the card
2234
- that changes (a .screen__centre group, card.css), never in the bar — the
2235
- dots move with what moves (Lh, 2026-09-12). Between the buttons goes a
2236
- "skip" link or nothing.
2258
+ wide button. Where you are is said by the .pager__at dots — under the card
2259
+ that changes (a .screen__centre group, card.css) for a card walk, never in
2260
+ that bar (Lh, 2026-09-12); a walk of words instead floats its dots as the
2261
+ bar's own first row (step.css, Lh 2026-09-23) — either way the dots move
2262
+ with what moves. Between the buttons goes a "skip" link or nothing.
2237
2263
 
2238
2264
  <div class="action-bar action-bar--pager">
2239
2265
  <button class="icon-btn" aria-label="ย้อนกลับ" disabled>…</button>
@@ -2250,14 +2276,14 @@ button { font: inherit; cursor: pointer; }
2250
2276
  height: var(--dot);
2251
2277
  border-radius: 50%;
2252
2278
  background: currentColor;
2253
- opacity: 0.3;
2279
+ opacity: var(--faint);
2254
2280
  }
2255
2281
  .pager__at > .is-at { background: var(--accent); opacity: 1; }
2256
2282
  /* ---- tiers: a control row above the buttons ----
2257
2283
  A screen whose one move comes with a control — the introduction's text-size
2258
2284
  slider above its pager. Each tier is a row; the bar stacks them, and any
2259
2285
  parent hosting the bar publishes the room for both, the way the plain
2260
- reserve is published above, so a .screen or a .card--centre stops clear.
2286
+ reserve is published above, so a .card--centre stops clear of it.
2261
2287
  <div class="action-bar action-bar--tiers">
2262
2288
  <div class="action-bar__tier"><label class="slider">…</label></div>
2263
2289
  <div class="action-bar__tier action-bar__tier--pager">…the round pager…</div>
@@ -2304,28 +2330,56 @@ button { font: inherit; cursor: pointer; }
2304
2330
  .action-note__words { display: block; } /* ONE run of words: an icon inside sits in the line, the spaces around it kept */
2305
2331
  .action-note__words > .icon { display: inline-block; vertical-align: -0.2em; width: 1.25em; height: 1.25em; margin-inline: 0.15em; color: var(--action-note-icon-ink, inherit); } /* a project may colour the icon apart from the words — e.g. the system blue of the control it points at (Lh 2026-09-24) */
2306
2332
 
2307
- /* PRESS (Lh 2026-09-25): every CONTROL answers a tap — it gives under the finger and springs back, its choice fill fades in;
2308
- INFORMATION never does (no pointer, no press). One list, zero specificity: a component's own rule always wins. */
2309
- :where(.btn, .icon-btn, .fab, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item,
2310
- .calendar__day, .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .sheet-stack__back, .dropdown__btn, .appbar__who, .menu__item) {
2333
+ /* PRESS (Lh 2026-09-25; every tappable thing, 2026-09-27): every CONTROL answers a tap, INFORMATION never does (no pointer, no
2334
+ press). Two answers, by size: GIVE — a control the size of a finger shrinks under it (--press-scale) and springs back; DIM —
2335
+ something as wide as its card (a row, a summary line, a card, a step bar, a peeked sheet) fades under the finger (--press-dim)
2336
+ instead, because a whole row shrinking reads as the card moving. A choice's fill fades in as it is picked. Disabled = no
2337
+ pointer, no press, --off. Zero specificity everywhere: a component's own rule always wins. A NEW control joins a list here. */
2338
+ :where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
2339
+ .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
2340
+ a.avatar, button.avatar, .menu__item) {
2311
2341
  cursor: pointer;
2312
2342
  touch-action: manipulation;
2313
2343
  transition: scale var(--motion-press) ease-out;
2314
2344
  }
2315
- /* a CHOICE's fill fades in as it is picked (a button's colour never changes on a tap, so it does not fade) */
2316
- :where(.chip--pick, .tile--pick, .day, .segment__item, .calendar__day, .float-bar__item, .menu__item) {
2317
- transition: scale var(--motion-press) ease-out, background-color var(--motion-press) ease-out, color var(--motion-press) ease-out, border-color var(--motion-press) ease-out;
2318
- }
2319
- :where(.btn, .icon-btn, .fab, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item,
2320
- .calendar__day, .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .sheet-stack__back, .dropdown__btn, .appbar__who, .menu__item):active:not(:disabled, [aria-disabled="true"]) {
2321
- scale: var(--press-scale);
2322
- }
2323
- :where(.btn, .icon-btn, .fab, .link, a.chip, button.chip, a.tile, button.tile, .day, .segment__item, .calendar__day, .float-bar__item,
2324
- .avatar-pick__item, .add-row, .picker__item, .sheet-stack__back, .dropdown__btn):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2325
- @media (prefers-reduced-motion: reduce) {
2326
- :where(.btn, .icon-btn, .fab, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item,
2327
- .calendar__day, .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .sheet-stack__back, .dropdown__btn, .appbar__who, .menu__item):active { scale: none; }
2328
- }
2345
+ :where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field) {
2346
+ cursor: pointer;
2347
+ touch-action: manipulation;
2348
+ transition: opacity var(--motion-press) ease-out;
2349
+ }
2350
+ /* a CHOICE's fill fades in as it is picked (a plain button's colour never changes on a tap, so it does not fade) */
2351
+ :where(.chip--pick, button.chip, .tile--pick, button.tile, .day, .segment__item, .calendar__day, .float-bar__item, .picker__item, .check,
2352
+ .icon-btn[role="tab"], .avatar-pick__item > .avatar) {
2353
+ transition-property: scale, background-color, color, border-color, opacity;
2354
+ transition-duration: var(--motion-press);
2355
+ transition-timing-function: ease-out;
2356
+ }
2357
+ :where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
2358
+ .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
2359
+ a.avatar, button.avatar, .menu__item):active:not(:disabled, [aria-disabled="true"], :has(:disabled)) { scale: var(--press-scale); }
2360
+ :where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field):active:not([aria-disabled="true"], :has(:disabled)) { opacity: var(--press-dim); }
2361
+ .sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
2362
+ /* SHADE — what has something BEHIND it (a peeked sheet over the pile, a postcard's face over its back) must not turn see-through:
2363
+ it darkens a touch instead (--press-shade) */
2364
+ :where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn) { cursor: pointer; touch-action: manipulation; transition: filter var(--motion-press) ease-out; }
2365
+ :where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn):active { filter: brightness(var(--press-shade)); }
2366
+ /* the focus ring: on the control, or on the label that wraps a hidden input (chip, tile, file button, a row that is a label) */
2367
+ :where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
2368
+ .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
2369
+ a.avatar, button.avatar, .menu__item):focus-visible,
2370
+ :where(.chip--pick, .tile--pick, label.btn, label.row, .toggle-field):has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
2371
+ :where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field):not(label, .toggle-field):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2372
+ /* disabled: no pointer anywhere; a control fades (a link and a row keep their own muted look) */
2373
+ :where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
2374
+ .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
2375
+ a.avatar, button.avatar, .menu__item, a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field):is(:disabled, [aria-disabled="true"]),
2376
+ :where(.chip--pick, .tile--pick, .mood__opt, .toggle-field, label.btn, label.row):has(:disabled) { cursor: default; }
2377
+ :where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
2378
+ .float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
2379
+ a.avatar, button.avatar, .menu__item):not(.link):is(:disabled, [aria-disabled="true"]),
2380
+ :where(.chip--pick, .tile--pick, .mood__opt, .toggle-field, label.btn):has(:disabled) { opacity: var(--off); }
2381
+ .sheet-stack--tap > .card > .card__head > :is(h1, h2, h3):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); } /* the keyboard's way into a stack sheet */
2382
+ @media (prefers-reduced-motion: reduce) { :root { --press-scale: 1; } } /* the give goes; the dim, the shade and the focus ring stay */
2329
2383
 
2330
2384
  /* ---- css/numbers.css — ring, track, band, dot grid, picker, badge, bars ---- */
2331
2385
  /* ============================================================
@@ -2363,7 +2417,6 @@ button { font: inherit; cursor: pointer; }
2363
2417
  }
2364
2418
  .ring__value { font: var(--ring-value-font, var(--type-stat)); letter-spacing: var(--track-display); }
2365
2419
  .ring__label { font: var(--ring-label-font, var(--type-caption)); color: var(--ring-label-ink, var(--card-muted)); }
2366
- .card--3 .ring__label { color: inherit; opacity: var(--dim); }
2367
2420
  .ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
2368
2421
  .ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
2369
2422
  .ring--xs { --_size: var(--tap-sm); --_w: var(--dot-xs); } /* inline counter (step 3 of 5) */
@@ -2402,7 +2455,7 @@ button { font: inherit; cursor: pointer; }
2402
2455
  content: "";
2403
2456
  flex: 1;
2404
2457
  height: var(--sp-3);
2405
- background: color-mix(in srgb, var(--_ink) 30%, transparent);
2458
+ background: var(--ink-rail); /* the track's own ink at 30% — identical to color-mix(in srgb, var(--_ink) 30%, transparent): --ink-rail already reads currentColor, and .track sets color to --_ink */
2406
2459
  }
2407
2460
  .track__step:first-child::before { display: none; }
2408
2461
  .track__step.is-done::before { background: var(--accent); }
@@ -2413,7 +2466,7 @@ button { font: inherit; cursor: pointer; }
2413
2466
  flex: none;
2414
2467
  padding: calc((var(--tap-lg) - var(--icon)) / 2);
2415
2468
  border-radius: 50%;
2416
- background: color-mix(in srgb, var(--_ink) 30%, transparent);
2469
+ background: var(--ink-rail);
2417
2470
  color: var(--_ink);
2418
2471
  }
2419
2472
  .track__step > .track__dot { display: grid; place-items: center; padding: 0; font: var(--track-dot-font, var(--type-title)); font-variant-numeric: tabular-nums; } /* a number in the round (TrackStep num) */
@@ -2438,7 +2491,6 @@ button { font: inherit; cursor: pointer; }
2438
2491
  }
2439
2492
  .band__num small { font-size: 0.5em; font-weight: 700; margin-right: 0.1em; } /* unit or % */
2440
2493
  .band__foot { grid-area: foot; font: var(--band-foot-font, var(--type-caption)); color: var(--band-foot-ink, var(--card-muted)); }
2441
- .card--3 .band__foot { color: inherit; opacity: var(--dim); }
2442
2494
  /* band stack: ONE card boundary; bands are frameless inside it, a hairline
2443
2495
  between each. Bands may still carry slot tints (card--2 / card--3). */
2444
2496
  .band-stack {
@@ -2485,14 +2537,6 @@ button { font: inherit; cursor: pointer; }
2485
2537
  @supports not (width: round(up, 1px, 1px)) {
2486
2538
  .dotgrid::before { display: none; }
2487
2539
  }
2488
- .dotgrid--stars {
2489
- background-image: none;
2490
- }
2491
- .dotgrid--stars,
2492
- .dotgrid--stars::before {
2493
- --_star: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
2494
- }
2495
-
2496
2540
  /* ---- picker: horizontal snap picker, centre value big ----
2497
2541
  <div class="picker"><div class="picker__rail"><button class="picker__item">100</button>…</div><span class="picker__unit">ml</span></div> */
2498
2542
  .picker { display: grid; gap: var(--picker-gap, var(--sp-1)); justify-items: center; position: relative; }
@@ -2507,20 +2551,15 @@ button { font: inherit; cursor: pointer; }
2507
2551
  grid-auto-columns: 100%;
2508
2552
  overflow-x: auto;
2509
2553
  scroll-snap-type: x mandatory;
2510
- scrollbar-width: none;
2511
2554
  position: relative;
2512
2555
  }
2513
- .picker__rail::-webkit-scrollbar { display: none; }
2514
2556
  .picker__item {
2515
2557
  scroll-snap-align: center;
2516
- border: 0;
2517
- background: transparent;
2518
- color: inherit;
2519
2558
  padding: var(--picker-item-pad-y, var(--sp-2)) var(--picker-item-pad-x, 0);
2520
2559
  font: var(--picker-item-font, var(--type-stat));
2521
2560
  letter-spacing: var(--track-display);
2522
2561
  font-variant-numeric: tabular-nums;
2523
- opacity: 0.3;
2562
+ opacity: var(--faint);
2524
2563
  text-align: center;
2525
2564
  }
2526
2565
  .picker__item[aria-selected="true"] { opacity: 1; }
@@ -2537,12 +2576,11 @@ button { font: inherit; cursor: pointer; }
2537
2576
  }
2538
2577
  .picker::after { left: auto; right: calc(100% / 3); }
2539
2578
  .picker__unit { font: var(--picker-unit-font, var(--type-caption)); color: var(--picker-unit-ink, var(--card-muted)); }
2540
- .card--3 .picker__unit { color: inherit; opacity: var(--dim); }
2541
2579
  /* the scrolled-to item lights up by position — no JS */
2542
2580
  @supports (animation-timeline: view(x)) {
2543
- .picker__item { opacity: 0.3; animation: picker-focus linear both; animation-timeline: view(x); }
2581
+ .picker__item { animation: picker-focus linear both; animation-timeline: view(x); } /* opacity: var(--faint) already set above — the animation alone is new here */
2544
2582
  @keyframes picker-focus {
2545
- 0%, 100% { opacity: 0.3; }
2583
+ 0%, 100% { opacity: var(--faint); }
2546
2584
  45%, 55% { opacity: 1; }
2547
2585
  }
2548
2586
  }
@@ -2664,10 +2702,8 @@ button { font: inherit; cursor: pointer; }
2664
2702
  display: flex;
2665
2703
  gap: var(--avatar-pick-gap, var(--sp-4));
2666
2704
  overflow-x: auto;
2667
- scroll-snap-type: x proximity;
2668
- scrollbar-width: none; /* starts at the card's padding edge, like every row */
2705
+ scroll-snap-type: x proximity; /* starts at the card's padding edge, like every row */
2669
2706
  }
2670
- .avatar-pick::-webkit-scrollbar { display: none; }
2671
2707
  .avatar-pick__item {
2672
2708
  scroll-snap-align: start;
2673
2709
  flex: none;
@@ -2675,18 +2711,13 @@ button { font: inherit; cursor: pointer; }
2675
2711
  display: grid;
2676
2712
  justify-items: center;
2677
2713
  gap: var(--avatar-pick-item-gap, var(--sp-2));
2678
- border: 0;
2679
- background: transparent;
2680
- color: inherit;
2681
- padding: 0;
2682
2714
  text-align: center;
2683
2715
  }
2684
2716
  .avatar-pick__name { font: var(--avatar-pick-name-font, var(--type-label)); font-size: var(--avatar-pick-name-font-size, var(--fs-sm)); }
2685
2717
  .avatar-pick__role { font: var(--avatar-pick-role-font, var(--type-caption)); color: var(--avatar-pick-role-ink, var(--card-muted)); margin-top: calc(-1 * var(--sp-1)); }
2686
2718
  .avatar-pick__item[aria-pressed="true"] > .avatar { background: var(--avatar-pick-on-bg, var(--accent)); color: var(--avatar-pick-on-ink, var(--on-accent)); border-color: transparent; } /* the picked person */
2687
- .card--3 .avatar-pick__role { color: inherit; opacity: var(--dim); }
2688
2719
 
2689
- /* ---- css/choice.css — check, toggle, toggle chip, chip grid, days, calendar, mood, pin, composer ---- */
2720
+ /* ---- css/choice.css — check, toggle, chip grid, days, calendar, mood, pin, composer ---- */
2690
2721
  /* ============================================================
2691
2722
  cardds/choice.css — thumb-size choice controls inside cards:
2692
2723
  check circle · toggle · toggle chip · chip grid · day strip ·
@@ -2710,7 +2741,6 @@ button { font: inherit; cursor: pointer; }
2710
2741
  background: var(--check-bg, var(--action-bg));
2711
2742
  display: grid;
2712
2743
  place-items: center;
2713
- cursor: pointer;
2714
2744
  }
2715
2745
  .check::after {
2716
2746
  content: "";
@@ -2723,15 +2753,14 @@ button { font: inherit; cursor: pointer; }
2723
2753
  }
2724
2754
  .check:checked { background: var(--accent); border-color: transparent; }
2725
2755
  .check:checked::after { opacity: 1; }
2726
- .check:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2727
2756
  .check--sm { width: var(--check-sm-size, var(--tap-sm)); height: var(--check-sm-size, var(--tap-sm)); }
2728
2757
 
2729
2758
  /* ---- toggle: big switch ----
2730
2759
  <input type="checkbox" role="switch" class="toggle" aria-label="Private"> */
2731
2760
  .toggle {
2732
2761
  --_w: var(--bar-h); /* 64 × 36, knob = height − 2 × pad */
2733
- --_h: 2.25rem;
2734
- --_pad: 0.1875rem;
2762
+ --_h: calc(var(--tap-base) * 3 / 4);
2763
+ --_pad: calc(var(--tap-base) / 16);
2735
2764
  appearance: none;
2736
2765
  width: var(--_w);
2737
2766
  height: var(--_h);
@@ -2741,8 +2770,7 @@ button { font: inherit; cursor: pointer; }
2741
2770
  border: var(--border-w) solid var(--toggle-border, var(--outline));
2742
2771
  background: var(--toggle-bg, var(--action-bg));
2743
2772
  position: relative;
2744
- cursor: pointer;
2745
- transition: background 0.2s ease;
2773
+ transition: background-color var(--motion-press) ease-out, border-color var(--motion-press) ease-out;
2746
2774
  }
2747
2775
  .toggle::after {
2748
2776
  content: "";
@@ -2754,11 +2782,10 @@ button { font: inherit; cursor: pointer; }
2754
2782
  border-radius: 50%;
2755
2783
  background: currentColor;
2756
2784
  opacity: 0.45;
2757
- transition: translate 0.2s ease, opacity 0.2s ease;
2785
+ transition: translate var(--motion-quick) ease, opacity var(--motion-quick) ease;
2758
2786
  }
2759
2787
  .toggle:checked { background: var(--accent); border-color: transparent; }
2760
2788
  .toggle:checked::after { translate: calc(var(--_w) - var(--_h)) 0; background: var(--on-accent); opacity: 1; }
2761
- .toggle:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2762
2789
 
2763
2790
  /* ---- chip pick: a chip that is a choice — a native radio/checkbox inside the label, the picked one filled with accent.
2764
2791
  Full-width in a .chip-grid (one slot / many options), or hugging its word on a .chip-row with --sign (an interest
@@ -2774,11 +2801,12 @@ button { font: inherit; cursor: pointer; }
2774
2801
  chip grid the whole point is equal, full-width pick targets, so stretch.
2775
2802
  Matters most in scripts without word spaces (Thai): min-content is the
2776
2803
  whole string, tracks get wide, and hugging chips leave a ragged grid. */
2777
- .chip-grid > .chip { justify-self: stretch; justify-content: center; min-height: var(--btn-h); padding-inline: var(--sp-2); position: relative; }
2778
- .chip--pick { cursor: pointer; position: relative; } /* the input covers the whole chip, on a row as in a grid */
2779
- .chip--pick > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
2780
- .chip--pick:has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
2781
- .chip--pick:has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
2804
+ .chip-grid > .chip { justify-self: stretch; justify-content: center; min-height: var(--btn-h); padding-inline: var(--sp-2); }
2805
+ .chip--pick { position: relative; } /* the input covers the whole chip, on a row as in a grid */
2806
+ /* the hidden input, the same shape wherever a native radio/checkbox drives a picked look:
2807
+ a chip, a mood dot, a tile (media.css) */
2808
+ :is(.chip--pick, .mood__opt, .tile--pick) > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
2809
+ :is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
2782
2810
  /* --sign: the trailing + that turns 45° into an × when picked — one mask, one rotate, no swap (Lh 2026-09-16) */
2783
2811
  .chip--sign { padding-right: var(--sp-2); }
2784
2812
  .chip--sign::after {
@@ -2811,11 +2839,8 @@ button { font: inherit; cursor: pointer; }
2811
2839
  padding-block: var(--day-pad-y, var(--sp-3)); /* equal air top and bottom, so the selected pill
2812
2840
  is the same shape on every day */
2813
2841
  padding-inline: 0;
2814
- border: 0;
2815
2842
  border-radius: var(--day-radius, var(--r-chip));
2816
- background: transparent;
2817
- color: inherit;
2818
- opacity: 0.6;
2843
+ opacity: var(--rest);
2819
2844
  }
2820
2845
  .day__name { font: var(--day-name-font, var(--type-caption)); }
2821
2846
  .day__num { font: 700 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
@@ -2828,17 +2853,13 @@ button { font: inherit; cursor: pointer; }
2828
2853
  .calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--calendar-head-gap, var(--sp-2)); }
2829
2854
  .calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--calendar-grid-gap, var(--sp-1)); justify-items: center; }
2830
2855
  .calendar__wd { font: var(--calendar-wd-font, var(--type-caption)); color: var(--calendar-wd-ink, var(--card-muted)); padding-block: var(--calendar-wd-pad-y, var(--sp-2)); }
2831
- .card--3 .calendar__wd { color: inherit; opacity: var(--dim); }
2832
2856
  .calendar__day {
2833
2857
  width: 100%;
2834
2858
  max-width: var(--calendar-day-max-w, var(--tap));
2835
2859
  aspect-ratio: 1;
2836
2860
  display: grid;
2837
2861
  place-items: center;
2838
- border: 0;
2839
2862
  border-radius: 50%;
2840
- background: transparent;
2841
- color: inherit;
2842
2863
  font: 500 var(--fs-title)/1 var(--font-ui);
2843
2864
  font-variant-numeric: tabular-nums;
2844
2865
  grid-column-start: var(--start, auto); /* --start on the 1st: weekday column 1–7 */
@@ -2861,8 +2882,7 @@ button { font: inherit; cursor: pointer; }
2861
2882
  display: flex;
2862
2883
  justify-content: space-between;
2863
2884
  }
2864
- .mood__opt { flex: 1 1 0; max-width: var(--mood-opt-max-w, var(--tap)); min-width: 0; height: var(--mood-opt-h, var(--tap)); display: grid; place-items: center; position: relative; cursor: pointer; }
2865
- .mood__opt > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
2885
+ .mood__opt { flex: 1 1 0; max-width: var(--mood-opt-max-w, var(--tap)); min-width: 0; height: var(--mood-opt-h, var(--tap)); display: grid; place-items: center; position: relative; }
2866
2886
  .mood__dot {
2867
2887
  width: var(--mood-dot-size, var(--icon-xs));
2868
2888
  height: var(--mood-dot-size, var(--icon-xs));
@@ -2893,12 +2913,11 @@ button { font: inherit; cursor: pointer; }
2893
2913
  text-align: center;
2894
2914
  font: var(--pin-cell-font, var(--type-stat));
2895
2915
  font-variant-numeric: tabular-nums;
2916
+ caret-color: transparent; /* the cell the cursor waits in (Lh 2026-09-22): NO outline ring — it sat outside the pill and got cropped by the sheet's
2917
+ edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
2918
+ is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
2896
2919
  }
2897
2920
  .pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
2898
- /* the cell the cursor waits in (Lh 2026-09-22): NO outline ring — it sat outside the pill and got cropped by the sheet's
2899
- edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
2900
- is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
2901
- .pin__cell { caret-color: transparent; }
2902
2921
  .pin--secret .pin__cell { -webkit-text-security: disc; text-security: disc; } /* a PIN: a dot per digit, the number pad kept (Lh 2026-09-25) */
2903
2922
  .pin__cell:focus { outline: none; border-color: var(--pin-cell-active-border, currentColor); }
2904
2923
  .pin__cell:focus:placeholder-shown {
@@ -2928,10 +2947,9 @@ button { font: inherit; cursor: pointer; }
2928
2947
  letter-spacing: var(--track-h2);
2929
2948
  outline: none;
2930
2949
  }
2931
- .composer__input::placeholder { color: inherit; opacity: 0.4; }
2950
+ .composer__input::placeholder { color: inherit; opacity: var(--off); }
2932
2951
  .composer__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--composer-bar-gap, var(--sp-3)); }
2933
2952
  .composer__hint { font: var(--composer-hint-font, var(--type-caption)); color: var(--composer-hint-ink, var(--card-muted)); }
2934
- .card--3 .composer__hint { color: inherit; opacity: var(--dim); }
2935
2953
 
2936
2954
  /* ---- slider: one value along a track, a thumb-size knob ----
2937
2955
  <label class="slider"><span class="slider__label">ขนาดตัวหนังสือ</span><input class="slider__range" type="range" min="100" max="160" step="15" aria-label="ขนาดตัวหนังสือ"></label>
@@ -2942,8 +2960,8 @@ button { font: inherit; cursor: pointer; }
2942
2960
  .slider { display: grid; gap: var(--slider-gap, var(--sp-1)); flex: 1; min-width: 0; position: relative; } /* relative: the glyph rides the knob */
2943
2961
  /* ---- the sample glyph: one letter IN the knob, on-accent, at the type size
2944
2962
  the slider sets — so a text-size slider shows the size before anything is
2945
- read (Lh, 2026-09-12). cardds.js publishes the knob's place on the .slider
2946
- as --slider-at (0..1) from the input's value; the glyph sits over the knob
2963
+ read (Lh, 2026-09-12). The Slider component publishes the knob's place on
2964
+ the .slider as --slider-at (0..1) from the input's value; the glyph sits over the knob
2947
2965
  and never takes the pointer. <span class="slider__glyph" aria-hidden="true">ก</span>
2948
2966
  before the input. */
2949
2967
  .slider__glyph {
@@ -2993,6 +3011,8 @@ button { font: inherit; cursor: pointer; }
2993
3011
  box-shadow: var(--shadow-float);
2994
3012
  }
2995
3013
  .slider__range:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
3014
+ .slider__range:active::-webkit-slider-thumb { scale: var(--press-scale); } /* the knob gives under the finger like any control */
3015
+ .slider__range:active::-moz-range-thumb { scale: var(--press-scale); }
2996
3016
 
2997
3017
  /* ---- a switch that names itself (Toggle text, Lh 2026-09-23 — "อยู่บ้าน" on the home's top): the words, then the switch,
2998
3018
  one label — the whole of it is the tap target ---- */
@@ -3000,8 +3020,7 @@ button { font: inherit; cursor: pointer; }
3000
3020
  display: inline-flex; align-items: center; gap: var(--toggle-field-gap, var(--sp-2));
3001
3021
  min-height: var(--tap);
3002
3022
  font: var(--toggle-field-font, var(--type-label));
3003
- cursor: pointer;
3004
- -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
3023
+ -webkit-user-select: none; user-select: none;
3005
3024
  flex: none;
3006
3025
  }
3007
3026
 
@@ -3048,7 +3067,7 @@ button { font: inherit; cursor: pointer; }
3048
3067
  who liked a postcard, the rest of a list. An end chevron turns when open. The app owns the list.
3049
3068
  <details class="row-expand"><summary class="row">…lead · body · end…</summary><div class="row-expand__more">…</div></details> */
3050
3069
  .row-expand { display: block; }
3051
- .row-expand > summary { list-style: none; cursor: pointer; }
3070
+ .row-expand > summary { list-style: none; }
3052
3071
  .row-expand > summary::-webkit-details-marker { display: none; }
3053
3072
  .row-expand > summary > .icon:last-child { transition: rotate var(--motion-slide); }
3054
3073
  .row-expand[open] > summary > .icon:last-child { rotate: 180deg; }
@@ -3200,14 +3219,9 @@ button { font: inherit; cursor: pointer; }
3200
3219
  align-items: center;
3201
3220
  gap: var(--link-gap, var(--sp-1));
3202
3221
  min-height: var(--link-min-h, var(--tap-sm));
3203
- color: inherit;
3204
3222
  text-decoration: none;
3205
3223
  font: var(--link-font, var(--type-label));
3206
3224
  font-size: var(--link-font-size, var(--fs-body));
3207
- border: 0;
3208
- background: transparent;
3209
- padding: 0;
3210
- cursor: pointer;
3211
3225
  }
3212
3226
  /* a SMALL link (Lh 2026-09-23): a thing tapped rarely — "details", a ctaLink — at the minor-link size (15) and 28px of height;
3213
3227
  its 44px tap target stays, an invisible ::after around it */
@@ -3234,7 +3248,7 @@ button { font: inherit; cursor: pointer; }
3234
3248
  --card-ink: var(--card-3-ink);
3235
3249
  --accent: var(--card-3-ink);
3236
3250
  --on-accent: var(--card-3-bg);
3237
- min-height: 22.5rem;
3251
+ min-height: var(--_cover-min-h);
3238
3252
  align-content: space-between;
3239
3253
  overflow: hidden;
3240
3254
  isolation: isolate;
@@ -3274,7 +3288,6 @@ button { font: inherit; cursor: pointer; }
3274
3288
  .quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
3275
3289
  .quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
3276
3290
  .quote__by { font: var(--quote-by-font, var(--type-caption)); color: var(--quote-by-ink, var(--card-muted)); }
3277
- .card--3 .quote__by { color: inherit; opacity: var(--dim); }
3278
3291
 
3279
3292
  /* ---- tile grid: big square tiles — icon top, label bottom ----
3280
3293
  <div class="tile-grid"><button class="tile"><svg class="icon"/><span class="tile__label">Light</span></button>
@@ -3306,13 +3319,10 @@ button { font: inherit; cursor: pointer; }
3306
3319
  .tile__sub { font: var(--tile-sub-font, var(--type-caption)); opacity: var(--dim); }
3307
3320
  .tile__text { display: grid; gap: var(--tile-text-gap, var(--sp-0)); }
3308
3321
  .tile--on { background: var(--tile-on-bg, var(--accent)); color: var(--tile-on-ink, var(--on-accent)); border-color: transparent; }
3309
- .tile--pick > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
3310
- .tile--pick:has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
3311
- .tile--pick:has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
3322
+ /* .tile--pick's hidden input and :has(:checked) fill are declared once in choice.css, alongside .chip--pick and .mood__opt */
3312
3323
  .tile--wide { grid-column: 1 / -1; }
3313
3324
  /* stat: READ, not pressed — no frame of its own inside a card (a frame says "tap me"): icon · value · words on the card's ground */
3314
- .tile--stat { border-color: transparent; background: none; padding-inline: 0; }
3315
- .tile--stat { min-height: var(--tile-stat-min-h, var(--avatar-xl)); gap: var(--tile-stat-gap, var(--sp-3)); grid-template-columns: auto 1fr; align-items: center; }
3325
+ .tile--stat { border-color: transparent; background: none; padding-inline: 0; min-height: var(--tile-stat-min-h, var(--avatar-xl)); gap: var(--tile-stat-gap, var(--sp-3)); grid-template-columns: auto 1fr; align-items: center; }
3316
3326
  /* upload: dashed, centred plus + label */
3317
3327
  .tile--upload {
3318
3328
  border-style: dashed;
@@ -3389,8 +3399,8 @@ button { font: inherit; cursor: pointer; }
3389
3399
  overflow: hidden;
3390
3400
  }
3391
3401
  .mosaic > :first-child { grid-row: span 2; }
3392
- .mosaic > * { min-height: 0; border-radius: 0; opacity: 1; border-width: 0; }
3393
- .mosaic > .ph { opacity: 0.4; border-width: var(--border-w); }
3402
+ .mosaic > * { min-height: 0; border-radius: 0; } /* opacity/border-width here were no-ops: .ph restates its own below, and nothing else in a mosaic carries either by default */
3403
+ .mosaic > .ph { opacity: var(--off); border-width: var(--border-w); }
3394
3404
  .mosaic > img { width: 100%; height: 100%; object-fit: cover; }
3395
3405
 
3396
3406
  /* ---- postcard: a member's introduction in the shape of a cover card — the photo fills it,
@@ -3419,7 +3429,7 @@ button { font: inherit; cursor: pointer; }
3419
3429
  liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
3420
3430
  .postcard {
3421
3431
  position: relative;
3422
- min-height: var(--postcard-min-h, 22.5rem);
3432
+ min-height: var(--postcard-min-h, var(--_cover-min-h));
3423
3433
  perspective: 60rem;
3424
3434
  border-radius: var(--postcard-radius, var(--r-card));
3425
3435
  isolation: isolate;
@@ -3445,14 +3455,10 @@ button { font: inherit; cursor: pointer; }
3445
3455
  display: grid;
3446
3456
  align-content: start;
3447
3457
  padding: var(--postcard-face-pad, var(--card-pad-rest));
3448
- border: 0;
3449
3458
  border-radius: inherit;
3450
- background: transparent;
3451
- color: inherit;
3452
3459
  text-align: left;
3453
- cursor: pointer;
3454
3460
  }
3455
- .postcard__turn:focus-visible { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
3461
+ .postcard__turn:focus-visible { outline-offset: calc(-1 * var(--sp-1)); } /* the ring inside the card's edge */
3456
3462
  /* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
3457
3463
  .postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); color: var(--postcard-front-ink, var(--card-3-ink)); }
3458
3464
  .postcard__front .postcard__turn > img,
@@ -3508,7 +3514,7 @@ button { font: inherit; cursor: pointer; }
3508
3514
  .postcard__face { transition: none; }
3509
3515
  }
3510
3516
 
3511
- /* ---- css/layover.css — ask drawer, popover menu, banner, float bar, callout, deck ---- */
3517
+ /* ---- css/layover.css — ask drawer, popover menu, banner, float bar, callout ---- */
3512
3518
  /* ============================================================
3513
3519
  cardds/layover.css — cards that lay OVER other content:
3514
3520
  Modal (ask) · popover menu · expanding banner · float bar ·
@@ -3535,10 +3541,11 @@ button { font: inherit; cursor: pointer; }
3535
3541
  2. the drawer comes out of the card's bottom edge while the lift moves
3536
3542
  up, both at once: the card grows from its centre, which never moves
3537
3543
  (the modal centres itself — absolute, margin auto; a growing card keeps its middle where it is)
3538
- Closing: a click on any button in the drawer plays the same animations in
3539
- reverse (cardds.js: Animation.reverse() on each — nothing duplicated), then
3540
- fires "cardds:modal" on the .sheet-stage (bubbles, detail.answer from the
3541
- button's data-answer) for the app to remove the stage.
3544
+ Closing: <Modal open onClosed> — set open={false} and the Modal component plays the
3545
+ same animations in reverse (Animation.reverse() on each — nothing duplicated), then
3546
+ calls onClosed for the app to unmount the stage. Modal no longer knows its own
3547
+ buttons (data-answer / the "cardds:modal" event are gone; the app's own onClick
3548
+ sets open={false}).
3542
3549
  Reduced motion: the finished state, no beats; closing fires at once. */
3543
3550
  .modal {
3544
3551
  --drawer-h: 14rem;
@@ -3617,8 +3624,8 @@ button { font: inherit; cursor: pointer; }
3617
3624
  element the button belongs to (a card, a sheet), opened by an .icon-btn:
3618
3625
  <button class="icon-btn icon-btn--sm" popovertarget="m1" aria-label="More">…</button>
3619
3626
  <div class="menu menu--actions menu--end" id="m1" popover><button class="menu__item"><svg class="icon"/>Edit</button>…</div>
3620
- No item stays current there (cardds.js skips the mark). .menu--end = the
3621
- trigger sits at the far right, so the panel grows leftwards. */
3627
+ No item stays current there — an actions menu's items carry no aria-current, unlike
3628
+ the dropdown's. .menu--end = the trigger sits at the far right, so the panel grows leftwards. */
3622
3629
  .menu {
3623
3630
  display: none; /* explicit closed state — never display on the base rule */
3624
3631
  margin: 0;
@@ -3644,15 +3651,11 @@ button { font: inherit; cursor: pointer; }
3644
3651
  min-height: var(--menu-item-min-h, var(--tap));
3645
3652
  width: 100%;
3646
3653
  padding: var(--menu-item-pad-y, 0) var(--menu-item-pad-x, var(--sp-3));
3647
- border: 0;
3648
3654
  /* concentric with the panel: the card radius minus the panel's padding, so the
3649
3655
  highlight's curve follows the outer corner (Lh 2026-09-16) */
3650
3656
  border-radius: calc(var(--r-card) - var(--menu-pad, var(--sp-2)));
3651
- background: transparent;
3652
- color: inherit;
3653
3657
  font: var(--menu-item-font, var(--type-title));
3654
3658
  text-align: left;
3655
- cursor: pointer;
3656
3659
  }
3657
3660
  .menu__item:hover, .menu__item:focus-visible { background: var(--ink-tint); outline: none; }
3658
3661
  .menu__item[aria-current] { background: var(--accent); color: var(--on-accent); }
@@ -3669,7 +3672,7 @@ button { font: inherit; cursor: pointer; }
3669
3672
  .menu[popover] {
3670
3673
  opacity: 0;
3671
3674
  translate: 0 calc(-1 * var(--sp-2));
3672
- transition: display 0.2s allow-discrete, overlay 0.2s allow-discrete, opacity 0.2s, translate 0.2s;
3675
+ transition: display var(--motion-quick) allow-discrete, overlay var(--motion-quick) allow-discrete, opacity var(--motion-quick), translate var(--motion-quick);
3673
3676
  }
3674
3677
  .menu:popover-open { opacity: 1; translate: 0 0; }
3675
3678
  .menu.\:popover-open { opacity: 1; translate: 0 0; }
@@ -3690,19 +3693,19 @@ button { font: inherit; cursor: pointer; }
3690
3693
  padding-inline: var(--card-pad, var(--screen-pad));
3691
3694
  }
3692
3695
  .banner__summary {
3693
- list-style: none; cursor: pointer;
3696
+ list-style: none;
3694
3697
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
3695
3698
  padding-block: var(--banner-summary-pad-y, calc(var(--sp-base) * 1.5)); font: var(--banner-summary-font, var(--type-caption)); font-weight: 400;
3696
3699
  }
3697
3700
  .banner__summary::-webkit-details-marker { display: none; }
3698
- .banner__summary .icon { transition: rotate 0.2s ease; }
3701
+ .banner__summary .icon { transition: rotate var(--motion-quick) ease; }
3699
3702
  .banner[open] .banner__summary .icon { rotate: 180deg; }
3700
3703
  .banner__body { display: grid; gap: var(--banner-body-gap, var(--sp-1)); padding-bottom: var(--sp-4); }
3701
3704
  .banner::details-content { width: 100%; }
3702
3705
  @supports (interpolate-size: allow-keywords) {
3703
3706
  @media (prefers-reduced-motion: no-preference) {
3704
3707
  .banner { interpolate-size: allow-keywords; }
3705
- .banner::details-content { block-size: 0; overflow: clip; transition: block-size 0.25s ease, content-visibility 0.25s allow-discrete; }
3708
+ .banner::details-content { block-size: 0; overflow: clip; transition: block-size var(--motion-quick) ease, content-visibility var(--motion-quick) allow-discrete; }
3706
3709
  .banner[open]::details-content { block-size: auto; }
3707
3710
  }
3708
3711
  }
@@ -3719,16 +3722,12 @@ button { font: inherit; cursor: pointer; }
3719
3722
  display: flex;
3720
3723
  gap: var(--float-bar-gap, var(--sp-2));
3721
3724
  align-items: center;
3722
- z-index: 30;
3725
+ z-index: var(--z-bar);
3723
3726
  }
3724
3727
  /* in a fill screen (its `bottom`) the float bar floats over the SCREEN's foot, not the window's — it lies over the content
3725
3728
  (a map, a stack), so it is not a row of the column; the screen is its box (Lh 2026-09-23) */
3726
3729
  .screen--fill > .float-bar { position: absolute; }
3727
- .sheet-stage > .float-bar { position: absolute; padding-inline: var(--sp-4); }
3728
- /* .screen's default bottom padding clears an .action-bar; a .float-bar is
3729
- taller (--bar-h + its own bottom offset), so reserve the difference. Any
3730
- parent that hosts the bar counts (see .action-bar's reserve, actions.css) */
3731
- :has(> .float-bar) .screen:not(.screen--fill) { padding-bottom: calc(var(--bar-h) + var(--sp-5) + var(--sp-6)); }
3730
+ .sheet-stage > .float-bar { position: absolute; } /* padding-inline removed 2026-09-27: it hard-coded --sp-4 over the --float-bar-pad-x knob, so the knob had no effect here */
3732
3731
  .float-bar__pill {
3733
3732
  flex: 1;
3734
3733
  min-width: 0;
@@ -3753,13 +3752,10 @@ button { font: inherit; cursor: pointer; }
3753
3752
  gap: var(--float-bar-item-gap, var(--sp-0));
3754
3753
  min-height: var(--float-bar-item-min-h, var(--tap-lg));
3755
3754
  padding: var(--float-bar-item-pad, var(--sp-1));
3756
- border: 0;
3757
3755
  border-radius: var(--float-bar-item-radius, var(--r-chip));
3758
- background: transparent;
3759
- color: inherit;
3760
3756
  font: var(--float-bar-item-font, var(--type-caption));
3761
3757
  text-decoration: none;
3762
- opacity: 0.6;
3758
+ opacity: var(--rest);
3763
3759
  overflow: hidden;
3764
3760
  white-space: nowrap;
3765
3761
  text-overflow: ellipsis;