@usableapp/cardds 0.6.4 → 0.6.6

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 (53) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +72 -26
  3. package/css/actions.css +15 -0
  4. package/css/base.css +9 -6
  5. package/css/card.css +4 -4
  6. package/css/choice.css +11 -0
  7. package/css/layover.css +3 -0
  8. package/css/lists.css +4 -0
  9. package/css/sheet.css +8 -1
  10. package/css/stack.css +55 -0
  11. package/css/step.css +43 -13
  12. package/css/tokens.css +2 -1
  13. package/dist/actions/Btn.d.ts +1 -1
  14. package/dist/actions/IconBtn.d.ts +1 -1
  15. package/dist/actions/Link.d.ts +3 -1
  16. package/dist/actions/Link.js +2 -2
  17. package/dist/cardds.css +154 -25
  18. package/dist/choice/Check.d.ts +6 -3
  19. package/dist/choice/Check.js +7 -4
  20. package/dist/index.d.ts +4 -1
  21. package/dist/index.js +4 -1
  22. package/dist/scaffold/ActionBar.d.ts +3 -2
  23. package/dist/scaffold/ActionBar.js +3 -2
  24. package/dist/scaffold/AppBar.d.ts +4 -1
  25. package/dist/scaffold/AppBar.js +4 -3
  26. package/dist/scaffold/Centre.d.ts +1 -1
  27. package/dist/scaffold/Centre.js +1 -1
  28. package/dist/scaffold/Page.d.ts +17 -0
  29. package/dist/scaffold/Page.js +24 -0
  30. package/dist/scaffold/Screen.d.ts +13 -8
  31. package/dist/scaffold/Screen.js +24 -7
  32. package/dist/sheets/SheetStack.d.ts +1 -1
  33. package/dist/sheets/SheetStack.js +6 -1
  34. package/dist/sheets/SheetStage.d.ts +4 -1
  35. package/dist/sheets/SheetStage.js +2 -2
  36. package/dist/sheets/StackBack.d.ts +2 -1
  37. package/dist/sheets/StackBack.js +3 -2
  38. package/dist/sheets/StackSheet.d.ts +35 -0
  39. package/dist/sheets/StackSheet.js +24 -0
  40. package/dist/sheets/Step.d.ts +4 -4
  41. package/dist/sheets/Step.js +4 -4
  42. package/dist/templates/Home.d.ts +48 -0
  43. package/dist/templates/Home.js +40 -0
  44. package/dist/templates/Walkthrough.d.ts +42 -0
  45. package/dist/templates/Walkthrough.js +35 -0
  46. package/dist/type/Text.d.ts +3 -1
  47. package/dist/type/Text.js +2 -2
  48. package/dist/type/icons.d.ts +1 -1
  49. package/dist/type/icons.js +2 -2
  50. package/icons.svg +2 -0
  51. package/package.json +1 -1
  52. package/dist/scaffold/BaseContent.d.ts +0 -17
  53. package/dist/scaffold/BaseContent.js +0 -18
@@ -3,6 +3,6 @@ import { createElement } from 'react';
3
3
  import { cx } from '../cx.js';
4
4
  import { Icon } from '../type/Icon.js';
5
5
  /** Link — an inline text action big enough to tap (40px): "See all ↗" in a card head, "Skip" between pager buttons. */
6
- export function Link({ muted, icon, href, className, children, ...rest }) {
7
- return createElement(href ? 'a' : 'button', { href, type: href ? undefined : 'button', className: cx('link', muted && 'link--muted', className), ...rest }, children, icon && _jsx(Icon, { name: icon, size: "xs" }));
6
+ export function Link({ muted, sm, icon, href, className, children, ...rest }) {
7
+ return createElement(href ? 'a' : 'button', { href, type: href ? undefined : 'button', className: cx('link', muted && 'link--muted', sm && 'link--sm', className), ...rest }, children, icon && _jsx(Icon, { name: icon, size: "xs" }));
8
8
  }
package/dist/cardds.css CHANGED
@@ -61,7 +61,7 @@
61
61
  and never a multiplier, so the proportions of the system hold at any size.
62
62
  The px in the comments are at these defaults (1rem = 16px). */
63
63
  --sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
64
- --fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.775 (17).
64
+ --fs-base: 1.375rem; /* 22 type: the BODY is the base (Lh 2026-09-22) — every --fs-* is a multiplier of it; nothing under 0.68 (15, minor links — was 0.775/17 until 2026-09-23). A new size is Lh's call only.
65
65
  Also the member's text-size setting: the app writes it on <html> at run time (2.2rem = 160%) — words grow, things don't */
66
66
  --r-base: 1rem; /* 16 radius: --r-* (--r-chip stays fully round) */
67
67
  --tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
@@ -144,6 +144,7 @@
144
144
 
145
145
  /* THE SCALE (Lh 2026-09-22): 7 sizes for 16 jobs — a size is a job's fit, not a rung; jobs share sizes.
146
146
  Nothing that reads is under 17 (the smallest ui: chips). No clamp/vw: a size is one number. */
147
+ --fs-2xs: calc(var(--fs-base) * 0.68); /* 15 minor links — a ctaLink, a "details" (Lh 2026-09-23: added by Lh; ONLY Lh adds a size) */
147
148
  --fs-xs: calc(var(--fs-base) * 0.775); /* 17 the smallest ui words: ring xs, a tile-badge label */
148
149
  --fs-caption: calc(var(--fs-base) * 0.775); /* 17 */
149
150
  --fs-label: calc(var(--fs-base) * 0.775); /* 17 chips, buttons' small words */
@@ -484,26 +485,29 @@ button { font: inherit; cursor: pointer; }
484
485
  .card--3 .t-muted { color: inherit; opacity: 0.7; } /* card-muted is tuned for light cards; dim instead on the dark slot */
485
486
  .t-caps { text-transform: uppercase; }
486
487
  .grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
488
+ /* read, not shown (Lh 2026-09-23): words a screen reader needs — a page's h1 the design leaves out — kept in the page, taking no room.
489
+ The 1px is the one the platform needs to keep it in the accessibility tree, not a length of the design. */
490
+ .t-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
487
491
 
488
492
  /* ---- base content: words straight on the base, no card around them — an
489
493
  a heading, a paragraph, maybe a .cta-pack. Was .section until
490
494
  2026-09-16 (Lh: the name said nothing about WHERE it lives; this one does).
491
495
  In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
492
- .base-content {
496
+ .page {
493
497
  display: grid;
494
498
  gap: var(--base-content-gap, var(--sp-3));
495
499
  align-content: start; /* a tall column never stretches the rows — the words hug the top */
496
500
  padding-block: var(--base-content-pad-y, var(--sp-4));
497
501
  }
498
- .base-content > .t-body { color: var(--base-content-body-ink, var(--on-surface)); max-width: 34ch; } /* full ink: a sentence is read, muted is for meta (Lh 2026-09-22 — the words on the base were grey and hard to read) */
502
+ .page > .t-body { color: var(--base-content-body-ink, var(--on-surface)); max-width: 34ch; } /* full ink: a sentence is read, muted is for meta (Lh 2026-09-22 — the words on the base were grey and hard to read) */
499
503
  /* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
500
504
  (the walk's dots are NOT here — they are the Step's, after its Pages: step.css, Lh 2026-09-22) */
501
505
  /* the page's ONE heading on the base (a screen with no TopBar: CardHead level="h1") reads at the card heading's size —
502
506
  h1 is the semantic level, not a bigger face; --fs-h1 is the bar title's size (Lh 2026-09-22) */
503
- .base-content > .card__head > .t-h1 { font-size: var(--fs-h2); }
504
- .base-content > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; align-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
505
- .base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
506
- .base-content .cta-pack {
507
+ .page > .card__head > .t-h1 { font-size: var(--fs-h2); }
508
+ .page > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; align-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
509
+ .page > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
510
+ .page .cta-pack {
507
511
  display: flex;
508
512
  align-items: start; /* buttons keep their own height, never the row's */
509
513
  gap: var(--sp-2);
@@ -755,13 +759,13 @@ button { font: inherit; cursor: pointer; }
755
759
  user-select: none; /* a walk is dragged, not read-and-selected — without this, a finger crossing the words highlights them instead of turning the page (timebank #59) */
756
760
  touch-action: pan-y; /* leaves vertical scroll to the browser; horizontal is the app's own drag, never a native pan */
757
761
  }
758
- .pages > :is(.card, .base-content) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
762
+ .pages > :is(.card, .page) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
759
763
  .pages > [inert] { visibility: hidden; }
760
- /* pages of WORDS (Lh, 2026-09-20): the same walk with .base-content pages, on the base — in a .step the
764
+ /* pages of WORDS (Lh, 2026-09-20): the same walk with .page pages, on the base — in a .step the
761
765
  .pages is the reading block (step.css), the round pager in the bar turns it, the same slideTo().
762
766
  <section class="step"><div class="pages">
763
- <section class="base-content" aria-current="step">…</section>
764
- <section class="base-content" inert>…</section>
767
+ <section class="page" aria-current="step">…</section>
768
+ <section class="page" inert>…</section>
765
769
  </div></section> <div class="action-bar action-bar--pager">← →</div> */
766
770
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
767
771
  ::view-transition-old(centre-card), ::view-transition-new(centre-card) {
@@ -1065,6 +1069,9 @@ button { font: inherit; cursor: pointer; }
1065
1069
  gap: var(--card-gap, var(--sp-5));
1066
1070
  overflow: clip;
1067
1071
  }
1072
+ /* a base that is ONE BaseContent is a page's words (a title, a welcome, the news): it reads from the TOP, like any page —
1073
+ loose things (a picture, a line) still sit against the peeks (Lh 2026-09-23, the home) */
1074
+ .sheet-stack__base:has(> .page:only-child) { align-content: start; }
1068
1075
 
1069
1076
  /* back button: lives in a slot the header reserves once open, and DISSOLVES
1070
1077
  into it — an opacity fade in place, never a slide in from the left (the
@@ -1092,6 +1099,58 @@ button { font: inherit; cursor: pointer; }
1092
1099
  visibility: visible;
1093
1100
  }
1094
1101
 
1102
+ /* ---- a stacked sheet with a SheetBody has the ONE sheet shape (Lh 2026-09-23, the home layout): sheet head
1103
+ pinned at the top · sheet body scrolls, as long as it needs · sheet foot (a CardFoot, the CTA) at the bottom.
1104
+ The card becomes a flex column and stops scrolling itself — only the body does. A sheet with no SheetBody
1105
+ keeps the old way: the whole open card scrolls. ---- */
1106
+ .sheet-stack--tap > .card:has(> .sheet__body) { display: flex; flex-direction: column; gap: var(--card-gap); }
1107
+ .sheet-stack--tap > .card:has(> .sheet__body).is-open { overflow: clip; }
1108
+ .sheet-stack > .card:has(> .sheet__body) > :not(.sheet__body) { flex-shrink: 0; }
1109
+ .sheet-stack > .card > .sheet__body { --_cover: 0rem; } /* nothing slid under here: the stack's sheet is always whole */
1110
+
1111
+ /* ---- StackSheet's tabs (Lh 2026-09-23): the head's end is a row of small icon buttons, the sheet's pages — the chosen one
1112
+ filled (.icon-btn--invert); a count is a small circle over the icon's top right corner ---- */
1113
+ .stack-sheet__tabs { display: flex; gap: var(--stack-sheet-tabs-gap, 0rem); flex: none; } /* no gap: each tab is a 40px target already, and an open head (back · title · tabs) must fit one line — Lh 2026-09-23 */
1114
+ .stack-sheet__tab { position: relative; }
1115
+ .stack-sheet__tab > .stack-sheet__count { /* outranks .badge (numbers.css loads after this file) */
1116
+ position: absolute;
1117
+ top: calc(-1 * var(--sp-1));
1118
+ right: calc(-1 * var(--sp-1));
1119
+ min-width: var(--stack-sheet-count-h, 1.25rem);
1120
+ height: var(--stack-sheet-count-h, 1.25rem);
1121
+ padding: 0 var(--sp-1);
1122
+ font: var(--type-caption);
1123
+ line-height: 1;
1124
+ pointer-events: none;
1125
+ }
1126
+ /* a tab not chosen is the icon alone — no ring, no ground (Lh 2026-09-23: "the circle around is not pretty"); the chosen one turns
1127
+ to the opposite colour (.icon-btn--invert). */
1128
+ .stack-sheet__tab:not(.icon-btn--invert) { border-color: transparent; background: transparent; box-shadow: none; color: inherit; } /* the sheet's own ink, on any paper */
1129
+ /* the last tab's ICON lines up with the inset, not its button's edge: an unchosen tab shows the glyph only, so the button's own
1130
+ margin around it reaches into the card's padding — and an open head (back · title · three tabs) keeps one line (Lh 2026-09-23) */
1131
+ .card__head > .stack-sheet__tabs { margin-right: calc(-1 * var(--sp-2)); }
1132
+
1133
+ /* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
1134
+ tappable"): a SMALL DISC — its ring kept, 32px instead of 44, the chevron in it at 20 — so the head gives it 32px + a gap
1135
+ (was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
1136
+ .sheet-stack--tap > .card.is-open > .card__head { padding-left: var(--stack-back-room, calc(var(--stack-back-size, calc(var(--tap-base) * 2 / 3)) + var(--sp-2))); }
1137
+ .sheet-stack--tap .sheet-stack__back {
1138
+ width: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
1139
+ height: var(--stack-back-size, calc(var(--tap-base) * 2 / 3));
1140
+ min-width: 0;
1141
+ min-height: 0;
1142
+ padding: 0;
1143
+ }
1144
+ .sheet-stack--tap .sheet-stack__back > .icon { width: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); height: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); flex: none; } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
1145
+ .sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--stack-back-size, calc(var(--tap-base) * 2 / 3)) - var(--tap-sm)) / 2); }
1146
+
1147
+ /* the open sheet's title folds it (SheetStack.tsx): it says so to a pointer */
1148
+ .sheet-stack--tap > .card.is-open > .card__head > :is(h1, h2, h3) { cursor: pointer; }
1149
+ /* a stacked sheet is a control until it is open — tapped, never read into: no text selection (a drag, a double tap, a long press),
1150
+ no grey flash, no iOS callout; its head stays one when open. Only the OPEN sheet's body is text to select (Lh 2026-09-23) */
1151
+ .sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; }
1152
+ .sheet-stack--tap > .card.is-open > :not(.card__head) { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
1153
+
1095
1154
  /* ---- css/sheet.css — sheet single: .sheet — one card as a bottom sheet, peek / half / full ---- */
1096
1155
  /* ============================================================
1097
1156
  cardds/sheet.css — sheet single: one card as a bottom sheet.
@@ -1189,7 +1248,7 @@ button { font: inherit; cursor: pointer; }
1189
1248
  setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
1190
1249
  :root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
1191
1250
 
1192
- /* ---- called sheets (Lh, 2026-09-20): a .step whose .base-content has buttons that each CALL a sheet ----
1251
+ /* ---- called sheets (Lh, 2026-09-20): a .step whose .page has buttons that each CALL a sheet ----
1193
1252
  .sheet--away = not on the screen: slid all the way out (translate, like every state — the material never
1194
1253
  changes), and hidden once it has left so its shadow does not lie on the step's bottom edge; write it
1195
1254
  with `inert` (the React Sheet does). WHO brings it onto the screen is the app's business — a button, a route:
@@ -1338,6 +1397,13 @@ button { font: inherit; cursor: pointer; }
1338
1397
  that bar — the stage keeps the bar's line free itself (--_top), so the reserve
1339
1398
  would be paid twice and the page would scroll by it */
1340
1399
  .screen > .sheet-stage { margin-inline: calc(-1 * var(--screen-pad)); width: auto; } /* auto: 100% would keep the inset width */
1400
+ /* a stage laid OVER its screen (a Modal a link opened, Lh 2026-09-23): the page shows through, the stage covers it and its bar */
1401
+ .sheet-stage--over, .screen > .sheet-stage--over {
1402
+ position: absolute; inset: 0; z-index: 40;
1403
+ width: auto; height: auto; max-width: none; margin: 0;
1404
+ background: transparent;
1405
+ --_top: 0rem;
1406
+ }
1341
1407
  .appbar + .sheet-stage { margin-top: calc(-1 * (var(--header-h) + var(--header-gap, 0rem))); }
1342
1408
  .sheet-stage__bg { position: absolute; inset: 0; } /* map / previous cards behind */
1343
1409
  /* the hero: ONE thing shown in the band a half sheet leaves open (a postcard being made,
@@ -1467,7 +1533,7 @@ button { font: inherit; cursor: pointer; }
1467
1533
  (see "LAYERS" below).
1468
1534
 
1469
1535
  <section class="step">
1470
- <section class="base-content">…the words…</section> ← first
1536
+ <section class="page">…the words…</section> ← first
1471
1537
  <article class="card sheet sheet--half">…the controls… <div class="card__foot">…</div></article> ← last, optional
1472
1538
  </section>
1473
1539
  <div class="action-bar">…</div> ← only when there is no sheet
@@ -1517,20 +1583,23 @@ button { font: inherit; cursor: pointer; }
1517
1583
  /* no bar at all — the step is the screen's first child (a page whose one heading is the base's own, Lh 2026-09-22):
1518
1584
  the words start a share of the screen down — --screen-lead, 15% of its height (Lh 2026-09-22), so a tall phone gives
1519
1585
  more air and a short one less; a screen with a bar keeps the bar's own room */
1520
- .screen > .step:first-child { --_lead: var(--screen-lead, 15cqh); }
1586
+ .screen > .step:first-child, .screen--lead > .step { --_lead: var(--screen-lead, 15cqh); } /* top="lead" says it; a Step first says it too */
1521
1587
 
1522
1588
  /* the bar's room — read from actions.css, never recomputed. Margin, not
1523
1589
  padding, so the clip cuts at the box edge and nothing shows behind the
1524
1590
  bar. Any parent that hosts the bar counts — <body>, an app shell, a demo
1525
1591
  frame — the bar is fixed to the viewport wherever it sits. */
1526
1592
  :has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
1593
+ /* a walk (the dots float on its foot): the reading block runs right to the bar's top edge — the reserve's breathing gap (--sp-6)
1594
+ is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
1595
+
1527
1596
  /* LAYERS (Lh, 2026-09-20): the base, then the sheet over it, then the top bar. A bar is the BASE's — the
1528
1597
  move of a page of words — so a step with a sheet has NO bar: the sheet finishes on its own .card__foot
1529
1598
  (sheet.css), and a form lives whole in ONE sheet, never item by item on the base. That is how cardds's
1530
1599
  own screens are composed (the guide); the framework does not police it — a component never changes
1531
1600
  because another one is there. */
1532
1601
 
1533
- /* the reading block: a .base-content (base.css) — the same words-on-the-base
1602
+ /* the reading block: a .page (base.css) — the same words-on-the-base
1534
1603
  block as anywhere else (it was .step__card, and a .card before 2026-09-16;
1535
1604
  Lh: a step reads like a page, not a card in a page — so it IS the page's
1536
1605
  block, one name). Here it keeps a card's inner rhythm (a head, --card-gap
@@ -1538,7 +1607,7 @@ button { font: inherit; cursor: pointer; }
1538
1607
  gets the flex rule: it hugs its content, and when the column runs out (a
1539
1608
  short phone, large text) it shrinks and scrolls inside itself instead of
1540
1609
  pushing the bar or the sheet */
1541
- .step > .base-content, .step > .pages > .base-content {
1610
+ .step > .page, .step > .pages > .page {
1542
1611
  --card-pad: 0rem; /* published for .bleed: nothing to cancel */
1543
1612
  --card-gap: var(--sp-4);
1544
1613
  gap: var(--card-gap);
@@ -1546,17 +1615,14 @@ button { font: inherit; cursor: pointer; }
1546
1615
  align-content: start;
1547
1616
  color: var(--on-surface);
1548
1617
  }
1549
- /* the reading block is the .base-content — or the .pages that holds several of them, a walk of words
1618
+ /* the reading block is the .page — or the .pages that holds several of them, a walk of words
1550
1619
  (card.css). Behind a PEEKED sheet the words keep the strip's room under them, so whatever is down there
1551
1620
  (the buttons that call the other sheets) scrolls clear of it — CSS alone: the box declares --sheet-peek. */
1552
- .step:has(> .sheet--peek) > :is(.base-content, .pages) { padding-bottom: var(--sheet-peek); }
1553
- .step > :is(.base-content, .pages) {
1621
+ .step:has(> .sheet--peek) > :is(.page, .pages) { padding-bottom: var(--sheet-peek); }
1622
+ .step > :is(.page, .pages) {
1554
1623
  flex: 0 1 auto;
1555
1624
  }
1556
- /* the "where am I" dots of a walk are the STEP's, one for all its pages — written after .pages, pinned to the base's foot above the bar's
1557
- room, so a short page never leaves them stranded under its last line (Lh 2026-09-22: the dots belong to the page, not a card, not a paragraph) */
1558
- .step > .pager__at { margin-top: auto; align-self: center; padding-block: var(--sp-3); flex: none; }
1559
- .step > :is(.base-content, .pages) {
1625
+ .step > :is(.page, .pages) {
1560
1626
  min-height: 0;
1561
1627
  margin: var(--_lead, 0) var(--screen-pad) 0; /* the screen's gap, moved in here from above the step; the inset, taken back from the bleed */
1562
1628
  overflow-y: auto;
@@ -1566,14 +1632,44 @@ button { font: inherit; cursor: pointer; }
1566
1632
  /* a picture that opens the words stays put, only the words scroll under it (Lh 2026-09-23, timebank's walk at large text:
1567
1633
  the picture scrolled away with the words). Sticky at the top of the reading block's scroller; its height is
1568
1634
  --base-content-media-h as everywhere, never shrunk (Lh 2026-09-23: a fixed height, no scaling). */
1569
- .step > .base-content > :is(img, .ph):first-child,
1570
- .step > .pages > .base-content > :is(img, .ph):first-child {
1635
+ .step > .page > :is(img, .ph):first-child,
1636
+ .step > .pages > .page > :is(img, .ph):first-child {
1571
1637
  position: sticky;
1572
1638
  top: 0;
1573
1639
  z-index: 1;
1574
1640
  background-color: var(--surface); /* the words pass UNDER it, never through a transparent picture */
1575
1641
  }
1576
1642
 
1643
+ /* ============================================================
1644
+ TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
1645
+ in a .screen--fill the content runs to the screen's bottom edge, and the bar lies over it at the foot — it covers what is
1646
+ under it, on purpose; nothing is added to the content to make room. The keyboard lifts the layer (--kb).
1647
+ (A bar OUTSIDE a fill screen — an app shell, a scrolling screen — is still fixed to the viewport, actions.css.)
1648
+ ============================================================ */
1649
+ .screen--fill > .action-bar {
1650
+ position: absolute; inset-inline: 0; top: auto; bottom: var(--kb, 0rem);
1651
+ max-width: none;
1652
+ margin: 0;
1653
+ padding-top: 0; /* no ground of its own, never a solid strip */
1654
+ }
1655
+ .screen--fill:has(> .action-bar) > .step { margin-bottom: 0; }
1656
+ /* the walk's dots are the BOTTOM layer's too (Lh 2026-09-23): a row of the bar, over its buttons, centred */
1657
+ .action-bar > .pager__at, .action-bar__tier > .pager__at { align-self: center; }
1658
+ /* the TOP is in the column too (Lh 2026-09-23): an .appbar in a fill screen is its first row, not fixed over it — the screen
1659
+ reserves nothing for it; the breathing under it stays the one it had (--sp-2), not the screen's gap */
1660
+ .screen--fill > .appbar {
1661
+ position: relative; inset: auto; top: auto;
1662
+ flex: none;
1663
+ max-width: none;
1664
+ margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge; its own padding is the inset */
1665
+ margin-bottom: calc(var(--sp-2) - var(--screen-gap));
1666
+ }
1667
+ .screen.screen--fill:has(> .appbar) { padding-top: 0; --header-gap: 0rem; }
1668
+ /* …and a STEP touches it, as it touches a TopBar: a full sheet's top edge on the bar's bottom edge (0.6.5 left 8px of base between
1669
+ them — timebank #137); the words keep the screen's gap as their lead (--_lead, .appbar + .step above) */
1670
+ .screen--fill > .appbar:has(+ .step) { margin-bottom: calc(-1 * var(--screen-gap)); }
1671
+ .screen--fill > .appbar + .sheet-stage { margin-top: 0; }
1672
+
1577
1673
  /* ---- css/forms.css — outlined fields, add-row ---- */
1578
1674
  /* ============================================================
1579
1675
  cardds/forms.css — inputs + inline form actions
@@ -1767,6 +1863,15 @@ button { font: inherit; cursor: pointer; }
1767
1863
  .screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
1768
1864
  .appbar__brand { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
1769
1865
  .appbar__text { display: grid; min-width: 0; flex: 1; } /* pushes trailing button to the right edge */
1866
+ /* the mark + the words as ONE button (AppBar brandButton — the member's name opens their menu, Lh 2026-09-23): the same row, a tap target */
1867
+ .appbar__who {
1868
+ flex: 1; min-width: 0; min-height: var(--tap);
1869
+ display: flex; align-items: center; gap: var(--sp-2);
1870
+ padding: 0; margin: 0; border: 0; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
1871
+ border-radius: var(--appbar-who-radius, var(--r-chip));
1872
+ -webkit-tap-highlight-color: transparent;
1873
+ }
1874
+ .appbar__who:focus-visible { outline: var(--focus); outline-offset: 2px; }
1770
1875
  .appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.35; } /* clipped lines: room for Thai marks */
1771
1876
  .appbar__title { font: var(--type-label); }
1772
1877
  .appbar__sub { font: var(--type-caption); color: var(--on-surface-muted); }
@@ -1983,7 +2088,13 @@ button { font: inherit; cursor: pointer; }
1983
2088
  shadow and read on their own (Lh, 2026-09-12; replaces the 09-11 fade) */
1984
2089
  .action-bar__tier { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); }
1985
2090
  .action-bar__tier > .btn { flex: 1; }
2091
+ .action-bar__tier--link { justify-content: center; min-height: 0; } /* a Link under the buttons, e.g. "details" that opens a Modal — its row as tall as a small link, not a tap (Lh 2026-09-23) */
1986
2092
  :has(> .action-bar--tiers) { --bar-reserve: calc(var(--tap) + var(--sp-3) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2093
+ /* more rows (Lh 2026-09-23: a bar may hold several rows of controls — keeping it short is the designer's job): each row past two
2094
+ adds a tap-tall row and its gap to the reserve, so the words above never run under the bar */
2095
+ :has(> .action-bar--tiers > .action-bar__tier:nth-child(3)) { --bar-reserve: calc(2 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2096
+ :has(> .action-bar--tiers > .action-bar__tier:nth-child(4)) { --bar-reserve: calc(3 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
2097
+ :has(> .action-bar--tiers > .action-bar__tier:nth-child(5)) { --bar-reserve: calc(4 * (var(--tap) + var(--sp-3)) + var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
1987
2098
 
1988
2099
  /* the first round button sits at the left edge, the last at the right; a skip link between them stays centred */
1989
2100
  :is(.action-bar--pager, .action-bar__tier--pager) > .icon-btn:first-child { margin-right: auto; }
@@ -2663,6 +2774,17 @@ button { font: inherit; cursor: pointer; }
2663
2774
  }
2664
2775
  .slider__range:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
2665
2776
 
2777
+ /* ---- a switch that names itself (Toggle text, Lh 2026-09-23 — "อยู่บ้าน" on the home's top): the words, then the switch,
2778
+ one label — the whole of it is the tap target ---- */
2779
+ .toggle-field {
2780
+ display: inline-flex; align-items: center; gap: var(--toggle-field-gap, var(--sp-2));
2781
+ min-height: var(--tap);
2782
+ font: var(--toggle-field-font, var(--type-label));
2783
+ cursor: pointer;
2784
+ -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
2785
+ flex: none;
2786
+ }
2787
+
2666
2788
  /* ---- css/lists.css — row, rows, key-value, kv grid, timeline, legend, link ---- */
2667
2789
  /* ============================================================
2668
2790
  cardds/lists.css — frameless rows and pairs INSIDE a card:
@@ -2871,6 +2993,10 @@ button { font: inherit; cursor: pointer; }
2871
2993
  padding: 0;
2872
2994
  cursor: pointer;
2873
2995
  }
2996
+ /* a SMALL link (Lh 2026-09-23): a thing tapped rarely — "details", a ctaLink — at the minor-link size (15) and 28px of height;
2997
+ its 44px tap target stays, an invisible ::after around it */
2998
+ .link--sm { position: relative; font-size: var(--link-sm-font-size, var(--fs-2xs)); min-height: var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)); }
2999
+ .link--sm::after { content: ""; position: absolute; inset: calc((var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)) - var(--tap-sm)) / 2) 0; }
2874
3000
  .link--muted { color: var(--link-muted-ink, var(--card-muted)); }
2875
3001
  .card--3 .link--muted { color: var(--card-3-muted); }
2876
3002
 
@@ -3343,6 +3469,9 @@ button { font: inherit; cursor: pointer; }
3343
3469
  align-items: center;
3344
3470
  z-index: 30;
3345
3471
  }
3472
+ /* in a fill screen (its `bottom`) the float bar floats over the SCREEN's foot, not the window's — it lies over the content
3473
+ (a map, a stack), so it is not a row of the column; the screen is its box (Lh 2026-09-23) */
3474
+ .screen--fill > .float-bar { position: absolute; }
3346
3475
  .sheet-stage > .float-bar { position: absolute; padding-inline: var(--sp-4); }
3347
3476
  /* .screen's default bottom padding clears an .action-bar; a .float-bar is
3348
3477
  taller (--bar-h + its own bottom offset), so reserve the difference. Any
@@ -1,4 +1,4 @@
1
- import type { ComponentProps } from 'react';
1
+ import type { ComponentProps, ReactNode } from 'react';
2
2
  export interface CheckProps extends ComponentProps<'input'> {
3
3
  /** 40px instead of 48px */
4
4
  sm?: boolean;
@@ -8,7 +8,10 @@ export interface CheckProps extends ComponentProps<'input'> {
8
8
  /** Check — the round check circle at a Row's end (48px); the row's label wraps it. */
9
9
  export declare function Check({ sm, label, className, ...rest }: CheckProps): import("react").JSX.Element;
10
10
  export interface ToggleProps extends ComponentProps<'input'> {
11
+ /** accessible name, when no words show */
11
12
  label?: string;
13
+ /** WORDS shown before the switch, naming it ("อยู่บ้าน") — the switch and its words are one label, one tap target */
14
+ text?: ReactNode;
12
15
  }
13
- /** Toggle — the big switch (64×36) at a Row's end. */
14
- export declare function Toggle({ label, className, ...rest }: ToggleProps): import("react").JSX.Element;
16
+ /** Toggle — the big switch (64×36) at a Row's end; with `text`, a switch that names itself (a bar's own switch — "at home"). */
17
+ export declare function Toggle({ label, text, className, ...rest }: ToggleProps): import("react").JSX.Element;
@@ -1,10 +1,13 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /** Check — the round check circle at a Row's end (48px); the row's label wraps it. */
4
4
  export function Check({ sm, label, className, ...rest }) {
5
5
  return _jsx("input", { type: "checkbox", className: cx('check', sm && 'check--sm', className), "aria-label": label, ...rest });
6
6
  }
7
- /** Toggle — the big switch (64×36) at a Row's end. */
8
- export function Toggle({ label, className, ...rest }) {
9
- return _jsx("input", { type: "checkbox", role: "switch", className: cx('toggle', className), "aria-label": label, ...rest });
7
+ /** Toggle — the big switch (64×36) at a Row's end; with `text`, a switch that names itself (a bar's own switch — "at home"). */
8
+ export function Toggle({ label, text, className, ...rest }) {
9
+ const input = _jsx("input", { type: "checkbox", role: "switch", className: cx('toggle', className), "aria-label": text == null ? label : undefined, ...rest });
10
+ if (text == null)
11
+ return input;
12
+ return _jsxs("label", { className: "toggle-field", children: [_jsx("span", { className: "toggle-field__text", children: text }), input] });
10
13
  }
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export * from './scaffold/ActionBar.js';
10
10
  export * from './scaffold/PagerAt.js';
11
11
  export * from './scaffold/FloatBar.js';
12
12
  export * from './scaffold/FilterRow.js';
13
- export * from './scaffold/BaseContent.js';
13
+ export * from './scaffold/Page.js';
14
14
  export * from './scaffold/EmptyState.js';
15
15
  export * from './scaffold/Centre.js';
16
16
  export * from './cards/Card.js';
@@ -72,3 +72,6 @@ export * from './layover/Deck.js';
72
72
  export * from './journey/Route.js';
73
73
  export * from './journey/TileBadge.js';
74
74
  export * from './journey/NoteRow.js';
75
+ export * from './templates/Walkthrough.js';
76
+ export * from './templates/Home.js';
77
+ export * from './sheets/StackSheet.js';
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ export * from './scaffold/ActionBar.js';
13
13
  export * from './scaffold/PagerAt.js';
14
14
  export * from './scaffold/FloatBar.js';
15
15
  export * from './scaffold/FilterRow.js';
16
- export * from './scaffold/BaseContent.js';
16
+ export * from './scaffold/Page.js';
17
17
  export * from './scaffold/EmptyState.js';
18
18
  export * from './scaffold/Centre.js';
19
19
  export * from './cards/Card.js';
@@ -75,3 +75,6 @@ export * from './layover/Deck.js';
75
75
  export * from './journey/Route.js';
76
76
  export * from './journey/TileBadge.js';
77
77
  export * from './journey/NoteRow.js';
78
+ export * from './templates/Walkthrough.js';
79
+ export * from './templates/Home.js';
80
+ export * from './sheets/StackSheet.js';
@@ -6,11 +6,12 @@ export interface ActionBarProps extends ComponentProps<'div'> {
6
6
  inline?: boolean;
7
7
  }
8
8
  /**
9
- * ActionBar — the screen's ONE move, fixed at the bottom, OUTSIDE the Screen/Step (a sibling after it).
9
+ * ActionBar — the screen's ONE move: the Screen's `bottom` (`<Screen fill bottom={<ActionBar…>}>`), the last part of its flex
10
+ * column, as tall as its rows. Never after `</Screen>`, never positioned by hand.
10
11
  * Plain: one wide primary Btn (the step that commits). `pager`: Back + Next as peers — buttons only; where you are is the
11
12
  * PagerAt under the card in a Centre group, never in the bar (Lh, 2026-09-12).
12
13
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
13
- * The parent hosting the bar reserves its room (`--bar-reserve`); never pad a screen for it by hand.
14
+ * In a `Screen fill` the content takes the rest; outside one (an app shell) the bar is still fixed and `--bar-reserve` keeps its room.
14
15
  */
15
16
  export declare function ActionBar({ variant, inline, className, style, ...rest }: ActionBarProps): import("react").JSX.Element;
16
17
  export interface ActionBarTierProps extends ComponentProps<'div'> {
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /**
4
- * ActionBar — the screen's ONE move, fixed at the bottom, OUTSIDE the Screen/Step (a sibling after it).
4
+ * ActionBar — the screen's ONE move: the Screen's `bottom` (`<Screen fill bottom={<ActionBar…>}>`), the last part of its flex
5
+ * column, as tall as its rows. Never after `</Screen>`, never positioned by hand.
5
6
  * Plain: one wide primary Btn (the step that commits). `pager`: Back + Next as peers — buttons only; where you are is the
6
7
  * PagerAt under the card in a Centre group, never in the bar (Lh, 2026-09-12).
7
8
  * `tiers`: an ActionBarTier with a control (a Slider) above the button tier.
8
- * The parent hosting the bar reserves its room (`--bar-reserve`); never pad a screen for it by hand.
9
+ * In a `Screen fill` the content takes the rest; outside one (an app shell) the bar is still fixed and `--bar-reserve` keeps its room.
9
10
  */
10
11
  export function ActionBar({ variant, inline, className, style, ...rest }) {
11
12
  return (_jsx("div", { className: cx('action-bar', variant && `action-bar--${variant}`, className), style: inline ? { position: 'static', width: '100%', maxWidth: 'none', paddingInline: 0, ...style } : style, ...rest }));
@@ -9,10 +9,13 @@ export interface AppBarProps extends Omit<ComponentProps<'header'>, 'title'> {
9
9
  /** the trailing control(s) — an IconBtn with a chevron-down to switch context; on the home bar a labelled
10
10
  * switch chip (ChipPick sign, --tap tall here) and the wallet Btn, side by side (Lh 2026-09-13) */
11
11
  action?: ReactNode;
12
+ /** makes the mark + the words ONE button (the member's own name → their menu): its props — `popoverTarget` to open a `Menu`
13
+ * (the app places the Menu), or an `onClick`. Needs an accessible name when the words don't say what it opens: `aria-label`. */
14
+ brandButton?: ComponentProps<'button'>;
12
15
  }
13
16
  /**
14
17
  * AppBar — the app-context bar fixed at the very top: logo · two-line label · switcher.
15
18
  * It is fixed, so the Screen it sits in reserves its height (`--header-h`, 60px) automatically.
16
19
  * Use a TopBar for a screen title; use both when the app context matters (home).
17
20
  */
18
- export declare function AppBar({ icon, title, sub, action, className, children, ...rest }: AppBarProps): import("react").JSX.Element;
21
+ export declare function AppBar({ icon, title, sub, action, brandButton, className, children, ...rest }: AppBarProps): import("react").JSX.Element;
@@ -1,10 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { cx } from '../cx.js';
3
3
  /**
4
4
  * AppBar — the app-context bar fixed at the very top: logo · two-line label · switcher.
5
5
  * It is fixed, so the Screen it sits in reserves its height (`--header-h`, 60px) automatically.
6
6
  * Use a TopBar for a screen title; use both when the app context matters (home).
7
7
  */
8
- export function AppBar({ icon, title, sub, action, className, children, ...rest }) {
9
- return (_jsxs("header", { className: cx('appbar', className), ...rest, children: [_jsxs("div", { className: "appbar__brand", children: [icon, _jsxs("span", { className: "appbar__text", children: [_jsx("span", { className: "appbar__title", children: title }), sub != null && _jsx("span", { className: "appbar__sub", children: sub })] }), action] }), children] }));
8
+ export function AppBar({ icon, title, sub, action, brandButton, className, children, ...rest }) {
9
+ const who = (_jsxs(_Fragment, { children: [icon, _jsxs("span", { className: "appbar__text", children: [_jsx("span", { className: "appbar__title", children: title }), sub != null && _jsx("span", { className: "appbar__sub", children: sub })] })] }));
10
+ return (_jsxs("header", { className: cx('appbar', className), ...rest, children: [_jsxs("div", { className: "appbar__brand", children: [brandButton ? (_jsx("button", { type: "button", ...brandButton, className: cx('appbar__who', brandButton.className), children: who })) : who, action] }), children] }));
10
11
  }
@@ -17,7 +17,7 @@ export interface PagesProps extends ComponentProps<'div'> {
17
17
  * Pages — the pages of a walk, all one height: every page is rendered, stacked in one cell as tall as the
18
18
  * tallest at any text size. Give the current one `aria-current="step"` and every other one `inert`
19
19
  * (hidden in place, out of the tab order). Turn with slideTo().
20
- * Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `BaseContent`s, as a Step's
20
+ * Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `Page`s, as a Step's
21
21
  * reading block (no Sheet on that screen — a pager never works beside one), the round pager in the ActionBar.
22
22
  */
23
23
  export declare function Pages({ className, ...rest }: PagesProps): import("react").JSX.Element;
@@ -17,7 +17,7 @@ export function Centre({ className, ...rest }) {
17
17
  * Pages — the pages of a walk, all one height: every page is rendered, stacked in one cell as tall as the
18
18
  * tallest at any text size. Give the current one `aria-current="step"` and every other one `inert`
19
19
  * (hidden in place, out of the tab order). Turn with slideTo().
20
- * Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `BaseContent`s, as a Step's
20
+ * Pages of cards: `Card`s, inside a Centre, above the PagerAt. Pages of words: `Page`s, as a Step's
21
21
  * reading block (no Sheet on that screen — a pager never works beside one), the round pager in the ActionBar.
22
22
  */
23
23
  export function Pages({ className, ...rest }) {
@@ -0,0 +1,17 @@
1
+ import type { ComponentProps } from 'react';
2
+ export interface PageProps extends ComponentProps<'section'> {
3
+ }
4
+ /**
5
+ * Page — ONE page of words in the content: a picture (optional, first — it stays put while the words scroll), a heading, the words,
6
+ * maybe a CtaPack. A component INSIDE the content box, never the box itself (Lh 2026-09-23): several Pages in a `Pages` = a walk the
7
+ * pager turns (slideTo); a single one sits in the content (a Step) directly. Was BaseContent until 2026-09-23 — "words on the base" meant
8
+ * a still, one-scaffold screen, which is gone.
9
+ */
10
+ export declare function Page({ className, ...rest }: PageProps): import("react").JSX.Element;
11
+ /** @deprecated renamed `Page` (2026-09-23) — same props, same markup; kept one version for the move */
12
+ export declare function BaseContent(props: PageProps): import("react").JSX.Element;
13
+ export type BaseContentProps = PageProps;
14
+ export interface CtaPackProps extends ComponentProps<'div'> {
15
+ }
16
+ /** CtaPack — the buttons under a Page's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
17
+ export declare function CtaPack({ className, ...rest }: CtaPackProps): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../cx.js';
3
+ /**
4
+ * Page — ONE page of words in the content: a picture (optional, first — it stays put while the words scroll), a heading, the words,
5
+ * maybe a CtaPack. A component INSIDE the content box, never the box itself (Lh 2026-09-23): several Pages in a `Pages` = a walk the
6
+ * pager turns (slideTo); a single one sits in the content (a Step) directly. Was BaseContent until 2026-09-23 — "words on the base" meant
7
+ * a still, one-scaffold screen, which is gone.
8
+ */
9
+ export function Page({ className, ...rest }) {
10
+ return _jsx("section", { className: cx('page', className), ...rest });
11
+ }
12
+ let warned = false;
13
+ /** @deprecated renamed `Page` (2026-09-23) — same props, same markup; kept one version for the move */
14
+ export function BaseContent(props) {
15
+ if (!warned) {
16
+ warned = true;
17
+ console.warn('cardds: BaseContent is now Page — rename it (same props).');
18
+ }
19
+ return _jsx(Page, { ...props });
20
+ }
21
+ /** CtaPack — the buttons under a Page's words, wrapping on one line. In a Step it holds ways in (each opens a sheet — the app wires that); the Step's own move is never here. */
22
+ export function CtaPack({ className, ...rest }) {
23
+ return _jsx("div", { className: cx('cta-pack', className), ...rest });
24
+ }
@@ -1,4 +1,4 @@
1
- import { type ComponentProps } from 'react';
1
+ import { type ComponentProps, type ReactNode, type Ref } from 'react';
2
2
  export type Palette = 'clay' | 'shine' | 'field' | 'roast' | 'hydro';
3
3
  export interface ScreenProps extends ComponentProps<'div'> {
4
4
  /** exactly one phone height (100dvh), clipped: the box a Step or a SheetStack fills — an app screen, not a scrolling page */
@@ -6,15 +6,20 @@ export interface ScreenProps extends ComponentProps<'div'> {
6
6
  /** a colour preset from palette.css; omit for the black-and-white wireframe */
7
7
  palette?: Palette;
8
8
  as?: 'div' | 'main' | 'section';
9
+ /** TOP: a `TopBar` / `AppBar`, or `"lead"` — no bar, the content starts `--screen-lead` (15% of the screen) down */
10
+ top?: ReactNode | 'lead';
11
+ /** BOTTOM: an `ActionBar` (or a `FloatBar`) — a LAYER over the content's foot, as tall as its rows need.
12
+ * Never with a `Sheet` in the content: a sheet finishes on its own foot (warns once). */
13
+ bottom?: ReactNode;
9
14
  }
10
15
  /**
11
- * Screen — the mobile screen column: max 25rem wide, the standard horizontal inset, NO vertical padding
12
- * (bars reserve their own room). Every screen is base · top menu · bottom menu · content.
13
- * `fill` makes it one phone height for a Step or a SheetStack; a plain Screen scrolls.
14
- * Put an AppBar/TopBar first, the content next, and an ActionBar AFTER the screen (outside it).
16
+ * Screen — the mobile screen in THREE parts: **top** and **content** (the children) in one flex column, the content down to the
17
+ * screen's foot; **bottom** a LAYER over it there (Lh 2026-09-23). Top = `TopBar` / `AppBar`, or `top="lead"` (no bar, the words start
18
+ * 15% down). Content = a box of components one after another — a `Step` (`Pages` of `Page`s / a `Page` [+ `Sheet`]), a `Centre`, a
19
+ * `SheetStack`… Bottom = an `ActionBar` (the walk's `PagerAt` its first row) or a `FloatBar`, covering the content's foot on purpose. Max 25rem wide, the standard inset, no vertical padding. A plain Screen (no `fill`) scrolls.
20
+ * The slots set the order; children laid by hand in the same order still work.
15
21
  */
16
- export declare function Screen({ fill, palette, as, className, ...rest }: ScreenProps): import("react").DetailedReactHTMLElement<{
17
- ref?: import("react").Ref<HTMLDivElement> | undefined;
22
+ export declare function Screen({ fill, palette, as, top, bottom, className, children, ref, ...rest }: ScreenProps): import("react").DetailedReactHTMLElement<{
18
23
  key?: import("react").Key | null | undefined;
19
24
  defaultChecked?: boolean | undefined;
20
25
  defaultValue?: string | number | readonly string[] | undefined;
@@ -123,7 +128,6 @@ export declare function Screen({ fill, palette, as, className, ...rest }: Screen
123
128
  "aria-valuemin"?: number | undefined;
124
129
  "aria-valuenow"?: number | undefined;
125
130
  "aria-valuetext"?: string | undefined;
126
- children?: import("react").ReactNode | undefined;
127
131
  dangerouslySetInnerHTML?: {
128
132
  __html: string | TrustedHTML;
129
133
  } | undefined;
@@ -297,4 +301,5 @@ export declare function Screen({ fill, palette, as, className, ...rest }: Screen
297
301
  onTransitionStartCapture?: import("react").TransitionEventHandler<HTMLDivElement> | undefined;
298
302
  className: string;
299
303
  'data-palette': Palette | undefined;
304
+ ref: Ref<HTMLDivElement>;
300
305
  }, HTMLDivElement>;