@usableapp/cardds 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +12 -8
  2. package/css/actions.css +38 -27
  3. package/css/base.css +9 -9
  4. package/css/card.css +24 -23
  5. package/css/choice.css +50 -50
  6. package/css/forms.css +18 -18
  7. package/css/journey.css +16 -16
  8. package/css/layover.css +57 -60
  9. package/css/lists.css +52 -43
  10. package/css/media.css +96 -60
  11. package/css/numbers.css +41 -25
  12. package/css/people.css +11 -11
  13. package/css/sheet.css +28 -24
  14. package/css/stack.css +3 -3
  15. package/css/theme.template.css +6 -3
  16. package/dist/actions/Btn.d.ts +109 -108
  17. package/dist/actions/BtnRow.d.ts +2 -2
  18. package/dist/actions/Chip.d.ts +102 -101
  19. package/dist/actions/Dot.d.ts +2 -2
  20. package/dist/actions/Dropdown.d.ts +4 -4
  21. package/dist/actions/Fab.d.ts +2 -2
  22. package/dist/actions/IconBtn.d.ts +107 -106
  23. package/dist/actions/Link.d.ts +105 -104
  24. package/dist/actions/Segment.d.ts +3 -3
  25. package/dist/cardds.css +443 -369
  26. package/dist/cards/Card.d.ts +99 -98
  27. package/dist/cards/CardFoot.d.ts +2 -2
  28. package/dist/cards/CardHead.d.ts +2 -2
  29. package/dist/cards/CardParts.d.ts +7 -7
  30. package/dist/cards/Placeholder.d.ts +2 -2
  31. package/dist/cards/Stat.d.ts +7 -5
  32. package/dist/cards/Stat.js +2 -2
  33. package/dist/choice/Calendar.d.ts +2 -2
  34. package/dist/choice/Check.d.ts +3 -3
  35. package/dist/choice/DayStrip.d.ts +3 -3
  36. package/dist/choice/Mood.d.ts +2 -2
  37. package/dist/choice/Slider.d.ts +2 -2
  38. package/dist/forms/AddRow.d.ts +2 -2
  39. package/dist/forms/Composer.d.ts +2 -2
  40. package/dist/forms/Field.d.ts +2 -2
  41. package/dist/forms/FileBtn.d.ts +2 -2
  42. package/dist/forms/Pin.d.ts +2 -2
  43. package/dist/index.d.ts +2 -1
  44. package/dist/index.js +2 -1
  45. package/dist/journey/NoteRow.d.ts +2 -2
  46. package/dist/journey/Route.d.ts +2 -2
  47. package/dist/journey/TileBadge.d.ts +2 -2
  48. package/dist/layover/Banner.d.ts +2 -2
  49. package/dist/layover/Callout.d.ts +2 -2
  50. package/dist/layover/Deck.d.ts +3 -3
  51. package/dist/lists/Bubble.d.ts +3 -3
  52. package/dist/lists/Kv.d.ts +5 -5
  53. package/dist/lists/Legend.d.ts +3 -3
  54. package/dist/lists/Row.d.ts +14 -285
  55. package/dist/lists/Row.js +10 -3
  56. package/dist/lists/Timeline.d.ts +3 -3
  57. package/dist/media/MapArea.d.ts +2 -2
  58. package/dist/media/Mosaic.d.ts +3 -3
  59. package/dist/media/Postcard.d.ts +17 -7
  60. package/dist/media/Postcard.js +8 -5
  61. package/dist/media/Quote.d.ts +2 -2
  62. package/dist/media/Tile.d.ts +5 -4
  63. package/dist/numbers/Badge.d.ts +2 -2
  64. package/dist/numbers/Band.d.ts +3 -3
  65. package/dist/numbers/Bars.d.ts +3 -3
  66. package/dist/numbers/DotGrid.d.ts +2 -2
  67. package/dist/numbers/Picker.d.ts +3 -3
  68. package/dist/numbers/Progress.d.ts +9 -0
  69. package/dist/numbers/Progress.js +7 -0
  70. package/dist/numbers/Ring.d.ts +2 -2
  71. package/dist/numbers/Track.d.ts +3 -3
  72. package/dist/people/Avatar.d.ts +100 -99
  73. package/dist/people/AvatarPick.d.ts +3 -3
  74. package/dist/scaffold/ActionBar.d.ts +3 -3
  75. package/dist/scaffold/AppBar.d.ts +2 -2
  76. package/dist/scaffold/BaseContent.d.ts +3 -3
  77. package/dist/scaffold/Centre.d.ts +3 -3
  78. package/dist/scaffold/EmptyState.d.ts +2 -2
  79. package/dist/scaffold/FilterRow.d.ts +2 -2
  80. package/dist/scaffold/FloatBar.d.ts +105 -104
  81. package/dist/scaffold/PagerAt.d.ts +2 -2
  82. package/dist/scaffold/Screen.d.ts +100 -99
  83. package/dist/scaffold/TopBar.d.ts +2 -2
  84. package/dist/sheets/Drawer.d.ts +7 -7
  85. package/dist/sheets/Drawer.js +1 -1
  86. package/dist/sheets/Sheet.d.ts +3 -5
  87. package/dist/sheets/Sheet.js +2 -2
  88. package/dist/sheets/SheetBody.d.ts +2 -2
  89. package/dist/sheets/SheetHead.d.ts +2 -2
  90. package/dist/sheets/SheetStack.d.ts +5 -5
  91. package/dist/sheets/SheetStack.js +3 -3
  92. package/dist/sheets/SheetStage.d.ts +5 -7
  93. package/dist/sheets/SheetStage.js +3 -3
  94. package/dist/sheets/StackBack.d.ts +7 -0
  95. package/dist/sheets/StackBack.js +7 -0
  96. package/dist/sheets/Step.d.ts +2 -2
  97. package/dist/type/Icon.d.ts +22 -7
  98. package/dist/type/Icon.js +27 -4
  99. package/dist/type/Text.d.ts +7 -1
  100. package/dist/type/Text.js +2 -2
  101. package/package.json +3 -2
  102. package/dist/sheets/CardBack.d.ts +0 -7
  103. package/dist/sheets/CardBack.js +0 -7
package/css/journey.css CHANGED
@@ -5,40 +5,40 @@
5
5
  ============================================================ */
6
6
 
7
7
  /* ---- route: endpoint · dotted connector (icon in middle) · endpoint ---- */
8
- .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 */
8
+ .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 */
9
9
  /* the two endpoints get EQUAL columns whatever their words measure, so the via
10
10
  (the icon alone) sits at the card's centre; each dotted run lives on ITS
11
11
  endpoint's code row and fills that side's leftover — with short names the
12
12
  runs are long and equal, a long place name wraps in its half instead of
13
13
  pushing the plane (Lh 2026-09-16) */
14
14
 
15
- .route__point { display: grid; gap: var(--sp-0); min-width: 0; }
15
+ .route__point { display: grid; gap: var(--route-point-gap, var(--sp-0)); min-width: 0; }
16
16
  .route__point--end { text-align: right; }
17
17
 
18
- .route__code { font: var(--type-h2); letter-spacing: var(--track-h2); display: flex; align-items: center; gap: var(--sp-3); }
18
+ .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)); }
19
19
  .route__code::after, .route__point--end .route__code::before { content: ""; flex: 1; border-top: var(--rule-w) dotted currentColor; opacity: 0.65; }
20
20
  .route__point--end .route__code::after { content: none; }
21
- .route__time { font: var(--type-body); font-variant-numeric: tabular-nums; }
22
- .route__place { font: var(--type-caption); color: var(--card-muted); }
21
+ .route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
22
+ .route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
23
23
  .card--3 .route__place { color: inherit; opacity: 0.7; }
24
24
 
25
- .route__via { display: flex; align-items: center; opacity: 0.65; font-size: var(--fs-body); }
25
+ .route__via { display: flex; align-items: center; opacity: 0.65; font-size: var(--route-via-font-size, var(--fs-body)); }
26
26
 
27
27
  /* ---- tile badge: tiny label over a big value (gate, seat, zone) ---- */
28
28
  .tile-badge {
29
29
  display: inline-grid;
30
30
  justify-items: center;
31
- gap: var(--sp-0);
32
- padding: var(--sp-1) var(--sp-2);
33
- border-radius: var(--r-badge);
34
- background: var(--card-3-bg);
35
- color: var(--card-3-ink);
31
+ gap: var(--tile-badge-gap, var(--sp-0));
32
+ padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
33
+ border-radius: var(--tile-badge-radius, var(--r-badge));
34
+ background: var(--tile-badge-bg, var(--card-3-bg));
35
+ color: var(--tile-badge-ink, var(--card-3-ink));
36
36
  }
37
- .tile-badge__label { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.7; }
37
+ .tile-badge__label { font: var(--tile-badge-label-font, var(--type-overline)); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.7; }
38
38
  .tile-badge__value { font: 800 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
39
39
 
40
40
  /* plain: no fill — a date box beside a title (Jun / 18) */
41
- .tile-badge--plain { background: transparent; color: inherit; padding: 0; min-width: var(--tap-sm); }
41
+ .tile-badge--plain { background: transparent; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
42
42
  .tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
43
43
 
44
44
  /* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
@@ -48,8 +48,8 @@
48
48
  .note-row {
49
49
  display: flex;
50
50
  align-items: center;
51
- gap: var(--sp-2);
52
- font: var(--type-caption);
53
- color: var(--card-muted);
51
+ gap: var(--note-row-gap, var(--sp-2));
52
+ font: var(--note-row-font, var(--type-caption));
53
+ color: var(--note-row-ink, var(--card-muted));
54
54
  }
55
55
  .card--3 .note-row { color: inherit; opacity: 0.7; }
package/css/layover.css CHANGED
@@ -5,8 +5,8 @@
5
5
  lives in sheet.css; these compose with .sheet-stage.
6
6
  ============================================================ */
7
7
 
8
- /* ---- ask: the Modal — the case drops in, its drawer opens below (Lh 2026-09-16: ONE card, two parts)
9
- <div class="sheet-stage sheet-stage--ask">
8
+ /* ---- the Modal — the case drops in, its drawer opens below (Lh 2026-09-16: ONE card, two parts)
9
+ <div class="sheet-stage sheet-stage--dim"> (dim = the page under a scrim; a Modal on a clean ground is the stage without it — Lh 2026-09-22, `--ask` retired: the stage does the ground, the modal centres itself)
10
10
  <article class="card modal">
11
11
  <div class="modal__lift">…the card's content (a head, rows, a timeline)…</div>
12
12
  <div class="modal__drawer"><h2 class="t-h2">Take the case?</h2><p class="t-caption">This cannot be undone.</p>
@@ -17,36 +17,33 @@
17
17
  .modal is the card — no border (the shadow is its edge on the scrim);
18
18
  .modal__lift the light part that holds the thing being asked about (it
19
19
  scrolls when tall); .modal__drawer the dark end that asks. The ground is
20
- the page under a scrim (like --dim). Two beats (2 × --motion-sheet) when
20
+ whatever the stage shows (dim it or not). Two beats (2 × --motion-sheet) when
21
21
  the stage appears, and the same two backwards when it closes:
22
22
  1. the card drops in from above the stage to the screen's centre — its
23
23
  drawer still inside it, height 0: nothing of the ask shows yet
24
24
  2. the drawer comes out of the card's bottom edge while the lift moves
25
25
  up, both at once: the card grows from its centre, which never moves
26
- (the stage centres it; a growing card keeps its middle where it is)
26
+ (the modal centres itself — absolute, margin auto; a growing card keeps its middle where it is)
27
27
  Closing: a click on any button in the drawer plays the same animations in
28
28
  reverse (cardds.js: Animation.reverse() on each — nothing duplicated), then
29
29
  fires "cardds:modal" on the .sheet-stage (bubbles, detail.answer from the
30
30
  button's data-answer) for the app to remove the stage.
31
31
  Reduced motion: the finished state, no beats; closing fires at once. */
32
- .sheet-stage--ask {
33
- --drawer-h: 14rem;
34
- display: grid;
35
- align-content: center; /* the card at the screen's centre — and kept there as it grows */
36
- padding-inline: var(--screen-pad);
37
- }
38
- .sheet-stage--ask::after { content: ""; position: absolute; inset: 0; background: var(--scrim); } /* the page dimmed */
39
- .sheet-stage--ask > .sheet-stage__bg { filter: blur(var(--sp-2)); }
40
32
  .modal {
41
- position: relative;
42
- z-index: 1; /* over the scrim */
43
- max-height: calc(100cqh - 2 * var(--sp-5)); /* cqh: the stage (a size container) — a % here would be of the grid area, which is the card itself */
33
+ --drawer-h: 14rem;
34
+ position: absolute; /* at the stage's centre, and kept there as it grows: absolute + margin auto re-centres on every height */
35
+ inset: 0;
36
+ margin: auto;
37
+ height: fit-content; /* inset 0 alone would stretch it to the stage: its own height, then the margins share the rest */
38
+ width: calc(100% - 2 * var(--screen-pad));
39
+ z-index: 2; /* over the dim stage's scrim (z 1) */
40
+ max-height: calc(100cqh - 2 * var(--sp-5)); /* cqh: the stage (a size container) */
44
41
  padding: 0; /* the parts pad themselves */
45
42
  border: 0; /* no frame on the modal (Lh 2026-09-16) — its edge is the shadow */
46
43
  display: grid;
47
44
  grid-template-rows: minmax(0, 1fr) auto;
48
45
  overflow: clip;
49
- box-shadow: var(--shadow-sheet);
46
+ box-shadow: var(--modal-shadow, var(--shadow-sheet));
50
47
  }
51
48
  .modal__lift {
52
49
  padding: var(--card-pad);
@@ -60,15 +57,15 @@
60
57
  .modal__drawer {
61
58
  height: var(--drawer-h);
62
59
  box-sizing: border-box;
63
- padding: var(--sp-5);
60
+ padding: var(--modal-drawer-pad, var(--sp-5));
64
61
  overflow: clip;
65
62
  display: grid;
66
63
  align-content: center;
67
64
  justify-items: center;
68
- gap: var(--sp-1);
65
+ gap: var(--modal-drawer-gap, var(--sp-1));
69
66
  text-align: center;
70
- background: var(--card-3-bg);
71
- color: var(--card-3-ink);
67
+ background: var(--modal-drawer-bg, var(--card-3-bg));
68
+ color: var(--modal-drawer-ink, var(--card-3-ink));
72
69
  --accent: var(--card-3-ink); /* the one solid button inverts on the dark drawer */
73
70
  --on-accent: var(--card-3-bg);
74
71
  }
@@ -84,14 +81,14 @@
84
81
  }
85
82
  @keyframes modal-open {
86
83
  0%, 50% { height: 0; padding-block: 0; } /* inside the card */
87
- 100% { height: var(--drawer-h); padding-block: var(--sp-5); } /* 2. out below, the lift up, together */
84
+ 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 */
88
85
  }
89
86
  }
90
87
 
91
- .btn-trio { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
88
+ .btn-trio { display: flex; align-items: center; gap: var(--btn-trio-gap, var(--sp-3)); margin-top: var(--sp-3); }
92
89
  .btn-trio__dots {
93
- width: var(--sp-5);
94
- height: var(--sp-1);
90
+ width: var(--btn-trio-dots-w, var(--sp-5));
91
+ height: var(--btn-trio-dots-h, var(--sp-1));
95
92
  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;
96
93
  opacity: 0.4;
97
94
  }
@@ -112,31 +109,31 @@
112
109
  display: none; /* explicit closed state — never display on the base rule */
113
110
  margin: 0;
114
111
  inset: auto;
115
- border: var(--border-w) solid var(--action-border);
116
- border-radius: var(--r-card);
112
+ border: var(--border-w) solid var(--menu-border, var(--action-border));
113
+ border-radius: var(--menu-radius, var(--r-card));
117
114
  --menu-pad: var(--sp-2); /* published: a row's radius is concentric with the panel's */
118
115
  padding: var(--menu-pad);
119
116
  min-width: 12rem;
120
- background: var(--action-bg);
121
- color: var(--action-ink);
122
- box-shadow: var(--shadow-float);
117
+ background: var(--menu-bg, var(--action-bg));
118
+ color: var(--menu-ink, var(--action-ink));
119
+ box-shadow: var(--menu-shadow, var(--shadow-float));
123
120
  }
124
121
  .menu:popover-open { display: grid; }
125
122
  .menu:not([popover]) { display: grid; position: relative; } /* inline usage (no attribute) */
126
123
  .menu__item {
127
124
  display: flex;
128
125
  align-items: center;
129
- gap: var(--sp-3);
130
- min-height: var(--tap);
126
+ gap: var(--menu-item-gap, var(--sp-3));
127
+ min-height: var(--menu-item-min-h, var(--tap));
131
128
  width: 100%;
132
- padding: 0 var(--sp-3);
129
+ padding: var(--menu-item-pad-y, 0) var(--menu-item-pad-x, var(--sp-3));
133
130
  border: 0;
134
131
  /* concentric with the panel: the card radius minus the panel's padding, so the
135
132
  highlight's curve follows the outer corner (Lh 2026-09-16) */
136
133
  border-radius: calc(var(--r-card) - var(--menu-pad, var(--sp-2)));
137
134
  background: transparent;
138
135
  color: inherit;
139
- font: var(--type-title);
136
+ font: var(--menu-item-font, var(--type-title));
140
137
  text-align: left;
141
138
  cursor: pointer;
142
139
  }
@@ -164,12 +161,12 @@
164
161
  /* ---- banner: a chip that opens into a tinted card, no JS ----
165
162
  <details class="banner"><summary class="chip chip--on">Save $30 on orders over $70 <svg class="icon icon--xs"/></summary>
166
163
  <div class="card card--3 banner__body">…</div></details> */
167
- .banner { display: grid; gap: var(--sp-2); justify-items: start; }
164
+ .banner { display: grid; gap: var(--banner-gap, var(--sp-2)); justify-items: start; }
168
165
  .banner > summary { list-style: none; cursor: pointer; }
169
166
  .banner > summary::-webkit-details-marker { display: none; }
170
167
  .banner > summary .icon { transition: rotate 0.2s ease; }
171
168
  .banner[open] > summary .icon { rotate: 180deg; }
172
- .banner__body { width: 100%; padding: var(--sp-4) var(--sp-5); --card-gap: var(--sp-1); }
169
+ .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); }
173
170
  .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 */
174
171
  @supports (interpolate-size: allow-keywords) {
175
172
  @media (prefers-reduced-motion: no-preference) {
@@ -187,9 +184,9 @@
187
184
  bottom: var(--sp-5);
188
185
  max-width: var(--screen-w);
189
186
  margin-inline: auto;
190
- padding-inline: var(--sp-4);
187
+ padding-inline: var(--float-bar-pad-x, var(--sp-4));
191
188
  display: flex;
192
- gap: var(--sp-2);
189
+ gap: var(--float-bar-gap, var(--sp-2));
193
190
  align-items: center;
194
191
  z-index: 30;
195
192
  }
@@ -204,29 +201,29 @@
204
201
  display: flex;
205
202
  justify-content: space-around;
206
203
  align-items: center;
207
- min-height: var(--bar-h);
208
- padding: var(--sp-1);
209
- border-radius: var(--r-chip);
210
- border: var(--border-w) solid var(--action-border);
211
- background: color-mix(in srgb, var(--action-bg) 82%, transparent);
212
- color: var(--action-ink);
204
+ min-height: var(--float-bar-pill-min-h, var(--bar-h));
205
+ padding: var(--float-bar-pill-pad, var(--sp-1));
206
+ border-radius: var(--float-bar-pill-radius, var(--r-chip));
207
+ border: var(--border-w) solid var(--float-bar-pill-border, var(--action-border));
208
+ background: var(--float-bar-pill-bg, color-mix(in srgb, var(--action-bg) 82%, transparent));
209
+ color: var(--float-bar-pill-ink, var(--action-ink));
213
210
  backdrop-filter: blur(var(--sp-3));
214
211
  -webkit-backdrop-filter: blur(var(--sp-3));
215
- box-shadow: var(--shadow-float);
212
+ box-shadow: var(--float-bar-pill-shadow, var(--shadow-float));
216
213
  }
217
214
  .float-bar__item {
218
215
  flex: 1;
219
216
  min-width: 0;
220
217
  display: grid;
221
218
  justify-items: center;
222
- gap: var(--sp-0);
223
- min-height: var(--tap-lg);
224
- padding: var(--sp-1);
219
+ gap: var(--float-bar-item-gap, var(--sp-0));
220
+ min-height: var(--float-bar-item-min-h, var(--tap-lg));
221
+ padding: var(--float-bar-item-pad, var(--sp-1));
225
222
  border: 0;
226
- border-radius: var(--r-chip);
223
+ border-radius: var(--float-bar-item-radius, var(--r-chip));
227
224
  background: transparent;
228
225
  color: inherit;
229
- font: var(--type-caption);
226
+ font: var(--float-bar-item-font, var(--type-caption));
230
227
  text-decoration: none;
231
228
  opacity: 0.6;
232
229
  overflow: hidden;
@@ -244,15 +241,15 @@
244
241
  .callout {
245
242
  position: absolute;
246
243
  display: grid;
247
- gap: var(--sp-0);
244
+ gap: var(--callout-gap, var(--sp-0));
248
245
  width: max-content;
249
246
  max-width: 15rem;
250
- padding: var(--sp-3) var(--sp-4);
251
- border: var(--border-w) solid var(--action-border);
252
- border-radius: var(--r-card);
253
- background: var(--action-bg);
254
- color: var(--action-ink);
255
- box-shadow: var(--shadow-float);
247
+ padding: var(--callout-pad-y, var(--sp-3)) var(--callout-pad-x, var(--sp-4));
248
+ border: var(--border-w) solid var(--callout-border, var(--action-border));
249
+ border-radius: var(--callout-radius, var(--r-card));
250
+ background: var(--callout-bg, var(--action-bg));
251
+ color: var(--callout-ink, var(--action-ink));
252
+ box-shadow: var(--callout-shadow, var(--shadow-float));
256
253
  text-align: center;
257
254
  }
258
255
  .callout::after {
@@ -269,15 +266,15 @@
269
266
  border-top: 0;
270
267
  border-left: 0;
271
268
  }
272
- .callout__title { font: var(--type-title); }
273
- .callout__sub { font: var(--type-caption); opacity: 0.7; }
269
+ .callout__title { font: var(--callout-title-font, var(--type-title)); }
270
+ .callout__sub { font: var(--callout-sub-font, var(--type-caption)); opacity: 0.7; }
274
271
  /* ---- deck: swipe cards; the last child is on top ----
275
272
  <div class="deck"><article class="card">…</article><article class="card">…</article><article class="card card--fold">front</article></div>
276
273
  <div class="deck__actions"><button class="icon-btn">×</button><button class="icon-btn icon-btn--invert">✓</button></div> */
277
- .deck { display: grid; place-items: center; padding: var(--sp-4) var(--sp-4) var(--sp-6); }
274
+ .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)); }
278
275
  .deck > .card { grid-area: 1 / 1; width: min(100%, 20rem); min-height: 12.5rem; transition: rotate 0.3s ease, translate 0.3s ease; }
279
276
  .deck > .card:nth-last-child(2) { rotate: -4deg; translate: calc(-1 * var(--sp-2)) var(--sp-2); opacity: 0.85; }
280
277
  .deck > .card:nth-last-child(3) { rotate: 5deg; translate: var(--sp-3) var(--sp-4); opacity: 0.6; }
281
278
  .deck > .card:nth-last-child(n + 4) { display: none; }
282
- .deck__actions { display: flex; justify-content: center; gap: var(--sp-8); }
279
+ .deck__actions { display: flex; justify-content: center; gap: var(--deck-actions-gap, var(--sp-8)); }
283
280
  .deck__actions > .icon-btn { width: var(--bar-h); height: var(--bar-h); }
package/css/lists.css CHANGED
@@ -11,8 +11,8 @@
11
11
  .row {
12
12
  display: flex;
13
13
  align-items: center;
14
- gap: var(--sp-3);
15
- min-height: var(--tap); /* the tap target's room — the row's only vertical size */
14
+ gap: var(--row-gap, var(--sp-3));
15
+ min-height: var(--row-min-h, var(--tap)); /* the tap target's room — the row's only vertical size */
16
16
  min-width: 0;
17
17
  color: inherit;
18
18
  text-decoration: none; /* .row works as <a> too — it is a tap target */
@@ -21,21 +21,30 @@
21
21
  Space BETWEEN rows is .rows' gap; a lone row as card content takes the card's
22
22
  gap. (.row--flush, which zeroed the old padding, is gone — every row is flush.) */
23
23
  .row__lead {
24
- width: var(--tap);
25
- height: var(--tap);
24
+ width: var(--row-lead-size, var(--tap));
25
+ height: var(--row-lead-size, var(--tap));
26
26
  flex: none;
27
- border-radius: var(--r-control);
28
- border: var(--border-w) solid var(--outline);
27
+ border-radius: var(--row-lead-radius, var(--r-control));
28
+ border: var(--border-w) solid var(--row-lead-border, var(--outline));
29
29
  display: grid;
30
30
  place-items: center;
31
31
  }
32
- .row__body { flex: 1; min-width: 0; display: grid; gap: var(--sp-0); }
33
- .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) */
34
- .row__meta { font: var(--type-caption); color: var(--card-muted); display: flex; align-items: center; gap: var(--sp-1); }
32
+ .row__body { flex: 1; min-width: 0; display: grid; gap: var(--row-body-gap, var(--sp-0)); }
33
+ .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) */
34
+ .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)); }
35
35
  .card--3 .row__meta { color: inherit; opacity: 0.7; }
36
- .row__end { margin-left: auto; text-align: right; display: grid; gap: var(--sp-0); justify-items: end; flex: none; }
37
- .row--lg { min-height: var(--fab); } /* menu rows: a taller target, round arrow at the end; .rows gives them more air */
36
+ .row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
37
+ .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 */
38
38
  .row--lg .row__title { font: 700 var(--fs-lg)/1.2 var(--font-display); }
39
+ /* a row that EXPANDS (Lh 2026-09-22): the row is a <details>' summary, what it shows comes out under it —
40
+ who liked a postcard, the rest of a list. An end chevron turns when open. The app owns the list.
41
+ <details class="row-expand"><summary class="row">…lead · body · end…</summary><div class="row-expand__more">…</div></details> */
42
+ .row-expand { display: block; }
43
+ .row-expand > summary { list-style: none; cursor: pointer; }
44
+ .row-expand > summary::-webkit-details-marker { display: none; }
45
+ .row-expand > summary > .icon:last-child { transition: rotate var(--motion-slide); }
46
+ .row-expand[open] > summary > .icon:last-child { rotate: 180deg; }
47
+ .row-expand__more { padding-top: var(--row-expand-gap, var(--sp-3)); }
39
48
  /* a finished item in a checklist (a to-do list, an onboarding "complete your introduction" list):
40
49
  the words go muted, a filled tick sits at the end, and the row is no longer a tap target —
41
50
  pointer-events off here, aria-disabled from the markup. The tick is the .check's checked look
@@ -44,13 +53,13 @@
44
53
  .row--done .row__title { color: var(--card-muted); }
45
54
  .card--3 .row--done .row__title { color: inherit; opacity: 0.7; }
46
55
  .row__done {
47
- width: var(--tap-sm);
48
- height: var(--tap-sm);
56
+ width: var(--row-done-size, var(--tap-sm));
57
+ height: var(--row-done-size, var(--tap-sm));
49
58
  flex: none;
50
59
  margin-left: auto;
51
60
  border-radius: 50%;
52
- background: var(--accent);
53
- color: var(--on-accent);
61
+ background: var(--row-done-bg, var(--accent));
62
+ color: var(--row-done-ink, var(--on-accent));
54
63
  display: grid;
55
64
  place-items: center;
56
65
  }
@@ -71,20 +80,20 @@
71
80
  /* ---- kv: label left, value right ----
72
81
  <dl class="kv"><div class="kv__row"><dt class="kv__key">Pay with</dt><dd class="kv__val">GHO</dd></div>…</dl> */
73
82
  .kv { margin: 0; display: grid; }
74
- .kv__row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); min-height: var(--tap); }
83
+ .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)); }
75
84
  .kv--lined > .kv__row + .kv__row { border-top: var(--border-w) solid var(--hairline); }
76
- .kv__key { font: var(--type-body); color: var(--card-muted); }
85
+ .kv__key { font: var(--kv-key-font, var(--type-body)); color: var(--kv-key-ink, var(--card-muted)); }
77
86
  .card--3 .kv__key { color: inherit; opacity: 0.7; }
78
- .kv__val { margin: 0; font: var(--type-title); display: inline-flex; align-items: center; gap: var(--sp-2); text-align: right; min-width: 0; }
87
+ .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; }
79
88
 
80
89
  /* ---- kv grid: value over label, cells split by a rule (Sex | Weight) ----
81
90
  <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> */
82
- .kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(var(--tile-min), 100%), 1fr)); gap: var(--sp-4); }
91
+ .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)); }
83
92
  .kv-grid > .kv-cell + .kv-cell { border-left: var(--border-w) solid var(--hairline); padding-left: var(--sp-4); }
84
- .kv-cell { display: grid; gap: var(--sp-0); align-content: start; min-width: 0; }
93
+ .kv-cell { display: grid; gap: var(--kv-cell-gap, var(--sp-0)); align-content: start; min-width: 0; }
85
94
  .kv-cell > .icon { margin-bottom: var(--sp-1); }
86
- .kv-cell__val { font: var(--type-h2); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
87
- .kv-cell__key { font: var(--type-caption); color: var(--card-muted); }
95
+ .kv-cell__val { font: var(--kv-cell-val-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
96
+ .kv-cell__key { font: var(--kv-cell-key-font, var(--type-caption)); color: var(--kv-cell-key-ink, var(--card-muted)); }
88
97
  .card--3 .kv-cell__key { color: inherit; opacity: 0.7; }
89
98
 
90
99
  /* ---- bubble: a chat message — theirs on the left, mine on the right (Lh 2026-09-18, replaces the chip's tail) ----
@@ -96,7 +105,7 @@
96
105
  <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>
97
106
  <div class="bubble bubble--right">…</div>
98
107
  </div> */
99
- .thread { display: grid; gap: var(--sp-4); }
108
+ .thread { display: grid; gap: var(--thread-gap, var(--sp-4)); }
100
109
  /* two columns: the message (max-content, but it gives way — minmax(0) — so a long one shrinks
101
110
  and wraps while the actions stay beside it) and the actions; the meta on a second row under the message */
102
111
  .bubble {
@@ -116,12 +125,12 @@
116
125
  .bubble__body {
117
126
  margin: 0;
118
127
  min-width: 0;
119
- padding: var(--sp-3) var(--sp-4);
120
- border-radius: var(--r-card);
128
+ padding: var(--bubble-body-pad-y, var(--sp-3)) var(--bubble-body-pad-x, var(--sp-4));
129
+ border-radius: var(--bubble-body-radius, var(--r-card));
121
130
  border-bottom-left-radius: var(--r-control);
122
- background: var(--card-2-bg);
123
- color: var(--card-2-ink);
124
- font: var(--type-body);
131
+ background: var(--bubble-body-bg, var(--card-2-bg));
132
+ color: var(--bubble-body-ink, var(--card-2-ink));
133
+ font: var(--bubble-body-font, var(--type-body));
125
134
  overflow-wrap: anywhere;
126
135
  }
127
136
  .bubble--right > .bubble__body {
@@ -131,8 +140,8 @@
131
140
  color: var(--on-accent);
132
141
  text-align: right;
133
142
  }
134
- .bubble__actions { display: flex; gap: var(--sp-1); flex: none; opacity: 0.7; }
135
- .bubble__meta { display: flex; align-items: center; gap: var(--sp-1); font: var(--type-caption); color: var(--on-surface-muted); }
143
+ .bubble__actions { display: flex; gap: var(--bubble-actions-gap, var(--sp-1)); flex: none; opacity: 0.7; }
144
+ .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)); }
136
145
  .bubble--right > .bubble__meta { justify-self: end; }
137
146
  .bubble__meta > .icon { color: var(--accent); }
138
147
  .card--3 .bubble__meta { color: inherit; opacity: 0.7; }
@@ -144,7 +153,7 @@
144
153
  margin: 0;
145
154
  padding: 0 0 0 var(--sp-6);
146
155
  display: grid;
147
- gap: var(--sp-5);
156
+ gap: var(--timeline-gap, var(--sp-5));
148
157
  position: relative;
149
158
  }
150
159
  .timeline::before {
@@ -155,7 +164,7 @@
155
164
  bottom: var(--sp-3);
156
165
  border-left: var(--border-w) solid var(--hairline);
157
166
  }
158
- .timeline__item { display: grid; gap: var(--sp-2); position: relative; min-width: 0; }
167
+ .timeline__item { display: grid; gap: var(--timeline-item-gap, var(--sp-2)); position: relative; min-width: 0; }
159
168
  .timeline__item::before {
160
169
  content: "";
161
170
  position: absolute;
@@ -168,35 +177,35 @@
168
177
  }
169
178
  .timeline__mark {
170
179
  justify-self: start;
171
- font: var(--type-overline);
180
+ font: var(--timeline-mark-font, var(--type-overline));
172
181
  letter-spacing: var(--track-overline);
173
182
  text-transform: uppercase;
174
- padding: var(--sp-1) var(--sp-2);
175
- border-radius: var(--r-chip);
176
- background: var(--ink-tint);
183
+ padding: var(--timeline-mark-pad-y, var(--sp-1)) var(--timeline-mark-pad-x, var(--sp-2));
184
+ border-radius: var(--timeline-mark-radius, var(--r-chip));
185
+ background: var(--timeline-mark-bg, var(--ink-tint));
177
186
  }
178
187
 
179
188
  /* ---- legend: signal dots with captions ----
180
189
  <div class="legend"><span class="legend__item"><span class="dot"></span> very high</span>…</div> */
181
- .legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); font: var(--type-caption); color: var(--card-muted); }
190
+ .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)); }
182
191
  .card--3 .legend { color: inherit; opacity: 0.8; }
183
- .legend__item { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--icon); }
192
+ .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)); }
184
193
 
185
194
  /* ---- link: "See all ↗" in a card head, big enough to tap ----
186
195
  <a class="link" href="#">See all <svg class="icon icon--xs"/></a> */
187
196
  .link {
188
197
  display: inline-flex;
189
198
  align-items: center;
190
- gap: var(--sp-1);
191
- min-height: var(--tap-sm);
199
+ gap: var(--link-gap, var(--sp-1));
200
+ min-height: var(--link-min-h, var(--tap-sm));
192
201
  color: inherit;
193
202
  text-decoration: none;
194
- font: var(--type-label);
195
- font-size: var(--fs-body);
203
+ font: var(--link-font, var(--type-label));
204
+ font-size: var(--link-font-size, var(--fs-body));
196
205
  border: 0;
197
206
  background: transparent;
198
207
  padding: 0;
199
208
  cursor: pointer;
200
209
  }
201
- .link--muted { color: var(--card-muted); }
210
+ .link--muted { color: var(--link-muted-ink, var(--card-muted)); }
202
211
  .card--3 .link--muted { color: inherit; opacity: 0.7; }