@usableapp/cardds 0.3.0 → 0.4.1

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 (103) hide show
  1. package/README.md +12 -8
  2. package/css/actions.css +38 -27
  3. package/css/base.css +9 -9
  4. package/css/card.css +24 -23
  5. package/css/choice.css +50 -50
  6. package/css/forms.css +18 -18
  7. package/css/journey.css +16 -16
  8. package/css/layover.css +57 -60
  9. package/css/lists.css +52 -43
  10. package/css/media.css +96 -60
  11. package/css/numbers.css +41 -25
  12. package/css/people.css +11 -11
  13. package/css/sheet.css +28 -24
  14. package/css/stack.css +3 -3
  15. package/css/theme.template.css +6 -3
  16. package/dist/actions/Btn.d.ts +109 -108
  17. package/dist/actions/BtnRow.d.ts +2 -2
  18. package/dist/actions/Chip.d.ts +102 -101
  19. package/dist/actions/Dot.d.ts +2 -2
  20. package/dist/actions/Dropdown.d.ts +4 -4
  21. package/dist/actions/Fab.d.ts +2 -2
  22. package/dist/actions/IconBtn.d.ts +107 -106
  23. package/dist/actions/Link.d.ts +105 -104
  24. package/dist/actions/Segment.d.ts +3 -3
  25. package/dist/cardds.css +443 -369
  26. package/dist/cards/Card.d.ts +99 -98
  27. package/dist/cards/CardFoot.d.ts +2 -2
  28. package/dist/cards/CardHead.d.ts +2 -2
  29. package/dist/cards/CardParts.d.ts +7 -7
  30. package/dist/cards/Placeholder.d.ts +2 -2
  31. package/dist/cards/Stat.d.ts +7 -5
  32. package/dist/cards/Stat.js +2 -2
  33. package/dist/choice/Calendar.d.ts +2 -2
  34. package/dist/choice/Check.d.ts +3 -3
  35. package/dist/choice/DayStrip.d.ts +3 -3
  36. package/dist/choice/Mood.d.ts +2 -2
  37. package/dist/choice/Slider.d.ts +2 -2
  38. package/dist/forms/AddRow.d.ts +2 -2
  39. package/dist/forms/Composer.d.ts +2 -2
  40. package/dist/forms/Field.d.ts +2 -2
  41. package/dist/forms/FileBtn.d.ts +2 -2
  42. package/dist/forms/Pin.d.ts +2 -2
  43. package/dist/index.d.ts +2 -1
  44. package/dist/index.js +2 -1
  45. package/dist/journey/NoteRow.d.ts +2 -2
  46. package/dist/journey/Route.d.ts +2 -2
  47. package/dist/journey/TileBadge.d.ts +2 -2
  48. package/dist/layover/Banner.d.ts +2 -2
  49. package/dist/layover/Callout.d.ts +2 -2
  50. package/dist/layover/Deck.d.ts +3 -3
  51. package/dist/lists/Bubble.d.ts +3 -3
  52. package/dist/lists/Kv.d.ts +5 -5
  53. package/dist/lists/Legend.d.ts +3 -3
  54. package/dist/lists/Row.d.ts +14 -285
  55. package/dist/lists/Row.js +10 -3
  56. package/dist/lists/Timeline.d.ts +3 -3
  57. package/dist/media/MapArea.d.ts +2 -2
  58. package/dist/media/Mosaic.d.ts +3 -3
  59. package/dist/media/Postcard.d.ts +17 -7
  60. package/dist/media/Postcard.js +8 -5
  61. package/dist/media/Quote.d.ts +2 -2
  62. package/dist/media/Tile.d.ts +5 -4
  63. package/dist/numbers/Badge.d.ts +2 -2
  64. package/dist/numbers/Band.d.ts +3 -3
  65. package/dist/numbers/Bars.d.ts +3 -3
  66. package/dist/numbers/DotGrid.d.ts +2 -2
  67. package/dist/numbers/Picker.d.ts +3 -3
  68. package/dist/numbers/Progress.d.ts +9 -0
  69. package/dist/numbers/Progress.js +7 -0
  70. package/dist/numbers/Ring.d.ts +2 -2
  71. package/dist/numbers/Track.d.ts +3 -3
  72. package/dist/people/Avatar.d.ts +100 -99
  73. package/dist/people/AvatarPick.d.ts +3 -3
  74. package/dist/scaffold/ActionBar.d.ts +3 -3
  75. package/dist/scaffold/AppBar.d.ts +2 -2
  76. package/dist/scaffold/BaseContent.d.ts +3 -3
  77. package/dist/scaffold/Centre.d.ts +3 -3
  78. package/dist/scaffold/EmptyState.d.ts +2 -2
  79. package/dist/scaffold/FilterRow.d.ts +2 -2
  80. package/dist/scaffold/FloatBar.d.ts +105 -104
  81. package/dist/scaffold/PagerAt.d.ts +2 -2
  82. package/dist/scaffold/Screen.d.ts +100 -99
  83. package/dist/scaffold/TopBar.d.ts +2 -2
  84. package/dist/sheets/Drawer.d.ts +7 -7
  85. package/dist/sheets/Drawer.js +1 -1
  86. package/dist/sheets/Sheet.d.ts +3 -5
  87. package/dist/sheets/Sheet.js +2 -2
  88. package/dist/sheets/SheetBody.d.ts +2 -2
  89. package/dist/sheets/SheetHead.d.ts +2 -2
  90. package/dist/sheets/SheetStack.d.ts +5 -5
  91. package/dist/sheets/SheetStack.js +3 -3
  92. package/dist/sheets/SheetStage.d.ts +5 -7
  93. package/dist/sheets/SheetStage.js +3 -3
  94. package/dist/sheets/StackBack.d.ts +7 -0
  95. package/dist/sheets/StackBack.js +7 -0
  96. package/dist/sheets/Step.d.ts +2 -2
  97. package/dist/type/Icon.d.ts +22 -7
  98. package/dist/type/Icon.js +27 -4
  99. package/dist/type/Text.d.ts +7 -1
  100. package/dist/type/Text.js +2 -2
  101. package/package.json +3 -2
  102. package/dist/sheets/CardBack.d.ts +0 -7
  103. package/dist/sheets/CardBack.js +0 -7
package/dist/cardds.css CHANGED
@@ -417,10 +417,10 @@ img, svg { display: block; max-width: 100%; }
417
417
  button { font: inherit; cursor: pointer; }
418
418
 
419
419
  /* icon from icons.svg sprite: <svg class="icon"><use href="icons.svg#bell"/></svg> */
420
- .icon { width: var(--icon); height: var(--icon); flex: none; }
421
- .icon--sm { width: var(--icon-sm); height: var(--icon-sm); }
422
- .icon--xs { width: var(--icon-xs); height: var(--icon-xs); } /* inside captions, chips, links */
423
- .icon--lg { width: var(--icon-lg); height: var(--icon-lg); } /* empty states */
420
+ .icon { width: var(--icon-size, var(--icon)); height: var(--icon-size, var(--icon)); flex: none; }
421
+ .icon--sm { width: var(--icon-sm-size, var(--icon-sm)); height: var(--icon-sm-size, var(--icon-sm)); }
422
+ .icon--xs { width: var(--icon-xs-size, var(--icon-xs)); height: var(--icon-xs-size, var(--icon-xs)); } /* inside captions, chips, links */
423
+ .icon--lg { width: var(--icon-lg-size, var(--icon-lg)); height: var(--icon-lg-size, var(--icon-lg)); } /* empty states */
424
424
 
425
425
  /* mobile screen column — the standard horizontal inset, and nothing vertical.
426
426
  Top and bottom are zero on purpose: vertical room is only ever a RESERVE for
@@ -487,9 +487,9 @@ button { font: inherit; cursor: pointer; }
487
487
  In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
488
488
  .base-content {
489
489
  display: grid;
490
- gap: var(--sp-3);
490
+ gap: var(--base-content-gap, var(--sp-3));
491
491
  align-content: start; /* a tall column never stretches the rows — the words hug the top */
492
- padding-block: var(--sp-4);
492
+ padding-block: var(--base-content-pad-y, var(--sp-4));
493
493
  }
494
494
  .base-content > .t-body { color: var(--on-surface-muted); max-width: 34ch; }
495
495
  .base-content .cta-pack {
@@ -505,9 +505,9 @@ button { font: inherit; cursor: pointer; }
505
505
  display: grid;
506
506
  justify-items: center;
507
507
  text-align: center;
508
- gap: var(--sp-2);
509
- padding: var(--sp-12) var(--sp-6);
510
- color: 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 */
508
+ gap: var(--empty-state-gap, var(--sp-2));
509
+ padding: var(--empty-state-pad-y, var(--sp-12)) var(--empty-state-pad-x, var(--sp-6));
510
+ 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 */
511
511
  }
512
512
  .empty-state__action { margin-top: var(--sp-2); }
513
513
  /* inside a card (a section's "nothing yet"): the card's ink */
@@ -528,7 +528,7 @@ button { font: inherit; cursor: pointer; }
528
528
  background: var(--card-bg);
529
529
  color: var(--card-ink);
530
530
  border: var(--border-w) solid var(--card-border);
531
- border-radius: var(--r-card);
531
+ border-radius: var(--card-radius, var(--r-card));
532
532
  padding: var(--card-pad);
533
533
  display: grid;
534
534
  gap: var(--card-gap);
@@ -559,8 +559,8 @@ button { font: inherit; cursor: pointer; }
559
559
  flex-wrap: wrap; /* the caption / overline take a line of their own — no wrapper div */
560
560
  align-items: center;
561
561
  justify-content: space-between;
562
- column-gap: var(--sp-3);
563
- row-gap: var(--sp-0);
562
+ column-gap: var(--card-head-col-gap, var(--sp-3));
563
+ row-gap: var(--card-head-row-gap, var(--sp-0));
564
564
  }
565
565
  /* flat head markup (Lh 2026-09-16 — no <p>, no wrapper, only padding and gap tokens):
566
566
  <div class="card__head"><span class="t-overline t-muted">…</span><h2 class="t-h2">…</h2><span class="t-caption t-muted">…</span><button class="icon-btn icon-btn--sm">…</button></div>
@@ -571,8 +571,8 @@ button { font: inherit; cursor: pointer; }
571
571
  .card__head > .t-overline { order: -1; }
572
572
  .card__head--top { align-items: flex-start; }
573
573
 
574
- .card__media { border-radius: var(--r-media); overflow: hidden; position: relative; } /* relative: corner badges */
575
- .card__end { text-align: right; display: grid; gap: var(--sp-0); justify-items: end; } /* trailing value + caption */
574
+ .card__media { border-radius: var(--card-media-radius, var(--r-media)); overflow: hidden; position: relative; } /* relative: corner badges */
575
+ .card__end { text-align: right; display: grid; gap: var(--card-end-gap, var(--sp-0)); justify-items: end; } /* trailing value + caption */
576
576
  .card__media > img { width: 100%; height: auto; object-fit: cover; }
577
577
 
578
578
  /* ---- the card skeleton: head · content · foot ----
@@ -636,7 +636,7 @@ button { font: inherit; cursor: pointer; }
636
636
  /* foot: the card's action buttons on one line. One button takes the width,
637
637
  up to four share it in even parts — so the gaps read even too — and from
638
638
  three on they drop their side padding so the labels keep the room. */
639
- .card__foot { display: flex; gap: var(--sp-2); align-items: center; }
639
+ .card__foot { display: flex; gap: var(--card-foot-gap, var(--sp-2)); align-items: center; }
640
640
  .card__foot > .btn { flex: 1 1 0; min-width: 0; }
641
641
  .card__foot:has(> :nth-child(3)) > .btn { padding-inline: var(--sp-2); }
642
642
 
@@ -770,51 +770,52 @@ button { font: inherit; cursor: pointer; }
770
770
  .screen__centre > .pager__at { justify-self: center; }
771
771
 
772
772
  /* compact card inside a card (nested note, promo) */
773
- .card--sm { padding: var(--sp-4); --card-gap: var(--sp-2); border-radius: var(--r-card); }
773
+ .card--sm { padding: var(--card-sm-pad, var(--sp-4)); --card-gap: var(--sp-2); border-radius: var(--card-sm-radius, var(--r-card)); }
774
774
 
775
775
  /* the sheet stack (.sheet-stack) — sheets stacked, heads peeking — lives in
776
776
  stack.css; a stacked sheet is a plain .card, the material */
777
777
 
778
778
  /* trailing icon cluster in a card header */
779
- .icon-row { display: flex; align-items: center; gap: var(--sp-4); opacity: 0.85; }
779
+ .icon-row { display: flex; align-items: center; gap: var(--icon-row-gap, var(--sp-4)); opacity: 0.85; }
780
780
 
781
781
  /* ---- card list: rows, each row its own boundary ---- */
782
- .card-list { display: grid; gap: var(--sp-3); }
782
+ .card-list { display: grid; gap: var(--card-list-gap, var(--sp-3)); }
783
783
 
784
784
  .card--row {
785
- border-radius: var(--r-card);
786
- padding: var(--sp-4);
785
+ border-radius: var(--card-row-radius, var(--r-card));
786
+ padding: var(--card-row-pad, var(--sp-4));
787
787
  display: flex;
788
788
  align-items: center;
789
- gap: var(--sp-3);
789
+ gap: var(--card-row-gap, var(--sp-3));
790
790
  }
791
791
  .card--row .card__body { flex: 1; display: grid; gap: var(--sp-0); }
792
792
 
793
793
  /* leading thumb/icon in a row */
794
794
  .card__thumb {
795
- width: var(--tap-sm); height: var(--tap-sm);
795
+ width: var(--card-thumb-size, var(--tap-sm)); height: var(--card-thumb-size, var(--tap-sm));
796
796
  flex: none;
797
- border-radius: var(--r-control);
797
+ border-radius: var(--card-thumb-radius, var(--r-control));
798
798
  border: var(--border-w) solid currentColor;
799
799
  display: grid;
800
800
  place-items: center;
801
- font: var(--type-label);
801
+ font: var(--card-thumb-font, var(--type-label));
802
802
  opacity: 0.85;
803
803
  }
804
804
 
805
805
  /* ---- inline stat block (revenue-style) ---- */
806
- .stat { display: grid; gap: var(--sp-1); }
807
- .stat__row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
806
+ .stat { display: grid; gap: var(--stat-gap, var(--sp-1)); }
807
+ .stat--centre { text-align: center; justify-items: center; } /* the one number of a sheet or a stage, under a centred avatar */
808
+ .stat__row { display: flex; align-items: center; justify-content: space-between; gap: var(--stat-row-gap, var(--sp-3)); }
808
809
 
809
810
  .delta {
810
- font: var(--type-caption);
811
+ font: var(--delta-font, var(--type-caption));
811
812
  font-weight: 700;
812
- padding: var(--sp-1) var(--sp-2);
813
- border-radius: var(--r-chip);
814
- background: var(--card-3-bg);
815
- color: var(--card-3-ink);
813
+ padding: var(--delta-pad-y, var(--sp-1)) var(--delta-pad-x, var(--sp-2));
814
+ border-radius: var(--delta-radius, var(--r-chip));
815
+ background: var(--delta-bg, var(--card-3-bg));
816
+ color: var(--delta-ink, var(--card-3-ink));
816
817
  }
817
- .delta--neg { background: var(--neg); color: var(--card-3-ink); } /* light ink on the signal color in every slot */
818
+ .delta--neg { background: var(--delta-neg-bg, var(--neg)); color: var(--delta-neg-ink, var(--card-3-ink)); } /* light ink on the signal color in every slot */
818
819
 
819
820
  /* ---- css/stack.css — sheet stack: .sheet-stack — the menu, sheets stacked, heads peek, tap opens ---- */
820
821
  /* ============================================================
@@ -838,7 +839,7 @@ button { font: inherit; cursor: pointer; }
838
839
  <div class="sheet-stack sheet-stack--tap"> (drop --tap for a static stack)
839
840
  <article class="card card--2">
840
841
  <div class="card__head">
841
- <button class="icon-btn icon-btn--sm card__back" aria-label="Back">…</button>
842
+ <button class="icon-btn icon-btn--sm sheet-stack__back" aria-label="Back">…</button>
842
843
  <h2 class="t-h2 t-caps">Shopping</h2>
843
844
  <span class="icon-row">…</span> (optional trailing icons)
844
845
  </div>
@@ -1062,7 +1063,7 @@ button { font: inherit; cursor: pointer; }
1062
1063
  .sheet-stack--tap > .card.is-open > .card__head {
1063
1064
  padding-left: calc(var(--tap-sm) + var(--sp-3));
1064
1065
  }
1065
- .sheet-stack--tap .card__back {
1066
+ .sheet-stack--tap .sheet-stack__back {
1066
1067
  position: absolute;
1067
1068
  left: 0;
1068
1069
  top: 50%;
@@ -1071,7 +1072,7 @@ button { font: inherit; cursor: pointer; }
1071
1072
  visibility: hidden;
1072
1073
  transition: opacity 0.35s ease, visibility 0.35s;
1073
1074
  }
1074
- .sheet-stack--tap > .card.is-open .card__back {
1075
+ .sheet-stack--tap > .card.is-open .sheet-stack__back {
1075
1076
  opacity: 1;
1076
1077
  visibility: visible;
1077
1078
  }
@@ -1159,7 +1160,7 @@ button { font: inherit; cursor: pointer; }
1159
1160
  overflow: clip;
1160
1161
  border-radius: var(--r-card) var(--r-card) 0 0;
1161
1162
  border-bottom: 0;
1162
- box-shadow: var(--shadow-sheet);
1163
+ box-shadow: var(--sheet-shadow, var(--shadow-sheet));
1163
1164
  }
1164
1165
  .sheet--full { --_y: 0%; --_cover: 0rem; }
1165
1166
  .sheet--half { --_y: calc(var(--sheet-half) * 100%); --_cover: calc(var(--sheet-half) * var(--_box)); }
@@ -1257,7 +1258,7 @@ button { font: inherit; cursor: pointer; }
1257
1258
  opacity: 0.3;
1258
1259
  width: var(--tap-sm);
1259
1260
  height: var(--_handle);
1260
- border-radius: var(--r-chip);
1261
+ border-radius: var(--sheet-handle-radius, var(--r-chip));
1261
1262
  margin: 0 auto;
1262
1263
  padding: 0;
1263
1264
  touch-action: none;
@@ -1289,8 +1290,8 @@ button { font: inherit; cursor: pointer; }
1289
1290
  min-height: var(--tap);
1290
1291
  text-align: center;
1291
1292
  }
1292
- .sheet__title { font: var(--type-h2); letter-spacing: var(--track-h2); margin: 0; }
1293
- .sheet__sub { font: var(--type-caption); color: var(--card-muted); }
1293
+ .sheet__title { font: var(--sheet-title-font, var(--type-h2)); letter-spacing: var(--track-h2); margin: 0; }
1294
+ .sheet__sub { font: var(--sheet-sub-font, var(--type-caption)); color: var(--sheet-sub-ink, var(--card-muted)); }
1294
1295
 
1295
1296
  /* ============================================================
1296
1297
  .sheet-stage — the overlay stage: a page-height box the sheet
@@ -1312,7 +1313,7 @@ button { font: inherit; cursor: pointer; }
1312
1313
  margin-inline: auto;
1313
1314
  overflow: clip; /* not hidden: a clipped box is not a scroll container, so revealing a focused field never shifts the stage */
1314
1315
  container-type: size; /* the sheet's body reads this box's height as 100cqh (--_box above) */
1315
- background: var(--surface);
1316
+ background: var(--sheet-stage-bg, var(--surface));
1316
1317
  }
1317
1318
  /* on an app screen the stage is the page: it takes the screen's inset back (edge to
1318
1319
  edge, like a step) and, after a fixed .appbar, the room the screen reserved for
@@ -1329,8 +1330,13 @@ button { font: inherit; cursor: pointer; }
1329
1330
  inset-inline: var(--screen-pad);
1330
1331
  top: var(--_band-centre);
1331
1332
  translate: 0 -50%;
1332
- /* no z-index: it follows the background in DOM order and the sheet, later, covers it at full */
1333
+ /* as tall as the band less the screen's inset at both ends (Lh 2026-09-22: the room under the hero = the room at its sides);
1334
+ what it holds fills it (a postcard's min-height gives way) */
1335
+ height: calc((100cqh - var(--_top, 0rem)) / 2 - 2 * var(--screen-pad));
1336
+ display: grid;
1333
1337
  }
1338
+ .sheet-stage > .sheet-stage__hero > * { min-height: 0; } /* outranks a component's own min-height (the postcard's) */
1339
+ /* no z-index on the hero: it follows the background in DOM order and the sheet, later, covers it at full */
1334
1340
 
1335
1341
  /* ---- map: the box a map engine draws in, behind a stage sheet ----
1336
1342
  <div class="sheet-stage__bg"><div class="map"><div>…the engine's canvas…</div>
@@ -1347,7 +1353,7 @@ button { font: inherit; cursor: pointer; }
1347
1353
  position: absolute;
1348
1354
  inset: 0;
1349
1355
  isolation: isolate; /* the ring and the hint stack inside the map only — a sheet over the map covers them */
1350
- color: var(--on-surface);
1356
+ color: var(--map-ink, var(--on-surface));
1351
1357
  background:
1352
1358
  repeating-linear-gradient(75deg, transparent 0 5.5rem, var(--hairline) 5.5rem 6.25rem),
1353
1359
  repeating-linear-gradient(-18deg, transparent 0 8.75rem, var(--ink-tint) 8.75rem 9.25rem),
@@ -1365,9 +1371,9 @@ button { font: inherit; cursor: pointer; }
1365
1371
  height: var(--map-ring);
1366
1372
  translate: -50% -50%;
1367
1373
  border-radius: 50%;
1368
- border: var(--border-w) solid var(--accent);
1369
- background: color-mix(in srgb, var(--accent) 22%, transparent);
1370
- box-shadow: var(--shadow-float);
1374
+ border: var(--border-w) solid var(--map-ring-border, var(--accent));
1375
+ background: var(--map-ring-bg, color-mix(in srgb, var(--accent) 22%, transparent));
1376
+ box-shadow: var(--map-ring-shadow, var(--shadow-float));
1371
1377
  pointer-events: none;
1372
1378
  z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
1373
1379
  }
@@ -1376,10 +1382,10 @@ button { font: inherit; cursor: pointer; }
1376
1382
  right: var(--sp-2);
1377
1383
  bottom: var(--sp-2);
1378
1384
  padding: var(--sp-0) var(--sp-2);
1379
- border-radius: var(--r-chip);
1380
- background: color-mix(in srgb, var(--surface) 80%, transparent);
1381
- color: var(--on-surface-muted);
1382
- font: var(--type-caption);
1385
+ border-radius: var(--map-credit-radius, var(--r-chip));
1386
+ background: var(--map-credit-bg, color-mix(in srgb, var(--surface) 80%, transparent));
1387
+ color: var(--map-credit-ink, var(--on-surface-muted));
1388
+ font: var(--map-credit-font, var(--type-caption));
1383
1389
  pointer-events: none;
1384
1390
  z-index: 1;
1385
1391
  }
@@ -1390,12 +1396,12 @@ button { font: inherit; cursor: pointer; }
1390
1396
  translate: -50% 0;
1391
1397
  max-width: calc(100% - 2 * var(--sp-4));
1392
1398
  padding: var(--sp-2) var(--sp-4);
1393
- border-radius: var(--r-chip);
1394
- background: var(--card-3-bg);
1395
- color: var(--card-3-ink);
1396
- font: var(--type-label);
1399
+ border-radius: var(--map-hint-radius, var(--r-chip));
1400
+ background: var(--map-hint-bg, var(--card-3-bg));
1401
+ color: var(--map-hint-ink, var(--card-3-ink));
1402
+ font: var(--map-hint-font, var(--type-label));
1397
1403
  text-align: center;
1398
- box-shadow: var(--shadow-float);
1404
+ box-shadow: var(--map-hint-shadow, var(--shadow-float));
1399
1405
  pointer-events: none;
1400
1406
  z-index: 1;
1401
1407
  }
@@ -1406,10 +1412,9 @@ button { font: inherit; cursor: pointer; }
1406
1412
  /* EVERY sheet has one shape (Lh, 2026-09-21): the handle and the head stay at the top and never scroll · the
1407
1413
  foot's button stands at the bottom, and ABOVE the keyboard when there is one · everything between them
1408
1414
  scrolls, as far as it needs to. In a .step the keyboard's room is the step's (--kb, step.css); on a stage
1409
- the sheet gives it up itself, as --_lift — the same token a raised sheet floats on. */
1415
+ the sheet gives it up itself, as --_lift. (A sheet ALWAYS stands on the floor — `.sheet--raised`, the one that
1416
+ floated above a footnote strip on a dim stage, was retired 2026-09-22: that is a Modal's job.) */
1410
1417
  .sheet-stage > .sheet { --_lift: var(--kb, 0rem); bottom: var(--_lift); }
1411
- .sheet-stage > .sheet--raised { --_lift: calc(var(--tap-lg) + var(--kb, 0rem)); }
1412
- .sheet--raised { --_lift: var(--tap-lg); bottom: var(--_lift); border-radius: var(--r-card); border-bottom: var(--border-w) solid var(--card-border); }
1413
1418
  .sheet-stage__foot {
1414
1419
  position: absolute;
1415
1420
  inset-inline: 0;
@@ -1418,8 +1423,8 @@ button { font: inherit; cursor: pointer; }
1418
1423
  margin: 0;
1419
1424
  display: grid;
1420
1425
  place-items: center;
1421
- font: var(--type-caption);
1422
- color: var(--card-3-ink);
1426
+ font: var(--sheet-stage-foot-font, var(--type-caption));
1427
+ color: var(--sheet-stage-foot-ink, var(--card-3-ink));
1423
1428
  z-index: 2;
1424
1429
  }
1425
1430
 
@@ -1543,13 +1548,13 @@ button { font: inherit; cursor: pointer; }
1543
1548
  .field {
1544
1549
  display: flex;
1545
1550
  align-items: center;
1546
- gap: var(--sp-2);
1547
- border: var(--border-w) solid var(--action-border);
1548
- border-radius: var(--r-control);
1549
- background: var(--action-bg);
1550
- color: var(--action-ink);
1551
- padding-inline: var(--sp-4);
1552
- min-height: var(--tap);
1551
+ gap: var(--field-gap, var(--sp-2));
1552
+ border: var(--border-w) solid var(--field-border, var(--action-border));
1553
+ border-radius: var(--field-radius, var(--r-control));
1554
+ background: var(--field-bg, var(--action-bg));
1555
+ color: var(--field-ink, var(--action-ink));
1556
+ padding-inline: var(--field-pad-x, var(--sp-4));
1557
+ min-height: var(--field-min-h, var(--tap));
1553
1558
  min-width: 0;
1554
1559
  }
1555
1560
 
@@ -1566,7 +1571,7 @@ button { font: inherit; cursor: pointer; }
1566
1571
  .field input::placeholder { color: inherit; opacity: 0.55; }
1567
1572
 
1568
1573
  /* field with its action inside: date pick, send, go — the button sits flush right */
1569
- .field--action { padding-right: var(--sp-1); min-height: var(--tap-lg); }
1574
+ .field--action { padding-right: var(--sp-1); min-height: var(--field-action-min-h, var(--tap-lg)); }
1570
1575
  .field--action > .icon-btn { margin-left: auto; }
1571
1576
 
1572
1577
  /* colored palettes drop the visible outline elsewhere — fields keep one */
@@ -1576,25 +1581,25 @@ button { font: inherit; cursor: pointer; }
1576
1581
  .add-row {
1577
1582
  display: inline-flex;
1578
1583
  align-items: center;
1579
- gap: var(--sp-2);
1584
+ gap: var(--add-row-gap, var(--sp-2));
1580
1585
  border: 0;
1581
1586
  background: none;
1582
1587
  color: inherit;
1583
- font: var(--type-label);
1584
- font-size: var(--fs-body);
1585
- padding: var(--sp-2) 0;
1586
- min-height: var(--tap-sm);
1588
+ font: var(--add-row-font, var(--type-label));
1589
+ font-size: var(--add-row-font-size, var(--fs-body));
1590
+ padding: var(--add-row-pad-y, var(--sp-2)) var(--add-row-pad-x, 0);
1591
+ min-height: var(--add-row-min-h, var(--tap-sm));
1587
1592
  }
1588
1593
  .add-row__plus {
1589
- width: var(--icon);
1590
- height: var(--icon);
1594
+ width: var(--add-row-plus-size, var(--icon));
1595
+ height: var(--add-row-plus-size, var(--icon));
1591
1596
  flex: none;
1592
1597
  border-radius: 50%;
1593
- background: var(--accent);
1594
- color: var(--on-accent);
1598
+ background: var(--add-row-plus-bg, var(--accent));
1599
+ color: var(--add-row-plus-ink, var(--on-accent));
1595
1600
  display: grid;
1596
1601
  place-items: center;
1597
- font-size: var(--fs-md);
1602
+ font-size: var(--add-row-plus-font-size, var(--fs-md));
1598
1603
  line-height: 1;
1599
1604
  }
1600
1605
  .add-row__plus .icon { width: var(--icon-xs); height: var(--icon-xs); }
@@ -1607,40 +1612,40 @@ button { font: inherit; cursor: pointer; }
1607
1612
  ============================================================ */
1608
1613
 
1609
1614
  /* ---- route: endpoint · dotted connector (icon in middle) · endpoint ---- */
1610
- .route { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; column-gap: var(--sp-1); } /* line → icon: --sp-1, as the via always had; code → line is the code row's own --sp-3 */
1615
+ .route { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; column-gap: var(--route-col-gap, var(--sp-1)); } /* line → icon: --sp-1, as the via always had; code → line is the code row's own --sp-3 */
1611
1616
  /* the two endpoints get EQUAL columns whatever their words measure, so the via
1612
1617
  (the icon alone) sits at the card's centre; each dotted run lives on ITS
1613
1618
  endpoint's code row and fills that side's leftover — with short names the
1614
1619
  runs are long and equal, a long place name wraps in its half instead of
1615
1620
  pushing the plane (Lh 2026-09-16) */
1616
1621
 
1617
- .route__point { display: grid; gap: var(--sp-0); min-width: 0; }
1622
+ .route__point { display: grid; gap: var(--route-point-gap, var(--sp-0)); min-width: 0; }
1618
1623
  .route__point--end { text-align: right; }
1619
1624
 
1620
- .route__code { font: var(--type-h2); letter-spacing: var(--track-h2); display: flex; align-items: center; gap: var(--sp-3); }
1625
+ .route__code { font: var(--route-code-font, var(--type-h2)); letter-spacing: var(--track-h2); display: flex; align-items: center; gap: var(--route-code-gap, var(--sp-3)); }
1621
1626
  .route__code::after, .route__point--end .route__code::before { content: ""; flex: 1; border-top: var(--rule-w) dotted currentColor; opacity: 0.65; }
1622
1627
  .route__point--end .route__code::after { content: none; }
1623
- .route__time { font: var(--type-body); font-variant-numeric: tabular-nums; }
1624
- .route__place { font: var(--type-caption); color: var(--card-muted); }
1628
+ .route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
1629
+ .route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
1625
1630
  .card--3 .route__place { color: inherit; opacity: 0.7; }
1626
1631
 
1627
- .route__via { display: flex; align-items: center; opacity: 0.65; font-size: var(--fs-body); }
1632
+ .route__via { display: flex; align-items: center; opacity: 0.65; font-size: var(--route-via-font-size, var(--fs-body)); }
1628
1633
 
1629
1634
  /* ---- tile badge: tiny label over a big value (gate, seat, zone) ---- */
1630
1635
  .tile-badge {
1631
1636
  display: inline-grid;
1632
1637
  justify-items: center;
1633
- gap: var(--sp-0);
1634
- padding: var(--sp-1) var(--sp-2);
1635
- border-radius: var(--r-badge);
1636
- background: var(--card-3-bg);
1637
- color: var(--card-3-ink);
1638
+ gap: var(--tile-badge-gap, var(--sp-0));
1639
+ padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
1640
+ border-radius: var(--tile-badge-radius, var(--r-badge));
1641
+ background: var(--tile-badge-bg, var(--card-3-bg));
1642
+ color: var(--tile-badge-ink, var(--card-3-ink));
1638
1643
  }
1639
- .tile-badge__label { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.7; }
1644
+ .tile-badge__label { font: var(--tile-badge-label-font, var(--type-overline)); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.7; }
1640
1645
  .tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
1641
1646
 
1642
1647
  /* plain: no fill — a date box beside a title (Jun / 18) */
1643
- .tile-badge--plain { background: transparent; color: inherit; padding: 0; min-width: var(--tap-sm); }
1648
+ .tile-badge--plain { background: transparent; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
1644
1649
  .tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
1645
1650
 
1646
1651
  /* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
@@ -1650,9 +1655,9 @@ button { font: inherit; cursor: pointer; }
1650
1655
  .note-row {
1651
1656
  display: flex;
1652
1657
  align-items: center;
1653
- gap: var(--sp-2);
1654
- font: var(--type-caption);
1655
- color: var(--card-muted);
1658
+ gap: var(--note-row-gap, var(--sp-2));
1659
+ font: var(--note-row-font, var(--type-caption));
1660
+ color: var(--note-row-ink, var(--card-muted));
1656
1661
  }
1657
1662
  .card--3 .note-row { color: inherit; opacity: 0.7; }
1658
1663
 
@@ -1662,6 +1667,14 @@ button { font: inherit; cursor: pointer; }
1662
1667
  topbar, chips, buttons, selects, bottom CTA pack
1663
1668
  ============================================================ */
1664
1669
 
1670
+ /* KNOBS (Lh 2026-09-21) — cardds is a framework: a project adjusts ONE component from outside, without touching this file.
1671
+ A knob is a custom property a rule reads WITH ITS DEFAULT AS THE FALLBACK — `padding-inline: var(--btn-pad-x, var(--sp-6))` —
1672
+ so it is declared nowhere: unset, nothing moves (tests/style-snapshot proves it); set in the project's theme.css it retunes
1673
+ that component everywhere; set on any element it retunes the components inside that element only. Names: --<component>-<what>
1674
+ (--btn-pad-x, --chip-bg, --icon-btn-size, --appbar-pad-x); a variant says so (--btn-primary-bg, --chip-on-ink, --icon-btn-sm-size).
1675
+ The sizes that already were root tokens (--btn-h, --chip-h, --bar-h, --header-h, --fab…) are knobs too — set them the same way.
1676
+ Prefer a BASE when the whole system should move (tokens.css); reach for a knob when ONE component should. `npm run knobs` lists them all. */
1677
+
1665
1678
  /* ---- the bars across the top of a screen: one height, whatever they carry ----
1666
1679
  .topbar (screen title) and .appbar (app context) are the same bar with
1667
1680
  different contents, so the height is declared here once rather than left to
@@ -1672,8 +1685,8 @@ button { font: inherit; cursor: pointer; }
1672
1685
  .topbar, .appbar {
1673
1686
  display: flex;
1674
1687
  align-items: center;
1675
- gap: var(--sp-3);
1676
- padding-block: var(--sp-2);
1688
+ gap: var(--bar-gap, var(--sp-3));
1689
+ padding-block: var(--bar-pad-y, var(--sp-2));
1677
1690
  min-height: var(--header-h);
1678
1691
  }
1679
1692
 
@@ -1702,9 +1715,9 @@ button { font: inherit; cursor: pointer; }
1702
1715
  top: 0;
1703
1716
  max-width: var(--screen-w);
1704
1717
  margin-inline: auto;
1705
- padding-inline: 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) */
1718
+ 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) */
1706
1719
  z-index: 40; /* topmost — card animations never cover it */
1707
- background: var(--surface); /* solid: sliding cards never show through */
1720
+ background: var(--appbar-bg, var(--surface)); /* solid: sliding cards never show through */
1708
1721
  }
1709
1722
  /* fixed, so it covers the top of the screen it sits in — reserve its height
1710
1723
  there, from the same token, the way the bottom bars reserve theirs. The
@@ -1721,19 +1734,19 @@ button { font: inherit; cursor: pointer; }
1721
1734
 
1722
1735
  /* ---- icon button (round) ---- */
1723
1736
  .icon-btn {
1724
- width: var(--tap); height: var(--tap);
1737
+ width: var(--icon-btn-size, var(--tap)); height: var(--icon-btn-size, var(--tap));
1725
1738
  flex: none;
1726
- border-radius: var(--r-chip);
1727
- border: var(--border-w) solid var(--outline); /* see .btn — same reason */
1728
- background: var(--action-bg);
1729
- color: var(--action-ink);
1739
+ border-radius: var(--icon-btn-radius, var(--r-chip));
1740
+ border: var(--border-w) solid var(--icon-btn-border, var(--outline)); /* see .btn — same reason */
1741
+ background: var(--icon-btn-bg, var(--action-bg));
1742
+ color: var(--icon-btn-ink, var(--action-ink));
1730
1743
  display: grid;
1731
1744
  place-items: center;
1732
1745
  font-size: var(--fs-lg);
1733
1746
  line-height: 1;
1734
1747
  }
1735
- .icon-btn--invert { background: var(--accent); color: var(--on-accent); border-color: transparent; }
1736
- .icon-btn--sm { width: var(--tap-sm); height: var(--tap-sm); font-size: var(--fs-title); }
1748
+ .icon-btn--invert { background: var(--icon-btn-invert-bg, var(--accent)); color: var(--icon-btn-invert-ink, var(--on-accent)); border-color: transparent; }
1749
+ .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); }
1737
1750
  /* fab: the square floating action, bottom-right of a stage or screen */
1738
1751
  .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; }
1739
1752
 
@@ -1742,21 +1755,21 @@ button { font: inherit; cursor: pointer; }
1742
1755
  /* --outline, not --action-border: same reason as .chip — on a palette where
1743
1756
  --card-1-bg equals --action-bg a transparent border makes a plain button
1744
1757
  invisible on a white card. --primary and --quiet set their own edge. */
1745
- border: var(--border-w) solid var(--outline);
1746
- background: var(--action-bg);
1747
- color: var(--action-ink);
1748
- border-radius: var(--r-chip);
1749
- padding: var(--sp-4) var(--sp-6);
1758
+ border: var(--border-w) solid var(--btn-border, var(--outline));
1759
+ background: var(--btn-bg, var(--action-bg));
1760
+ color: var(--btn-ink, var(--action-ink));
1761
+ border-radius: var(--btn-radius, var(--r-chip));
1762
+ padding: var(--btn-pad-y, var(--sp-4)) var(--btn-pad-x, var(--sp-6));
1750
1763
  font: var(--type-label);
1751
- font-size: var(--fs-md);
1764
+ font-size: var(--btn-font-size, var(--fs-md));
1752
1765
  display: inline-flex;
1753
1766
  align-items: center;
1754
1767
  justify-content: center;
1755
- gap: var(--sp-2);
1768
+ gap: var(--btn-gap, var(--sp-2));
1756
1769
  min-height: var(--btn-h);
1757
1770
  text-decoration: none; /* .btn works as <a> too */
1758
1771
  }
1759
- .btn--primary { background: var(--accent); color: var(--on-accent); border-color: transparent; }
1772
+ .btn--primary { background: var(--btn-primary-bg, var(--accent)); color: var(--btn-primary-ink, var(--on-accent)); border-color: transparent; }
1760
1773
  /* quiet keeps a faint outline so it still reads as a button on colored palettes;
1761
1774
  it is ink on whatever it sits on — inherit, or on the dark slot (card--3) it
1762
1775
  would take the light-card action ink and vanish */
@@ -1776,25 +1789,25 @@ button { font: inherit; cursor: pointer; }
1776
1789
  .chip {
1777
1790
  display: inline-flex;
1778
1791
  align-items: center;
1779
- gap: var(--sp-2);
1780
- border-radius: var(--r-chip);
1792
+ gap: var(--chip-gap, var(--sp-2));
1793
+ border-radius: var(--chip-radius, var(--r-chip));
1781
1794
  /* --outline, not --action-border: a chip is a control, and on a palette
1782
1795
  where --card-1-bg == --action-bg (field, roast, hydro) a transparent
1783
1796
  border makes it invisible on a white card. Same edge fields, checks,
1784
1797
  toggles, tiles and the dropdown already use. */
1785
- border: var(--border-w) solid var(--outline);
1786
- background: var(--action-bg);
1787
- color: var(--action-ink);
1788
- padding: var(--sp-2) var(--sp-3);
1798
+ border: var(--border-w) solid var(--chip-border, var(--outline));
1799
+ background: var(--chip-bg, var(--action-bg));
1800
+ color: var(--chip-ink, var(--action-ink));
1801
+ padding: var(--chip-pad-y, var(--sp-2)) var(--chip-pad-x, var(--sp-3));
1789
1802
  font: var(--type-label);
1790
- font-size: var(--fs-sm);
1803
+ font-size: var(--chip-font-size, var(--fs-sm));
1791
1804
  min-height: var(--chip-h);
1792
1805
  text-decoration: none;
1793
1806
  justify-self: start; /* as a grid child it hugs its text, never stretches */
1794
1807
  }
1795
1808
  .dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); } /* signal dot: chips, legends */
1796
1809
  .dot--b { background: var(--dot-b); }
1797
- .chip--on { background: var(--accent); color: var(--on-accent); border-color: transparent; }
1810
+ .chip--on { background: var(--chip-on-bg, var(--accent)); color: var(--chip-on-ink, var(--on-accent)); border-color: transparent; }
1798
1811
  /* dark-slot inversion lives in card.css (.card--3 :is(...)) */
1799
1812
 
1800
1813
  .chip-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
@@ -1930,6 +1943,9 @@ button { font: inherit; cursor: pointer; }
1930
1943
 
1931
1944
  /* the first round button sits at the left edge, the last at the right; a skip link between them stays centred */
1932
1945
  :is(.action-bar--pager, .action-bar__tier--pager) > .icon-btn:first-child { margin-right: auto; }
1946
+ /* …and whatever goes BETWEEN the two (a "skip" link) stands in the middle: the bar places it, never the link itself */
1947
+ :is(.action-bar--pager, .action-bar__tier--pager) > :not(.icon-btn):not(:first-child):not(:last-child) { margin-inline: auto; }
1948
+ :is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > .icon-btn:first-child { margin-right: 0; }
1933
1949
 
1934
1950
  /* .screen's default bottom padding is a constant; derive the reserve from the
1935
1951
  bar instead, so a taller button (a longer label, a script with more leading)
@@ -1973,8 +1989,8 @@ button { font: inherit; cursor: pointer; }
1973
1989
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
1974
1990
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
1975
1991
  }
1976
- .ring__value { font: var(--type-stat); letter-spacing: var(--track-display); }
1977
- .ring__label { font: var(--type-caption); color: var(--card-muted); }
1992
+ .ring__value { font: var(--ring-value-font, var(--type-stat)); letter-spacing: var(--track-display); }
1993
+ .ring__label { font: var(--ring-label-font, var(--type-caption)); color: var(--ring-label-ink, var(--card-muted)); }
1978
1994
  .card--3 .ring__label { color: inherit; opacity: 0.7; }
1979
1995
  .ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
1980
1996
  .ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
@@ -1994,10 +2010,10 @@ button { font: inherit; cursor: pointer; }
1994
2010
  --on-accent: var(--_bg);
1995
2011
  list-style: none;
1996
2012
  margin: 0;
1997
- padding: var(--sp-2);
2013
+ padding: var(--track-pad, var(--sp-2));
1998
2014
  display: flex;
1999
2015
  align-items: center;
2000
- border-radius: var(--r-chip);
2016
+ border-radius: var(--track-radius, var(--r-chip));
2001
2017
  background: var(--_bg);
2002
2018
  color: var(--_ink);
2003
2019
  }
@@ -2038,9 +2054,9 @@ button { font: inherit; cursor: pointer; }
2038
2054
  grid-template-areas: "label num" "foot num";
2039
2055
  align-items: end;
2040
2056
  gap: var(--sp-2) var(--sp-4);
2041
- min-height: var(--tile-h);
2057
+ min-height: var(--card-band-min-h, var(--tile-h));
2042
2058
  }
2043
- .band__label { grid-area: label; align-self: start; font: var(--type-label); font-size: var(--fs-body); line-height: 1.2; }
2059
+ .band__label { grid-area: label; align-self: start; font: var(--band-label-font, var(--type-label)); font-size: var(--band-label-font-size, var(--fs-body)); line-height: 1.2; }
2044
2060
  .band__num {
2045
2061
  grid-area: num;
2046
2062
  font: 800 var(--fs-giant) / 0.9 var(--font-display);
@@ -2048,14 +2064,14 @@ button { font: inherit; cursor: pointer; }
2048
2064
  font-variant-numeric: tabular-nums;
2049
2065
  }
2050
2066
  .band__num small { font-size: 0.5em; font-weight: 700; margin-right: 0.1em; } /* unit or % */
2051
- .band__foot { grid-area: foot; font: var(--type-caption); color: var(--card-muted); }
2067
+ .band__foot { grid-area: foot; font: var(--band-foot-font, var(--type-caption)); color: var(--band-foot-ink, var(--card-muted)); }
2052
2068
  .card--3 .band__foot { color: inherit; opacity: 0.7; }
2053
2069
  /* band stack: ONE card boundary; bands are frameless inside it, a hairline
2054
2070
  between each. Bands may still carry slot tints (card--2 / card--3). */
2055
2071
  .band-stack {
2056
2072
  display: grid;
2057
- border: var(--border-w) solid var(--card-border);
2058
- border-radius: var(--r-card);
2073
+ border: var(--border-w) solid var(--band-stack-border, var(--card-border));
2074
+ border-radius: var(--band-stack-radius, var(--r-card));
2059
2075
  overflow: hidden;
2060
2076
  }
2061
2077
  .band-stack > .card--band { border: 0; border-radius: 0; box-shadow: none; }
@@ -2106,7 +2122,7 @@ button { font: inherit; cursor: pointer; }
2106
2122
 
2107
2123
  /* ---- picker: horizontal snap picker, centre value big ----
2108
2124
  <div class="picker"><div class="picker__rail"><button class="picker__item">100</button>…</div><span class="picker__unit">ml</span></div> */
2109
- .picker { display: grid; gap: var(--sp-1); justify-items: center; position: relative; }
2125
+ .picker { display: grid; gap: var(--picker-gap, var(--sp-1)); justify-items: center; position: relative; }
2110
2126
  .picker__rail {
2111
2127
  width: 100%;
2112
2128
  display: grid;
@@ -2114,7 +2130,7 @@ button { font: inherit; cursor: pointer; }
2114
2130
  /* side padding = one third of the rail, so the first and last value can sit
2115
2131
  in the centre; the content box is then one third wide, and each column
2116
2132
  fills it (percent columns resolve against the content box) */
2117
- padding-inline: calc(100% / 3);
2133
+ padding-inline: var(--picker-rail-pad-x, calc(100% / 3));
2118
2134
  grid-auto-columns: 100%;
2119
2135
  overflow-x: auto;
2120
2136
  scroll-snap-type: x mandatory;
@@ -2127,8 +2143,8 @@ button { font: inherit; cursor: pointer; }
2127
2143
  border: 0;
2128
2144
  background: transparent;
2129
2145
  color: inherit;
2130
- padding: var(--sp-2) 0;
2131
- font: var(--type-stat);
2146
+ padding: var(--picker-item-pad-y, var(--sp-2)) var(--picker-item-pad-x, 0);
2147
+ font: var(--picker-item-font, var(--type-stat));
2132
2148
  letter-spacing: var(--track-display);
2133
2149
  font-variant-numeric: tabular-nums;
2134
2150
  opacity: 0.3;
@@ -2147,7 +2163,7 @@ button { font: inherit; cursor: pointer; }
2147
2163
  left: calc(100% / 3);
2148
2164
  }
2149
2165
  .picker::after { left: auto; right: calc(100% / 3); }
2150
- .picker__unit { font: var(--type-caption); color: var(--card-muted); }
2166
+ .picker__unit { font: var(--picker-unit-font, var(--type-caption)); color: var(--picker-unit-ink, var(--card-muted)); }
2151
2167
  .card--3 .picker__unit { color: inherit; opacity: 0.7; }
2152
2168
  /* the scrolled-to item lights up by position — no JS */
2153
2169
  @supports (animation-timeline: view(x)) {
@@ -2163,30 +2179,30 @@ button { font: inherit; cursor: pointer; }
2163
2179
  .badge {
2164
2180
  display: inline-grid;
2165
2181
  place-items: center;
2166
- min-width: var(--badge);
2167
- height: var(--badge);
2168
- padding: 0 var(--sp-2);
2169
- border-radius: var(--r-chip);
2170
- background: var(--accent);
2171
- color: var(--on-accent);
2172
- font: var(--type-label);
2182
+ min-width: var(--badge-min-w, var(--badge));
2183
+ height: var(--badge-h, var(--badge));
2184
+ padding: var(--badge-pad-y, 0) var(--badge-pad-x, var(--sp-2));
2185
+ border-radius: var(--badge-radius, var(--r-chip));
2186
+ background: var(--badge-bg, var(--accent));
2187
+ color: var(--badge-ink, var(--on-accent));
2188
+ font: var(--badge-font, var(--type-label));
2173
2189
  font-variant-numeric: tabular-nums;
2174
2190
  vertical-align: middle;
2175
2191
  }
2176
- .badge--soft { background: var(--ink-tint); color: inherit; }
2192
+ .badge--soft { background: var(--badge-soft-bg, var(--ink-tint)); color: inherit; }
2177
2193
 
2178
2194
  /* ---- bars: three or four bars with a count on top — the glance chart ----
2179
2195
  <div class="bars"><div class="bar" style="--v: 16"><span>+16</span></div>… --max on the parent */
2180
- .bars { --max: 20; --_h: 5rem; display: flex; align-items: end; gap: var(--sp-2); height: calc(var(--_h) + var(--sp-8)); }
2196
+ .bars { --max: 20; --_h: 5rem; display: flex; align-items: end; gap: var(--bars-gap, var(--sp-2)); height: calc(var(--_h) + var(--sp-8)); }
2181
2197
  .bar {
2182
2198
  flex: 1;
2183
2199
  min-width: 0;
2184
2200
  display: grid;
2185
2201
  align-content: end;
2186
2202
  justify-items: center;
2187
- gap: var(--sp-1);
2203
+ gap: var(--bar-gap, var(--sp-1));
2188
2204
  height: 100%;
2189
- font: var(--type-caption);
2205
+ font: var(--bar-font, var(--type-caption));
2190
2206
  font-weight: 700;
2191
2207
  }
2192
2208
  .bar::after {
@@ -2199,6 +2215,22 @@ button { font: inherit; cursor: pointer; }
2199
2215
  .bar--on::after { background: var(--accent); }
2200
2216
  .bar--on > span { background: var(--accent); color: var(--on-accent); padding: var(--sp-0) var(--sp-2); border-radius: var(--r-chip); }
2201
2217
 
2218
+ /* ---- progress: one straight line, the done share solid, the rest faint (Lh 2026-09-22) — a voice note's
2219
+ timeline, a download, a step count as a line. <div class="progress" role="progressbar" style="--progress-at: 0.4"></div>
2220
+ --progress-at is DATA the component writes (0–1), not a knob (scripts/knobs.mjs lists it). The done part is a
2221
+ gradient of the line's ink sized by the share; the rest is the background colour. */
2222
+ .progress {
2223
+ display: block;
2224
+ width: 100%;
2225
+ height: var(--progress-h, var(--sp-1));
2226
+ border-radius: var(--progress-radius, var(--r-chip));
2227
+ color: var(--progress-done-ink, inherit);
2228
+ background-color: var(--progress-rest-bg, color-mix(in srgb, currentColor 25%, transparent));
2229
+ background-image: linear-gradient(currentColor, currentColor);
2230
+ background-size: calc(var(--progress-at, 0) * 100%) 100%;
2231
+ background-repeat: no-repeat;
2232
+ }
2233
+
2202
2234
  /* ---- css/people.css — avatar, avatar stack, chooser row ---- */
2203
2235
  /* ============================================================
2204
2236
  cardds/people.css — avatars: single, stacked, chooser row.
@@ -2217,9 +2249,9 @@ button { font: inherit; cursor: pointer; }
2217
2249
  border-radius: 50%;
2218
2250
  display: grid;
2219
2251
  place-items: center;
2220
- background: var(--card-2-bg);
2221
- color: var(--card-2-ink);
2222
- font: var(--type-label);
2252
+ background: var(--avatar-bg, var(--card-2-bg));
2253
+ color: var(--avatar-ink, var(--card-2-ink));
2254
+ font: var(--avatar-font, var(--type-label));
2223
2255
  font-size: calc(var(--_size) * 0.3);
2224
2256
  text-transform: uppercase;
2225
2257
  letter-spacing: 0.02em;
@@ -2232,10 +2264,10 @@ button { font: inherit; cursor: pointer; }
2232
2264
  .avatar--lg { --_size: var(--avatar-lg); }
2233
2265
  .avatar--xl { --_size: var(--avatar-xl); } /* sheet head icon ring */
2234
2266
  /* outline avatar: no fill, an edge — icons in a ring, "add" slots */
2235
- .avatar--outline { background: transparent; color: inherit; border: var(--border-w) solid var(--outline); }
2236
- .avatar--add { background: transparent; color: inherit; border: var(--border-w) dashed var(--outline); cursor: pointer; }
2267
+ .avatar--outline { background: transparent; color: inherit; border: var(--border-w) solid var(--avatar-outline-border, var(--outline)); }
2268
+ .avatar--add { background: transparent; color: inherit; border: var(--border-w) dashed var(--avatar-add-border, var(--outline)); cursor: pointer; }
2237
2269
  /* accent avatar: the "+7" tail, the selected person */
2238
- .avatar--on { background: var(--accent); color: var(--on-accent); }
2270
+ .avatar--on { background: var(--avatar-on-bg, var(--accent)); color: var(--avatar-on-ink, var(--on-accent)); }
2239
2271
  /* halo: faint concentric rings behind a hero avatar (decorative) */
2240
2272
  .avatar--halo::before {
2241
2273
  content: "";
@@ -2257,7 +2289,7 @@ button { font: inherit; cursor: pointer; }
2257
2289
  <div class="avatar-pick"><button class="avatar-pick__item"><span class="avatar avatar--lg avatar--add">+</span><span class="avatar-pick__name">Add</span><span class="avatar-pick__role">member</span></button>…</div> */
2258
2290
  .avatar-pick {
2259
2291
  display: flex;
2260
- gap: var(--sp-4);
2292
+ gap: var(--avatar-pick-gap, var(--sp-4));
2261
2293
  overflow-x: auto;
2262
2294
  scroll-snap-type: x proximity;
2263
2295
  scrollbar-width: none; /* starts at the card's padding edge, like every row */
@@ -2266,18 +2298,18 @@ button { font: inherit; cursor: pointer; }
2266
2298
  .avatar-pick__item {
2267
2299
  scroll-snap-align: start;
2268
2300
  flex: none;
2269
- width: var(--avatar-lg);
2301
+ width: var(--avatar-pick-item-w, var(--avatar-lg));
2270
2302
  display: grid;
2271
2303
  justify-items: center;
2272
- gap: var(--sp-2);
2304
+ gap: var(--avatar-pick-item-gap, var(--sp-2));
2273
2305
  border: 0;
2274
2306
  background: transparent;
2275
2307
  color: inherit;
2276
2308
  padding: 0;
2277
2309
  text-align: center;
2278
2310
  }
2279
- .avatar-pick__name { font: var(--type-label); font-size: var(--fs-sm); }
2280
- .avatar-pick__role { font: var(--type-caption); color: var(--card-muted); margin-top: calc(-1 * var(--sp-1)); }
2311
+ .avatar-pick__name { font: var(--avatar-pick-name-font, var(--type-label)); font-size: var(--avatar-pick-name-font-size, var(--fs-sm)); }
2312
+ .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)); }
2281
2313
  .card--3 .avatar-pick__role { color: inherit; opacity: 0.7; }
2282
2314
 
2283
2315
  /* ---- css/choice.css — check, toggle, toggle chip, chip grid, days, calendar, mood, pin, composer ---- */
@@ -2295,13 +2327,13 @@ button { font: inherit; cursor: pointer; }
2295
2327
  <input type="checkbox" class="check" aria-label="Done"> */
2296
2328
  .check {
2297
2329
  appearance: none;
2298
- width: var(--tap);
2299
- height: var(--tap);
2330
+ width: var(--check-size, var(--tap));
2331
+ height: var(--check-size, var(--tap));
2300
2332
  flex: none;
2301
2333
  margin: 0;
2302
2334
  border-radius: 50%;
2303
- border: var(--border-w) solid var(--outline);
2304
- background: var(--action-bg);
2335
+ border: var(--border-w) solid var(--check-border, var(--outline));
2336
+ background: var(--check-bg, var(--action-bg));
2305
2337
  display: grid;
2306
2338
  place-items: center;
2307
2339
  cursor: pointer;
@@ -2318,7 +2350,7 @@ button { font: inherit; cursor: pointer; }
2318
2350
  .check:checked { background: var(--accent); border-color: transparent; }
2319
2351
  .check:checked::after { opacity: 1; }
2320
2352
  .check:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2321
- .check--sm { width: var(--tap-sm); height: var(--tap-sm); }
2353
+ .check--sm { width: var(--check-sm-size, var(--tap-sm)); height: var(--check-sm-size, var(--tap-sm)); }
2322
2354
 
2323
2355
  /* ---- toggle: big switch ----
2324
2356
  <input type="checkbox" role="switch" class="toggle" aria-label="Private"> */
@@ -2331,9 +2363,9 @@ button { font: inherit; cursor: pointer; }
2331
2363
  height: var(--_h);
2332
2364
  flex: none;
2333
2365
  margin: 0;
2334
- border-radius: var(--r-chip);
2335
- border: var(--border-w) solid var(--outline);
2336
- background: var(--action-bg);
2366
+ border-radius: var(--toggle-radius, var(--r-chip));
2367
+ border: var(--border-w) solid var(--toggle-border, var(--outline));
2368
+ background: var(--toggle-bg, var(--action-bg));
2337
2369
  position: relative;
2338
2370
  cursor: pointer;
2339
2371
  transition: background 0.2s ease;
@@ -2362,7 +2394,7 @@ button { font: inherit; cursor: pointer; }
2362
2394
  .chip-grid {
2363
2395
  display: grid;
2364
2396
  grid-template-columns: repeat(auto-fill, minmax(min(5.5rem, 100%), 1fr));
2365
- gap: var(--sp-2);
2397
+ gap: var(--chip-grid-gap, var(--sp-2));
2366
2398
  }
2367
2399
  /* .chip hugs its text as a loose grid child (justify-self: start); inside a
2368
2400
  chip grid the whole point is equal, full-width pick targets, so stretch.
@@ -2389,7 +2421,7 @@ button { font: inherit; cursor: pointer; }
2389
2421
 
2390
2422
  /* ---- day strip: a week, one day selected ----
2391
2423
  <div class="day-strip" role="tablist"><button class="day" role="tab" aria-selected="true"><span class="day__name">Thu</span><span class="day__num">16</span></button>…</div> */
2392
- .day-strip { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--sp-1); }
2424
+ .day-strip { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--day-strip-gap, var(--sp-1)); }
2393
2425
  .day {
2394
2426
  display: grid;
2395
2427
  /* three rows, always: name · number · dot. The dot's row is reserved even on
@@ -2399,33 +2431,33 @@ button { font: inherit; cursor: pointer; }
2399
2431
  grid-template-rows: auto auto var(--dot-xs);
2400
2432
  align-content: center; /* the group sits centred in the cell, not spread out */
2401
2433
  justify-items: center;
2402
- gap: var(--sp-0);
2434
+ gap: var(--day-gap, var(--sp-0));
2403
2435
  min-width: 0;
2404
- min-height: var(--bar-h);
2405
- padding-block: var(--sp-3); /* equal air top and bottom, so the selected pill
2436
+ min-height: var(--day-min-h, var(--bar-h));
2437
+ padding-block: var(--day-pad-y, var(--sp-3)); /* equal air top and bottom, so the selected pill
2406
2438
  is the same shape on every day */
2407
2439
  padding-inline: 0;
2408
2440
  border: 0;
2409
- border-radius: var(--r-chip);
2441
+ border-radius: var(--day-radius, var(--r-chip));
2410
2442
  background: transparent;
2411
2443
  color: inherit;
2412
2444
  opacity: 0.6;
2413
2445
  }
2414
- .day__name { font: var(--type-caption); }
2446
+ .day__name { font: var(--day-name-font, var(--type-caption)); }
2415
2447
  .day__num { font: 700 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
2416
- .day[aria-selected="true"] { background: var(--accent); color: var(--on-accent); opacity: 1; }
2417
- .day__dot { grid-row: 3; align-self: center; width: var(--dot-xs); height: var(--dot-xs); border-radius: 50%; background: currentColor; }
2448
+ .day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent)); opacity: 1; }
2449
+ .day__dot { grid-row: 3; align-self: center; width: var(--day-dot-size, var(--dot-xs)); height: var(--day-dot-size, var(--dot-xs)); border-radius: 50%; background: currentColor; }
2418
2450
 
2419
2451
  /* ---- calendar: month grid, 44px cells, one accent day, optional marks ----
2420
2452
  <div class="calendar"><div class="calendar__head">…</div><div class="calendar__grid"><span class="calendar__wd">Mo</span>×7 <button class="calendar__day" style="--start: 3">1</button>…</div></div> */
2421
- .calendar { display: grid; gap: var(--sp-3); }
2422
- .calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
2423
- .calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--sp-1); justify-items: center; }
2424
- .calendar__wd { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; color: var(--card-muted); padding-block: var(--sp-2); }
2453
+ .calendar { display: grid; gap: var(--calendar-gap, var(--sp-3)); }
2454
+ .calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--calendar-head-gap, var(--sp-2)); }
2455
+ .calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--calendar-grid-gap, var(--sp-1)); justify-items: center; }
2456
+ .calendar__wd { font: var(--calendar-wd-font, var(--type-overline)); letter-spacing: var(--track-overline); text-transform: uppercase; color: var(--calendar-wd-ink, var(--card-muted)); padding-block: var(--calendar-wd-pad-y, var(--sp-2)); }
2425
2457
  .card--3 .calendar__wd { color: inherit; opacity: 0.7; }
2426
2458
  .calendar__day {
2427
2459
  width: 100%;
2428
- max-width: var(--tap);
2460
+ max-width: var(--calendar-day-max-w, var(--tap));
2429
2461
  aspect-ratio: 1;
2430
2462
  display: grid;
2431
2463
  place-items: center;
@@ -2437,9 +2469,9 @@ button { font: inherit; cursor: pointer; }
2437
2469
  font-variant-numeric: tabular-nums;
2438
2470
  grid-column-start: var(--start, auto); /* --start on the 1st: weekday column 1–7 */
2439
2471
  }
2440
- .calendar__day--mark { background: var(--accent-soft); }
2441
- .calendar__day--mark2 { background: var(--accent-mid); color: var(--on-accent); }
2442
- .calendar__day[aria-selected="true"] { background: var(--accent); color: var(--on-accent); font-weight: 700; }
2472
+ .calendar__day--mark { background: var(--calendar-day-mark-bg, var(--accent-soft)); }
2473
+ .calendar__day--mark2 { background: var(--calendar-day-mark2-bg, var(--accent-mid)); color: var(--calendar-day-mark2-ink, var(--on-accent)); }
2474
+ .calendar__day[aria-selected="true"] { background: var(--calendar-day-selected-bg, var(--accent)); color: var(--calendar-day-selected-ink, var(--on-accent)); font-weight: 700; }
2443
2475
  .calendar__day:disabled { opacity: 0.3; }
2444
2476
 
2445
2477
  /* ---- mood: five dots in a pill, the picked one grows ----
@@ -2447,19 +2479,19 @@ button { font: inherit; cursor: pointer; }
2447
2479
  .mood {
2448
2480
  margin: 0;
2449
2481
  min-inline-size: 0; /* fieldset UA default is min-content — it must shrink with the card */
2450
- padding: var(--sp-1);
2451
- border: var(--border-w) solid var(--outline);
2452
- border-radius: var(--r-chip);
2453
- background: var(--action-bg);
2454
- color: var(--action-ink);
2482
+ padding: var(--mood-pad, var(--sp-1));
2483
+ border: var(--border-w) solid var(--mood-border, var(--outline));
2484
+ border-radius: var(--mood-radius, var(--r-chip));
2485
+ background: var(--mood-bg, var(--action-bg));
2486
+ color: var(--mood-ink, var(--action-ink));
2455
2487
  display: flex;
2456
2488
  justify-content: space-between;
2457
2489
  }
2458
- .mood__opt { flex: 1 1 0; max-width: var(--tap); min-width: 0; height: var(--tap); display: grid; place-items: center; position: relative; cursor: pointer; }
2490
+ .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; }
2459
2491
  .mood__opt > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
2460
2492
  .mood__dot {
2461
- width: var(--icon-xs);
2462
- height: var(--icon-xs);
2493
+ width: var(--mood-dot-size, var(--icon-xs));
2494
+ height: var(--mood-dot-size, var(--icon-xs));
2463
2495
  border-radius: 50%;
2464
2496
  background: var(--_mood, currentColor);
2465
2497
  transition: scale 0.2s ease, box-shadow 0.2s ease;
@@ -2474,18 +2506,18 @@ button { font: inherit; cursor: pointer; }
2474
2506
 
2475
2507
  /* ---- pin: big code entry, one digit per pill ----
2476
2508
  <div class="pin"><input class="pin__cell" inputmode="numeric" maxlength="1" placeholder=" ">×4</div> */
2477
- .pin { display: flex; gap: var(--sp-3); }
2509
+ .pin { display: flex; gap: var(--pin-gap, var(--sp-3)); }
2478
2510
  .pin__cell {
2479
2511
  flex: 1;
2480
2512
  min-width: 0;
2481
- max-width: var(--fab);
2513
+ max-width: var(--pin-cell-max-w, var(--fab));
2482
2514
  aspect-ratio: 2 / 3;
2483
- border: var(--border-w) solid var(--outline);
2484
- border-radius: var(--r-chip);
2485
- background: var(--action-bg);
2486
- color: var(--action-ink);
2515
+ border: var(--border-w) solid var(--pin-cell-border, var(--outline));
2516
+ border-radius: var(--pin-cell-radius, var(--r-chip));
2517
+ background: var(--pin-cell-bg, var(--action-bg));
2518
+ color: var(--pin-cell-ink, var(--action-ink));
2487
2519
  text-align: center;
2488
- font: var(--type-stat);
2520
+ font: var(--pin-cell-font, var(--type-stat));
2489
2521
  font-variant-numeric: tabular-nums;
2490
2522
  caret-color: currentColor;
2491
2523
  }
@@ -2495,7 +2527,7 @@ button { font: inherit; cursor: pointer; }
2495
2527
  /* ---- composer: big text area with a bottom bar (icon · hint · send) ----
2496
2528
  <div class="composer"><textarea class="composer__input" placeholder="What do you need?"></textarea>
2497
2529
  <div class="composer__bar"><button class="icon-btn">…</button><span class="composer__hint">Your idea is born</span><button class="icon-btn icon-btn--invert">↑</button></div></div> */
2498
- .composer { display: grid; gap: var(--sp-3); }
2530
+ .composer { display: grid; gap: var(--composer-gap, var(--sp-3)); }
2499
2531
  .composer__input {
2500
2532
  width: 100%;
2501
2533
  min-height: 11rem;
@@ -2509,8 +2541,8 @@ button { font: inherit; cursor: pointer; }
2509
2541
  outline: none;
2510
2542
  }
2511
2543
  .composer__input::placeholder { color: inherit; opacity: 0.4; }
2512
- .composer__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
2513
- .composer__hint { font: var(--type-caption); color: var(--card-muted); }
2544
+ .composer__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--composer-bar-gap, var(--sp-3)); }
2545
+ .composer__hint { font: var(--composer-hint-font, var(--type-caption)); color: var(--composer-hint-ink, var(--card-muted)); }
2514
2546
  .card--3 .composer__hint { color: inherit; opacity: 0.7; }
2515
2547
 
2516
2548
  /* ---- slider: one value along a track, a thumb-size knob ----
@@ -2519,7 +2551,7 @@ button { font: inherit; cursor: pointer; }
2519
2551
  slider does, the input's aria-label is enough. The track is a hairline rail;
2520
2552
  the knob is a full --tap, and the whole range is --tap tall, so the thumb has
2521
2553
  a real target anywhere along it. The value is the input's own. */
2522
- .slider { display: grid; gap: var(--sp-1); flex: 1; min-width: 0; position: relative; } /* relative: the glyph rides the knob */
2554
+ .slider { display: grid; gap: var(--slider-gap, var(--sp-1)); flex: 1; min-width: 0; position: relative; } /* relative: the glyph rides the knob */
2523
2555
  /* ---- the sample glyph: one letter IN the knob, on-accent, at the type size
2524
2556
  the slider sets — so a text-size slider shows the size before anything is
2525
2557
  read (Lh, 2026-09-12). cardds.js publishes the knob's place on the .slider
@@ -2530,8 +2562,8 @@ button { font: inherit; cursor: pointer; }
2530
2562
  position: absolute;
2531
2563
  bottom: 0;
2532
2564
  left: calc(var(--tap) / 2 + (100% - var(--tap)) * var(--slider-at, 0));
2533
- width: var(--tap);
2534
- height: var(--tap);
2565
+ width: var(--slider-glyph-size, var(--tap));
2566
+ height: var(--slider-glyph-size, var(--tap));
2535
2567
  /* the eye centres the INK, not the line box: a letter that stands on the
2536
2568
  baseline (ก) has no descender, while the face's box keeps 0.30em under it
2537
2569
  (fonts.css) — so the ink lands low when the box is centred; lifted 0.15em
@@ -2539,15 +2571,15 @@ button { font: inherit; cursor: pointer; }
2539
2571
  translate: -50% -0.15em;
2540
2572
  display: grid;
2541
2573
  place-items: center;
2542
- font: var(--type-h2);
2543
- color: var(--on-accent);
2574
+ font: var(--slider-glyph-font, var(--type-h2));
2575
+ color: var(--slider-glyph-ink, var(--on-accent));
2544
2576
  pointer-events: none;
2545
2577
  }
2546
- .slider__label { font: var(--type-label); }
2578
+ .slider__label { font: var(--slider-label-font, var(--type-label)); }
2547
2579
  .slider__range {
2548
2580
  appearance: none;
2549
2581
  width: 100%;
2550
- height: var(--tap);
2582
+ height: var(--slider-range-h, var(--tap));
2551
2583
  margin: 0;
2552
2584
  background: transparent;
2553
2585
  cursor: pointer;
@@ -2588,8 +2620,8 @@ button { font: inherit; cursor: pointer; }
2588
2620
  .row {
2589
2621
  display: flex;
2590
2622
  align-items: center;
2591
- gap: var(--sp-3);
2592
- min-height: var(--tap); /* the tap target's room — the row's only vertical size */
2623
+ gap: var(--row-gap, var(--sp-3));
2624
+ min-height: var(--row-min-h, var(--tap)); /* the tap target's room — the row's only vertical size */
2593
2625
  min-width: 0;
2594
2626
  color: inherit;
2595
2627
  text-decoration: none; /* .row works as <a> too — it is a tap target */
@@ -2598,21 +2630,30 @@ button { font: inherit; cursor: pointer; }
2598
2630
  Space BETWEEN rows is .rows' gap; a lone row as card content takes the card's
2599
2631
  gap. (.row--flush, which zeroed the old padding, is gone — every row is flush.) */
2600
2632
  .row__lead {
2601
- width: var(--tap);
2602
- height: var(--tap);
2633
+ width: var(--row-lead-size, var(--tap));
2634
+ height: var(--row-lead-size, var(--tap));
2603
2635
  flex: none;
2604
- border-radius: var(--r-control);
2605
- border: var(--border-w) solid var(--outline);
2636
+ border-radius: var(--row-lead-radius, var(--r-control));
2637
+ border: var(--border-w) solid var(--row-lead-border, var(--outline));
2606
2638
  display: grid;
2607
2639
  place-items: center;
2608
2640
  }
2609
- .row__body { flex: 1; min-width: 0; display: grid; gap: var(--sp-0); }
2610
- .row__title { font: var(--type-title); overflow-wrap: anywhere; } /* wraps freely — no clip, so no padding trick for Thai marks (Lh 2026-09-16, same call as the card heading) */
2611
- .row__meta { font: var(--type-caption); color: var(--card-muted); display: flex; align-items: center; gap: var(--sp-1); }
2641
+ .row__body { flex: 1; min-width: 0; display: grid; gap: var(--row-body-gap, var(--sp-0)); }
2642
+ .row__title { font: var(--row-title-font, var(--type-title)); overflow-wrap: anywhere; } /* wraps freely — no clip, so no padding trick for Thai marks (Lh 2026-09-16, same call as the card heading) */
2643
+ .row__meta { font: var(--row-meta-font, var(--type-caption)); color: var(--row-meta-ink, var(--card-muted)); display: flex; align-items: center; gap: var(--row-meta-gap, var(--sp-1)); }
2612
2644
  .card--3 .row__meta { color: inherit; opacity: 0.7; }
2613
- .row__end { margin-left: auto; text-align: right; display: grid; gap: var(--sp-0); justify-items: end; flex: none; }
2614
- .row--lg { min-height: var(--fab); } /* menu rows: a taller target, round arrow at the end; .rows gives them more air */
2645
+ .row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
2646
+ .row--lg { min-height: var(--row-lg-min-h, var(--fab)); } /* menu rows: a taller target, round arrow at the end; .rows gives them more air */
2615
2647
  .row--lg .row__title { font: 700 var(--fs-lg)/1.2 var(--font-display); }
2648
+ /* a row that EXPANDS (Lh 2026-09-22): the row is a <details>' summary, what it shows comes out under it —
2649
+ who liked a postcard, the rest of a list. An end chevron turns when open. The app owns the list.
2650
+ <details class="row-expand"><summary class="row">…lead · body · end…</summary><div class="row-expand__more">…</div></details> */
2651
+ .row-expand { display: block; }
2652
+ .row-expand > summary { list-style: none; cursor: pointer; }
2653
+ .row-expand > summary::-webkit-details-marker { display: none; }
2654
+ .row-expand > summary > .icon:last-child { transition: rotate var(--motion-slide); }
2655
+ .row-expand[open] > summary > .icon:last-child { rotate: 180deg; }
2656
+ .row-expand__more { padding-top: var(--row-expand-gap, var(--sp-3)); }
2616
2657
  /* a finished item in a checklist (a to-do list, an onboarding "complete your introduction" list):
2617
2658
  the words go muted, a filled tick sits at the end, and the row is no longer a tap target —
2618
2659
  pointer-events off here, aria-disabled from the markup. The tick is the .check's checked look
@@ -2621,13 +2662,13 @@ button { font: inherit; cursor: pointer; }
2621
2662
  .row--done .row__title { color: var(--card-muted); }
2622
2663
  .card--3 .row--done .row__title { color: inherit; opacity: 0.7; }
2623
2664
  .row__done {
2624
- width: var(--tap-sm);
2625
- height: var(--tap-sm);
2665
+ width: var(--row-done-size, var(--tap-sm));
2666
+ height: var(--row-done-size, var(--tap-sm));
2626
2667
  flex: none;
2627
2668
  margin-left: auto;
2628
2669
  border-radius: 50%;
2629
- background: var(--accent);
2630
- color: var(--on-accent);
2670
+ background: var(--row-done-bg, var(--accent));
2671
+ color: var(--row-done-ink, var(--on-accent));
2631
2672
  display: grid;
2632
2673
  place-items: center;
2633
2674
  }
@@ -2648,20 +2689,20 @@ button { font: inherit; cursor: pointer; }
2648
2689
  /* ---- kv: label left, value right ----
2649
2690
  <dl class="kv"><div class="kv__row"><dt class="kv__key">Pay with</dt><dd class="kv__val">GHO</dd></div>…</dl> */
2650
2691
  .kv { margin: 0; display: grid; }
2651
- .kv__row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); min-height: var(--tap); }
2692
+ .kv__row { display: flex; align-items: center; justify-content: space-between; gap: var(--kv-row-gap, var(--sp-3)); min-height: var(--kv-row-min-h, var(--tap)); }
2652
2693
  .kv--lined > .kv__row + .kv__row { border-top: var(--border-w) solid var(--hairline); }
2653
- .kv__key { font: var(--type-body); color: var(--card-muted); }
2694
+ .kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-muted)); }
2654
2695
  .card--3 .kv__key { color: inherit; opacity: 0.7; }
2655
- .kv__val { margin: 0; font: var(--type-title); display: inline-flex; align-items: center; gap: var(--sp-2); text-align: right; min-width: 0; }
2696
+ .kv__val { margin: 0; font: var(--kv-val-font, var(--type-title)); display: inline-flex; align-items: center; gap: var(--kv-val-gap, var(--sp-2)); text-align: right; min-width: 0; }
2656
2697
 
2657
2698
  /* ---- kv grid: value over label, cells split by a rule (Sex | Weight) ----
2658
2699
  <div class="kv-grid"><div class="kv-cell"><svg class="icon"/><span class="kv-cell__val">15.6 kg</span><span class="kv-cell__key">Weight</span></div>…</div> */
2659
- .kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(var(--tile-min), 100%), 1fr)); gap: var(--sp-4); }
2700
+ .kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(var(--tile-min), 100%), 1fr)); gap: var(--kv-grid-gap, var(--sp-4)); }
2660
2701
  .kv-grid > .kv-cell + .kv-cell { border-left: var(--border-w) solid var(--hairline); padding-left: var(--sp-4); }
2661
- .kv-cell { display: grid; gap: var(--sp-0); align-content: start; min-width: 0; }
2702
+ .kv-cell { display: grid; gap: var(--kv-cell-gap, var(--sp-0)); align-content: start; min-width: 0; }
2662
2703
  .kv-cell > .icon { margin-bottom: var(--sp-1); }
2663
- .kv-cell__val { font: var(--type-h2); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
2664
- .kv-cell__key { font: var(--type-caption); color: var(--card-muted); }
2704
+ .kv-cell__val { font: var(--kv-cell-val-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
2705
+ .kv-cell__key { font: var(--kv-cell-key-font, var(--type-caption)); color: var(--kv-cell-key-ink, var(--card-muted)); }
2665
2706
  .card--3 .kv-cell__key { color: inherit; opacity: 0.7; }
2666
2707
 
2667
2708
  /* ---- bubble: a chat message — theirs on the left, mine on the right (Lh 2026-09-18, replaces the chip's tail) ----
@@ -2673,7 +2714,7 @@ button { font: inherit; cursor: pointer; }
2673
2714
  <div class="bubble"><p class="bubble__body">…</p><span class="bubble__actions">…icon-btns…</span><span class="bubble__meta">Dec 4 · 8:15</span></div>
2674
2715
  <div class="bubble bubble--right">…</div>
2675
2716
  </div> */
2676
- .thread { display: grid; gap: var(--sp-4); }
2717
+ .thread { display: grid; gap: var(--thread-gap, var(--sp-4)); }
2677
2718
  /* two columns: the message (max-content, but it gives way — minmax(0) — so a long one shrinks
2678
2719
  and wraps while the actions stay beside it) and the actions; the meta on a second row under the message */
2679
2720
  .bubble {
@@ -2693,12 +2734,12 @@ button { font: inherit; cursor: pointer; }
2693
2734
  .bubble__body {
2694
2735
  margin: 0;
2695
2736
  min-width: 0;
2696
- padding: var(--sp-3) var(--sp-4);
2697
- border-radius: var(--r-card);
2737
+ padding: var(--bubble-body-pad-y, var(--sp-3)) var(--bubble-body-pad-x, var(--sp-4));
2738
+ border-radius: var(--bubble-body-radius, var(--r-card));
2698
2739
  border-bottom-left-radius: var(--r-control);
2699
- background: var(--card-2-bg);
2700
- color: var(--card-2-ink);
2701
- font: var(--type-body);
2740
+ background: var(--bubble-body-bg, var(--card-2-bg));
2741
+ color: var(--bubble-body-ink, var(--card-2-ink));
2742
+ font: var(--bubble-body-font, var(--type-body));
2702
2743
  overflow-wrap: anywhere;
2703
2744
  }
2704
2745
  .bubble--right > .bubble__body {
@@ -2708,8 +2749,8 @@ button { font: inherit; cursor: pointer; }
2708
2749
  color: var(--on-accent);
2709
2750
  text-align: right;
2710
2751
  }
2711
- .bubble__actions { display: flex; gap: var(--sp-1); flex: none; opacity: 0.7; }
2712
- .bubble__meta { display: flex; align-items: center; gap: var(--sp-1); font: var(--type-caption); color: var(--on-surface-muted); }
2752
+ .bubble__actions { display: flex; gap: var(--bubble-actions-gap, var(--sp-1)); flex: none; opacity: 0.7; }
2753
+ .bubble__meta { display: flex; align-items: center; gap: var(--bubble-meta-gap, var(--sp-1)); font: var(--bubble-meta-font, var(--type-caption)); color: var(--bubble-meta-ink, var(--on-surface-muted)); }
2713
2754
  .bubble--right > .bubble__meta { justify-self: end; }
2714
2755
  .bubble__meta > .icon { color: var(--accent); }
2715
2756
  .card--3 .bubble__meta { color: inherit; opacity: 0.7; }
@@ -2721,7 +2762,7 @@ button { font: inherit; cursor: pointer; }
2721
2762
  margin: 0;
2722
2763
  padding: 0 0 0 var(--sp-6);
2723
2764
  display: grid;
2724
- gap: var(--sp-5);
2765
+ gap: var(--timeline-gap, var(--sp-5));
2725
2766
  position: relative;
2726
2767
  }
2727
2768
  .timeline::before {
@@ -2732,7 +2773,7 @@ button { font: inherit; cursor: pointer; }
2732
2773
  bottom: var(--sp-3);
2733
2774
  border-left: var(--border-w) solid var(--hairline);
2734
2775
  }
2735
- .timeline__item { display: grid; gap: var(--sp-2); position: relative; min-width: 0; }
2776
+ .timeline__item { display: grid; gap: var(--timeline-item-gap, var(--sp-2)); position: relative; min-width: 0; }
2736
2777
  .timeline__item::before {
2737
2778
  content: "";
2738
2779
  position: absolute;
@@ -2745,37 +2786,37 @@ button { font: inherit; cursor: pointer; }
2745
2786
  }
2746
2787
  .timeline__mark {
2747
2788
  justify-self: start;
2748
- font: var(--type-overline);
2789
+ font: var(--timeline-mark-font, var(--type-overline));
2749
2790
  letter-spacing: var(--track-overline);
2750
2791
  text-transform: uppercase;
2751
- padding: var(--sp-1) var(--sp-2);
2752
- border-radius: var(--r-chip);
2753
- background: var(--ink-tint);
2792
+ padding: var(--timeline-mark-pad-y, var(--sp-1)) var(--timeline-mark-pad-x, var(--sp-2));
2793
+ border-radius: var(--timeline-mark-radius, var(--r-chip));
2794
+ background: var(--timeline-mark-bg, var(--ink-tint));
2754
2795
  }
2755
2796
 
2756
2797
  /* ---- legend: signal dots with captions ----
2757
2798
  <div class="legend"><span class="legend__item"><span class="dot"></span> very high</span>…</div> */
2758
- .legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); font: var(--type-caption); color: var(--card-muted); }
2799
+ .legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); font: var(--legend-font, var(--type-caption)); color: var(--legend-ink, var(--card-muted)); }
2759
2800
  .card--3 .legend { color: inherit; opacity: 0.8; }
2760
- .legend__item { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--icon); }
2801
+ .legend__item { display: inline-flex; align-items: center; gap: var(--legend-item-gap, var(--sp-2)); min-height: var(--legend-item-min-h, var(--icon)); }
2761
2802
 
2762
2803
  /* ---- link: "See all ↗" in a card head, big enough to tap ----
2763
2804
  <a class="link" href="#">See all <svg class="icon icon--xs"/></a> */
2764
2805
  .link {
2765
2806
  display: inline-flex;
2766
2807
  align-items: center;
2767
- gap: var(--sp-1);
2768
- min-height: var(--tap-sm);
2808
+ gap: var(--link-gap, var(--sp-1));
2809
+ min-height: var(--link-min-h, var(--tap-sm));
2769
2810
  color: inherit;
2770
2811
  text-decoration: none;
2771
- font: var(--type-label);
2772
- font-size: var(--fs-body);
2812
+ font: var(--link-font, var(--type-label));
2813
+ font-size: var(--link-font-size, var(--fs-body));
2773
2814
  border: 0;
2774
2815
  background: transparent;
2775
2816
  padding: 0;
2776
2817
  cursor: pointer;
2777
2818
  }
2778
- .link--muted { color: var(--card-muted); }
2819
+ .link--muted { color: var(--link-muted-ink, var(--card-muted)); }
2779
2820
  .card--3 .link--muted { color: inherit; opacity: 0.7; }
2780
2821
 
2781
2822
  /* ---- css/media.css — cover card, quote, tile grid, fold, mosaic, wave ---- */
@@ -2824,16 +2865,16 @@ button { font: inherit; cursor: pointer; }
2824
2865
 
2825
2866
  /* ---- quote: giant mark, big text, attribution ----
2826
2867
  <blockquote class="quote"><p class="quote__text">It's overcast…</p><footer class="quote__by">Wednesday 15</footer></blockquote> */
2827
- .quote { margin: 0; display: grid; gap: var(--sp-3); }
2868
+ .quote { margin: 0; display: grid; gap: var(--quote-gap, var(--sp-3)); }
2828
2869
  .quote::before {
2829
2870
  content: "\201C";
2830
2871
  font: 900 calc(var(--fs-h1) * 3)/0.55 var(--font-display);
2831
2872
  height: var(--sp-10);
2832
2873
  opacity: 0.9;
2833
2874
  }
2834
- .quote__text { margin: 0; font: var(--type-h1); letter-spacing: var(--track-h1); text-wrap: balance; }
2875
+ .quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
2835
2876
  .quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
2836
- .quote__by { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; color: var(--card-muted); }
2877
+ .quote__by { font: var(--quote-by-font, var(--type-overline)); letter-spacing: var(--track-overline); text-transform: uppercase; color: var(--quote-by-ink, var(--card-muted)); }
2837
2878
  .card--3 .quote__by { color: inherit; opacity: 0.7; }
2838
2879
 
2839
2880
  /* ---- tile grid: big square tiles — icon top, label bottom ----
@@ -2842,36 +2883,36 @@ button { font: inherit; cursor: pointer; }
2842
2883
  .tile-grid {
2843
2884
  display: grid;
2844
2885
  grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), 100%), 1fr));
2845
- gap: var(--sp-3);
2886
+ gap: var(--tile-grid-gap, var(--sp-3));
2846
2887
  }
2847
2888
  .tile-grid--3 { --tile-min: 6rem; } /* three across on a phone (attachments, upload kinds) */
2848
2889
  .tile {
2849
2890
  display: grid;
2850
2891
  align-content: space-between;
2851
2892
  justify-items: start;
2852
- gap: var(--sp-5);
2853
- min-height: var(--tile-h);
2893
+ gap: var(--tile-gap, var(--sp-5));
2894
+ min-height: var(--tile-min-h, var(--tile-h));
2854
2895
  min-width: 0;
2855
- padding: var(--sp-4);
2856
- border: var(--border-w) solid var(--outline);
2857
- border-radius: var(--r-card);
2858
- background: var(--action-bg);
2859
- color: var(--action-ink);
2896
+ padding: var(--tile-pad, var(--sp-4));
2897
+ border: var(--border-w) solid var(--tile-border, var(--outline));
2898
+ border-radius: var(--tile-radius, var(--r-card));
2899
+ background: var(--tile-bg, var(--action-bg));
2900
+ color: var(--tile-ink, var(--action-ink));
2860
2901
  font: inherit;
2861
2902
  text-align: left;
2862
2903
  position: relative;
2863
2904
  cursor: pointer;
2864
2905
  }
2865
- .tile__label { font: var(--type-title); }
2866
- .tile__value { font: var(--type-h2); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
2867
- .tile__sub { font: var(--type-caption); opacity: 0.7; }
2868
- .tile__text { display: grid; gap: var(--sp-0); }
2869
- .tile--on { background: var(--accent); color: var(--on-accent); border-color: transparent; }
2906
+ .tile__label { font: var(--tile-label-font, var(--type-title)); }
2907
+ .tile__value { font: var(--tile-value-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
2908
+ .tile__sub { font: var(--tile-sub-font, var(--type-caption)); opacity: 0.7; }
2909
+ .tile__text { display: grid; gap: var(--tile-text-gap, var(--sp-0)); }
2910
+ .tile--on { background: var(--tile-on-bg, var(--accent)); color: var(--tile-on-ink, var(--on-accent)); border-color: transparent; }
2870
2911
  .tile--pick > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
2871
2912
  .tile--pick:has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
2872
2913
  .tile--pick:has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
2873
2914
  .tile--wide { grid-column: 1 / -1; }
2874
- .tile--stat { min-height: var(--avatar-xl); gap: var(--sp-3); grid-template-columns: auto 1fr; align-items: center; }
2915
+ .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; }
2875
2916
  /* upload: dashed, centred plus + label */
2876
2917
  .tile--upload {
2877
2918
  border-style: dashed;
@@ -2879,12 +2920,12 @@ button { font: inherit; cursor: pointer; }
2879
2920
  color: inherit;
2880
2921
  align-content: center;
2881
2922
  justify-items: center;
2882
- gap: var(--sp-2);
2923
+ gap: var(--tile-upload-gap, var(--sp-2));
2883
2924
  text-align: center;
2884
2925
  }
2885
2926
  /* tinted tiles: slot colors inside a card (schedule, notes) */
2886
- .tile--2 { background: var(--card-2-bg); color: var(--card-2-ink); border-color: transparent; }
2887
- .tile--3 { background: var(--card-3-bg); color: var(--card-3-ink); border-color: transparent; }
2927
+ .tile--2 { background: var(--tile-2-bg, var(--card-2-bg)); color: var(--tile-2-ink, var(--card-2-ink)); border-color: transparent; }
2928
+ .tile--3 { background: var(--tile-3-bg, var(--card-3-bg)); color: var(--tile-3-ink, var(--card-3-ink)); border-color: transparent; }
2888
2929
 
2889
2930
  /* ---- fold: a sticky-note corner on any card (decorative) ----
2890
2931
  No border. The card's outline follows its border box, so it ran straight
@@ -2908,7 +2949,7 @@ button { font: inherit; cursor: pointer; }
2908
2949
  <span class="wave"></span> — sits in a .row__body, fills the width */
2909
2950
  .wave {
2910
2951
  display: block;
2911
- height: var(--icon);
2952
+ height: var(--wave-h, var(--icon));
2912
2953
  width: 100%;
2913
2954
  background: repeating-linear-gradient(90deg, currentColor 0 var(--sp-1), transparent var(--sp-1) var(--sp-2));
2914
2955
  /* masks only read alpha: currentColor = "opaque" without naming a color */
@@ -2923,9 +2964,9 @@ button { font: inherit; cursor: pointer; }
2923
2964
  display: grid;
2924
2965
  grid-template-columns: 2fr 1fr;
2925
2966
  grid-auto-rows: 1fr;
2926
- gap: var(--sp-1);
2967
+ gap: var(--mosaic-gap, var(--sp-1));
2927
2968
  aspect-ratio: 4 / 3;
2928
- border-radius: var(--r-media);
2969
+ border-radius: var(--mosaic-radius, var(--r-media));
2929
2970
  overflow: hidden;
2930
2971
  }
2931
2972
  .mosaic > :first-child { grid-row: span 2; }
@@ -2933,59 +2974,75 @@ button { font: inherit; cursor: pointer; }
2933
2974
  .mosaic > .ph { opacity: 0.4; border-width: var(--border-w); }
2934
2975
  .mosaic > img { width: 100%; height: 100%; object-fit: cover; }
2935
2976
 
2936
- /* ---- postcard: a member's introduction, a card in the hand — photo in front,
2937
- words on the back; tap to turn it over (Lh, 2026-09-12; card #63) ----
2977
+ /* ---- postcard: a member's introduction in the shape of a cover card — the photo fills it,
2978
+ the band at the bottom names it; tap the photo to turn it over (Lh 2026-09-12; reshaped 2026-09-22) ----
2938
2979
  <div class="postcard">
2939
- <button class="postcard__face postcard__front" type="button" aria-label="พลิกดูด้านหลัง">
2940
- <img src="…" alt=""> — or <div class="ph"></div> —
2941
- <span class="postcard__label">ป้าแจ๋ว</span>
2942
- </button>
2943
- <button class="postcard__face postcard__back" type="button" aria-label="พลิกดูด้านหน้า">
2944
- <p class="postcard__caption">ชอบชงกาแฟดริปตอนเช้า</p>
2945
- <span class="postcard__label">ป้าแจ๋ว</span>
2946
- </button>
2980
+ <div class="postcard__face postcard__front">
2981
+ <button class="postcard__turn" type="button" aria-label="พลิกดูด้านหลัง"><img src="…" alt=""> — or <div class="ph"></div> —</button>
2982
+ <div class="postcard__band">
2983
+ <div class="postcard__words"><span class="t-h1">ป้าแจ๋ว</span><span class="t-body">บ้านสวนผัก</span></div>
2984
+ <button class="icon-btn icon-btn--invert" aria-label="ถูกใจ" aria-pressed="false">♥</button>
2985
+ </div>
2986
+ </div>
2987
+ <div class="postcard__face postcard__back">
2988
+ <button class="postcard__turn" type="button" aria-label="พลิกดูด้านหน้า">
2989
+ <div class="postcard__words">…</div><p class="postcard__caption">ชอบชงกาแฟดริปตอนเช้า</p>
2990
+ </button>
2991
+ <div class="postcard__voice"><div class="progress"></div><span class="t-caption">0:07 / 0:12</span><button class="icon-btn icon-btn--invert" aria-label="ฟัง">▶</button></div> (a voice note: no caption — the line, its time, the control under them, at the middle)
2992
+ </div>
2947
2993
  </div>
2948
- Each face is the whole tap target; cardds.js toggles .is-flipped on the
2949
- postcard (either face, Enter/Space too). A voice note, a like, a reply live
2950
- OUTSIDE the postcard (a Row under it): a face is a button, and a button
2951
- holds no button. The faces share one box (a 3:2 landscape card, the media
2952
- radius); the turn is a Y rotation at --motion-flip, a plain swap under
2953
- prefers-reduced-motion. Reads ink from the card slot it sits in. */
2994
+ The two faces share one box and turn (a Y rotation at --motion-flip, a plain
2995
+ swap under prefers-reduced-motion). A face is a div: the TURN is the button
2996
+ that fills it (the photo, or the caption), the band lies over its foot and
2997
+ turns with it — words and the heart; the back sets the name a fifth of the way down, the words after it; a
2998
+ button in the band is never inside the turn button. The size is the cover
2999
+ card's (--postcard-min-h = the cover's min-height, the card radius). Who
3000
+ liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
2954
3001
  .postcard {
2955
3002
  position: relative;
2956
- aspect-ratio: 3 / 2;
3003
+ min-height: var(--postcard-min-h, 22.5rem);
2957
3004
  perspective: 60rem;
2958
- border-radius: var(--r-media);
3005
+ border-radius: var(--postcard-radius, var(--r-card));
3006
+ isolation: isolate;
2959
3007
  }
2960
3008
  .postcard__face {
2961
3009
  position: absolute;
2962
3010
  inset: 0;
2963
3011
  display: grid;
2964
3012
  align-content: end;
2965
- padding: var(--sp-4);
2966
- gap: var(--sp-2);
2967
- border: var(--border-w) solid var(--card-border);
3013
+ border: var(--border-w) solid var(--postcard-face-border, var(--card-border));
2968
3014
  border-radius: inherit;
2969
3015
  overflow: hidden;
2970
3016
  isolation: isolate;
2971
- color: inherit;
2972
- text-align: left;
2973
3017
  backface-visibility: hidden;
2974
3018
  -webkit-backface-visibility: hidden;
2975
3019
  transition: transform var(--motion-flip) ease-in-out;
2976
- box-shadow: var(--shadow-float);
3020
+ box-shadow: var(--postcard-face-shadow, var(--shadow-float));
2977
3021
  }
2978
- .postcard__face:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2979
- /* the front: the photo (or the wireframe box) fills it, the name reads on a scrim */
2980
- .postcard__front { background: var(--card-3-bg); color: var(--card-3-ink); }
2981
- .postcard__front > img,
2982
- .postcard__front > .ph {
3022
+ /* the turn: the button that fills the face, under the band */
3023
+ .postcard__turn {
3024
+ position: absolute;
3025
+ inset: 0;
3026
+ display: grid;
3027
+ align-content: start;
3028
+ padding: var(--postcard-face-pad, var(--card-pad-rest));
3029
+ border: 0;
3030
+ border-radius: inherit;
3031
+ background: transparent;
3032
+ color: inherit;
3033
+ text-align: left;
3034
+ cursor: pointer;
3035
+ }
3036
+ .postcard__turn:focus-visible { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
3037
+ /* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
3038
+ .postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); color: var(--postcard-front-ink, var(--card-3-ink)); }
3039
+ .postcard__front .postcard__turn > img,
3040
+ .postcard__front .postcard__turn > .ph {
2983
3041
  position: absolute;
2984
3042
  inset: 0;
2985
3043
  width: 100%;
2986
3044
  height: 100%;
2987
3045
  object-fit: cover;
2988
- z-index: -2;
2989
3046
  border: 0;
2990
3047
  border-radius: 0;
2991
3048
  min-height: 0;
@@ -2994,20 +3051,40 @@ button { font: inherit; cursor: pointer; }
2994
3051
  content: "";
2995
3052
  position: absolute;
2996
3053
  inset: 0;
2997
- z-index: -1;
3054
+ z-index: 1;
3055
+ pointer-events: none;
2998
3056
  background: linear-gradient(to top, var(--scrim), transparent 45%);
2999
3057
  }
3000
- /* the back: paper — the words, big enough to read at arm's length */
3058
+ /* the back: paper — the caption and the name packed at the middle, big enough to read at arm's length */
3001
3059
  .postcard__back {
3002
- background: var(--card-2-bg);
3003
- color: var(--card-2-ink);
3004
- align-content: space-between;
3060
+ background: var(--postcard-back-bg, var(--card-2-bg));
3061
+ color: var(--postcard-back-ink, var(--card-2-ink));
3005
3062
  transform: rotateY(180deg);
3006
3063
  }
3007
- .postcard__caption { margin: 0; font: var(--type-title); text-wrap: pretty; }
3008
- .postcard__label { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.85; }
3064
+ .postcard__back .postcard__turn { align-content: start; padding-top: var(--postcard-back-top, 20%); gap: var(--postcard-back-gap, var(--sp-5)); } /* the name a fifth down the paper, the words after it */
3065
+ .postcard__caption { margin: 0; font: var(--postcard-caption-font, var(--type-title)); text-wrap: pretty; }
3066
+ /* the band: the face's foot — name and line left, the heart right; in flow at the face's end, over the turn.
3067
+ Taps between its words fall through to the turn; its buttons take their own. */
3068
+ .postcard__band {
3069
+ position: relative;
3070
+ z-index: 2;
3071
+ display: flex;
3072
+ align-items: end;
3073
+ gap: var(--postcard-band-gap, var(--sp-3));
3074
+ padding: var(--postcard-face-pad, var(--card-pad-rest));
3075
+ pointer-events: none;
3076
+ }
3077
+ .postcard__band > * { pointer-events: auto; }
3078
+ .postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
3079
+ .postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-3-ink)); --on-accent: var(--postcard-front-bg, var(--card-3-bg)); }
3080
+ .postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
3009
3081
  .postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
3010
3082
  .postcard.is-flipped > .postcard__back { transform: rotateY(0); }
3083
+ /* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
3084
+ under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
3085
+ .postcard__voice { position: absolute; inset: 0; z-index: 2; display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
3086
+ .postcard__voice > * { pointer-events: auto; }
3087
+ .postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
3011
3088
  @media (prefers-reduced-motion: reduce) {
3012
3089
  .postcard__face { transition: none; }
3013
3090
  }
@@ -3020,8 +3097,8 @@ button { font: inherit; cursor: pointer; }
3020
3097
  lives in sheet.css; these compose with .sheet-stage.
3021
3098
  ============================================================ */
3022
3099
 
3023
- /* ---- ask: the Modal — the case drops in, its drawer opens below (Lh 2026-09-16: ONE card, two parts)
3024
- <div class="sheet-stage sheet-stage--ask">
3100
+ /* ---- the Modal — the case drops in, its drawer opens below (Lh 2026-09-16: ONE card, two parts)
3101
+ <div class="sheet-stage sheet-stage--dim"> (dim = the page under a scrim; a Modal on a clean ground is the stage without it — Lh 2026-09-22, `--ask` retired: the stage does the ground, the modal centres itself)
3025
3102
  <article class="card modal">
3026
3103
  <div class="modal__lift">…the card's content (a head, rows, a timeline)…</div>
3027
3104
  <div class="modal__drawer"><h2 class="t-h2">Take the case?</h2><p class="t-caption">This cannot be undone.</p>
@@ -3032,36 +3109,33 @@ button { font: inherit; cursor: pointer; }
3032
3109
  .modal is the card — no border (the shadow is its edge on the scrim);
3033
3110
  .modal__lift the light part that holds the thing being asked about (it
3034
3111
  scrolls when tall); .modal__drawer the dark end that asks. The ground is
3035
- the page under a scrim (like --dim). Two beats (2 × --motion-sheet) when
3112
+ whatever the stage shows (dim it or not). Two beats (2 × --motion-sheet) when
3036
3113
  the stage appears, and the same two backwards when it closes:
3037
3114
  1. the card drops in from above the stage to the screen's centre — its
3038
3115
  drawer still inside it, height 0: nothing of the ask shows yet
3039
3116
  2. the drawer comes out of the card's bottom edge while the lift moves
3040
3117
  up, both at once: the card grows from its centre, which never moves
3041
- (the stage centres it; a growing card keeps its middle where it is)
3118
+ (the modal centres itself — absolute, margin auto; a growing card keeps its middle where it is)
3042
3119
  Closing: a click on any button in the drawer plays the same animations in
3043
3120
  reverse (cardds.js: Animation.reverse() on each — nothing duplicated), then
3044
3121
  fires "cardds:modal" on the .sheet-stage (bubbles, detail.answer from the
3045
3122
  button's data-answer) for the app to remove the stage.
3046
3123
  Reduced motion: the finished state, no beats; closing fires at once. */
3047
- .sheet-stage--ask {
3048
- --drawer-h: 14rem;
3049
- display: grid;
3050
- align-content: center; /* the card at the screen's centre — and kept there as it grows */
3051
- padding-inline: var(--screen-pad);
3052
- }
3053
- .sheet-stage--ask::after { content: ""; position: absolute; inset: 0; background: var(--scrim); } /* the page dimmed */
3054
- .sheet-stage--ask > .sheet-stage__bg { filter: blur(var(--sp-2)); }
3055
3124
  .modal {
3056
- position: relative;
3057
- z-index: 1; /* over the scrim */
3058
- max-height: calc(100cqh - 2 * var(--sp-5)); /* cqh: the stage (a size container) — a % here would be of the grid area, which is the card itself */
3125
+ --drawer-h: 14rem;
3126
+ position: absolute; /* at the stage's centre, and kept there as it grows: absolute + margin auto re-centres on every height */
3127
+ inset: 0;
3128
+ margin: auto;
3129
+ height: fit-content; /* inset 0 alone would stretch it to the stage: its own height, then the margins share the rest */
3130
+ width: calc(100% - 2 * var(--screen-pad));
3131
+ z-index: 2; /* over the dim stage's scrim (z 1) */
3132
+ max-height: calc(100cqh - 2 * var(--sp-5)); /* cqh: the stage (a size container) */
3059
3133
  padding: 0; /* the parts pad themselves */
3060
3134
  border: 0; /* no frame on the modal (Lh 2026-09-16) — its edge is the shadow */
3061
3135
  display: grid;
3062
3136
  grid-template-rows: minmax(0, 1fr) auto;
3063
3137
  overflow: clip;
3064
- box-shadow: var(--shadow-sheet);
3138
+ box-shadow: var(--modal-shadow, var(--shadow-sheet));
3065
3139
  }
3066
3140
  .modal__lift {
3067
3141
  padding: var(--card-pad);
@@ -3075,15 +3149,15 @@ button { font: inherit; cursor: pointer; }
3075
3149
  .modal__drawer {
3076
3150
  height: var(--drawer-h);
3077
3151
  box-sizing: border-box;
3078
- padding: var(--sp-5);
3152
+ padding: var(--modal-drawer-pad, var(--sp-5));
3079
3153
  overflow: clip;
3080
3154
  display: grid;
3081
3155
  align-content: center;
3082
3156
  justify-items: center;
3083
- gap: var(--sp-1);
3157
+ gap: var(--modal-drawer-gap, var(--sp-1));
3084
3158
  text-align: center;
3085
- background: var(--card-3-bg);
3086
- color: var(--card-3-ink);
3159
+ background: var(--modal-drawer-bg, var(--card-3-bg));
3160
+ color: var(--modal-drawer-ink, var(--card-3-ink));
3087
3161
  --accent: var(--card-3-ink); /* the one solid button inverts on the dark drawer */
3088
3162
  --on-accent: var(--card-3-bg);
3089
3163
  }
@@ -3099,14 +3173,14 @@ button { font: inherit; cursor: pointer; }
3099
3173
  }
3100
3174
  @keyframes modal-open {
3101
3175
  0%, 50% { height: 0; padding-block: 0; } /* inside the card */
3102
- 100% { height: var(--drawer-h); padding-block: var(--sp-5); } /* 2. out below, the lift up, together */
3176
+ 100% { height: var(--drawer-h); padding-block: var(--modal-drawer-pad, var(--sp-5)); } /* 2. out below, the lift up, together — the same knob the rule reads: a keyframe that pinned the value would make the knob dead */
3103
3177
  }
3104
3178
  }
3105
3179
 
3106
- .btn-trio { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
3180
+ .btn-trio { display: flex; align-items: center; gap: var(--btn-trio-gap, var(--sp-3)); margin-top: var(--sp-3); }
3107
3181
  .btn-trio__dots {
3108
- width: var(--sp-5);
3109
- height: var(--sp-1);
3182
+ width: var(--btn-trio-dots-w, var(--sp-5));
3183
+ height: var(--btn-trio-dots-h, var(--sp-1));
3110
3184
  background: radial-gradient(circle, currentColor var(--sp-0), transparent calc(var(--sp-0) + 0.5px)) 0 0 / calc(var(--sp-5) / 2) 100% repeat-x;
3111
3185
  opacity: 0.4;
3112
3186
  }
@@ -3127,31 +3201,31 @@ button { font: inherit; cursor: pointer; }
3127
3201
  display: none; /* explicit closed state — never display on the base rule */
3128
3202
  margin: 0;
3129
3203
  inset: auto;
3130
- border: var(--border-w) solid var(--action-border);
3131
- border-radius: var(--r-card);
3204
+ border: var(--border-w) solid var(--menu-border, var(--action-border));
3205
+ border-radius: var(--menu-radius, var(--r-card));
3132
3206
  --menu-pad: var(--sp-2); /* published: a row's radius is concentric with the panel's */
3133
3207
  padding: var(--menu-pad);
3134
3208
  min-width: 12rem;
3135
- background: var(--action-bg);
3136
- color: var(--action-ink);
3137
- box-shadow: var(--shadow-float);
3209
+ background: var(--menu-bg, var(--action-bg));
3210
+ color: var(--menu-ink, var(--action-ink));
3211
+ box-shadow: var(--menu-shadow, var(--shadow-float));
3138
3212
  }
3139
3213
  .menu:popover-open { display: grid; }
3140
3214
  .menu:not([popover]) { display: grid; position: relative; } /* inline usage (no attribute) */
3141
3215
  .menu__item {
3142
3216
  display: flex;
3143
3217
  align-items: center;
3144
- gap: var(--sp-3);
3145
- min-height: var(--tap);
3218
+ gap: var(--menu-item-gap, var(--sp-3));
3219
+ min-height: var(--menu-item-min-h, var(--tap));
3146
3220
  width: 100%;
3147
- padding: 0 var(--sp-3);
3221
+ padding: var(--menu-item-pad-y, 0) var(--menu-item-pad-x, var(--sp-3));
3148
3222
  border: 0;
3149
3223
  /* concentric with the panel: the card radius minus the panel's padding, so the
3150
3224
  highlight's curve follows the outer corner (Lh 2026-09-16) */
3151
3225
  border-radius: calc(var(--r-card) - var(--menu-pad, var(--sp-2)));
3152
3226
  background: transparent;
3153
3227
  color: inherit;
3154
- font: var(--type-title);
3228
+ font: var(--menu-item-font, var(--type-title));
3155
3229
  text-align: left;
3156
3230
  cursor: pointer;
3157
3231
  }
@@ -3179,12 +3253,12 @@ button { font: inherit; cursor: pointer; }
3179
3253
  /* ---- banner: a chip that opens into a tinted card, no JS ----
3180
3254
  <details class="banner"><summary class="chip chip--on">Save $30 on orders over $70 <svg class="icon icon--xs"/></summary>
3181
3255
  <div class="card card--3 banner__body">…</div></details> */
3182
- .banner { display: grid; gap: var(--sp-2); justify-items: start; }
3256
+ .banner { display: grid; gap: var(--banner-gap, var(--sp-2)); justify-items: start; }
3183
3257
  .banner > summary { list-style: none; cursor: pointer; }
3184
3258
  .banner > summary::-webkit-details-marker { display: none; }
3185
3259
  .banner > summary .icon { transition: rotate 0.2s ease; }
3186
3260
  .banner[open] > summary .icon { rotate: 180deg; }
3187
- .banner__body { width: 100%; padding: var(--sp-4) var(--sp-5); --card-gap: var(--sp-1); }
3261
+ .banner__body { width: 100%; padding: var(--banner-body-pad-y, var(--sp-4)) var(--banner-body-pad-x, var(--sp-5)); --card-gap: var(--sp-1); }
3188
3262
  .banner::details-content { width: 100%; } /* Chromium wraps the content in this pseudo; justify-items: start shrink-wrapped it, so the body never reached the width */
3189
3263
  @supports (interpolate-size: allow-keywords) {
3190
3264
  @media (prefers-reduced-motion: no-preference) {
@@ -3202,9 +3276,9 @@ button { font: inherit; cursor: pointer; }
3202
3276
  bottom: var(--sp-5);
3203
3277
  max-width: var(--screen-w);
3204
3278
  margin-inline: auto;
3205
- padding-inline: var(--sp-4);
3279
+ padding-inline: var(--float-bar-pad-x, var(--sp-4));
3206
3280
  display: flex;
3207
- gap: var(--sp-2);
3281
+ gap: var(--float-bar-gap, var(--sp-2));
3208
3282
  align-items: center;
3209
3283
  z-index: 30;
3210
3284
  }
@@ -3219,29 +3293,29 @@ button { font: inherit; cursor: pointer; }
3219
3293
  display: flex;
3220
3294
  justify-content: space-around;
3221
3295
  align-items: center;
3222
- min-height: var(--bar-h);
3223
- padding: var(--sp-1);
3224
- border-radius: var(--r-chip);
3225
- border: var(--border-w) solid var(--action-border);
3226
- background: color-mix(in srgb, var(--action-bg) 82%, transparent);
3227
- color: var(--action-ink);
3296
+ min-height: var(--float-bar-pill-min-h, var(--bar-h));
3297
+ padding: var(--float-bar-pill-pad, var(--sp-1));
3298
+ border-radius: var(--float-bar-pill-radius, var(--r-chip));
3299
+ border: var(--border-w) solid var(--float-bar-pill-border, var(--action-border));
3300
+ background: var(--float-bar-pill-bg, color-mix(in srgb, var(--action-bg) 82%, transparent));
3301
+ color: var(--float-bar-pill-ink, var(--action-ink));
3228
3302
  backdrop-filter: blur(var(--sp-3));
3229
3303
  -webkit-backdrop-filter: blur(var(--sp-3));
3230
- box-shadow: var(--shadow-float);
3304
+ box-shadow: var(--float-bar-pill-shadow, var(--shadow-float));
3231
3305
  }
3232
3306
  .float-bar__item {
3233
3307
  flex: 1;
3234
3308
  min-width: 0;
3235
3309
  display: grid;
3236
3310
  justify-items: center;
3237
- gap: var(--sp-0);
3238
- min-height: var(--tap-lg);
3239
- padding: var(--sp-1);
3311
+ gap: var(--float-bar-item-gap, var(--sp-0));
3312
+ min-height: var(--float-bar-item-min-h, var(--tap-lg));
3313
+ padding: var(--float-bar-item-pad, var(--sp-1));
3240
3314
  border: 0;
3241
- border-radius: var(--r-chip);
3315
+ border-radius: var(--float-bar-item-radius, var(--r-chip));
3242
3316
  background: transparent;
3243
3317
  color: inherit;
3244
- font: var(--type-caption);
3318
+ font: var(--float-bar-item-font, var(--type-caption));
3245
3319
  text-decoration: none;
3246
3320
  opacity: 0.6;
3247
3321
  overflow: hidden;
@@ -3259,15 +3333,15 @@ button { font: inherit; cursor: pointer; }
3259
3333
  .callout {
3260
3334
  position: absolute;
3261
3335
  display: grid;
3262
- gap: var(--sp-0);
3336
+ gap: var(--callout-gap, var(--sp-0));
3263
3337
  width: max-content;
3264
3338
  max-width: 15rem;
3265
- padding: var(--sp-3) var(--sp-4);
3266
- border: var(--border-w) solid var(--action-border);
3267
- border-radius: var(--r-card);
3268
- background: var(--action-bg);
3269
- color: var(--action-ink);
3270
- box-shadow: var(--shadow-float);
3339
+ padding: var(--callout-pad-y, var(--sp-3)) var(--callout-pad-x, var(--sp-4));
3340
+ border: var(--border-w) solid var(--callout-border, var(--action-border));
3341
+ border-radius: var(--callout-radius, var(--r-card));
3342
+ background: var(--callout-bg, var(--action-bg));
3343
+ color: var(--callout-ink, var(--action-ink));
3344
+ box-shadow: var(--callout-shadow, var(--shadow-float));
3271
3345
  text-align: center;
3272
3346
  }
3273
3347
  .callout::after {
@@ -3284,16 +3358,16 @@ button { font: inherit; cursor: pointer; }
3284
3358
  border-top: 0;
3285
3359
  border-left: 0;
3286
3360
  }
3287
- .callout__title { font: var(--type-title); }
3288
- .callout__sub { font: var(--type-caption); opacity: 0.7; }
3361
+ .callout__title { font: var(--callout-title-font, var(--type-title)); }
3362
+ .callout__sub { font: var(--callout-sub-font, var(--type-caption)); opacity: 0.7; }
3289
3363
  /* ---- deck: swipe cards; the last child is on top ----
3290
3364
  <div class="deck"><article class="card">…</article><article class="card">…</article><article class="card card--fold">front</article></div>
3291
3365
  <div class="deck__actions"><button class="icon-btn">×</button><button class="icon-btn icon-btn--invert">✓</button></div> */
3292
- .deck { display: grid; place-items: center; padding: var(--sp-4) var(--sp-4) var(--sp-6); }
3366
+ .deck { display: grid; place-items: center; padding: var(--deck-pad-top, var(--sp-4)) var(--deck-pad-x, var(--sp-4)) var(--deck-pad-bottom, var(--sp-6)); }
3293
3367
  .deck > .card { grid-area: 1 / 1; width: min(100%, 20rem); min-height: 12.5rem; transition: rotate 0.3s ease, translate 0.3s ease; }
3294
3368
  .deck > .card:nth-last-child(2) { rotate: -4deg; translate: calc(-1 * var(--sp-2)) var(--sp-2); opacity: 0.85; }
3295
3369
  .deck > .card:nth-last-child(3) { rotate: 5deg; translate: var(--sp-3) var(--sp-4); opacity: 0.6; }
3296
3370
  .deck > .card:nth-last-child(n + 4) { display: none; }
3297
- .deck__actions { display: flex; justify-content: center; gap: var(--sp-8); }
3371
+ .deck__actions { display: flex; justify-content: center; gap: var(--deck-actions-gap, var(--sp-8)); }
3298
3372
  .deck__actions > .icon-btn { width: var(--bar-h); height: var(--bar-h); }
3299
3373