@usableapp/cardds 0.7.103 → 0.7.104

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 (60) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/css/actions.css +102 -36
  3. package/css/base.css +139 -15
  4. package/css/card.css +308 -79
  5. package/css/choice.css +25 -33
  6. package/css/finish.css +28 -27
  7. package/css/forms.css +6 -14
  8. package/css/journey.css +4 -4
  9. package/css/layover.css +26 -24
  10. package/css/lists.css +8 -6
  11. package/css/media.css +13 -74
  12. package/css/numbers.css +37 -32
  13. package/css/palette.css +1 -1
  14. package/css/people.css +3 -8
  15. package/css/post.css +23 -19
  16. package/css/sheet.css +84 -56
  17. package/css/stack.css +39 -215
  18. package/css/stamp.css +41 -29
  19. package/css/step.css +1 -107
  20. package/css/sticky.css +43 -41
  21. package/css/theme.template.css +12 -0
  22. package/css/tokens.css +100 -26
  23. package/dist/actions/Chip.d.ts +1 -1
  24. package/dist/actions/Chip.js +3 -2
  25. package/dist/cardds.css +1053 -868
  26. package/dist/cards/Card.d.ts +3 -3
  27. package/dist/cards/Card.js +1 -1
  28. package/dist/cards/CardFeed.js +7 -9
  29. package/dist/catalog.json +1 -1
  30. package/dist/layover/Snackbar.js +2 -2
  31. package/dist/lists/Bubble.d.ts +1 -1
  32. package/dist/lists/Bubble.js +1 -1
  33. package/dist/lists/Row.js +5 -7
  34. package/dist/numbers/Picker.js +11 -18
  35. package/dist/people/Avatar.d.ts +1 -1
  36. package/dist/people/Avatar.js +3 -2
  37. package/dist/post/PostDesk.js +6 -9
  38. package/dist/post/PostLetter.js +4 -4
  39. package/dist/pressable.d.ts +9 -0
  40. package/dist/pressable.js +6 -0
  41. package/dist/room.d.ts +5 -0
  42. package/dist/room.js +9 -0
  43. package/dist/scaffold/Panes.js +8 -14
  44. package/dist/sheets/Sheet.d.ts +1 -1
  45. package/dist/sheets/Sheet.js +2 -3
  46. package/dist/sheets/SheetSteps.js +3 -3
  47. package/dist/sheets/StackSheet.d.ts +1 -1
  48. package/dist/sheets/sheetStepsPile.js +2 -3
  49. package/dist/sheets/stackCard.js +67 -115
  50. package/dist/state.d.ts +21 -0
  51. package/dist/state.js +28 -1
  52. package/dist/sticky/Sticky.js +3 -7
  53. package/dist/templates/Home.d.ts +1 -1
  54. package/dist/templates/Splash.js +2 -2
  55. package/dist/templates/Walkthrough.js +4 -5
  56. package/docs/reference.md +71 -9
  57. package/lib/catalog.mjs +23 -7
  58. package/lint/rules.mjs +7 -5
  59. package/lint/values.mjs +144 -0
  60. package/package.json +6 -4
package/css/sheet.css CHANGED
@@ -71,7 +71,7 @@
71
71
  inset: 0;
72
72
  top: var(--_top, 0); /* a stage keeps its top bar's line free — see .sheet-stage */
73
73
  translate: 0 calc(var(--_y) + var(--_drag, 0rem)); /* --_drag: the Sheet component, only while the handle is held */
74
- transition: translate var(--motion-sheet) ease;
74
+ transition: translate var(--motion-sheet) var(--ease);
75
75
 
76
76
  /* handle · head · body: a column, the body takes what is left and scrolls
77
77
  (below) — so the handle and the head never move, whatever the content */
@@ -104,7 +104,7 @@
104
104
  that holds that way's WHOLE form, its submit on the sheet's foot, leading to the next page. The base
105
105
  carries no control. It is NOT for a form taken apart — one sheet per field, a "send" left on the base:
106
106
  a form lives whole in one sheet. */
107
- .sheet--away { --_y: 100%; --_cover: var(--_box); visibility: hidden; transition: translate var(--motion-sheet) ease, visibility 0s linear var(--motion-sheet); }
107
+ .sheet--away { --_y: 100%; --_cover: var(--_box); visibility: hidden; transition: translate var(--motion-sheet) var(--ease), visibility 0s var(--ease-linear) var(--motion-sheet); }
108
108
  .sheet.is-dragging { transition: none; } /* follows the finger; the snap gets the ease back */
109
109
 
110
110
  /* the body — everything under the head, the one thing in a sheet that
@@ -149,23 +149,8 @@
149
149
  One row, ≤ 2 buttons. <div class="card__foot"><button class="btn btn--primary btn--block">…</button></div> */
150
150
  /* …and the link row under the button, when there is one (its line + the gap over it) */
151
151
  .sheet:has(> .card__foot--link) { --_foot: calc(var(--card-gap) + var(--sheet-foot-h, calc(var(--btn-h) + var(--sp-1) + var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)))) + var(--card-pad) + var(--_safe-b-open)); } /* --sheet-foot-h: the foot's rows as the Sheet measured them (a button grows with the text size) */
152
- .sheet > .card__foot {
153
- position: absolute; inset: calc(-1 * var(--border-w)) 0 0; z-index: 1; /* the sheet's border box: the same height the sheet's own % reads */
154
- align-items: end;
155
- align-content: end;
156
- padding: 0 var(--card-pad) calc(var(--card-pad) + var(--_safe-b-open));
157
- background: linear-gradient(var(--card-bg), var(--card-bg)) bottom / 100% var(--_foot) no-repeat;
158
- pointer-events: none;
159
- translate: 0 calc(-1 * var(--_y) - var(--_drag, 0rem));
160
- transition: translate var(--motion-sheet) ease, visibility 0s;
161
- }
162
152
  .sheet > .card__foot > * { pointer-events: auto; }
163
153
  .sheet:has(> .card__foot) > .sheet__body { margin-bottom: calc(var(--_foot) - var(--card-pad)); } /* the body stops above the strip (the card's own padding is the rest) */
164
- .sheet.is-dragging > .card__foot { transition: none; }
165
- /* at peek and away the foot is NOT held on the box's edge: it stands on the sheet's own bottom edge (no counter-slide), so it
166
- LEAVES AND ARRIVES WITH ITS SHEET — solid the whole way, hidden only once the slide is over. It used to stay in place and
167
- fade: the body slid through the strip behind a half-transparent button (timebank, Lh 2026-10-01: "ตัวหนังสือลอดใต้ปุ่ม"). */
168
- :is(.sheet--peek, .sheet--away) > .card__foot { translate: 0 0; visibility: hidden; transition: translate var(--motion-sheet) ease, visibility 0s linear var(--motion-sheet); }
169
154
  /* that padding can be taller than the body's own box (a short box: a small phone with the keyboard up) and a
170
155
  box is never smaller than its padding — so the pinned parts must not give way to it: the body runs past
171
156
  the sheet's clipped edge instead, where nothing shows anyway. At peek and away no part of the body shows. */
@@ -182,14 +167,14 @@
182
167
  touch-action: none hands a touch drag to the Sheet component instead of to the scroller. */
183
168
  .sheet__handle {
184
169
  position: absolute;
185
- z-index: 1;
170
+ z-index: var(--z-over);
186
171
  top: calc((var(--card-pad) - var(--sp-1)) / 2); /* the dots' top: (pad − 4) / 2. The line starts there too */
187
172
  left: 50%;
188
173
  translate: -50% 0;
189
174
  border: 0;
190
175
  color: inherit; /* a <button> has its own UA colour; the mark is the card's ink */
191
176
  background: currentColor;
192
- opacity: 0.3;
177
+ opacity: var(--fade-dim);
193
178
  width: calc(var(--sp-base) * 18); /* 72: the line. The grab is the ::before, not this width */
194
179
  height: var(--_handle);
195
180
  border-radius: var(--sheet-handle-radius, var(--r-chip));
@@ -198,22 +183,22 @@
198
183
  touch-action: none;
199
184
  cursor: grab;
200
185
  }
201
- /* paper: the head keeps the sheet's own colour, the content is white (Lh 2026-10-05).
186
+ /* paper: the head keeps the sheet's own colour, the content is the theme's paper, --action-bg, the ground of every field (Lh 2026-10-05: white; Lh 2026-10-20: the theme's, never a fixed white).
202
187
  The body and the foot read as paper; the handle and the head stay on the card.
203
188
  The flex gap under the head is outside the body, so it stays the sheet's tint.
204
- The first field would sit on that colour seam. The white inset is --sp-6 (24px at
189
+ The first field would sit on that colour seam. The paper's inset is --sp-6 (24px at
205
190
  the default spacing), the body's own top padding, and only when the sheet is paper.
206
191
  A plain sheet keeps padding-top 0. A sheet in a stack wears the same class and the same
207
- inset: the stack's own gap under the head is the card's colour, so the row still needs this white.
208
- The paper is a surface of the plain card's side: on a dark sheet its words still turn to onlight. */
192
+ inset: the stack's own gap under the head is the card's colour, so the row still needs this paper.
193
+ The paper takes the side of the controls' paper (--action-dark): on a dark sheet its words still turn to onlight. */
209
194
  .sheet--paper > .sheet__body {
210
- --card-bg: var(--color-paper, #fff);
211
- --dark: var(--card-1-dark);
195
+ --card-bg: var(--action-bg);
196
+ --dark: var(--action-dark);
212
197
  background: var(--card-bg);
213
198
  color: var(--card-ink);
214
199
  padding-top: var(--sp-6);
215
200
  }
216
- .sheet--paper > .card__foot { --card-bg: var(--color-paper, #fff); color: var(--color-ink, #141414); }
201
+ .sheet--paper > .card__foot { --card-bg: var(--action-bg); --dark: var(--action-dark); --card-foot-ink: var(--card-ink); }
217
202
  .sheet__handle--dots {
218
203
  --_dot: var(--sp-1);
219
204
  --_step: calc(var(--_dot) * 2.5);
@@ -230,15 +215,15 @@
230
215
  background-position: 0 0, var(--_step) 0, calc(2 * var(--_step)) 0, calc(3 * var(--_step)) 0;
231
216
  }
232
217
  /* the app reloads what the sheet shows (`refreshing`, off unless set): the mark changes colour exactly as a stack sheet's grip
233
- does — white → black and back, so a light sheet and a dark one each get a moment it stands out. --_grip and the keyframes are
218
+ does — the ondark ink → the onlight ink and back, so a light sheet and a dark one each get a moment it stands out. --_grip and the keyframes are
234
219
  the stack's (stack.css): one sign for both. Nothing travels. */
235
220
  .sheet.is-loading > .sheet__handle {
236
221
  opacity: 1;
237
- background-color: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
238
- animation: stack-grip-dissolve 0.4s ease-in-out infinite alternate;
222
+ background-color: color-mix(in srgb, var(--ink-onlight) calc(var(--_grip) * 100%), var(--ink-ondark));
223
+ animation: stack-grip-dissolve var(--motion-pulse) var(--ease-in-out) infinite alternate;
239
224
  }
240
225
  .sheet.is-loading > .sheet__handle--dots {
241
- --_ink: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
226
+ --_ink: color-mix(in srgb, var(--ink-onlight) calc(var(--_grip) * 100%), var(--ink-ondark));
242
227
  background-color: transparent;
243
228
  }
244
229
  @media (prefers-reduced-motion: reduce) {
@@ -262,18 +247,18 @@
262
247
  - a label .row with its .check / .toggle may be the head when the sheet's
263
248
  whole ask is that switch: peek shows it (a row is --tap tall plus its own
264
249
  padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
265
- .sheet > .card__head { min-height: var(--sheet-head-h); }
250
+ .sheet > .card__head { --card-head-min-h: var(--sheet-head-h); }
266
251
  /* a sheet's title is ONE line, always (Lh 2026-09-28, timebank #153: "ข้อกำหนดและเงื่อนไขการใช้งาน" wrapped to two lines at 390px) — the
267
252
  thing at its end keeps its place beside it; a caption still takes its own row. A title too long for the line RUNS sideways
268
253
  like a song title (Lh 2026-10-03, as the StackSheet's — it was cut with …, and the end of it could never be read): the Sheet
269
254
  measures --_over, the width it overflows by, and sets .is-long; one way — it waits, runs to its end, waits, and starts again
270
255
  from the first letter. The move is text-indent: the heading is the app's own element, there is no inner box to slide.
271
256
  Before the Sheet has measured, and where motion is off, it is cut with …. */
272
- .sheet > .card__head:not(:has(> :is(.t-caption, .t-body))) { flex-wrap: nowrap; }
257
+ .sheet > .card__head:not(:has(> :is(.t-caption, .t-body))) { --card-head-wrap: nowrap; }
273
258
  .sheet > .card__head > :is(h1, h2, h3):first-child, .sheet__title { white-space: nowrap; overflow-x: clip; overflow-y: visible; text-overflow: ellipsis; min-width: 0; } /* clip SIDEWAYS only: hidden cut the Thai tone marks over the line */
274
259
  .sheet > .card__head > :is(h1, h2, h3):first-child.is-long, .sheet__title.is-long {
275
260
  text-overflow: clip;
276
- animation: sheet-title-run var(--sheet-title-run-time, var(--_run, 6s)) ease-in-out infinite; /* --_run: from the overflow, 30px a second + the pauses */
261
+ animation: sheet-title-run var(--sheet-title-run-time, var(--_run, var(--motion-run))) var(--ease-in-out) infinite; /* --_run: from the overflow, 30px a second + the pauses */
277
262
  }
278
263
  @keyframes sheet-title-run { 0%, 20% { text-indent: 0; } 85%, 100% { text-indent: calc(-1 * var(--_over)); } }
279
264
  @media (prefers-reduced-motion: reduce) {
@@ -364,11 +349,11 @@
364
349
  isolation: isolate; /* the ring and the hint stack inside the map only — a sheet over the map covers them */
365
350
  color: var(--map-ink, var(--on-surface));
366
351
  background:
367
- repeating-linear-gradient(75deg, transparent 0 5.5rem, var(--hairline) 5.5rem 6.25rem),
368
- repeating-linear-gradient(-18deg, transparent 0 8.75rem, var(--ink-tint) 8.75rem 9.25rem),
352
+ repeating-linear-gradient(var(--street-1), transparent 0 calc(var(--tap-base) * 11 / 6), var(--hairline) calc(var(--tap-base) * 11 / 6) calc(var(--tap-base) * 25 / 12)),
353
+ repeating-linear-gradient(var(--street-2), transparent 0 calc(var(--tap-base) * 35 / 12), var(--ink-tint) calc(var(--tap-base) * 35 / 12) calc(var(--tap-base) * 37 / 12)),
369
354
  var(--card-2-bg);
370
355
  }
371
- .map > :first-child { position: absolute; inset: 0; z-index: 0; isolation: isolate; background: transparent; } /* its own stacking context: an engine's layered panes stay under the ring; an engine's own grey ground would hide the pattern while tiles load */
356
+ .map > :first-child { position: absolute; inset: 0; z-index: var(--z-flat); isolation: isolate; background: transparent; } /* its own stacking context: an engine's layered panes stay under the ring; an engine's own grey ground would hide the pattern while tiles load */
372
357
  .map__ring {
373
358
  position: absolute;
374
359
  left: 50%;
@@ -383,7 +368,7 @@
383
368
  background: var(--map-ring-bg, var(--accent-soft));
384
369
  box-shadow: var(--map-ring-shadow, var(--shadow-float));
385
370
  pointer-events: none;
386
- z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
371
+ z-index: var(--z-over); /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
387
372
  }
388
373
  .map__pin { /* mark="pin": an exact point — the pin's TIP is where the ring's centre sits, so the engine reads the element's bottom-centre */
389
374
  position: absolute;
@@ -398,11 +383,11 @@
398
383
  color: var(--map-pin-ink, var(--card-ink));
399
384
  filter: drop-shadow(var(--map-pin-drop, var(--shadow-float)));
400
385
  pointer-events: none;
401
- z-index: 1;
386
+ z-index: var(--z-over);
402
387
  }
403
388
  /* the lucide tip stops 2.2 of 24 short of the box's bottom: lowered so the tip IS the bottom-centre; filled with the surface so the
404
389
  street pattern never shows through the pin's body */
405
- .map__pin > .icon { width: 100%; height: 100%; translate: 0 9%; fill: var(--surface); }
390
+ .map__pin > .icon { --icon-size: 100%; --icon-shift: 0 9%; }
406
391
  .map__credit { /* the tile provider's credit — required by OSM; a caption, not the engine's own control */
407
392
  position: absolute;
408
393
  right: var(--sp-2);
@@ -413,7 +398,7 @@
413
398
  color: var(--map-credit-ink, var(--on-surface-muted));
414
399
  font: var(--map-credit-font, var(--type-caption));
415
400
  pointer-events: none;
416
- z-index: 1;
401
+ z-index: var(--z-over);
417
402
  }
418
403
  .map__hint {
419
404
  position: absolute;
@@ -431,7 +416,7 @@
431
416
  text-align: center;
432
417
  box-shadow: var(--map-hint-shadow, var(--shadow-float));
433
418
  pointer-events: none;
434
- z-index: 1;
419
+ z-index: var(--z-over);
435
420
  }
436
421
 
437
422
  /* ---- the map IN FLOW (Lh 2026-10-17): `.map--inline` — a box in a card, or the whole of a sheet's body ----
@@ -461,7 +446,7 @@
461
446
  name; the picked one fills with accent. Above the ring: the ring takes no taps, the places do. */
462
447
  .map-pick {
463
448
  position: absolute;
464
- z-index: 2;
449
+ z-index: var(--z-over-2);
465
450
  width: var(--tap);
466
451
  height: var(--tap);
467
452
  translate: -50% -50%;
@@ -492,7 +477,7 @@
492
477
  background: var(--map-pick-name-bg, color-mix(in srgb, var(--surface) 85%, transparent));
493
478
  font: var(--map-pick-font, var(--type-caption));
494
479
  }
495
- .map-pick:has(:checked) { z-index: 3; } /* the picked place's name lies over a neighbour's */
480
+ .map-pick:has(:checked) { z-index: var(--z-over-3); } /* the picked place's name lies over a neighbour's */
496
481
  .map-pick:has(:checked) > :is(.map-pick__mark, .map-pick__name) { background: var(--map-pick-on-bg, var(--accent)); color: var(--map-pick-on-ink, var(--on-accent)); }
497
482
  .map-pick:has(:focus-visible) > .map-pick__mark { outline: var(--focus); outline-offset: var(--sp-0); }
498
483
  .map-pick:has(:disabled) { opacity: var(--off); cursor: default; }
@@ -517,8 +502,8 @@
517
502
  /* words on the scrim: white on a strip of the scrim over it. The scrim alone is half black, so over a white page it is #808080, where white
518
503
  is 3.9:1; doubled, #404040, 10:1. It read --card-3-ink, a brand's light ink, on a black scrim */
519
504
  background: var(--sheet-stage-foot-bg, var(--scrim));
520
- color: var(--sheet-stage-foot-ink, #ffffff);
521
- z-index: 2;
505
+ color: var(--sheet-stage-foot-ink, var(--ink-ondark));
506
+ z-index: var(--z-over-2);
522
507
  }
523
508
 
524
509
  /* dim stage: blur + darken whatever sits behind the sheet */
@@ -528,9 +513,9 @@
528
513
  position: absolute;
529
514
  inset: 0;
530
515
  background: var(--scrim);
531
- z-index: 1;
516
+ z-index: var(--z-over);
532
517
  }
533
- .sheet-stage--dim .sheet { z-index: 2; }
518
+ .sheet-stage--dim .sheet { z-index: var(--z-over-2); }
534
519
 
535
520
  /* ---- sheet steps: one task in steps inside one sheet (Lh 2026-09-24) ----
536
521
  <div class="sheet-steps"> › <ol class="sheet-steps__track"> of <li class="sheet-step sheet-step--done|now|next">. The done steps
@@ -541,14 +526,14 @@
541
526
  falls — no height is written. Nothing fades: done and next read at full ink. */
542
527
  .sheet__body:has(> .sheet-steps) { display: flex; flex-direction: column; }
543
528
  .sheet-steps { flex-shrink: 0; min-height: calc(100% + var(--kb, 0rem)); /* the keyboard's height more: the pile of next steps stays under the keyboard, it never rises with it */ display: flex; flex-direction: column; touch-action: pan-y; position: relative; }
544
- .sheet-steps__holder { position: absolute; top: 0; left: 0; width: 1rem; height: 1rem; padding: 0; border: 0; opacity: 0; pointer-events: none; font-size: 1rem; } /* holds the cursor (and the phone's keyboard) while a step folds; font 1rem = the browser's own size, so iOS does not zoom to it */
529
+ .sheet-steps__holder { position: absolute; top: 0; left: 0; width: var(--browser-text); height: var(--browser-text); padding: 0; border: 0; opacity: 0; pointer-events: none; font-size: var(--browser-text); } /* holds the cursor (and the phone's keyboard) while a step folds; font 1rem = the browser's own size, so iOS does not zoom to it */
545
530
  .sheet-steps__track { list-style: none; margin: 0; padding: 0 0 var(--sheet-steps-end, var(--sp-6)); flex: 1; /* room under the LAST step: scrolled to the end it never touches the foot's button (Lh 2026-09-28, #153) */ display: flex; flex-direction: column; }
546
531
 
547
532
  .sheet-step {
548
533
  position: relative; min-width: 0;
549
534
  display: grid; grid-template-columns: minmax(0, 1fr); /* the column is the step's width, never its words' — a long summary is cut with …, it never pushes the row past the sheet's edge (Lh 2026-09-28, #153) */ grid-template-rows: auto 0fr; align-content: start; /* mid-fold the rows sum to less than the box: the spare room stays BELOW, never spread into the title row (Lh: it looked like a spring) */
550
535
  flex: 0 0 auto;
551
- transition: grid-template-rows var(--motion-sheet) ease, flex-grow var(--motion-sheet) ease;
536
+ transition: grid-template-rows var(--motion-sheet) var(--ease), flex-grow var(--motion-sheet) var(--ease);
552
537
  --_row-h: var(--sheet-steps-row-h, calc(var(--tap-base) * 2 / 3)); /* the bar's own floor, read once (__head and __go below share it) */
553
538
  }
554
539
  /* the open step GROWS into the room left, so the steps not done yet wait in a pile at the bottom — and a move is an ACCORDION:
@@ -556,7 +541,7 @@
556
541
  with them, nothing jumps (Lh 2026-09-24) */
557
542
  .sheet-step--now { grid-template-rows: auto 1fr; flex-grow: 1; }
558
543
  .sheet-step + .sheet-step { margin-top: var(--sheet-steps-gap, var(--sp-1)); } /* the folded bars read as a pile, a little ground between them (Lh 2026-09-25) */
559
- .sheet-step__head { /* every step's title line is a quiet BAR (a tint of the ink, not the brand — a brand bar read as a button, Lh 2026-09-25), the same height open or folded — opening shows the white room under it, never a taller bar (Lh 2026-09-25; was a thin rule on top) */
544
+ .sheet-step__head { /* every step's title line is a quiet BAR (a tint of the ink, not the brand — a brand bar read as a button, Lh 2026-09-25), the same height open or folded — opening shows the card's room under it, never a taller bar (Lh 2026-09-25; was a thin rule on top) */
560
545
  display: flex; align-items: center; gap: var(--sp-3); min-height: var(--_row-h); min-width: 0; /* THIN: 32px, a summary line, not a button (Lh 2026-09-25) — the tap stays 44, see __go */
561
546
  padding-inline: var(--sheet-step-bar-pad-x, var(--sp-3)); border-radius: var(--sheet-step-bar-radius, var(--r-chip));
562
547
  background: var(--sheet-step-bar-bg, var(--ink-tint)); color: var(--sheet-step-bar-ink, inherit);
@@ -564,11 +549,11 @@
564
549
  border-top: var(--sheet-step-border-w, 0rem) solid var(--sheet-step-border, currentColor); /* a rule instead of the bar: set this, the bar's bg transparent, radius 0 */
565
550
  }
566
551
  .sheet-step__title { font: var(--sheet-step-title-font, var(--type-label)); margin: 0; flex: 0 1 auto; min-width: 0; outline: none; /* the label size, not a body title: a pile of rows must stay small (Lh 2026-09-24) */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
567
- .sheet-step__num { /* `numbered`: the step's number in a disc BEFORE its title (Lh 2026-09-25) — a white disc on the bar, the number in the ink */
552
+ .sheet-step__num { /* `numbered`: the step's number in a disc BEFORE its title (Lh 2026-09-25) — a disc of the card's colour on the bar, the number in the ink */
568
553
  flex: none; display: grid; place-items: center; box-sizing: border-box; inline-size: var(--sheet-step-num-size, 1.408em); block-size: var(--sheet-step-num-size, 1.408em); /* em, not rem (Lh 2026-10-03, as the StackSheet's count): the disc is cut from ITS OWN number's size —
569
554
  at a large text size the number grew and a 24px disc did not, so the digit sat on its bottom edge; 1.408em of a 17px caption = the 24px it always was */
570
555
  padding-bottom: var(--sheet-step-num-lift, 0.1em); /* the room UNDER the number lifts it onto the disc's middle: a digit has no descender, so a centred line box sets it low */
571
- border-radius: 50%; font: var(--sheet-step-num-font, var(--type-caption)); line-height: 1; font-variant-numeric: tabular-nums;
556
+ border-radius: 50%; font: var(--sheet-step-num-font, var(--type-caption)); line-height: var(--lh-solid); font-variant-numeric: tabular-nums;
572
557
  background: var(--sheet-step-num-bg, var(--card-bg)); color: var(--sheet-step-num-ink, inherit);
573
558
  }
574
559
  .sheet-step__summary { color: var(--sheet-step-summary-ink, var(--card-muted)); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -576,11 +561,54 @@
576
561
  .sheet-step__body { min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
577
562
  .sheet-step__inner { display: grid; gap: var(--card-gap); padding-block: var(--card-gap); padding-inline: var(--sheet-step-inner-pad-x, var(--sheet-step-bar-pad-x, var(--sp-3))); } /* the open step's content in the frame of its title's words (the bar's own inset), so it reads as that step's (Lh 2026-09-28, #153) */
578
563
  .sheet-step__inner > * { min-width: 0; }
579
- .sheet-step__inner > .link { justify-self: center; } /* a link of its own row in a step (skip, "change number", "set PIN again"): centred under the step, as the skip (Lh 2026-09-25) */
580
- .sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s linear var(--motion-sheet); }
564
+ .sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s var(--ease-linear) var(--motion-sheet); }
581
565
  .sheet-step__go { position: absolute; inset: min(0rem, (var(--_row-h) - var(--tap-sm)) / 2) 0 auto; min-height: max(100%, var(--tap-sm)); /* a thin bar still takes a 44px tap */ margin: 0; cursor: pointer; }
582
566
  /* the last step's finish, when the app gives one: under the steps */
583
567
  .sheet-steps__finish { display: grid; padding-top: var(--card-gap); }
584
568
  @media (prefers-reduced-motion: reduce) { .sheet-step { transition: none; } }
585
- /* the step waiting for its answer: its buttons take no taps (the app guards its own handler too) */
586
- .sheet-step--busy .btn { pointer-events: none; opacity: 0.5; }
569
+
570
+ /* ==== the step bar answers a press, as PRESS in actions.css answers every other control (#62: a block is written in its own file) ==== */
571
+ .sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
572
+
573
+ /* ==== a body that holds only an EmptyState stands it at --empty-state-at of its room, as a Step does (step.css) (#62: a block is written in its own file) ==== */
574
+ .sheet__body:has(> .empty-state:only-child) { display: flex; flex-direction: column; gap: 0; }
575
+ .sheet__body:has(> .empty-state:only-child)::before { content: ""; flex: calc(var(--empty-state-at, 0) * 5) 1 0; }
576
+ .sheet__body:has(> .empty-state:only-child)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0) * 5) 1 0; }
577
+
578
+ /* ---- where it sits in what stack.css draws (docs/reference.md rule 15) ---- */
579
+ /* a pull in the content, already at the top: the words move down, the card stays. --_refresh is the finger, private.
580
+ A paper body adds its own --sp-6 on top of this (the rule below): the stack's gap under the head is the
581
+ card's colour, so without the paper's inset the first row sits on that seam. */
582
+ .sheet-stack > .card > .sheet__body { padding-top: var(--_refresh, 0rem); transition: padding-top var(--motion-sheet) var(--ease); }
583
+ /* the head's band is as slim as it can be (Lh 2026-10-06: 20 over · 48 head · 20 under read as a heavy bar): the paper starts
584
+ --sp-3 under the head, not a whole --card-gap (--sp-2 at first; Lh added 4 the same day). The body is pulled up by the difference — the card's gap is left alone, the
585
+ stack's step and the body's own rows read it. */
586
+ .sheet-stack > .card.sheet--paper > .sheet__body {
587
+ margin-top: calc(var(--sp-3) - var(--card-gap));
588
+ padding-top: calc(var(--sp-6) + var(--_refresh, 0rem));
589
+ transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
590
+ }
591
+ /* a pull follows the finger at once, on paper too: the rule above is as strong as the stack's own `is-refreshing` one and comes later */
592
+ .sheet-stack > .card.sheet--paper > .sheet__body.is-refreshing { transition: none; }
593
+ /* a closed paper sheet's body stays clear (Lh 2026-10-06). The next sheet's round top does not
594
+ cover the corners, and a paper body showed there beside the head. Clear, the card's own colour
595
+ shows in that pocket. Open, the body is paper, and the first row sits --sp-6 down, the same paper
596
+ inset a single paper sheet keeps: the flex gap under the head is the card's colour, so the row
597
+ would otherwise sit on that seam. A pull adds --_refresh on top of the inset. On the way back
598
+ into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
599
+ sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
600
+ at the moment the sheet stops. Rising, the body is paper at once. */
601
+ .sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
602
+ background-color: transparent;
603
+ transition:
604
+ padding-top var(--motion-sheet) var(--ease),
605
+ background-color var(--motion-quick) var(--ease) calc(var(--stagger, 0) * var(--motion-stagger) + var(--motion-sheet) - var(--motion-quick));
606
+ }
607
+ @media (prefers-reduced-motion: reduce) { .sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
608
+ transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
609
+ } }
610
+ /* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
611
+ .screen--fill > .appbar + .sheet-stage { margin-top: 0; }
612
+
613
+ /* ---- where it sits in what stack.css draws: a body pulled to refresh holds still (docs/reference.md rule 15) ---- */
614
+ .sheet-stack > .card > .sheet__body.is-refreshing { transition: none; }