@usableapp/cardds 0.3.0 → 0.4.0

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 (89) hide show
  1. package/README.md +9 -5
  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 +45 -45
  9. package/css/lists.css +43 -43
  10. package/css/media.css +33 -33
  11. package/css/numbers.css +25 -25
  12. package/css/people.css +11 -11
  13. package/css/sheet.css +21 -21
  14. package/css/theme.template.css +6 -3
  15. package/dist/actions/Btn.d.ts +109 -108
  16. package/dist/actions/BtnRow.d.ts +2 -2
  17. package/dist/actions/Chip.d.ts +102 -101
  18. package/dist/actions/Dot.d.ts +2 -2
  19. package/dist/actions/Dropdown.d.ts +4 -4
  20. package/dist/actions/Fab.d.ts +2 -2
  21. package/dist/actions/IconBtn.d.ts +107 -106
  22. package/dist/actions/Link.d.ts +105 -104
  23. package/dist/actions/Segment.d.ts +3 -3
  24. package/dist/cardds.css +333 -321
  25. package/dist/cards/Card.d.ts +99 -98
  26. package/dist/cards/CardFoot.d.ts +2 -2
  27. package/dist/cards/CardHead.d.ts +2 -2
  28. package/dist/cards/CardParts.d.ts +7 -7
  29. package/dist/cards/Placeholder.d.ts +2 -2
  30. package/dist/cards/Stat.d.ts +7 -5
  31. package/dist/cards/Stat.js +2 -2
  32. package/dist/choice/Calendar.d.ts +2 -2
  33. package/dist/choice/Check.d.ts +3 -3
  34. package/dist/choice/DayStrip.d.ts +3 -3
  35. package/dist/choice/Mood.d.ts +2 -2
  36. package/dist/choice/Slider.d.ts +2 -2
  37. package/dist/forms/AddRow.d.ts +2 -2
  38. package/dist/forms/Composer.d.ts +2 -2
  39. package/dist/forms/Field.d.ts +2 -2
  40. package/dist/forms/FileBtn.d.ts +2 -2
  41. package/dist/forms/Pin.d.ts +2 -2
  42. package/dist/journey/NoteRow.d.ts +2 -2
  43. package/dist/journey/Route.d.ts +2 -2
  44. package/dist/journey/TileBadge.d.ts +2 -2
  45. package/dist/layover/Banner.d.ts +2 -2
  46. package/dist/layover/Callout.d.ts +2 -2
  47. package/dist/layover/Deck.d.ts +3 -3
  48. package/dist/lists/Bubble.d.ts +3 -3
  49. package/dist/lists/Kv.d.ts +5 -5
  50. package/dist/lists/Legend.d.ts +3 -3
  51. package/dist/lists/Row.d.ts +5 -4
  52. package/dist/lists/Timeline.d.ts +3 -3
  53. package/dist/media/MapArea.d.ts +2 -2
  54. package/dist/media/Mosaic.d.ts +3 -3
  55. package/dist/media/Postcard.d.ts +2 -2
  56. package/dist/media/Quote.d.ts +2 -2
  57. package/dist/media/Tile.d.ts +5 -4
  58. package/dist/numbers/Badge.d.ts +2 -2
  59. package/dist/numbers/Band.d.ts +3 -3
  60. package/dist/numbers/Bars.d.ts +3 -3
  61. package/dist/numbers/DotGrid.d.ts +2 -2
  62. package/dist/numbers/Picker.d.ts +3 -3
  63. package/dist/numbers/Ring.d.ts +2 -2
  64. package/dist/numbers/Track.d.ts +3 -3
  65. package/dist/people/Avatar.d.ts +100 -99
  66. package/dist/people/AvatarPick.d.ts +3 -3
  67. package/dist/scaffold/ActionBar.d.ts +3 -3
  68. package/dist/scaffold/AppBar.d.ts +2 -2
  69. package/dist/scaffold/BaseContent.d.ts +3 -3
  70. package/dist/scaffold/Centre.d.ts +3 -3
  71. package/dist/scaffold/EmptyState.d.ts +2 -2
  72. package/dist/scaffold/FilterRow.d.ts +2 -2
  73. package/dist/scaffold/FloatBar.d.ts +105 -104
  74. package/dist/scaffold/PagerAt.d.ts +2 -2
  75. package/dist/scaffold/Screen.d.ts +100 -99
  76. package/dist/scaffold/TopBar.d.ts +2 -2
  77. package/dist/sheets/CardBack.d.ts +2 -2
  78. package/dist/sheets/Drawer.d.ts +5 -5
  79. package/dist/sheets/Sheet.d.ts +2 -2
  80. package/dist/sheets/SheetBody.d.ts +2 -2
  81. package/dist/sheets/SheetHead.d.ts +2 -2
  82. package/dist/sheets/SheetStack.d.ts +2 -2
  83. package/dist/sheets/SheetStage.d.ts +2 -2
  84. package/dist/sheets/Step.d.ts +2 -2
  85. package/dist/type/Icon.d.ts +22 -7
  86. package/dist/type/Icon.js +27 -4
  87. package/dist/type/Text.d.ts +7 -1
  88. package/dist/type/Text.js +2 -2
  89. package/package.json +3 -2
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
  /* ============================================================
@@ -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
@@ -1347,7 +1348,7 @@ button { font: inherit; cursor: pointer; }
1347
1348
  position: absolute;
1348
1349
  inset: 0;
1349
1350
  isolation: isolate; /* the ring and the hint stack inside the map only — a sheet over the map covers them */
1350
- color: var(--on-surface);
1351
+ color: var(--map-ink, var(--on-surface));
1351
1352
  background:
1352
1353
  repeating-linear-gradient(75deg, transparent 0 5.5rem, var(--hairline) 5.5rem 6.25rem),
1353
1354
  repeating-linear-gradient(-18deg, transparent 0 8.75rem, var(--ink-tint) 8.75rem 9.25rem),
@@ -1365,9 +1366,9 @@ button { font: inherit; cursor: pointer; }
1365
1366
  height: var(--map-ring);
1366
1367
  translate: -50% -50%;
1367
1368
  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);
1369
+ border: var(--border-w) solid var(--map-ring-border, var(--accent));
1370
+ background: var(--map-ring-bg, color-mix(in srgb, var(--accent) 22%, transparent));
1371
+ box-shadow: var(--map-ring-shadow, var(--shadow-float));
1371
1372
  pointer-events: none;
1372
1373
  z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
1373
1374
  }
@@ -1376,10 +1377,10 @@ button { font: inherit; cursor: pointer; }
1376
1377
  right: var(--sp-2);
1377
1378
  bottom: var(--sp-2);
1378
1379
  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);
1380
+ border-radius: var(--map-credit-radius, var(--r-chip));
1381
+ background: var(--map-credit-bg, color-mix(in srgb, var(--surface) 80%, transparent));
1382
+ color: var(--map-credit-ink, var(--on-surface-muted));
1383
+ font: var(--map-credit-font, var(--type-caption));
1383
1384
  pointer-events: none;
1384
1385
  z-index: 1;
1385
1386
  }
@@ -1390,12 +1391,12 @@ button { font: inherit; cursor: pointer; }
1390
1391
  translate: -50% 0;
1391
1392
  max-width: calc(100% - 2 * var(--sp-4));
1392
1393
  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);
1394
+ border-radius: var(--map-hint-radius, var(--r-chip));
1395
+ background: var(--map-hint-bg, var(--card-3-bg));
1396
+ color: var(--map-hint-ink, var(--card-3-ink));
1397
+ font: var(--map-hint-font, var(--type-label));
1397
1398
  text-align: center;
1398
- box-shadow: var(--shadow-float);
1399
+ box-shadow: var(--map-hint-shadow, var(--shadow-float));
1399
1400
  pointer-events: none;
1400
1401
  z-index: 1;
1401
1402
  }
@@ -1409,7 +1410,7 @@ button { font: inherit; cursor: pointer; }
1409
1410
  the sheet gives it up itself, as --_lift — the same token a raised sheet floats on. */
1410
1411
  .sheet-stage > .sheet { --_lift: var(--kb, 0rem); bottom: var(--_lift); }
1411
1412
  .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
+ .sheet--raised { --_lift: var(--tap-lg); bottom: var(--_lift); border-radius: var(--sheet-raised-radius, var(--r-card)); border-bottom: var(--border-w) solid var(--card-border); }
1413
1414
  .sheet-stage__foot {
1414
1415
  position: absolute;
1415
1416
  inset-inline: 0;
@@ -1418,8 +1419,8 @@ button { font: inherit; cursor: pointer; }
1418
1419
  margin: 0;
1419
1420
  display: grid;
1420
1421
  place-items: center;
1421
- font: var(--type-caption);
1422
- color: var(--card-3-ink);
1422
+ font: var(--sheet-stage-foot-font, var(--type-caption));
1423
+ color: var(--sheet-stage-foot-ink, var(--card-3-ink));
1423
1424
  z-index: 2;
1424
1425
  }
1425
1426
 
@@ -1543,13 +1544,13 @@ button { font: inherit; cursor: pointer; }
1543
1544
  .field {
1544
1545
  display: flex;
1545
1546
  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);
1547
+ gap: var(--field-gap, var(--sp-2));
1548
+ border: var(--border-w) solid var(--field-border, var(--action-border));
1549
+ border-radius: var(--field-radius, var(--r-control));
1550
+ background: var(--field-bg, var(--action-bg));
1551
+ color: var(--field-ink, var(--action-ink));
1552
+ padding-inline: var(--field-pad-x, var(--sp-4));
1553
+ min-height: var(--field-min-h, var(--tap));
1553
1554
  min-width: 0;
1554
1555
  }
1555
1556
 
@@ -1566,7 +1567,7 @@ button { font: inherit; cursor: pointer; }
1566
1567
  .field input::placeholder { color: inherit; opacity: 0.55; }
1567
1568
 
1568
1569
  /* 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); }
1570
+ .field--action { padding-right: var(--sp-1); min-height: var(--field-action-min-h, var(--tap-lg)); }
1570
1571
  .field--action > .icon-btn { margin-left: auto; }
1571
1572
 
1572
1573
  /* colored palettes drop the visible outline elsewhere — fields keep one */
@@ -1576,25 +1577,25 @@ button { font: inherit; cursor: pointer; }
1576
1577
  .add-row {
1577
1578
  display: inline-flex;
1578
1579
  align-items: center;
1579
- gap: var(--sp-2);
1580
+ gap: var(--add-row-gap, var(--sp-2));
1580
1581
  border: 0;
1581
1582
  background: none;
1582
1583
  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);
1584
+ font: var(--add-row-font, var(--type-label));
1585
+ font-size: var(--add-row-font-size, var(--fs-body));
1586
+ padding: var(--add-row-pad-y, var(--sp-2)) var(--add-row-pad-x, 0);
1587
+ min-height: var(--add-row-min-h, var(--tap-sm));
1587
1588
  }
1588
1589
  .add-row__plus {
1589
- width: var(--icon);
1590
- height: var(--icon);
1590
+ width: var(--add-row-plus-size, var(--icon));
1591
+ height: var(--add-row-plus-size, var(--icon));
1591
1592
  flex: none;
1592
1593
  border-radius: 50%;
1593
- background: var(--accent);
1594
- color: var(--on-accent);
1594
+ background: var(--add-row-plus-bg, var(--accent));
1595
+ color: var(--add-row-plus-ink, var(--on-accent));
1595
1596
  display: grid;
1596
1597
  place-items: center;
1597
- font-size: var(--fs-md);
1598
+ font-size: var(--add-row-plus-font-size, var(--fs-md));
1598
1599
  line-height: 1;
1599
1600
  }
1600
1601
  .add-row__plus .icon { width: var(--icon-xs); height: var(--icon-xs); }
@@ -1607,40 +1608,40 @@ button { font: inherit; cursor: pointer; }
1607
1608
  ============================================================ */
1608
1609
 
1609
1610
  /* ---- 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 */
1611
+ .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
1612
  /* the two endpoints get EQUAL columns whatever their words measure, so the via
1612
1613
  (the icon alone) sits at the card's centre; each dotted run lives on ITS
1613
1614
  endpoint's code row and fills that side's leftover — with short names the
1614
1615
  runs are long and equal, a long place name wraps in its half instead of
1615
1616
  pushing the plane (Lh 2026-09-16) */
1616
1617
 
1617
- .route__point { display: grid; gap: var(--sp-0); min-width: 0; }
1618
+ .route__point { display: grid; gap: var(--route-point-gap, var(--sp-0)); min-width: 0; }
1618
1619
  .route__point--end { text-align: right; }
1619
1620
 
1620
- .route__code { font: var(--type-h2); letter-spacing: var(--track-h2); display: flex; align-items: center; gap: var(--sp-3); }
1621
+ .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
1622
  .route__code::after, .route__point--end .route__code::before { content: ""; flex: 1; border-top: var(--rule-w) dotted currentColor; opacity: 0.65; }
1622
1623
  .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); }
1624
+ .route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
1625
+ .route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
1625
1626
  .card--3 .route__place { color: inherit; opacity: 0.7; }
1626
1627
 
1627
- .route__via { display: flex; align-items: center; opacity: 0.65; font-size: var(--fs-body); }
1628
+ .route__via { display: flex; align-items: center; opacity: 0.65; font-size: var(--route-via-font-size, var(--fs-body)); }
1628
1629
 
1629
1630
  /* ---- tile badge: tiny label over a big value (gate, seat, zone) ---- */
1630
1631
  .tile-badge {
1631
1632
  display: inline-grid;
1632
1633
  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);
1634
+ gap: var(--tile-badge-gap, var(--sp-0));
1635
+ padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
1636
+ border-radius: var(--tile-badge-radius, var(--r-badge));
1637
+ background: var(--tile-badge-bg, var(--card-3-bg));
1638
+ color: var(--tile-badge-ink, var(--card-3-ink));
1638
1639
  }
1639
- .tile-badge__label { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.7; }
1640
+ .tile-badge__label { font: var(--tile-badge-label-font, var(--type-overline)); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.7; }
1640
1641
  .tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
1641
1642
 
1642
1643
  /* 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); }
1644
+ .tile-badge--plain { background: transparent; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
1644
1645
  .tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
1645
1646
 
1646
1647
  /* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
@@ -1650,9 +1651,9 @@ button { font: inherit; cursor: pointer; }
1650
1651
  .note-row {
1651
1652
  display: flex;
1652
1653
  align-items: center;
1653
- gap: var(--sp-2);
1654
- font: var(--type-caption);
1655
- color: var(--card-muted);
1654
+ gap: var(--note-row-gap, var(--sp-2));
1655
+ font: var(--note-row-font, var(--type-caption));
1656
+ color: var(--note-row-ink, var(--card-muted));
1656
1657
  }
1657
1658
  .card--3 .note-row { color: inherit; opacity: 0.7; }
1658
1659
 
@@ -1662,6 +1663,14 @@ button { font: inherit; cursor: pointer; }
1662
1663
  topbar, chips, buttons, selects, bottom CTA pack
1663
1664
  ============================================================ */
1664
1665
 
1666
+ /* KNOBS (Lh 2026-09-21) — cardds is a framework: a project adjusts ONE component from outside, without touching this file.
1667
+ A knob is a custom property a rule reads WITH ITS DEFAULT AS THE FALLBACK — `padding-inline: var(--btn-pad-x, var(--sp-6))` —
1668
+ so it is declared nowhere: unset, nothing moves (tests/style-snapshot proves it); set in the project's theme.css it retunes
1669
+ that component everywhere; set on any element it retunes the components inside that element only. Names: --<component>-<what>
1670
+ (--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).
1671
+ The sizes that already were root tokens (--btn-h, --chip-h, --bar-h, --header-h, --fab…) are knobs too — set them the same way.
1672
+ 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. */
1673
+
1665
1674
  /* ---- the bars across the top of a screen: one height, whatever they carry ----
1666
1675
  .topbar (screen title) and .appbar (app context) are the same bar with
1667
1676
  different contents, so the height is declared here once rather than left to
@@ -1672,8 +1681,8 @@ button { font: inherit; cursor: pointer; }
1672
1681
  .topbar, .appbar {
1673
1682
  display: flex;
1674
1683
  align-items: center;
1675
- gap: var(--sp-3);
1676
- padding-block: var(--sp-2);
1684
+ gap: var(--bar-gap, var(--sp-3));
1685
+ padding-block: var(--bar-pad-y, var(--sp-2));
1677
1686
  min-height: var(--header-h);
1678
1687
  }
1679
1688
 
@@ -1702,9 +1711,9 @@ button { font: inherit; cursor: pointer; }
1702
1711
  top: 0;
1703
1712
  max-width: var(--screen-w);
1704
1713
  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) */
1714
+ 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
1715
  z-index: 40; /* topmost — card animations never cover it */
1707
- background: var(--surface); /* solid: sliding cards never show through */
1716
+ background: var(--appbar-bg, var(--surface)); /* solid: sliding cards never show through */
1708
1717
  }
1709
1718
  /* fixed, so it covers the top of the screen it sits in — reserve its height
1710
1719
  there, from the same token, the way the bottom bars reserve theirs. The
@@ -1721,19 +1730,19 @@ button { font: inherit; cursor: pointer; }
1721
1730
 
1722
1731
  /* ---- icon button (round) ---- */
1723
1732
  .icon-btn {
1724
- width: var(--tap); height: var(--tap);
1733
+ width: var(--icon-btn-size, var(--tap)); height: var(--icon-btn-size, var(--tap));
1725
1734
  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);
1735
+ border-radius: var(--icon-btn-radius, var(--r-chip));
1736
+ border: var(--border-w) solid var(--icon-btn-border, var(--outline)); /* see .btn — same reason */
1737
+ background: var(--icon-btn-bg, var(--action-bg));
1738
+ color: var(--icon-btn-ink, var(--action-ink));
1730
1739
  display: grid;
1731
1740
  place-items: center;
1732
1741
  font-size: var(--fs-lg);
1733
1742
  line-height: 1;
1734
1743
  }
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); }
1744
+ .icon-btn--invert { background: var(--icon-btn-invert-bg, var(--accent)); color: var(--icon-btn-invert-ink, var(--on-accent)); border-color: transparent; }
1745
+ .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
1746
  /* fab: the square floating action, bottom-right of a stage or screen */
1738
1747
  .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
1748
 
@@ -1742,21 +1751,21 @@ button { font: inherit; cursor: pointer; }
1742
1751
  /* --outline, not --action-border: same reason as .chip — on a palette where
1743
1752
  --card-1-bg equals --action-bg a transparent border makes a plain button
1744
1753
  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);
1754
+ border: var(--border-w) solid var(--btn-border, var(--outline));
1755
+ background: var(--btn-bg, var(--action-bg));
1756
+ color: var(--btn-ink, var(--action-ink));
1757
+ border-radius: var(--btn-radius, var(--r-chip));
1758
+ padding: var(--btn-pad-y, var(--sp-4)) var(--btn-pad-x, var(--sp-6));
1750
1759
  font: var(--type-label);
1751
- font-size: var(--fs-md);
1760
+ font-size: var(--btn-font-size, var(--fs-md));
1752
1761
  display: inline-flex;
1753
1762
  align-items: center;
1754
1763
  justify-content: center;
1755
- gap: var(--sp-2);
1764
+ gap: var(--btn-gap, var(--sp-2));
1756
1765
  min-height: var(--btn-h);
1757
1766
  text-decoration: none; /* .btn works as <a> too */
1758
1767
  }
1759
- .btn--primary { background: var(--accent); color: var(--on-accent); border-color: transparent; }
1768
+ .btn--primary { background: var(--btn-primary-bg, var(--accent)); color: var(--btn-primary-ink, var(--on-accent)); border-color: transparent; }
1760
1769
  /* quiet keeps a faint outline so it still reads as a button on colored palettes;
1761
1770
  it is ink on whatever it sits on — inherit, or on the dark slot (card--3) it
1762
1771
  would take the light-card action ink and vanish */
@@ -1776,25 +1785,25 @@ button { font: inherit; cursor: pointer; }
1776
1785
  .chip {
1777
1786
  display: inline-flex;
1778
1787
  align-items: center;
1779
- gap: var(--sp-2);
1780
- border-radius: var(--r-chip);
1788
+ gap: var(--chip-gap, var(--sp-2));
1789
+ border-radius: var(--chip-radius, var(--r-chip));
1781
1790
  /* --outline, not --action-border: a chip is a control, and on a palette
1782
1791
  where --card-1-bg == --action-bg (field, roast, hydro) a transparent
1783
1792
  border makes it invisible on a white card. Same edge fields, checks,
1784
1793
  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);
1794
+ border: var(--border-w) solid var(--chip-border, var(--outline));
1795
+ background: var(--chip-bg, var(--action-bg));
1796
+ color: var(--chip-ink, var(--action-ink));
1797
+ padding: var(--chip-pad-y, var(--sp-2)) var(--chip-pad-x, var(--sp-3));
1789
1798
  font: var(--type-label);
1790
- font-size: var(--fs-sm);
1799
+ font-size: var(--chip-font-size, var(--fs-sm));
1791
1800
  min-height: var(--chip-h);
1792
1801
  text-decoration: none;
1793
1802
  justify-self: start; /* as a grid child it hugs its text, never stretches */
1794
1803
  }
1795
1804
  .dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); } /* signal dot: chips, legends */
1796
1805
  .dot--b { background: var(--dot-b); }
1797
- .chip--on { background: var(--accent); color: var(--on-accent); border-color: transparent; }
1806
+ .chip--on { background: var(--chip-on-bg, var(--accent)); color: var(--chip-on-ink, var(--on-accent)); border-color: transparent; }
1798
1807
  /* dark-slot inversion lives in card.css (.card--3 :is(...)) */
1799
1808
 
1800
1809
  .chip-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
@@ -1930,6 +1939,9 @@ button { font: inherit; cursor: pointer; }
1930
1939
 
1931
1940
  /* the first round button sits at the left edge, the last at the right; a skip link between them stays centred */
1932
1941
  :is(.action-bar--pager, .action-bar__tier--pager) > .icon-btn:first-child { margin-right: auto; }
1942
+ /* …and whatever goes BETWEEN the two (a "skip" link) stands in the middle: the bar places it, never the link itself */
1943
+ :is(.action-bar--pager, .action-bar__tier--pager) > :not(.icon-btn):not(:first-child):not(:last-child) { margin-inline: auto; }
1944
+ :is(.action-bar--pager, .action-bar__tier--pager):has(> :nth-child(3)) > .icon-btn:first-child { margin-right: 0; }
1933
1945
 
1934
1946
  /* .screen's default bottom padding is a constant; derive the reserve from the
1935
1947
  bar instead, so a taller button (a longer label, a script with more leading)
@@ -1973,8 +1985,8 @@ button { font: inherit; cursor: pointer; }
1973
1985
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
1974
1986
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
1975
1987
  }
1976
- .ring__value { font: var(--type-stat); letter-spacing: var(--track-display); }
1977
- .ring__label { font: var(--type-caption); color: var(--card-muted); }
1988
+ .ring__value { font: var(--ring-value-font, var(--type-stat)); letter-spacing: var(--track-display); }
1989
+ .ring__label { font: var(--ring-label-font, var(--type-caption)); color: var(--ring-label-ink, var(--card-muted)); }
1978
1990
  .card--3 .ring__label { color: inherit; opacity: 0.7; }
1979
1991
  .ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
1980
1992
  .ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
@@ -1994,10 +2006,10 @@ button { font: inherit; cursor: pointer; }
1994
2006
  --on-accent: var(--_bg);
1995
2007
  list-style: none;
1996
2008
  margin: 0;
1997
- padding: var(--sp-2);
2009
+ padding: var(--track-pad, var(--sp-2));
1998
2010
  display: flex;
1999
2011
  align-items: center;
2000
- border-radius: var(--r-chip);
2012
+ border-radius: var(--track-radius, var(--r-chip));
2001
2013
  background: var(--_bg);
2002
2014
  color: var(--_ink);
2003
2015
  }
@@ -2038,9 +2050,9 @@ button { font: inherit; cursor: pointer; }
2038
2050
  grid-template-areas: "label num" "foot num";
2039
2051
  align-items: end;
2040
2052
  gap: var(--sp-2) var(--sp-4);
2041
- min-height: var(--tile-h);
2053
+ min-height: var(--card-band-min-h, var(--tile-h));
2042
2054
  }
2043
- .band__label { grid-area: label; align-self: start; font: var(--type-label); font-size: var(--fs-body); line-height: 1.2; }
2055
+ .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
2056
  .band__num {
2045
2057
  grid-area: num;
2046
2058
  font: 800 var(--fs-giant) / 0.9 var(--font-display);
@@ -2048,14 +2060,14 @@ button { font: inherit; cursor: pointer; }
2048
2060
  font-variant-numeric: tabular-nums;
2049
2061
  }
2050
2062
  .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); }
2063
+ .band__foot { grid-area: foot; font: var(--band-foot-font, var(--type-caption)); color: var(--band-foot-ink, var(--card-muted)); }
2052
2064
  .card--3 .band__foot { color: inherit; opacity: 0.7; }
2053
2065
  /* band stack: ONE card boundary; bands are frameless inside it, a hairline
2054
2066
  between each. Bands may still carry slot tints (card--2 / card--3). */
2055
2067
  .band-stack {
2056
2068
  display: grid;
2057
- border: var(--border-w) solid var(--card-border);
2058
- border-radius: var(--r-card);
2069
+ border: var(--border-w) solid var(--band-stack-border, var(--card-border));
2070
+ border-radius: var(--band-stack-radius, var(--r-card));
2059
2071
  overflow: hidden;
2060
2072
  }
2061
2073
  .band-stack > .card--band { border: 0; border-radius: 0; box-shadow: none; }
@@ -2106,7 +2118,7 @@ button { font: inherit; cursor: pointer; }
2106
2118
 
2107
2119
  /* ---- picker: horizontal snap picker, centre value big ----
2108
2120
  <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; }
2121
+ .picker { display: grid; gap: var(--picker-gap, var(--sp-1)); justify-items: center; position: relative; }
2110
2122
  .picker__rail {
2111
2123
  width: 100%;
2112
2124
  display: grid;
@@ -2114,7 +2126,7 @@ button { font: inherit; cursor: pointer; }
2114
2126
  /* side padding = one third of the rail, so the first and last value can sit
2115
2127
  in the centre; the content box is then one third wide, and each column
2116
2128
  fills it (percent columns resolve against the content box) */
2117
- padding-inline: calc(100% / 3);
2129
+ padding-inline: var(--picker-rail-pad-x, calc(100% / 3));
2118
2130
  grid-auto-columns: 100%;
2119
2131
  overflow-x: auto;
2120
2132
  scroll-snap-type: x mandatory;
@@ -2127,8 +2139,8 @@ button { font: inherit; cursor: pointer; }
2127
2139
  border: 0;
2128
2140
  background: transparent;
2129
2141
  color: inherit;
2130
- padding: var(--sp-2) 0;
2131
- font: var(--type-stat);
2142
+ padding: var(--picker-item-pad-y, var(--sp-2)) var(--picker-item-pad-x, 0);
2143
+ font: var(--picker-item-font, var(--type-stat));
2132
2144
  letter-spacing: var(--track-display);
2133
2145
  font-variant-numeric: tabular-nums;
2134
2146
  opacity: 0.3;
@@ -2147,7 +2159,7 @@ button { font: inherit; cursor: pointer; }
2147
2159
  left: calc(100% / 3);
2148
2160
  }
2149
2161
  .picker::after { left: auto; right: calc(100% / 3); }
2150
- .picker__unit { font: var(--type-caption); color: var(--card-muted); }
2162
+ .picker__unit { font: var(--picker-unit-font, var(--type-caption)); color: var(--picker-unit-ink, var(--card-muted)); }
2151
2163
  .card--3 .picker__unit { color: inherit; opacity: 0.7; }
2152
2164
  /* the scrolled-to item lights up by position — no JS */
2153
2165
  @supports (animation-timeline: view(x)) {
@@ -2163,30 +2175,30 @@ button { font: inherit; cursor: pointer; }
2163
2175
  .badge {
2164
2176
  display: inline-grid;
2165
2177
  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);
2178
+ min-width: var(--badge-min-w, var(--badge));
2179
+ height: var(--badge-h, var(--badge));
2180
+ padding: var(--badge-pad-y, 0) var(--badge-pad-x, var(--sp-2));
2181
+ border-radius: var(--badge-radius, var(--r-chip));
2182
+ background: var(--badge-bg, var(--accent));
2183
+ color: var(--badge-ink, var(--on-accent));
2184
+ font: var(--badge-font, var(--type-label));
2173
2185
  font-variant-numeric: tabular-nums;
2174
2186
  vertical-align: middle;
2175
2187
  }
2176
- .badge--soft { background: var(--ink-tint); color: inherit; }
2188
+ .badge--soft { background: var(--badge-soft-bg, var(--ink-tint)); color: inherit; }
2177
2189
 
2178
2190
  /* ---- bars: three or four bars with a count on top — the glance chart ----
2179
2191
  <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)); }
2192
+ .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
2193
  .bar {
2182
2194
  flex: 1;
2183
2195
  min-width: 0;
2184
2196
  display: grid;
2185
2197
  align-content: end;
2186
2198
  justify-items: center;
2187
- gap: var(--sp-1);
2199
+ gap: var(--bar-gap, var(--sp-1));
2188
2200
  height: 100%;
2189
- font: var(--type-caption);
2201
+ font: var(--bar-font, var(--type-caption));
2190
2202
  font-weight: 700;
2191
2203
  }
2192
2204
  .bar::after {
@@ -2217,9 +2229,9 @@ button { font: inherit; cursor: pointer; }
2217
2229
  border-radius: 50%;
2218
2230
  display: grid;
2219
2231
  place-items: center;
2220
- background: var(--card-2-bg);
2221
- color: var(--card-2-ink);
2222
- font: var(--type-label);
2232
+ background: var(--avatar-bg, var(--card-2-bg));
2233
+ color: var(--avatar-ink, var(--card-2-ink));
2234
+ font: var(--avatar-font, var(--type-label));
2223
2235
  font-size: calc(var(--_size) * 0.3);
2224
2236
  text-transform: uppercase;
2225
2237
  letter-spacing: 0.02em;
@@ -2232,10 +2244,10 @@ button { font: inherit; cursor: pointer; }
2232
2244
  .avatar--lg { --_size: var(--avatar-lg); }
2233
2245
  .avatar--xl { --_size: var(--avatar-xl); } /* sheet head icon ring */
2234
2246
  /* 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; }
2247
+ .avatar--outline { background: transparent; color: inherit; border: var(--border-w) solid var(--avatar-outline-border, var(--outline)); }
2248
+ .avatar--add { background: transparent; color: inherit; border: var(--border-w) dashed var(--avatar-add-border, var(--outline)); cursor: pointer; }
2237
2249
  /* accent avatar: the "+7" tail, the selected person */
2238
- .avatar--on { background: var(--accent); color: var(--on-accent); }
2250
+ .avatar--on { background: var(--avatar-on-bg, var(--accent)); color: var(--avatar-on-ink, var(--on-accent)); }
2239
2251
  /* halo: faint concentric rings behind a hero avatar (decorative) */
2240
2252
  .avatar--halo::before {
2241
2253
  content: "";
@@ -2257,7 +2269,7 @@ button { font: inherit; cursor: pointer; }
2257
2269
  <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
2270
  .avatar-pick {
2259
2271
  display: flex;
2260
- gap: var(--sp-4);
2272
+ gap: var(--avatar-pick-gap, var(--sp-4));
2261
2273
  overflow-x: auto;
2262
2274
  scroll-snap-type: x proximity;
2263
2275
  scrollbar-width: none; /* starts at the card's padding edge, like every row */
@@ -2266,18 +2278,18 @@ button { font: inherit; cursor: pointer; }
2266
2278
  .avatar-pick__item {
2267
2279
  scroll-snap-align: start;
2268
2280
  flex: none;
2269
- width: var(--avatar-lg);
2281
+ width: var(--avatar-pick-item-w, var(--avatar-lg));
2270
2282
  display: grid;
2271
2283
  justify-items: center;
2272
- gap: var(--sp-2);
2284
+ gap: var(--avatar-pick-item-gap, var(--sp-2));
2273
2285
  border: 0;
2274
2286
  background: transparent;
2275
2287
  color: inherit;
2276
2288
  padding: 0;
2277
2289
  text-align: center;
2278
2290
  }
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)); }
2291
+ .avatar-pick__name { font: var(--avatar-pick-name-font, var(--type-label)); font-size: var(--avatar-pick-name-font-size, var(--fs-sm)); }
2292
+ .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
2293
  .card--3 .avatar-pick__role { color: inherit; opacity: 0.7; }
2282
2294
 
2283
2295
  /* ---- css/choice.css — check, toggle, toggle chip, chip grid, days, calendar, mood, pin, composer ---- */
@@ -2295,13 +2307,13 @@ button { font: inherit; cursor: pointer; }
2295
2307
  <input type="checkbox" class="check" aria-label="Done"> */
2296
2308
  .check {
2297
2309
  appearance: none;
2298
- width: var(--tap);
2299
- height: var(--tap);
2310
+ width: var(--check-size, var(--tap));
2311
+ height: var(--check-size, var(--tap));
2300
2312
  flex: none;
2301
2313
  margin: 0;
2302
2314
  border-radius: 50%;
2303
- border: var(--border-w) solid var(--outline);
2304
- background: var(--action-bg);
2315
+ border: var(--border-w) solid var(--check-border, var(--outline));
2316
+ background: var(--check-bg, var(--action-bg));
2305
2317
  display: grid;
2306
2318
  place-items: center;
2307
2319
  cursor: pointer;
@@ -2318,7 +2330,7 @@ button { font: inherit; cursor: pointer; }
2318
2330
  .check:checked { background: var(--accent); border-color: transparent; }
2319
2331
  .check:checked::after { opacity: 1; }
2320
2332
  .check:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2321
- .check--sm { width: var(--tap-sm); height: var(--tap-sm); }
2333
+ .check--sm { width: var(--check-sm-size, var(--tap-sm)); height: var(--check-sm-size, var(--tap-sm)); }
2322
2334
 
2323
2335
  /* ---- toggle: big switch ----
2324
2336
  <input type="checkbox" role="switch" class="toggle" aria-label="Private"> */
@@ -2331,9 +2343,9 @@ button { font: inherit; cursor: pointer; }
2331
2343
  height: var(--_h);
2332
2344
  flex: none;
2333
2345
  margin: 0;
2334
- border-radius: var(--r-chip);
2335
- border: var(--border-w) solid var(--outline);
2336
- background: var(--action-bg);
2346
+ border-radius: var(--toggle-radius, var(--r-chip));
2347
+ border: var(--border-w) solid var(--toggle-border, var(--outline));
2348
+ background: var(--toggle-bg, var(--action-bg));
2337
2349
  position: relative;
2338
2350
  cursor: pointer;
2339
2351
  transition: background 0.2s ease;
@@ -2362,7 +2374,7 @@ button { font: inherit; cursor: pointer; }
2362
2374
  .chip-grid {
2363
2375
  display: grid;
2364
2376
  grid-template-columns: repeat(auto-fill, minmax(min(5.5rem, 100%), 1fr));
2365
- gap: var(--sp-2);
2377
+ gap: var(--chip-grid-gap, var(--sp-2));
2366
2378
  }
2367
2379
  /* .chip hugs its text as a loose grid child (justify-self: start); inside a
2368
2380
  chip grid the whole point is equal, full-width pick targets, so stretch.
@@ -2389,7 +2401,7 @@ button { font: inherit; cursor: pointer; }
2389
2401
 
2390
2402
  /* ---- day strip: a week, one day selected ----
2391
2403
  <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); }
2404
+ .day-strip { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--day-strip-gap, var(--sp-1)); }
2393
2405
  .day {
2394
2406
  display: grid;
2395
2407
  /* three rows, always: name · number · dot. The dot's row is reserved even on
@@ -2399,33 +2411,33 @@ button { font: inherit; cursor: pointer; }
2399
2411
  grid-template-rows: auto auto var(--dot-xs);
2400
2412
  align-content: center; /* the group sits centred in the cell, not spread out */
2401
2413
  justify-items: center;
2402
- gap: var(--sp-0);
2414
+ gap: var(--day-gap, var(--sp-0));
2403
2415
  min-width: 0;
2404
- min-height: var(--bar-h);
2405
- padding-block: var(--sp-3); /* equal air top and bottom, so the selected pill
2416
+ min-height: var(--day-min-h, var(--bar-h));
2417
+ padding-block: var(--day-pad-y, var(--sp-3)); /* equal air top and bottom, so the selected pill
2406
2418
  is the same shape on every day */
2407
2419
  padding-inline: 0;
2408
2420
  border: 0;
2409
- border-radius: var(--r-chip);
2421
+ border-radius: var(--day-radius, var(--r-chip));
2410
2422
  background: transparent;
2411
2423
  color: inherit;
2412
2424
  opacity: 0.6;
2413
2425
  }
2414
- .day__name { font: var(--type-caption); }
2426
+ .day__name { font: var(--day-name-font, var(--type-caption)); }
2415
2427
  .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; }
2428
+ .day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent)); opacity: 1; }
2429
+ .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
2430
 
2419
2431
  /* ---- calendar: month grid, 44px cells, one accent day, optional marks ----
2420
2432
  <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); }
2433
+ .calendar { display: grid; gap: var(--calendar-gap, var(--sp-3)); }
2434
+ .calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--calendar-head-gap, var(--sp-2)); }
2435
+ .calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--calendar-grid-gap, var(--sp-1)); justify-items: center; }
2436
+ .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
2437
  .card--3 .calendar__wd { color: inherit; opacity: 0.7; }
2426
2438
  .calendar__day {
2427
2439
  width: 100%;
2428
- max-width: var(--tap);
2440
+ max-width: var(--calendar-day-max-w, var(--tap));
2429
2441
  aspect-ratio: 1;
2430
2442
  display: grid;
2431
2443
  place-items: center;
@@ -2437,9 +2449,9 @@ button { font: inherit; cursor: pointer; }
2437
2449
  font-variant-numeric: tabular-nums;
2438
2450
  grid-column-start: var(--start, auto); /* --start on the 1st: weekday column 1–7 */
2439
2451
  }
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; }
2452
+ .calendar__day--mark { background: var(--calendar-day-mark-bg, var(--accent-soft)); }
2453
+ .calendar__day--mark2 { background: var(--calendar-day-mark2-bg, var(--accent-mid)); color: var(--calendar-day-mark2-ink, var(--on-accent)); }
2454
+ .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
2455
  .calendar__day:disabled { opacity: 0.3; }
2444
2456
 
2445
2457
  /* ---- mood: five dots in a pill, the picked one grows ----
@@ -2447,19 +2459,19 @@ button { font: inherit; cursor: pointer; }
2447
2459
  .mood {
2448
2460
  margin: 0;
2449
2461
  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);
2462
+ padding: var(--mood-pad, var(--sp-1));
2463
+ border: var(--border-w) solid var(--mood-border, var(--outline));
2464
+ border-radius: var(--mood-radius, var(--r-chip));
2465
+ background: var(--mood-bg, var(--action-bg));
2466
+ color: var(--mood-ink, var(--action-ink));
2455
2467
  display: flex;
2456
2468
  justify-content: space-between;
2457
2469
  }
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; }
2470
+ .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
2471
  .mood__opt > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
2460
2472
  .mood__dot {
2461
- width: var(--icon-xs);
2462
- height: var(--icon-xs);
2473
+ width: var(--mood-dot-size, var(--icon-xs));
2474
+ height: var(--mood-dot-size, var(--icon-xs));
2463
2475
  border-radius: 50%;
2464
2476
  background: var(--_mood, currentColor);
2465
2477
  transition: scale 0.2s ease, box-shadow 0.2s ease;
@@ -2474,18 +2486,18 @@ button { font: inherit; cursor: pointer; }
2474
2486
 
2475
2487
  /* ---- pin: big code entry, one digit per pill ----
2476
2488
  <div class="pin"><input class="pin__cell" inputmode="numeric" maxlength="1" placeholder=" ">×4</div> */
2477
- .pin { display: flex; gap: var(--sp-3); }
2489
+ .pin { display: flex; gap: var(--pin-gap, var(--sp-3)); }
2478
2490
  .pin__cell {
2479
2491
  flex: 1;
2480
2492
  min-width: 0;
2481
- max-width: var(--fab);
2493
+ max-width: var(--pin-cell-max-w, var(--fab));
2482
2494
  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);
2495
+ border: var(--border-w) solid var(--pin-cell-border, var(--outline));
2496
+ border-radius: var(--pin-cell-radius, var(--r-chip));
2497
+ background: var(--pin-cell-bg, var(--action-bg));
2498
+ color: var(--pin-cell-ink, var(--action-ink));
2487
2499
  text-align: center;
2488
- font: var(--type-stat);
2500
+ font: var(--pin-cell-font, var(--type-stat));
2489
2501
  font-variant-numeric: tabular-nums;
2490
2502
  caret-color: currentColor;
2491
2503
  }
@@ -2495,7 +2507,7 @@ button { font: inherit; cursor: pointer; }
2495
2507
  /* ---- composer: big text area with a bottom bar (icon · hint · send) ----
2496
2508
  <div class="composer"><textarea class="composer__input" placeholder="What do you need?"></textarea>
2497
2509
  <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); }
2510
+ .composer { display: grid; gap: var(--composer-gap, var(--sp-3)); }
2499
2511
  .composer__input {
2500
2512
  width: 100%;
2501
2513
  min-height: 11rem;
@@ -2509,8 +2521,8 @@ button { font: inherit; cursor: pointer; }
2509
2521
  outline: none;
2510
2522
  }
2511
2523
  .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); }
2524
+ .composer__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--composer-bar-gap, var(--sp-3)); }
2525
+ .composer__hint { font: var(--composer-hint-font, var(--type-caption)); color: var(--composer-hint-ink, var(--card-muted)); }
2514
2526
  .card--3 .composer__hint { color: inherit; opacity: 0.7; }
2515
2527
 
2516
2528
  /* ---- slider: one value along a track, a thumb-size knob ----
@@ -2519,7 +2531,7 @@ button { font: inherit; cursor: pointer; }
2519
2531
  slider does, the input's aria-label is enough. The track is a hairline rail;
2520
2532
  the knob is a full --tap, and the whole range is --tap tall, so the thumb has
2521
2533
  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 */
2534
+ .slider { display: grid; gap: var(--slider-gap, var(--sp-1)); flex: 1; min-width: 0; position: relative; } /* relative: the glyph rides the knob */
2523
2535
  /* ---- the sample glyph: one letter IN the knob, on-accent, at the type size
2524
2536
  the slider sets — so a text-size slider shows the size before anything is
2525
2537
  read (Lh, 2026-09-12). cardds.js publishes the knob's place on the .slider
@@ -2530,8 +2542,8 @@ button { font: inherit; cursor: pointer; }
2530
2542
  position: absolute;
2531
2543
  bottom: 0;
2532
2544
  left: calc(var(--tap) / 2 + (100% - var(--tap)) * var(--slider-at, 0));
2533
- width: var(--tap);
2534
- height: var(--tap);
2545
+ width: var(--slider-glyph-size, var(--tap));
2546
+ height: var(--slider-glyph-size, var(--tap));
2535
2547
  /* the eye centres the INK, not the line box: a letter that stands on the
2536
2548
  baseline (ก) has no descender, while the face's box keeps 0.30em under it
2537
2549
  (fonts.css) — so the ink lands low when the box is centred; lifted 0.15em
@@ -2539,15 +2551,15 @@ button { font: inherit; cursor: pointer; }
2539
2551
  translate: -50% -0.15em;
2540
2552
  display: grid;
2541
2553
  place-items: center;
2542
- font: var(--type-h2);
2543
- color: var(--on-accent);
2554
+ font: var(--slider-glyph-font, var(--type-h2));
2555
+ color: var(--slider-glyph-ink, var(--on-accent));
2544
2556
  pointer-events: none;
2545
2557
  }
2546
- .slider__label { font: var(--type-label); }
2558
+ .slider__label { font: var(--slider-label-font, var(--type-label)); }
2547
2559
  .slider__range {
2548
2560
  appearance: none;
2549
2561
  width: 100%;
2550
- height: var(--tap);
2562
+ height: var(--slider-range-h, var(--tap));
2551
2563
  margin: 0;
2552
2564
  background: transparent;
2553
2565
  cursor: pointer;
@@ -2588,8 +2600,8 @@ button { font: inherit; cursor: pointer; }
2588
2600
  .row {
2589
2601
  display: flex;
2590
2602
  align-items: center;
2591
- gap: var(--sp-3);
2592
- min-height: var(--tap); /* the tap target's room — the row's only vertical size */
2603
+ gap: var(--row-gap, var(--sp-3));
2604
+ min-height: var(--row-min-h, var(--tap)); /* the tap target's room — the row's only vertical size */
2593
2605
  min-width: 0;
2594
2606
  color: inherit;
2595
2607
  text-decoration: none; /* .row works as <a> too — it is a tap target */
@@ -2598,20 +2610,20 @@ button { font: inherit; cursor: pointer; }
2598
2610
  Space BETWEEN rows is .rows' gap; a lone row as card content takes the card's
2599
2611
  gap. (.row--flush, which zeroed the old padding, is gone — every row is flush.) */
2600
2612
  .row__lead {
2601
- width: var(--tap);
2602
- height: var(--tap);
2613
+ width: var(--row-lead-size, var(--tap));
2614
+ height: var(--row-lead-size, var(--tap));
2603
2615
  flex: none;
2604
- border-radius: var(--r-control);
2605
- border: var(--border-w) solid var(--outline);
2616
+ border-radius: var(--row-lead-radius, var(--r-control));
2617
+ border: var(--border-w) solid var(--row-lead-border, var(--outline));
2606
2618
  display: grid;
2607
2619
  place-items: center;
2608
2620
  }
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); }
2621
+ .row__body { flex: 1; min-width: 0; display: grid; gap: var(--row-body-gap, var(--sp-0)); }
2622
+ .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) */
2623
+ .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
2624
  .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 */
2625
+ .row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
2626
+ .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
2627
  .row--lg .row__title { font: 700 var(--fs-lg)/1.2 var(--font-display); }
2616
2628
  /* a finished item in a checklist (a to-do list, an onboarding "complete your introduction" list):
2617
2629
  the words go muted, a filled tick sits at the end, and the row is no longer a tap target —
@@ -2621,13 +2633,13 @@ button { font: inherit; cursor: pointer; }
2621
2633
  .row--done .row__title { color: var(--card-muted); }
2622
2634
  .card--3 .row--done .row__title { color: inherit; opacity: 0.7; }
2623
2635
  .row__done {
2624
- width: var(--tap-sm);
2625
- height: var(--tap-sm);
2636
+ width: var(--row-done-size, var(--tap-sm));
2637
+ height: var(--row-done-size, var(--tap-sm));
2626
2638
  flex: none;
2627
2639
  margin-left: auto;
2628
2640
  border-radius: 50%;
2629
- background: var(--accent);
2630
- color: var(--on-accent);
2641
+ background: var(--row-done-bg, var(--accent));
2642
+ color: var(--row-done-ink, var(--on-accent));
2631
2643
  display: grid;
2632
2644
  place-items: center;
2633
2645
  }
@@ -2648,20 +2660,20 @@ button { font: inherit; cursor: pointer; }
2648
2660
  /* ---- kv: label left, value right ----
2649
2661
  <dl class="kv"><div class="kv__row"><dt class="kv__key">Pay with</dt><dd class="kv__val">GHO</dd></div>…</dl> */
2650
2662
  .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); }
2663
+ .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
2664
  .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); }
2665
+ .kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-muted)); }
2654
2666
  .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; }
2667
+ .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
2668
 
2657
2669
  /* ---- kv grid: value over label, cells split by a rule (Sex | Weight) ----
2658
2670
  <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); }
2671
+ .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
2672
  .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; }
2673
+ .kv-cell { display: grid; gap: var(--kv-cell-gap, var(--sp-0)); align-content: start; min-width: 0; }
2662
2674
  .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); }
2675
+ .kv-cell__val { font: var(--kv-cell-val-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
2676
+ .kv-cell__key { font: var(--kv-cell-key-font, var(--type-caption)); color: var(--kv-cell-key-ink, var(--card-muted)); }
2665
2677
  .card--3 .kv-cell__key { color: inherit; opacity: 0.7; }
2666
2678
 
2667
2679
  /* ---- bubble: a chat message — theirs on the left, mine on the right (Lh 2026-09-18, replaces the chip's tail) ----
@@ -2673,7 +2685,7 @@ button { font: inherit; cursor: pointer; }
2673
2685
  <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
2686
  <div class="bubble bubble--right">…</div>
2675
2687
  </div> */
2676
- .thread { display: grid; gap: var(--sp-4); }
2688
+ .thread { display: grid; gap: var(--thread-gap, var(--sp-4)); }
2677
2689
  /* two columns: the message (max-content, but it gives way — minmax(0) — so a long one shrinks
2678
2690
  and wraps while the actions stay beside it) and the actions; the meta on a second row under the message */
2679
2691
  .bubble {
@@ -2693,12 +2705,12 @@ button { font: inherit; cursor: pointer; }
2693
2705
  .bubble__body {
2694
2706
  margin: 0;
2695
2707
  min-width: 0;
2696
- padding: var(--sp-3) var(--sp-4);
2697
- border-radius: var(--r-card);
2708
+ padding: var(--bubble-body-pad-y, var(--sp-3)) var(--bubble-body-pad-x, var(--sp-4));
2709
+ border-radius: var(--bubble-body-radius, var(--r-card));
2698
2710
  border-bottom-left-radius: var(--r-control);
2699
- background: var(--card-2-bg);
2700
- color: var(--card-2-ink);
2701
- font: var(--type-body);
2711
+ background: var(--bubble-body-bg, var(--card-2-bg));
2712
+ color: var(--bubble-body-ink, var(--card-2-ink));
2713
+ font: var(--bubble-body-font, var(--type-body));
2702
2714
  overflow-wrap: anywhere;
2703
2715
  }
2704
2716
  .bubble--right > .bubble__body {
@@ -2708,8 +2720,8 @@ button { font: inherit; cursor: pointer; }
2708
2720
  color: var(--on-accent);
2709
2721
  text-align: right;
2710
2722
  }
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); }
2723
+ .bubble__actions { display: flex; gap: var(--bubble-actions-gap, var(--sp-1)); flex: none; opacity: 0.7; }
2724
+ .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
2725
  .bubble--right > .bubble__meta { justify-self: end; }
2714
2726
  .bubble__meta > .icon { color: var(--accent); }
2715
2727
  .card--3 .bubble__meta { color: inherit; opacity: 0.7; }
@@ -2721,7 +2733,7 @@ button { font: inherit; cursor: pointer; }
2721
2733
  margin: 0;
2722
2734
  padding: 0 0 0 var(--sp-6);
2723
2735
  display: grid;
2724
- gap: var(--sp-5);
2736
+ gap: var(--timeline-gap, var(--sp-5));
2725
2737
  position: relative;
2726
2738
  }
2727
2739
  .timeline::before {
@@ -2732,7 +2744,7 @@ button { font: inherit; cursor: pointer; }
2732
2744
  bottom: var(--sp-3);
2733
2745
  border-left: var(--border-w) solid var(--hairline);
2734
2746
  }
2735
- .timeline__item { display: grid; gap: var(--sp-2); position: relative; min-width: 0; }
2747
+ .timeline__item { display: grid; gap: var(--timeline-item-gap, var(--sp-2)); position: relative; min-width: 0; }
2736
2748
  .timeline__item::before {
2737
2749
  content: "";
2738
2750
  position: absolute;
@@ -2745,37 +2757,37 @@ button { font: inherit; cursor: pointer; }
2745
2757
  }
2746
2758
  .timeline__mark {
2747
2759
  justify-self: start;
2748
- font: var(--type-overline);
2760
+ font: var(--timeline-mark-font, var(--type-overline));
2749
2761
  letter-spacing: var(--track-overline);
2750
2762
  text-transform: uppercase;
2751
- padding: var(--sp-1) var(--sp-2);
2752
- border-radius: var(--r-chip);
2753
- background: var(--ink-tint);
2763
+ padding: var(--timeline-mark-pad-y, var(--sp-1)) var(--timeline-mark-pad-x, var(--sp-2));
2764
+ border-radius: var(--timeline-mark-radius, var(--r-chip));
2765
+ background: var(--timeline-mark-bg, var(--ink-tint));
2754
2766
  }
2755
2767
 
2756
2768
  /* ---- legend: signal dots with captions ----
2757
2769
  <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); }
2770
+ .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
2771
  .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); }
2772
+ .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
2773
 
2762
2774
  /* ---- link: "See all ↗" in a card head, big enough to tap ----
2763
2775
  <a class="link" href="#">See all <svg class="icon icon--xs"/></a> */
2764
2776
  .link {
2765
2777
  display: inline-flex;
2766
2778
  align-items: center;
2767
- gap: var(--sp-1);
2768
- min-height: var(--tap-sm);
2779
+ gap: var(--link-gap, var(--sp-1));
2780
+ min-height: var(--link-min-h, var(--tap-sm));
2769
2781
  color: inherit;
2770
2782
  text-decoration: none;
2771
- font: var(--type-label);
2772
- font-size: var(--fs-body);
2783
+ font: var(--link-font, var(--type-label));
2784
+ font-size: var(--link-font-size, var(--fs-body));
2773
2785
  border: 0;
2774
2786
  background: transparent;
2775
2787
  padding: 0;
2776
2788
  cursor: pointer;
2777
2789
  }
2778
- .link--muted { color: var(--card-muted); }
2790
+ .link--muted { color: var(--link-muted-ink, var(--card-muted)); }
2779
2791
  .card--3 .link--muted { color: inherit; opacity: 0.7; }
2780
2792
 
2781
2793
  /* ---- css/media.css — cover card, quote, tile grid, fold, mosaic, wave ---- */
@@ -2824,16 +2836,16 @@ button { font: inherit; cursor: pointer; }
2824
2836
 
2825
2837
  /* ---- quote: giant mark, big text, attribution ----
2826
2838
  <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); }
2839
+ .quote { margin: 0; display: grid; gap: var(--quote-gap, var(--sp-3)); }
2828
2840
  .quote::before {
2829
2841
  content: "\201C";
2830
2842
  font: 900 calc(var(--fs-h1) * 3)/0.55 var(--font-display);
2831
2843
  height: var(--sp-10);
2832
2844
  opacity: 0.9;
2833
2845
  }
2834
- .quote__text { margin: 0; font: var(--type-h1); letter-spacing: var(--track-h1); text-wrap: balance; }
2846
+ .quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
2835
2847
  .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); }
2848
+ .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
2849
  .card--3 .quote__by { color: inherit; opacity: 0.7; }
2838
2850
 
2839
2851
  /* ---- tile grid: big square tiles — icon top, label bottom ----
@@ -2842,36 +2854,36 @@ button { font: inherit; cursor: pointer; }
2842
2854
  .tile-grid {
2843
2855
  display: grid;
2844
2856
  grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), 100%), 1fr));
2845
- gap: var(--sp-3);
2857
+ gap: var(--tile-grid-gap, var(--sp-3));
2846
2858
  }
2847
2859
  .tile-grid--3 { --tile-min: 6rem; } /* three across on a phone (attachments, upload kinds) */
2848
2860
  .tile {
2849
2861
  display: grid;
2850
2862
  align-content: space-between;
2851
2863
  justify-items: start;
2852
- gap: var(--sp-5);
2853
- min-height: var(--tile-h);
2864
+ gap: var(--tile-gap, var(--sp-5));
2865
+ min-height: var(--tile-min-h, var(--tile-h));
2854
2866
  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);
2867
+ padding: var(--tile-pad, var(--sp-4));
2868
+ border: var(--border-w) solid var(--tile-border, var(--outline));
2869
+ border-radius: var(--tile-radius, var(--r-card));
2870
+ background: var(--tile-bg, var(--action-bg));
2871
+ color: var(--tile-ink, var(--action-ink));
2860
2872
  font: inherit;
2861
2873
  text-align: left;
2862
2874
  position: relative;
2863
2875
  cursor: pointer;
2864
2876
  }
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; }
2877
+ .tile__label { font: var(--tile-label-font, var(--type-title)); }
2878
+ .tile__value { font: var(--tile-value-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
2879
+ .tile__sub { font: var(--tile-sub-font, var(--type-caption)); opacity: 0.7; }
2880
+ .tile__text { display: grid; gap: var(--tile-text-gap, var(--sp-0)); }
2881
+ .tile--on { background: var(--tile-on-bg, var(--accent)); color: var(--tile-on-ink, var(--on-accent)); border-color: transparent; }
2870
2882
  .tile--pick > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
2871
2883
  .tile--pick:has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
2872
2884
  .tile--pick:has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
2873
2885
  .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; }
2886
+ .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
2887
  /* upload: dashed, centred plus + label */
2876
2888
  .tile--upload {
2877
2889
  border-style: dashed;
@@ -2879,12 +2891,12 @@ button { font: inherit; cursor: pointer; }
2879
2891
  color: inherit;
2880
2892
  align-content: center;
2881
2893
  justify-items: center;
2882
- gap: var(--sp-2);
2894
+ gap: var(--tile-upload-gap, var(--sp-2));
2883
2895
  text-align: center;
2884
2896
  }
2885
2897
  /* 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; }
2898
+ .tile--2 { background: var(--tile-2-bg, var(--card-2-bg)); color: var(--tile-2-ink, var(--card-2-ink)); border-color: transparent; }
2899
+ .tile--3 { background: var(--tile-3-bg, var(--card-3-bg)); color: var(--tile-3-ink, var(--card-3-ink)); border-color: transparent; }
2888
2900
 
2889
2901
  /* ---- fold: a sticky-note corner on any card (decorative) ----
2890
2902
  No border. The card's outline follows its border box, so it ran straight
@@ -2908,7 +2920,7 @@ button { font: inherit; cursor: pointer; }
2908
2920
  <span class="wave"></span> — sits in a .row__body, fills the width */
2909
2921
  .wave {
2910
2922
  display: block;
2911
- height: var(--icon);
2923
+ height: var(--wave-h, var(--icon));
2912
2924
  width: 100%;
2913
2925
  background: repeating-linear-gradient(90deg, currentColor 0 var(--sp-1), transparent var(--sp-1) var(--sp-2));
2914
2926
  /* masks only read alpha: currentColor = "opaque" without naming a color */
@@ -2923,9 +2935,9 @@ button { font: inherit; cursor: pointer; }
2923
2935
  display: grid;
2924
2936
  grid-template-columns: 2fr 1fr;
2925
2937
  grid-auto-rows: 1fr;
2926
- gap: var(--sp-1);
2938
+ gap: var(--mosaic-gap, var(--sp-1));
2927
2939
  aspect-ratio: 4 / 3;
2928
- border-radius: var(--r-media);
2940
+ border-radius: var(--mosaic-radius, var(--r-media));
2929
2941
  overflow: hidden;
2930
2942
  }
2931
2943
  .mosaic > :first-child { grid-row: span 2; }
@@ -2955,16 +2967,16 @@ button { font: inherit; cursor: pointer; }
2955
2967
  position: relative;
2956
2968
  aspect-ratio: 3 / 2;
2957
2969
  perspective: 60rem;
2958
- border-radius: var(--r-media);
2970
+ border-radius: var(--postcard-radius, var(--r-media));
2959
2971
  }
2960
2972
  .postcard__face {
2961
2973
  position: absolute;
2962
2974
  inset: 0;
2963
2975
  display: grid;
2964
2976
  align-content: end;
2965
- padding: var(--sp-4);
2966
- gap: var(--sp-2);
2967
- border: var(--border-w) solid var(--card-border);
2977
+ padding: var(--postcard-face-pad, var(--sp-4));
2978
+ gap: var(--postcard-face-gap, var(--sp-2));
2979
+ border: var(--border-w) solid var(--postcard-face-border, var(--card-border));
2968
2980
  border-radius: inherit;
2969
2981
  overflow: hidden;
2970
2982
  isolation: isolate;
@@ -2973,11 +2985,11 @@ button { font: inherit; cursor: pointer; }
2973
2985
  backface-visibility: hidden;
2974
2986
  -webkit-backface-visibility: hidden;
2975
2987
  transition: transform var(--motion-flip) ease-in-out;
2976
- box-shadow: var(--shadow-float);
2988
+ box-shadow: var(--postcard-face-shadow, var(--shadow-float));
2977
2989
  }
2978
2990
  .postcard__face:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2979
2991
  /* 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); }
2992
+ .postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); color: var(--postcard-front-ink, var(--card-3-ink)); }
2981
2993
  .postcard__front > img,
2982
2994
  .postcard__front > .ph {
2983
2995
  position: absolute;
@@ -2999,13 +3011,13 @@ button { font: inherit; cursor: pointer; }
2999
3011
  }
3000
3012
  /* the back: paper — the words, big enough to read at arm's length */
3001
3013
  .postcard__back {
3002
- background: var(--card-2-bg);
3003
- color: var(--card-2-ink);
3014
+ background: var(--postcard-back-bg, var(--card-2-bg));
3015
+ color: var(--postcard-back-ink, var(--card-2-ink));
3004
3016
  align-content: space-between;
3005
3017
  transform: rotateY(180deg);
3006
3018
  }
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; }
3019
+ .postcard__caption { margin: 0; font: var(--postcard-caption-font, var(--type-title)); text-wrap: pretty; }
3020
+ .postcard__label { font: var(--postcard-label-font, var(--type-overline)); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.85; }
3009
3021
  .postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
3010
3022
  .postcard.is-flipped > .postcard__back { transform: rotateY(0); }
3011
3023
  @media (prefers-reduced-motion: reduce) {
@@ -3061,7 +3073,7 @@ button { font: inherit; cursor: pointer; }
3061
3073
  display: grid;
3062
3074
  grid-template-rows: minmax(0, 1fr) auto;
3063
3075
  overflow: clip;
3064
- box-shadow: var(--shadow-sheet);
3076
+ box-shadow: var(--modal-shadow, var(--shadow-sheet));
3065
3077
  }
3066
3078
  .modal__lift {
3067
3079
  padding: var(--card-pad);
@@ -3075,15 +3087,15 @@ button { font: inherit; cursor: pointer; }
3075
3087
  .modal__drawer {
3076
3088
  height: var(--drawer-h);
3077
3089
  box-sizing: border-box;
3078
- padding: var(--sp-5);
3090
+ padding: var(--modal-drawer-pad, var(--sp-5));
3079
3091
  overflow: clip;
3080
3092
  display: grid;
3081
3093
  align-content: center;
3082
3094
  justify-items: center;
3083
- gap: var(--sp-1);
3095
+ gap: var(--modal-drawer-gap, var(--sp-1));
3084
3096
  text-align: center;
3085
- background: var(--card-3-bg);
3086
- color: var(--card-3-ink);
3097
+ background: var(--modal-drawer-bg, var(--card-3-bg));
3098
+ color: var(--modal-drawer-ink, var(--card-3-ink));
3087
3099
  --accent: var(--card-3-ink); /* the one solid button inverts on the dark drawer */
3088
3100
  --on-accent: var(--card-3-bg);
3089
3101
  }
@@ -3099,14 +3111,14 @@ button { font: inherit; cursor: pointer; }
3099
3111
  }
3100
3112
  @keyframes modal-open {
3101
3113
  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 */
3114
+ 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
3115
  }
3104
3116
  }
3105
3117
 
3106
- .btn-trio { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
3118
+ .btn-trio { display: flex; align-items: center; gap: var(--btn-trio-gap, var(--sp-3)); margin-top: var(--sp-3); }
3107
3119
  .btn-trio__dots {
3108
- width: var(--sp-5);
3109
- height: var(--sp-1);
3120
+ width: var(--btn-trio-dots-w, var(--sp-5));
3121
+ height: var(--btn-trio-dots-h, var(--sp-1));
3110
3122
  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
3123
  opacity: 0.4;
3112
3124
  }
@@ -3127,31 +3139,31 @@ button { font: inherit; cursor: pointer; }
3127
3139
  display: none; /* explicit closed state — never display on the base rule */
3128
3140
  margin: 0;
3129
3141
  inset: auto;
3130
- border: var(--border-w) solid var(--action-border);
3131
- border-radius: var(--r-card);
3142
+ border: var(--border-w) solid var(--menu-border, var(--action-border));
3143
+ border-radius: var(--menu-radius, var(--r-card));
3132
3144
  --menu-pad: var(--sp-2); /* published: a row's radius is concentric with the panel's */
3133
3145
  padding: var(--menu-pad);
3134
3146
  min-width: 12rem;
3135
- background: var(--action-bg);
3136
- color: var(--action-ink);
3137
- box-shadow: var(--shadow-float);
3147
+ background: var(--menu-bg, var(--action-bg));
3148
+ color: var(--menu-ink, var(--action-ink));
3149
+ box-shadow: var(--menu-shadow, var(--shadow-float));
3138
3150
  }
3139
3151
  .menu:popover-open { display: grid; }
3140
3152
  .menu:not([popover]) { display: grid; position: relative; } /* inline usage (no attribute) */
3141
3153
  .menu__item {
3142
3154
  display: flex;
3143
3155
  align-items: center;
3144
- gap: var(--sp-3);
3145
- min-height: var(--tap);
3156
+ gap: var(--menu-item-gap, var(--sp-3));
3157
+ min-height: var(--menu-item-min-h, var(--tap));
3146
3158
  width: 100%;
3147
- padding: 0 var(--sp-3);
3159
+ padding: var(--menu-item-pad-y, 0) var(--menu-item-pad-x, var(--sp-3));
3148
3160
  border: 0;
3149
3161
  /* concentric with the panel: the card radius minus the panel's padding, so the
3150
3162
  highlight's curve follows the outer corner (Lh 2026-09-16) */
3151
3163
  border-radius: calc(var(--r-card) - var(--menu-pad, var(--sp-2)));
3152
3164
  background: transparent;
3153
3165
  color: inherit;
3154
- font: var(--type-title);
3166
+ font: var(--menu-item-font, var(--type-title));
3155
3167
  text-align: left;
3156
3168
  cursor: pointer;
3157
3169
  }
@@ -3179,12 +3191,12 @@ button { font: inherit; cursor: pointer; }
3179
3191
  /* ---- banner: a chip that opens into a tinted card, no JS ----
3180
3192
  <details class="banner"><summary class="chip chip--on">Save $30 on orders over $70 <svg class="icon icon--xs"/></summary>
3181
3193
  <div class="card card--3 banner__body">…</div></details> */
3182
- .banner { display: grid; gap: var(--sp-2); justify-items: start; }
3194
+ .banner { display: grid; gap: var(--banner-gap, var(--sp-2)); justify-items: start; }
3183
3195
  .banner > summary { list-style: none; cursor: pointer; }
3184
3196
  .banner > summary::-webkit-details-marker { display: none; }
3185
3197
  .banner > summary .icon { transition: rotate 0.2s ease; }
3186
3198
  .banner[open] > summary .icon { rotate: 180deg; }
3187
- .banner__body { width: 100%; padding: var(--sp-4) var(--sp-5); --card-gap: var(--sp-1); }
3199
+ .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
3200
  .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
3201
  @supports (interpolate-size: allow-keywords) {
3190
3202
  @media (prefers-reduced-motion: no-preference) {
@@ -3202,9 +3214,9 @@ button { font: inherit; cursor: pointer; }
3202
3214
  bottom: var(--sp-5);
3203
3215
  max-width: var(--screen-w);
3204
3216
  margin-inline: auto;
3205
- padding-inline: var(--sp-4);
3217
+ padding-inline: var(--float-bar-pad-x, var(--sp-4));
3206
3218
  display: flex;
3207
- gap: var(--sp-2);
3219
+ gap: var(--float-bar-gap, var(--sp-2));
3208
3220
  align-items: center;
3209
3221
  z-index: 30;
3210
3222
  }
@@ -3219,29 +3231,29 @@ button { font: inherit; cursor: pointer; }
3219
3231
  display: flex;
3220
3232
  justify-content: space-around;
3221
3233
  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);
3234
+ min-height: var(--float-bar-pill-min-h, var(--bar-h));
3235
+ padding: var(--float-bar-pill-pad, var(--sp-1));
3236
+ border-radius: var(--float-bar-pill-radius, var(--r-chip));
3237
+ border: var(--border-w) solid var(--float-bar-pill-border, var(--action-border));
3238
+ background: var(--float-bar-pill-bg, color-mix(in srgb, var(--action-bg) 82%, transparent));
3239
+ color: var(--float-bar-pill-ink, var(--action-ink));
3228
3240
  backdrop-filter: blur(var(--sp-3));
3229
3241
  -webkit-backdrop-filter: blur(var(--sp-3));
3230
- box-shadow: var(--shadow-float);
3242
+ box-shadow: var(--float-bar-pill-shadow, var(--shadow-float));
3231
3243
  }
3232
3244
  .float-bar__item {
3233
3245
  flex: 1;
3234
3246
  min-width: 0;
3235
3247
  display: grid;
3236
3248
  justify-items: center;
3237
- gap: var(--sp-0);
3238
- min-height: var(--tap-lg);
3239
- padding: var(--sp-1);
3249
+ gap: var(--float-bar-item-gap, var(--sp-0));
3250
+ min-height: var(--float-bar-item-min-h, var(--tap-lg));
3251
+ padding: var(--float-bar-item-pad, var(--sp-1));
3240
3252
  border: 0;
3241
- border-radius: var(--r-chip);
3253
+ border-radius: var(--float-bar-item-radius, var(--r-chip));
3242
3254
  background: transparent;
3243
3255
  color: inherit;
3244
- font: var(--type-caption);
3256
+ font: var(--float-bar-item-font, var(--type-caption));
3245
3257
  text-decoration: none;
3246
3258
  opacity: 0.6;
3247
3259
  overflow: hidden;
@@ -3259,15 +3271,15 @@ button { font: inherit; cursor: pointer; }
3259
3271
  .callout {
3260
3272
  position: absolute;
3261
3273
  display: grid;
3262
- gap: var(--sp-0);
3274
+ gap: var(--callout-gap, var(--sp-0));
3263
3275
  width: max-content;
3264
3276
  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);
3277
+ padding: var(--callout-pad-y, var(--sp-3)) var(--callout-pad-x, var(--sp-4));
3278
+ border: var(--border-w) solid var(--callout-border, var(--action-border));
3279
+ border-radius: var(--callout-radius, var(--r-card));
3280
+ background: var(--callout-bg, var(--action-bg));
3281
+ color: var(--callout-ink, var(--action-ink));
3282
+ box-shadow: var(--callout-shadow, var(--shadow-float));
3271
3283
  text-align: center;
3272
3284
  }
3273
3285
  .callout::after {
@@ -3284,16 +3296,16 @@ button { font: inherit; cursor: pointer; }
3284
3296
  border-top: 0;
3285
3297
  border-left: 0;
3286
3298
  }
3287
- .callout__title { font: var(--type-title); }
3288
- .callout__sub { font: var(--type-caption); opacity: 0.7; }
3299
+ .callout__title { font: var(--callout-title-font, var(--type-title)); }
3300
+ .callout__sub { font: var(--callout-sub-font, var(--type-caption)); opacity: 0.7; }
3289
3301
  /* ---- deck: swipe cards; the last child is on top ----
3290
3302
  <div class="deck"><article class="card">…</article><article class="card">…</article><article class="card card--fold">front</article></div>
3291
3303
  <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); }
3304
+ .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
3305
  .deck > .card { grid-area: 1 / 1; width: min(100%, 20rem); min-height: 12.5rem; transition: rotate 0.3s ease, translate 0.3s ease; }
3294
3306
  .deck > .card:nth-last-child(2) { rotate: -4deg; translate: calc(-1 * var(--sp-2)) var(--sp-2); opacity: 0.85; }
3295
3307
  .deck > .card:nth-last-child(3) { rotate: 5deg; translate: var(--sp-3) var(--sp-4); opacity: 0.6; }
3296
3308
  .deck > .card:nth-last-child(n + 4) { display: none; }
3297
- .deck__actions { display: flex; justify-content: center; gap: var(--sp-8); }
3309
+ .deck__actions { display: flex; justify-content: center; gap: var(--deck-actions-gap, var(--sp-8)); }
3298
3310
  .deck__actions > .icon-btn { width: var(--bar-h); height: var(--bar-h); }
3299
3311