@usableapp/cardds 0.6.0 → 0.6.2

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.
package/CHANGELOG.md CHANGED
@@ -2,8 +2,20 @@
2
2
 
3
3
  > For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
4
4
 
5
+ ## 0.6.2 — 2026-09-23
6
+
7
+ - **The picture stays put, only the words scroll** (Lh 2026-09-23): in a `Step`, a `BaseContent` whose FIRST child is a picture (`img` / `Placeholder`) keeps it sticky at the top of the reading block, and the heading and words scroll under it. The picture is capped at 40% of the step's height (`min(--base-content-media-h, --step-media-share)`, new knob `--step-media-share`, default `40cqh`), so on a short phone or at a large text size the picture gives way, never the words. Nothing to change in an app.
8
+ - A larger opening picture is a theme setting, not a code change: `:root { --base-content-media-h: 18.75rem; }` (300px; the 40% cap still applies inside a Step).
9
+
10
+ ## 0.6.1 — 2026-09-22
11
+
12
+ - **A screen with no bar** (Lh 2026-09-22): a `Step` that is the `Screen`'s first child starts its words 15% of the screen's height down — `--screen-lead` (a knob, `15cqh`; `Screen fill` is a size container, `--_lead` is registered so the share resolves against the screen). Story `Step · NoBar`.
13
+ - **The base's own h1**: `CardHead level="h1"` inside a `BaseContent` reads at the card heading's size (`--fs-h2`) — h1 is the semantic level; `--fs-h1` stays the bar title's size.
14
+
5
15
  ## 0.6.0 — 2026-09-22
6
16
 
17
+ Also in 0.6.0 (same commit): `slideTo(direction, update, scope?)` names the moving page for the move only — CSS no longer names every walk's current page (two walks on one page aborted the transition); on a base page the picture comes FIRST, then the heading, then the words; `--card-3-muted` is a real colour role (the five tone-3 opacity rules in lists.css read it; default = the wireframe's old look); `--dot-drop` (0.09em) sits a signal dot on the letters' middle.
18
+
7
19
  ### The walk's dots are the Step's, not each page's (Lh 2026-09-22) — BREAKING
8
20
  Reverses 0.5.2. A walk of `BaseContent` pages now carries **one** `PagerAt`, written after `Pages` — not one inside every page. `.step > .pager__at` sits at the base's foot, above the bar; `.base-content:has(> .pager__at:last-child)` (the 0.5.2 per-page flex-column form) is gone. An app with a `PagerAt` at the end of each page's `BaseContent` should move it to a single `PagerAt` after the `Pages` block instead.
9
21
 
package/css/base.css CHANGED
@@ -78,6 +78,7 @@ button { font: inherit; cursor: pointer; }
78
78
  height: 100dvh;
79
79
  overflow: clip;
80
80
  position: relative; /* the box a .card--centre or a .screen__centre floats in (card.css) */
81
+ container-type: size; /* a share of the SCREEN's height (a no-bar step's lead, step.css) reads it as cqh — a real phone's height, or the gallery's frame */
81
82
  }
82
83
  /* min-height: 0 — a flex item's floor is its content, and the stack's content
83
84
  is its own full-height sheets, so without it the stack grew to the whole
@@ -118,6 +119,9 @@ button { font: inherit; cursor: pointer; }
118
119
  .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) */
119
120
  /* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
120
121
  (the walk's dots are NOT here — they are the Step's, after its Pages: step.css, Lh 2026-09-22) */
122
+ /* the page's ONE heading on the base (a screen with no TopBar: CardHead level="h1") reads at the card heading's size —
123
+ h1 is the semantic level, not a bigger face; --fs-h1 is the bar title's size (Lh 2026-09-22) */
124
+ .base-content > .card__head > .t-h1 { font-size: var(--fs-h2); }
121
125
  .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 */
122
126
  .base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
123
127
  .base-content .cta-pack {
package/css/card.css CHANGED
@@ -222,7 +222,11 @@
222
222
  <article class="card" aria-current="step">…</article>
223
223
  <article class="card" inert>…</article>
224
224
  </div><div class="pager__at">…</div></div> */
225
- .pages { display: grid; }
225
+ .pages {
226
+ display: grid;
227
+ 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) */
228
+ touch-action: pan-y; /* leaves vertical scroll to the browser; horizontal is the app's own drag, never a native pan */
229
+ }
226
230
  .pages > :is(.card, .base-content) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
227
231
  .pages > [inert] { visibility: hidden; }
228
232
  /* pages of WORDS (Lh, 2026-09-20): the same walk with .base-content pages, on the base — in a .step the
package/css/media.css CHANGED
@@ -116,7 +116,7 @@
116
116
  border-color: transparent;
117
117
  border-bottom-right-radius: 0;
118
118
  background-image: linear-gradient(to top left,
119
- var(--surface) 0 50%,
119
+ var(--card-bg) 0 50%, /* the card's own background, not --surface (the page's) — a mismatch here drew a seam at the flap's edge */
120
120
  color-mix(in srgb, currentColor 18%, var(--card-bg)) 50% 100%);
121
121
  background-repeat: no-repeat;
122
122
  background-position: bottom right;
package/css/step.css CHANGED
@@ -54,6 +54,10 @@
54
54
  that thing. */
55
55
  .topbar + .step { margin-top: calc(-1 * var(--screen-gap)); --_lead: var(--screen-gap); }
56
56
  .appbar + .step { margin-top: calc(-1 * var(--header-gap)); --_lead: var(--screen-gap); }
57
+ /* 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):
58
+ the words start a share of the screen down — --screen-lead, 15% of its height (Lh 2026-09-22), so a tall phone gives
59
+ more air and a short one less; a screen with a bar keeps the bar's own room */
60
+ .screen > .step:first-child { --_lead: var(--screen-lead, 15cqh); }
57
61
 
58
62
  /* the bar's room — read from actions.css, never recomputed. Margin, not
59
63
  padding, so the clip cuts at the box edge and nothing shows behind the
@@ -98,3 +102,18 @@
98
102
  overflow-y: auto;
99
103
  overscroll-behavior: contain;
100
104
  }
105
+
106
+ /* a picture that opens the words stays put, only the words scroll under it (Lh 2026-09-23, timebank's walk at large text:
107
+ the picture scrolled away with the words). Sticky at the top of the reading block's scroller, on the base's ground so the
108
+ words pass under it; capped at a share of the STEP (a size container) so the words always keep more than half the column —
109
+ the picture gives way on a short phone or at a big text size, never the words. */
110
+ .step > .base-content > :is(img, .ph):first-child,
111
+ .step > .pages > .base-content > :is(img, .ph):first-child {
112
+ position: sticky;
113
+ top: 0;
114
+ z-index: 1;
115
+ background-color: var(--surface); /* the words pass UNDER it, never through a transparent picture */
116
+ max-height: min(var(--base-content-media-h, 14rem), var(--step-media-share, 40cqh));
117
+ }
118
+ .step > .base-content > .ph:first-child,
119
+ .step > .pages > .base-content > .ph:first-child { height: min(var(--base-content-media-h, 14rem), var(--step-media-share, 40cqh)); }
package/css/tokens.css CHANGED
@@ -211,6 +211,7 @@
211
211
  falls back to when its own declaration cannot resolve.
212
212
  ============================================================ */
213
213
  @property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
214
- @property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; } /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
214
+ @property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; }
215
+ @property --_lead { syntax: "<length>"; inherits: true; initial-value: 0px; } /* the room above a step's words: registered so a cqh share (a no-bar step, 15% of the SCREEN) resolves where it is declared — on the step, against the screen — not on the words, against the step */ /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
215
216
  @property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
216
217
  @property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
package/dist/cardds.css CHANGED
@@ -252,7 +252,8 @@
252
252
  falls back to when its own declaration cannot resolve.
253
253
  ============================================================ */
254
254
  @property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
255
- @property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; } /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
255
+ @property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; }
256
+ @property --_lead { syntax: "<length>"; inherits: true; initial-value: 0px; } /* the room above a step's words: registered so a cqh share (a no-bar step, 15% of the SCREEN) resolves where it is declared — on the step, against the screen — not on the words, against the step */ /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
256
257
  @property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
257
258
  @property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
258
259
 
@@ -456,6 +457,7 @@ button { font: inherit; cursor: pointer; }
456
457
  height: 100dvh;
457
458
  overflow: clip;
458
459
  position: relative; /* the box a .card--centre or a .screen__centre floats in (card.css) */
460
+ container-type: size; /* a share of the SCREEN's height (a no-bar step's lead, step.css) reads it as cqh — a real phone's height, or the gallery's frame */
459
461
  }
460
462
  /* min-height: 0 — a flex item's floor is its content, and the stack's content
461
463
  is its own full-height sheets, so without it the stack grew to the whole
@@ -496,6 +498,9 @@ button { font: inherit; cursor: pointer; }
496
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) */
497
499
  /* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
498
500
  (the walk's dots are NOT here — they are the Step's, after its Pages: step.css, Lh 2026-09-22) */
501
+ /* the page's ONE heading on the base (a screen with no TopBar: CardHead level="h1") reads at the card heading's size —
502
+ 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); }
499
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 */
500
505
  .base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
501
506
  .base-content .cta-pack {
@@ -745,7 +750,11 @@ button { font: inherit; cursor: pointer; }
745
750
  <article class="card" aria-current="step">…</article>
746
751
  <article class="card" inert>…</article>
747
752
  </div><div class="pager__at">…</div></div> */
748
- .pages { display: grid; }
753
+ .pages {
754
+ display: grid;
755
+ 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
+ touch-action: pan-y; /* leaves vertical scroll to the browser; horizontal is the app's own drag, never a native pan */
757
+ }
749
758
  .pages > :is(.card, .base-content) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
750
759
  .pages > [inert] { visibility: hidden; }
751
760
  /* pages of WORDS (Lh, 2026-09-20): the same walk with .base-content pages, on the base — in a .step the
@@ -1505,6 +1514,10 @@ button { font: inherit; cursor: pointer; }
1505
1514
  that thing. */
1506
1515
  .topbar + .step { margin-top: calc(-1 * var(--screen-gap)); --_lead: var(--screen-gap); }
1507
1516
  .appbar + .step { margin-top: calc(-1 * var(--header-gap)); --_lead: var(--screen-gap); }
1517
+ /* 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
+ 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
+ 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); }
1508
1521
 
1509
1522
  /* the bar's room — read from actions.css, never recomputed. Margin, not
1510
1523
  padding, so the clip cuts at the box edge and nothing shows behind the
@@ -1550,6 +1563,21 @@ button { font: inherit; cursor: pointer; }
1550
1563
  overscroll-behavior: contain;
1551
1564
  }
1552
1565
 
1566
+ /* 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
+ the picture scrolled away with the words). Sticky at the top of the reading block's scroller, on the base's ground so the
1568
+ words pass under it; capped at a share of the STEP (a size container) so the words always keep more than half the column —
1569
+ the picture gives way on a short phone or at a big text size, never the words. */
1570
+ .step > .base-content > :is(img, .ph):first-child,
1571
+ .step > .pages > .base-content > :is(img, .ph):first-child {
1572
+ position: sticky;
1573
+ top: 0;
1574
+ z-index: 1;
1575
+ background-color: var(--surface); /* the words pass UNDER it, never through a transparent picture */
1576
+ max-height: min(var(--base-content-media-h, 14rem), var(--step-media-share, 40cqh));
1577
+ }
1578
+ .step > .base-content > .ph:first-child,
1579
+ .step > .pages > .base-content > .ph:first-child { height: min(var(--base-content-media-h, 14rem), var(--step-media-share, 40cqh)); }
1580
+
1553
1581
  /* ---- css/forms.css — outlined fields, add-row ---- */
1554
1582
  /* ============================================================
1555
1583
  cardds/forms.css — inputs + inline form actions
@@ -2969,7 +2997,7 @@ button { font: inherit; cursor: pointer; }
2969
2997
  border-color: transparent;
2970
2998
  border-bottom-right-radius: 0;
2971
2999
  background-image: linear-gradient(to top left,
2972
- var(--surface) 0 50%,
3000
+ var(--card-bg) 0 50%, /* the card's own background, not --surface (the page's) — a mismatch here drew a seam at the flap's edge */
2973
3001
  color-mix(in srgb, currentColor 18%, var(--card-bg)) 50% 100%);
2974
3002
  background-repeat: no-repeat;
2975
3003
  background-position: bottom right;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usableapp/cardds",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",