@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/media.css CHANGED
@@ -43,16 +43,16 @@
43
43
 
44
44
  /* ---- quote: giant mark, big text, attribution ----
45
45
  <blockquote class="quote"><p class="quote__text">It's overcast…</p><footer class="quote__by">Wednesday 15</footer></blockquote> */
46
- .quote { margin: 0; display: grid; gap: var(--sp-3); }
46
+ .quote { margin: 0; display: grid; gap: var(--quote-gap, var(--sp-3)); }
47
47
  .quote::before {
48
48
  content: "\201C";
49
49
  font: 900 calc(var(--fs-h1) * 3)/0.55 var(--font-display);
50
50
  height: var(--sp-10);
51
51
  opacity: 0.9;
52
52
  }
53
- .quote__text { margin: 0; font: var(--type-h1); letter-spacing: var(--track-h1); text-wrap: balance; }
53
+ .quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
54
54
  .quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
55
- .quote__by { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; color: var(--card-muted); }
55
+ .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)); }
56
56
  .card--3 .quote__by { color: inherit; opacity: 0.7; }
57
57
 
58
58
  /* ---- tile grid: big square tiles — icon top, label bottom ----
@@ -61,36 +61,36 @@
61
61
  .tile-grid {
62
62
  display: grid;
63
63
  grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), 100%), 1fr));
64
- gap: var(--sp-3);
64
+ gap: var(--tile-grid-gap, var(--sp-3));
65
65
  }
66
66
  .tile-grid--3 { --tile-min: 6rem; } /* three across on a phone (attachments, upload kinds) */
67
67
  .tile {
68
68
  display: grid;
69
69
  align-content: space-between;
70
70
  justify-items: start;
71
- gap: var(--sp-5);
72
- min-height: var(--tile-h);
71
+ gap: var(--tile-gap, var(--sp-5));
72
+ min-height: var(--tile-min-h, var(--tile-h));
73
73
  min-width: 0;
74
- padding: var(--sp-4);
75
- border: var(--border-w) solid var(--outline);
76
- border-radius: var(--r-card);
77
- background: var(--action-bg);
78
- color: var(--action-ink);
74
+ padding: var(--tile-pad, var(--sp-4));
75
+ border: var(--border-w) solid var(--tile-border, var(--outline));
76
+ border-radius: var(--tile-radius, var(--r-card));
77
+ background: var(--tile-bg, var(--action-bg));
78
+ color: var(--tile-ink, var(--action-ink));
79
79
  font: inherit;
80
80
  text-align: left;
81
81
  position: relative;
82
82
  cursor: pointer;
83
83
  }
84
- .tile__label { font: var(--type-title); }
85
- .tile__value { font: var(--type-h2); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
86
- .tile__sub { font: var(--type-caption); opacity: 0.7; }
87
- .tile__text { display: grid; gap: var(--sp-0); }
88
- .tile--on { background: var(--accent); color: var(--on-accent); border-color: transparent; }
84
+ .tile__label { font: var(--tile-label-font, var(--type-title)); }
85
+ .tile__value { font: var(--tile-value-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
86
+ .tile__sub { font: var(--tile-sub-font, var(--type-caption)); opacity: 0.7; }
87
+ .tile__text { display: grid; gap: var(--tile-text-gap, var(--sp-0)); }
88
+ .tile--on { background: var(--tile-on-bg, var(--accent)); color: var(--tile-on-ink, var(--on-accent)); border-color: transparent; }
89
89
  .tile--pick > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
90
90
  .tile--pick:has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
91
91
  .tile--pick:has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
92
92
  .tile--wide { grid-column: 1 / -1; }
93
- .tile--stat { min-height: var(--avatar-xl); gap: var(--sp-3); grid-template-columns: auto 1fr; align-items: center; }
93
+ .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; }
94
94
  /* upload: dashed, centred plus + label */
95
95
  .tile--upload {
96
96
  border-style: dashed;
@@ -98,12 +98,12 @@
98
98
  color: inherit;
99
99
  align-content: center;
100
100
  justify-items: center;
101
- gap: var(--sp-2);
101
+ gap: var(--tile-upload-gap, var(--sp-2));
102
102
  text-align: center;
103
103
  }
104
104
  /* tinted tiles: slot colors inside a card (schedule, notes) */
105
- .tile--2 { background: var(--card-2-bg); color: var(--card-2-ink); border-color: transparent; }
106
- .tile--3 { background: var(--card-3-bg); color: var(--card-3-ink); border-color: transparent; }
105
+ .tile--2 { background: var(--tile-2-bg, var(--card-2-bg)); color: var(--tile-2-ink, var(--card-2-ink)); border-color: transparent; }
106
+ .tile--3 { background: var(--tile-3-bg, var(--card-3-bg)); color: var(--tile-3-ink, var(--card-3-ink)); border-color: transparent; }
107
107
 
108
108
  /* ---- fold: a sticky-note corner on any card (decorative) ----
109
109
  No border. The card's outline follows its border box, so it ran straight
@@ -127,7 +127,7 @@
127
127
  <span class="wave"></span> — sits in a .row__body, fills the width */
128
128
  .wave {
129
129
  display: block;
130
- height: var(--icon);
130
+ height: var(--wave-h, var(--icon));
131
131
  width: 100%;
132
132
  background: repeating-linear-gradient(90deg, currentColor 0 var(--sp-1), transparent var(--sp-1) var(--sp-2));
133
133
  /* masks only read alpha: currentColor = "opaque" without naming a color */
@@ -142,9 +142,9 @@
142
142
  display: grid;
143
143
  grid-template-columns: 2fr 1fr;
144
144
  grid-auto-rows: 1fr;
145
- gap: var(--sp-1);
145
+ gap: var(--mosaic-gap, var(--sp-1));
146
146
  aspect-ratio: 4 / 3;
147
- border-radius: var(--r-media);
147
+ border-radius: var(--mosaic-radius, var(--r-media));
148
148
  overflow: hidden;
149
149
  }
150
150
  .mosaic > :first-child { grid-row: span 2; }
@@ -152,59 +152,75 @@
152
152
  .mosaic > .ph { opacity: 0.4; border-width: var(--border-w); }
153
153
  .mosaic > img { width: 100%; height: 100%; object-fit: cover; }
154
154
 
155
- /* ---- postcard: a member's introduction, a card in the hand — photo in front,
156
- words on the back; tap to turn it over (Lh, 2026-09-12; card #63) ----
155
+ /* ---- postcard: a member's introduction in the shape of a cover card — the photo fills it,
156
+ the band at the bottom names it; tap the photo to turn it over (Lh 2026-09-12; reshaped 2026-09-22) ----
157
157
  <div class="postcard">
158
- <button class="postcard__face postcard__front" type="button" aria-label="พลิกดูด้านหลัง">
159
- <img src="…" alt=""> — or <div class="ph"></div> —
160
- <span class="postcard__label">ป้าแจ๋ว</span>
161
- </button>
162
- <button class="postcard__face postcard__back" type="button" aria-label="พลิกดูด้านหน้า">
163
- <p class="postcard__caption">ชอบชงกาแฟดริปตอนเช้า</p>
164
- <span class="postcard__label">ป้าแจ๋ว</span>
165
- </button>
158
+ <div class="postcard__face postcard__front">
159
+ <button class="postcard__turn" type="button" aria-label="พลิกดูด้านหลัง"><img src="…" alt=""> — or <div class="ph"></div> —</button>
160
+ <div class="postcard__band">
161
+ <div class="postcard__words"><span class="t-h1">ป้าแจ๋ว</span><span class="t-body">บ้านสวนผัก</span></div>
162
+ <button class="icon-btn icon-btn--invert" aria-label="ถูกใจ" aria-pressed="false">♥</button>
163
+ </div>
164
+ </div>
165
+ <div class="postcard__face postcard__back">
166
+ <button class="postcard__turn" type="button" aria-label="พลิกดูด้านหน้า">
167
+ <div class="postcard__words">…</div><p class="postcard__caption">ชอบชงกาแฟดริปตอนเช้า</p>
168
+ </button>
169
+ <div class="postcard__voice"><div class="progress"></div><span class="t-caption">0:07 / 0:12</span><button class="icon-btn icon-btn--invert" aria-label="ฟัง">▶</button></div> (a voice note: no caption — the line, its time, the control under them, at the middle)
170
+ </div>
166
171
  </div>
167
- Each face is the whole tap target; cardds.js toggles .is-flipped on the
168
- postcard (either face, Enter/Space too). A voice note, a like, a reply live
169
- OUTSIDE the postcard (a Row under it): a face is a button, and a button
170
- holds no button. The faces share one box (a 3:2 landscape card, the media
171
- radius); the turn is a Y rotation at --motion-flip, a plain swap under
172
- prefers-reduced-motion. Reads ink from the card slot it sits in. */
172
+ The two faces share one box and turn (a Y rotation at --motion-flip, a plain
173
+ swap under prefers-reduced-motion). A face is a div: the TURN is the button
174
+ that fills it (the photo, or the caption), the band lies over its foot and
175
+ turns with it — words and the heart; the back sets the name a fifth of the way down, the words after it; a
176
+ button in the band is never inside the turn button. The size is the cover
177
+ card's (--postcard-min-h = the cover's min-height, the card radius). Who
178
+ liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
173
179
  .postcard {
174
180
  position: relative;
175
- aspect-ratio: 3 / 2;
181
+ min-height: var(--postcard-min-h, 22.5rem);
176
182
  perspective: 60rem;
177
- border-radius: var(--r-media);
183
+ border-radius: var(--postcard-radius, var(--r-card));
184
+ isolation: isolate;
178
185
  }
179
186
  .postcard__face {
180
187
  position: absolute;
181
188
  inset: 0;
182
189
  display: grid;
183
190
  align-content: end;
184
- padding: var(--sp-4);
185
- gap: var(--sp-2);
186
- border: var(--border-w) solid var(--card-border);
191
+ border: var(--border-w) solid var(--postcard-face-border, var(--card-border));
187
192
  border-radius: inherit;
188
193
  overflow: hidden;
189
194
  isolation: isolate;
190
- color: inherit;
191
- text-align: left;
192
195
  backface-visibility: hidden;
193
196
  -webkit-backface-visibility: hidden;
194
197
  transition: transform var(--motion-flip) ease-in-out;
195
- box-shadow: var(--shadow-float);
198
+ box-shadow: var(--postcard-face-shadow, var(--shadow-float));
199
+ }
200
+ /* the turn: the button that fills the face, under the band */
201
+ .postcard__turn {
202
+ position: absolute;
203
+ inset: 0;
204
+ display: grid;
205
+ align-content: start;
206
+ padding: var(--postcard-face-pad, var(--card-pad-rest));
207
+ border: 0;
208
+ border-radius: inherit;
209
+ background: transparent;
210
+ color: inherit;
211
+ text-align: left;
212
+ cursor: pointer;
196
213
  }
197
- .postcard__face:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
198
- /* the front: the photo (or the wireframe box) fills it, the name reads on a scrim */
199
- .postcard__front { background: var(--card-3-bg); color: var(--card-3-ink); }
200
- .postcard__front > img,
201
- .postcard__front > .ph {
214
+ .postcard__turn:focus-visible { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
215
+ /* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
216
+ .postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); color: var(--postcard-front-ink, var(--card-3-ink)); }
217
+ .postcard__front .postcard__turn > img,
218
+ .postcard__front .postcard__turn > .ph {
202
219
  position: absolute;
203
220
  inset: 0;
204
221
  width: 100%;
205
222
  height: 100%;
206
223
  object-fit: cover;
207
- z-index: -2;
208
224
  border: 0;
209
225
  border-radius: 0;
210
226
  min-height: 0;
@@ -213,20 +229,40 @@
213
229
  content: "";
214
230
  position: absolute;
215
231
  inset: 0;
216
- z-index: -1;
232
+ z-index: 1;
233
+ pointer-events: none;
217
234
  background: linear-gradient(to top, var(--scrim), transparent 45%);
218
235
  }
219
- /* the back: paper — the words, big enough to read at arm's length */
236
+ /* the back: paper — the caption and the name packed at the middle, big enough to read at arm's length */
220
237
  .postcard__back {
221
- background: var(--card-2-bg);
222
- color: var(--card-2-ink);
223
- align-content: space-between;
238
+ background: var(--postcard-back-bg, var(--card-2-bg));
239
+ color: var(--postcard-back-ink, var(--card-2-ink));
224
240
  transform: rotateY(180deg);
225
241
  }
226
- .postcard__caption { margin: 0; font: var(--type-title); text-wrap: pretty; }
227
- .postcard__label { font: var(--type-overline); letter-spacing: var(--track-overline); text-transform: uppercase; opacity: 0.85; }
242
+ .postcard__back .postcard__turn { align-content: start; padding-top: var(--postcard-back-top, 20%); gap: var(--postcard-back-gap, var(--sp-5)); } /* the name a fifth down the paper, the words after it */
243
+ .postcard__caption { margin: 0; font: var(--postcard-caption-font, var(--type-title)); text-wrap: pretty; }
244
+ /* the band: the face's foot — name and line left, the heart right; in flow at the face's end, over the turn.
245
+ Taps between its words fall through to the turn; its buttons take their own. */
246
+ .postcard__band {
247
+ position: relative;
248
+ z-index: 2;
249
+ display: flex;
250
+ align-items: end;
251
+ gap: var(--postcard-band-gap, var(--sp-3));
252
+ padding: var(--postcard-face-pad, var(--card-pad-rest));
253
+ pointer-events: none;
254
+ }
255
+ .postcard__band > * { pointer-events: auto; }
256
+ .postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
257
+ .postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-3-ink)); --on-accent: var(--postcard-front-bg, var(--card-3-bg)); }
258
+ .postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
228
259
  .postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
229
260
  .postcard.is-flipped > .postcard__back { transform: rotateY(0); }
261
+ /* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
262
+ under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
263
+ .postcard__voice { position: absolute; inset: 0; z-index: 2; display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
264
+ .postcard__voice > * { pointer-events: auto; }
265
+ .postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
230
266
  @media (prefers-reduced-motion: reduce) {
231
267
  .postcard__face { transition: none; }
232
268
  }
package/css/numbers.css CHANGED
@@ -31,8 +31,8 @@
31
31
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
32
32
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - 1px), currentColor calc(100% - var(--_w)));
33
33
  }
34
- .ring__value { font: var(--type-stat); letter-spacing: var(--track-display); }
35
- .ring__label { font: var(--type-caption); color: var(--card-muted); }
34
+ .ring__value { font: var(--ring-value-font, var(--type-stat)); letter-spacing: var(--track-display); }
35
+ .ring__label { font: var(--ring-label-font, var(--type-caption)); color: var(--ring-label-ink, var(--card-muted)); }
36
36
  .card--3 .ring__label { color: inherit; opacity: 0.7; }
37
37
  .ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
38
38
  .ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
@@ -52,10 +52,10 @@
52
52
  --on-accent: var(--_bg);
53
53
  list-style: none;
54
54
  margin: 0;
55
- padding: var(--sp-2);
55
+ padding: var(--track-pad, var(--sp-2));
56
56
  display: flex;
57
57
  align-items: center;
58
- border-radius: var(--r-chip);
58
+ border-radius: var(--track-radius, var(--r-chip));
59
59
  background: var(--_bg);
60
60
  color: var(--_ink);
61
61
  }
@@ -96,9 +96,9 @@
96
96
  grid-template-areas: "label num" "foot num";
97
97
  align-items: end;
98
98
  gap: var(--sp-2) var(--sp-4);
99
- min-height: var(--tile-h);
99
+ min-height: var(--card-band-min-h, var(--tile-h));
100
100
  }
101
- .band__label { grid-area: label; align-self: start; font: var(--type-label); font-size: var(--fs-body); line-height: 1.2; }
101
+ .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; }
102
102
  .band__num {
103
103
  grid-area: num;
104
104
  font: 800 var(--fs-giant) / 0.9 var(--font-display);
@@ -106,14 +106,14 @@
106
106
  font-variant-numeric: tabular-nums;
107
107
  }
108
108
  .band__num small { font-size: 0.5em; font-weight: 700; margin-right: 0.1em; } /* unit or % */
109
- .band__foot { grid-area: foot; font: var(--type-caption); color: var(--card-muted); }
109
+ .band__foot { grid-area: foot; font: var(--band-foot-font, var(--type-caption)); color: var(--band-foot-ink, var(--card-muted)); }
110
110
  .card--3 .band__foot { color: inherit; opacity: 0.7; }
111
111
  /* band stack: ONE card boundary; bands are frameless inside it, a hairline
112
112
  between each. Bands may still carry slot tints (card--2 / card--3). */
113
113
  .band-stack {
114
114
  display: grid;
115
- border: var(--border-w) solid var(--card-border);
116
- border-radius: var(--r-card);
115
+ border: var(--border-w) solid var(--band-stack-border, var(--card-border));
116
+ border-radius: var(--band-stack-radius, var(--r-card));
117
117
  overflow: hidden;
118
118
  }
119
119
  .band-stack > .card--band { border: 0; border-radius: 0; box-shadow: none; }
@@ -164,7 +164,7 @@
164
164
 
165
165
  /* ---- picker: horizontal snap picker, centre value big ----
166
166
  <div class="picker"><div class="picker__rail"><button class="picker__item">100</button>…</div><span class="picker__unit">ml</span></div> */
167
- .picker { display: grid; gap: var(--sp-1); justify-items: center; position: relative; }
167
+ .picker { display: grid; gap: var(--picker-gap, var(--sp-1)); justify-items: center; position: relative; }
168
168
  .picker__rail {
169
169
  width: 100%;
170
170
  display: grid;
@@ -172,7 +172,7 @@
172
172
  /* side padding = one third of the rail, so the first and last value can sit
173
173
  in the centre; the content box is then one third wide, and each column
174
174
  fills it (percent columns resolve against the content box) */
175
- padding-inline: calc(100% / 3);
175
+ padding-inline: var(--picker-rail-pad-x, calc(100% / 3));
176
176
  grid-auto-columns: 100%;
177
177
  overflow-x: auto;
178
178
  scroll-snap-type: x mandatory;
@@ -185,8 +185,8 @@
185
185
  border: 0;
186
186
  background: transparent;
187
187
  color: inherit;
188
- padding: var(--sp-2) 0;
189
- font: var(--type-stat);
188
+ padding: var(--picker-item-pad-y, var(--sp-2)) var(--picker-item-pad-x, 0);
189
+ font: var(--picker-item-font, var(--type-stat));
190
190
  letter-spacing: var(--track-display);
191
191
  font-variant-numeric: tabular-nums;
192
192
  opacity: 0.3;
@@ -205,7 +205,7 @@
205
205
  left: calc(100% / 3);
206
206
  }
207
207
  .picker::after { left: auto; right: calc(100% / 3); }
208
- .picker__unit { font: var(--type-caption); color: var(--card-muted); }
208
+ .picker__unit { font: var(--picker-unit-font, var(--type-caption)); color: var(--picker-unit-ink, var(--card-muted)); }
209
209
  .card--3 .picker__unit { color: inherit; opacity: 0.7; }
210
210
  /* the scrolled-to item lights up by position — no JS */
211
211
  @supports (animation-timeline: view(x)) {
@@ -221,30 +221,30 @@
221
221
  .badge {
222
222
  display: inline-grid;
223
223
  place-items: center;
224
- min-width: var(--badge);
225
- height: var(--badge);
226
- padding: 0 var(--sp-2);
227
- border-radius: var(--r-chip);
228
- background: var(--accent);
229
- color: var(--on-accent);
230
- font: var(--type-label);
224
+ min-width: var(--badge-min-w, var(--badge));
225
+ height: var(--badge-h, var(--badge));
226
+ padding: var(--badge-pad-y, 0) var(--badge-pad-x, var(--sp-2));
227
+ border-radius: var(--badge-radius, var(--r-chip));
228
+ background: var(--badge-bg, var(--accent));
229
+ color: var(--badge-ink, var(--on-accent));
230
+ font: var(--badge-font, var(--type-label));
231
231
  font-variant-numeric: tabular-nums;
232
232
  vertical-align: middle;
233
233
  }
234
- .badge--soft { background: var(--ink-tint); color: inherit; }
234
+ .badge--soft { background: var(--badge-soft-bg, var(--ink-tint)); color: inherit; }
235
235
 
236
236
  /* ---- bars: three or four bars with a count on top — the glance chart ----
237
237
  <div class="bars"><div class="bar" style="--v: 16"><span>+16</span></div>… --max on the parent */
238
- .bars { --max: 20; --_h: 5rem; display: flex; align-items: end; gap: var(--sp-2); height: calc(var(--_h) + var(--sp-8)); }
238
+ .bars { --max: 20; --_h: 5rem; display: flex; align-items: end; gap: var(--bars-gap, var(--sp-2)); height: calc(var(--_h) + var(--sp-8)); }
239
239
  .bar {
240
240
  flex: 1;
241
241
  min-width: 0;
242
242
  display: grid;
243
243
  align-content: end;
244
244
  justify-items: center;
245
- gap: var(--sp-1);
245
+ gap: var(--bar-gap, var(--sp-1));
246
246
  height: 100%;
247
- font: var(--type-caption);
247
+ font: var(--bar-font, var(--type-caption));
248
248
  font-weight: 700;
249
249
  }
250
250
  .bar::after {
@@ -256,3 +256,19 @@
256
256
  }
257
257
  .bar--on::after { background: var(--accent); }
258
258
  .bar--on > span { background: var(--accent); color: var(--on-accent); padding: var(--sp-0) var(--sp-2); border-radius: var(--r-chip); }
259
+
260
+ /* ---- progress: one straight line, the done share solid, the rest faint (Lh 2026-09-22) — a voice note's
261
+ timeline, a download, a step count as a line. <div class="progress" role="progressbar" style="--progress-at: 0.4"></div>
262
+ --progress-at is DATA the component writes (0–1), not a knob (scripts/knobs.mjs lists it). The done part is a
263
+ gradient of the line's ink sized by the share; the rest is the background colour. */
264
+ .progress {
265
+ display: block;
266
+ width: 100%;
267
+ height: var(--progress-h, var(--sp-1));
268
+ border-radius: var(--progress-radius, var(--r-chip));
269
+ color: var(--progress-done-ink, inherit);
270
+ background-color: var(--progress-rest-bg, color-mix(in srgb, currentColor 25%, transparent));
271
+ background-image: linear-gradient(currentColor, currentColor);
272
+ background-size: calc(var(--progress-at, 0) * 100%) 100%;
273
+ background-repeat: no-repeat;
274
+ }
package/css/people.css CHANGED
@@ -15,9 +15,9 @@
15
15
  border-radius: 50%;
16
16
  display: grid;
17
17
  place-items: center;
18
- background: var(--card-2-bg);
19
- color: var(--card-2-ink);
20
- font: var(--type-label);
18
+ background: var(--avatar-bg, var(--card-2-bg));
19
+ color: var(--avatar-ink, var(--card-2-ink));
20
+ font: var(--avatar-font, var(--type-label));
21
21
  font-size: calc(var(--_size) * 0.3);
22
22
  text-transform: uppercase;
23
23
  letter-spacing: 0.02em;
@@ -30,10 +30,10 @@
30
30
  .avatar--lg { --_size: var(--avatar-lg); }
31
31
  .avatar--xl { --_size: var(--avatar-xl); } /* sheet head icon ring */
32
32
  /* outline avatar: no fill, an edge — icons in a ring, "add" slots */
33
- .avatar--outline { background: transparent; color: inherit; border: var(--border-w) solid var(--outline); }
34
- .avatar--add { background: transparent; color: inherit; border: var(--border-w) dashed var(--outline); cursor: pointer; }
33
+ .avatar--outline { background: transparent; color: inherit; border: var(--border-w) solid var(--avatar-outline-border, var(--outline)); }
34
+ .avatar--add { background: transparent; color: inherit; border: var(--border-w) dashed var(--avatar-add-border, var(--outline)); cursor: pointer; }
35
35
  /* accent avatar: the "+7" tail, the selected person */
36
- .avatar--on { background: var(--accent); color: var(--on-accent); }
36
+ .avatar--on { background: var(--avatar-on-bg, var(--accent)); color: var(--avatar-on-ink, var(--on-accent)); }
37
37
  /* halo: faint concentric rings behind a hero avatar (decorative) */
38
38
  .avatar--halo::before {
39
39
  content: "";
@@ -55,7 +55,7 @@
55
55
  <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> */
56
56
  .avatar-pick {
57
57
  display: flex;
58
- gap: var(--sp-4);
58
+ gap: var(--avatar-pick-gap, var(--sp-4));
59
59
  overflow-x: auto;
60
60
  scroll-snap-type: x proximity;
61
61
  scrollbar-width: none; /* starts at the card's padding edge, like every row */
@@ -64,16 +64,16 @@
64
64
  .avatar-pick__item {
65
65
  scroll-snap-align: start;
66
66
  flex: none;
67
- width: var(--avatar-lg);
67
+ width: var(--avatar-pick-item-w, var(--avatar-lg));
68
68
  display: grid;
69
69
  justify-items: center;
70
- gap: var(--sp-2);
70
+ gap: var(--avatar-pick-item-gap, var(--sp-2));
71
71
  border: 0;
72
72
  background: transparent;
73
73
  color: inherit;
74
74
  padding: 0;
75
75
  text-align: center;
76
76
  }
77
- .avatar-pick__name { font: var(--type-label); font-size: var(--fs-sm); }
78
- .avatar-pick__role { font: var(--type-caption); color: var(--card-muted); margin-top: calc(-1 * var(--sp-1)); }
77
+ .avatar-pick__name { font: var(--avatar-pick-name-font, var(--type-label)); font-size: var(--avatar-pick-name-font-size, var(--fs-sm)); }
78
+ .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)); }
79
79
  .card--3 .avatar-pick__role { color: inherit; opacity: 0.7; }
package/css/sheet.css CHANGED
@@ -80,7 +80,7 @@
80
80
  overflow: clip;
81
81
  border-radius: var(--r-card) var(--r-card) 0 0;
82
82
  border-bottom: 0;
83
- box-shadow: var(--shadow-sheet);
83
+ box-shadow: var(--sheet-shadow, var(--shadow-sheet));
84
84
  }
85
85
  .sheet--full { --_y: 0%; --_cover: 0rem; }
86
86
  .sheet--half { --_y: calc(var(--sheet-half) * 100%); --_cover: calc(var(--sheet-half) * var(--_box)); }
@@ -178,7 +178,7 @@
178
178
  opacity: 0.3;
179
179
  width: var(--tap-sm);
180
180
  height: var(--_handle);
181
- border-radius: var(--r-chip);
181
+ border-radius: var(--sheet-handle-radius, var(--r-chip));
182
182
  margin: 0 auto;
183
183
  padding: 0;
184
184
  touch-action: none;
@@ -210,8 +210,8 @@
210
210
  min-height: var(--tap);
211
211
  text-align: center;
212
212
  }
213
- .sheet__title { font: var(--type-h2); letter-spacing: var(--track-h2); margin: 0; }
214
- .sheet__sub { font: var(--type-caption); color: var(--card-muted); }
213
+ .sheet__title { font: var(--sheet-title-font, var(--type-h2)); letter-spacing: var(--track-h2); margin: 0; }
214
+ .sheet__sub { font: var(--sheet-sub-font, var(--type-caption)); color: var(--sheet-sub-ink, var(--card-muted)); }
215
215
 
216
216
  /* ============================================================
217
217
  .sheet-stage — the overlay stage: a page-height box the sheet
@@ -233,7 +233,7 @@
233
233
  margin-inline: auto;
234
234
  overflow: clip; /* not hidden: a clipped box is not a scroll container, so revealing a focused field never shifts the stage */
235
235
  container-type: size; /* the sheet's body reads this box's height as 100cqh (--_box above) */
236
- background: var(--surface);
236
+ background: var(--sheet-stage-bg, var(--surface));
237
237
  }
238
238
  /* on an app screen the stage is the page: it takes the screen's inset back (edge to
239
239
  edge, like a step) and, after a fixed .appbar, the room the screen reserved for
@@ -250,8 +250,13 @@
250
250
  inset-inline: var(--screen-pad);
251
251
  top: var(--_band-centre);
252
252
  translate: 0 -50%;
253
- /* no z-index: it follows the background in DOM order and the sheet, later, covers it at full */
253
+ /* as tall as the band less the screen's inset at both ends (Lh 2026-09-22: the room under the hero = the room at its sides);
254
+ what it holds fills it (a postcard's min-height gives way) */
255
+ height: calc((100cqh - var(--_top, 0rem)) / 2 - 2 * var(--screen-pad));
256
+ display: grid;
254
257
  }
258
+ .sheet-stage > .sheet-stage__hero > * { min-height: 0; } /* outranks a component's own min-height (the postcard's) */
259
+ /* no z-index on the hero: it follows the background in DOM order and the sheet, later, covers it at full */
255
260
 
256
261
  /* ---- map: the box a map engine draws in, behind a stage sheet ----
257
262
  <div class="sheet-stage__bg"><div class="map"><div>…the engine's canvas…</div>
@@ -268,7 +273,7 @@
268
273
  position: absolute;
269
274
  inset: 0;
270
275
  isolation: isolate; /* the ring and the hint stack inside the map only — a sheet over the map covers them */
271
- color: var(--on-surface);
276
+ color: var(--map-ink, var(--on-surface));
272
277
  background:
273
278
  repeating-linear-gradient(75deg, transparent 0 5.5rem, var(--hairline) 5.5rem 6.25rem),
274
279
  repeating-linear-gradient(-18deg, transparent 0 8.75rem, var(--ink-tint) 8.75rem 9.25rem),
@@ -286,9 +291,9 @@
286
291
  height: var(--map-ring);
287
292
  translate: -50% -50%;
288
293
  border-radius: 50%;
289
- border: var(--border-w) solid var(--accent);
290
- background: color-mix(in srgb, var(--accent) 22%, transparent);
291
- box-shadow: var(--shadow-float);
294
+ border: var(--border-w) solid var(--map-ring-border, var(--accent));
295
+ background: var(--map-ring-bg, color-mix(in srgb, var(--accent) 22%, transparent));
296
+ box-shadow: var(--map-ring-shadow, var(--shadow-float));
292
297
  pointer-events: none;
293
298
  z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
294
299
  }
@@ -297,10 +302,10 @@
297
302
  right: var(--sp-2);
298
303
  bottom: var(--sp-2);
299
304
  padding: var(--sp-0) var(--sp-2);
300
- border-radius: var(--r-chip);
301
- background: color-mix(in srgb, var(--surface) 80%, transparent);
302
- color: var(--on-surface-muted);
303
- font: var(--type-caption);
305
+ border-radius: var(--map-credit-radius, var(--r-chip));
306
+ background: var(--map-credit-bg, color-mix(in srgb, var(--surface) 80%, transparent));
307
+ color: var(--map-credit-ink, var(--on-surface-muted));
308
+ font: var(--map-credit-font, var(--type-caption));
304
309
  pointer-events: none;
305
310
  z-index: 1;
306
311
  }
@@ -311,12 +316,12 @@
311
316
  translate: -50% 0;
312
317
  max-width: calc(100% - 2 * var(--sp-4));
313
318
  padding: var(--sp-2) var(--sp-4);
314
- border-radius: var(--r-chip);
315
- background: var(--card-3-bg);
316
- color: var(--card-3-ink);
317
- font: var(--type-label);
319
+ border-radius: var(--map-hint-radius, var(--r-chip));
320
+ background: var(--map-hint-bg, var(--card-3-bg));
321
+ color: var(--map-hint-ink, var(--card-3-ink));
322
+ font: var(--map-hint-font, var(--type-label));
318
323
  text-align: center;
319
- box-shadow: var(--shadow-float);
324
+ box-shadow: var(--map-hint-shadow, var(--shadow-float));
320
325
  pointer-events: none;
321
326
  z-index: 1;
322
327
  }
@@ -327,10 +332,9 @@
327
332
  /* EVERY sheet has one shape (Lh, 2026-09-21): the handle and the head stay at the top and never scroll · the
328
333
  foot's button stands at the bottom, and ABOVE the keyboard when there is one · everything between them
329
334
  scrolls, as far as it needs to. In a .step the keyboard's room is the step's (--kb, step.css); on a stage
330
- the sheet gives it up itself, as --_lift — the same token a raised sheet floats on. */
335
+ the sheet gives it up itself, as --_lift. (A sheet ALWAYS stands on the floor — `.sheet--raised`, the one that
336
+ floated above a footnote strip on a dim stage, was retired 2026-09-22: that is a Modal's job.) */
331
337
  .sheet-stage > .sheet { --_lift: var(--kb, 0rem); bottom: var(--_lift); }
332
- .sheet-stage > .sheet--raised { --_lift: calc(var(--tap-lg) + var(--kb, 0rem)); }
333
- .sheet--raised { --_lift: var(--tap-lg); bottom: var(--_lift); border-radius: var(--r-card); border-bottom: var(--border-w) solid var(--card-border); }
334
338
  .sheet-stage__foot {
335
339
  position: absolute;
336
340
  inset-inline: 0;
@@ -339,8 +343,8 @@
339
343
  margin: 0;
340
344
  display: grid;
341
345
  place-items: center;
342
- font: var(--type-caption);
343
- color: var(--card-3-ink);
346
+ font: var(--sheet-stage-foot-font, var(--type-caption));
347
+ color: var(--sheet-stage-foot-ink, var(--card-3-ink));
344
348
  z-index: 2;
345
349
  }
346
350
 
package/css/stack.css CHANGED
@@ -19,7 +19,7 @@
19
19
  <div class="sheet-stack sheet-stack--tap"> (drop --tap for a static stack)
20
20
  <article class="card card--2">
21
21
  <div class="card__head">
22
- <button class="icon-btn icon-btn--sm card__back" aria-label="Back">…</button>
22
+ <button class="icon-btn icon-btn--sm sheet-stack__back" aria-label="Back">…</button>
23
23
  <h2 class="t-h2 t-caps">Shopping</h2>
24
24
  <span class="icon-row">…</span> (optional trailing icons)
25
25
  </div>
@@ -243,7 +243,7 @@
243
243
  .sheet-stack--tap > .card.is-open > .card__head {
244
244
  padding-left: calc(var(--tap-sm) + var(--sp-3));
245
245
  }
246
- .sheet-stack--tap .card__back {
246
+ .sheet-stack--tap .sheet-stack__back {
247
247
  position: absolute;
248
248
  left: 0;
249
249
  top: 50%;
@@ -252,7 +252,7 @@
252
252
  visibility: hidden;
253
253
  transition: opacity 0.35s ease, visibility 0.35s;
254
254
  }
255
- .sheet-stack--tap > .card.is-open .card__back {
255
+ .sheet-stack--tap > .card.is-open .sheet-stack__back {
256
256
  opacity: 1;
257
257
  visibility: visible;
258
258
  }
@@ -4,9 +4,12 @@
4
4
  Not part of the bundle: copy this file into the project as its own
5
5
  `theme.css` and load it AFTER cardds.css. It is the whole public
6
6
  surface of the system — the four size bases and the colour roles.
7
- Everything else in tokens.css is derived from these; a project never
8
- sets a derived token (--sp-4, --fs-h1, --r-card, --tap, --sheet-*…)
9
- and never a multiplier.
7
+ Widest first: a BASE moves a whole family; a role / root token one meaning
8
+ everywhere (--accent, --btn-h, --header-h…); a KNOB one component
9
+ (--btn-pad-x, --chip-bg… — docs/knobs.md lists them; also settable on any
10
+ element, for the components inside it). Never a step of the scale
11
+ (--sp-4, --fs-h1, --r-card), a multiplier, a private --_* or a published
12
+ value (--card-pad, --bar-reserve, --sheet-*).
10
13
 
11
14
  The values below are the defaults (the wireframe): as shipped, this
12
15
  file changes nothing (tests/tokens.spec.js holds it to that).