@usableapp/cardds 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/css/base.css +5 -7
- package/css/card.css +7 -2
- package/css/lists.css +5 -5
- package/css/media.css +1 -1
- package/css/step.css +7 -1
- package/css/tokens.css +4 -1
- package/dist/cardds.css +29 -17
- package/dist/scaffold/Centre.d.ts +1 -1
- package/dist/scaffold/Centre.js +12 -2
- package/dist/scaffold/PagerAt.d.ts +1 -1
- package/dist/scaffold/PagerAt.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,22 @@
|
|
|
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.1 — 2026-09-22
|
|
6
|
+
|
|
7
|
+
- **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`.
|
|
8
|
+
- **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.
|
|
9
|
+
|
|
10
|
+
## 0.6.0 — 2026-09-22
|
|
11
|
+
|
|
12
|
+
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.
|
|
13
|
+
|
|
14
|
+
### The walk's dots are the Step's, not each page's (Lh 2026-09-22) — BREAKING
|
|
15
|
+
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.
|
|
16
|
+
|
|
5
17
|
## 0.5.2 — 2026-09-22
|
|
6
18
|
|
|
7
19
|
### Dots at the foot (Lh 2026-09-22)
|
|
8
|
-
A `BaseContent` whose last child is `PagerAt` now sits its dots at the bottom of the reading block, above the bar; the block fills the step for it. Nothing to change in an app.
|
|
20
|
+
A `BaseContent` whose last child is `PagerAt` now sits its dots at the bottom of the reading block, above the bar; the block fills the step for it. Nothing to change in an app. Superseded by 0.6.0 — see above.
|
|
9
21
|
|
|
10
22
|
## 0.5.1 — 2026-09-22
|
|
11
23
|
|
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
|
|
@@ -117,15 +118,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
117
118
|
}
|
|
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
|
-
the
|
|
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
|
-
.base-content > .pager__at { justify-self: center; }
|
|
124
|
-
/* the dots at the FOOT of the block (Lh 2026-09-22): a block that ends on .pager__at becomes a flex column so the dots
|
|
125
|
-
ride its bottom edge — the words keep the top, the free room opens between. The block must fill its cell for that:
|
|
126
|
-
in .pages it is stretched to the cell; a lone .step > .base-content grows for it (step.css). */
|
|
127
|
-
.base-content:has(> .pager__at:last-child) { display: flex; flex-direction: column; }
|
|
128
|
-
.base-content > .pager__at:last-child { margin-top: auto; align-self: center; }
|
|
129
127
|
.base-content .cta-pack {
|
|
130
128
|
display: flex;
|
|
131
129
|
align-items: start; /* buttons keep their own height, never the row's */
|
package/css/card.css
CHANGED
|
@@ -208,7 +208,8 @@
|
|
|
208
208
|
and updates inside it; a browser without them (the floor: Chrome 105 / iOS 16)
|
|
209
209
|
simply switches. Only the card is named; the rest of the page must not
|
|
210
210
|
cross-fade. */
|
|
211
|
-
|
|
211
|
+
/* the name is set by slideTo() on the moving element for the move only (2026-09-22): a static name on every walk's current
|
|
212
|
+
page made two walks on one page — the gallery — share it, and the browser aborts a transition with a duplicate name */
|
|
212
213
|
|
|
213
214
|
/* ---- pages: the cards of a walk, all one height ----
|
|
214
215
|
A sequence the member walks (an introduction) reads steadier when every card
|
|
@@ -221,7 +222,11 @@
|
|
|
221
222
|
<article class="card" aria-current="step">…</article>
|
|
222
223
|
<article class="card" inert>…</article>
|
|
223
224
|
</div><div class="pager__at">…</div></div> */
|
|
224
|
-
.pages {
|
|
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
|
+
}
|
|
225
230
|
.pages > :is(.card, .base-content) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
|
|
226
231
|
.pages > [inert] { visibility: hidden; }
|
|
227
232
|
/* pages of WORDS (Lh, 2026-09-20): the same walk with .base-content pages, on the base — in a .step the
|
package/css/lists.css
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
.row__body { flex: 1; min-width: 0; display: grid; gap: var(--row-body-gap, var(--sp-0)); }
|
|
33
33
|
.row__title { font: var(--row-title-font, var(--type-title)); overflow-wrap: anywhere; } /* wraps freely — no clip, so no padding trick for Thai marks (Lh 2026-09-16, same call as the card heading) */
|
|
34
34
|
.row__meta { font: var(--row-meta-font, var(--type-caption)); color: var(--row-meta-ink, var(--card-muted)); display: flex; align-items: center; gap: var(--row-meta-gap, var(--sp-1)); }
|
|
35
|
-
.card--3 .row__meta { color:
|
|
35
|
+
.card--3 .row__meta { color: var(--card-3-muted); }
|
|
36
36
|
.row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
|
|
37
37
|
.row--lg { min-height: var(--row-lg-min-h, var(--fab)); } /* menu rows: a taller target, round arrow at the end; .rows gives them more air */
|
|
38
38
|
.row--lg .row__title { font: 700 var(--fs-lg)/1.2 var(--font-display); }
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
at --tap-sm, but not an input: nothing to press. */
|
|
52
52
|
.row--done { pointer-events: none; cursor: default; }
|
|
53
53
|
.row--done .row__title { color: var(--card-muted); }
|
|
54
|
-
.card--3 .row--done .row__title { color:
|
|
54
|
+
.card--3 .row--done .row__title { color: var(--card-3-muted); }
|
|
55
55
|
.row__done {
|
|
56
56
|
width: var(--row-done-size, var(--tap-sm));
|
|
57
57
|
height: var(--row-done-size, var(--tap-sm));
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
.kv-cell > .icon { margin-bottom: var(--sp-1); }
|
|
94
94
|
.kv-cell__val { font: var(--kv-cell-val-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
|
|
95
95
|
.kv-cell__key { font: var(--kv-cell-key-font, var(--type-caption)); color: var(--kv-cell-key-ink, var(--card-muted)); }
|
|
96
|
-
.card--3 .kv-cell__key { color:
|
|
96
|
+
.card--3 .kv-cell__key { color: var(--card-3-muted); }
|
|
97
97
|
|
|
98
98
|
/* ---- bubble: a chat message — theirs on the left, mine on the right (Lh 2026-09-18, replaces the chip's tail) ----
|
|
99
99
|
No tail: a rounded block whose corner nearest the sender is square-ish
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
.bubble__meta { display: flex; align-items: center; gap: var(--bubble-meta-gap, var(--sp-1)); font: var(--bubble-meta-font, var(--type-caption)); color: var(--bubble-meta-ink, var(--on-surface-muted)); }
|
|
144
144
|
.bubble--right > .bubble__meta { justify-self: end; }
|
|
145
145
|
.bubble__meta > .icon { color: var(--accent); }
|
|
146
|
-
.card--3 .bubble__meta { color:
|
|
146
|
+
.card--3 .bubble__meta { color: var(--card-3-muted); }
|
|
147
147
|
|
|
148
148
|
/* ---- timeline: a hairline with date chips, entries to the right ----
|
|
149
149
|
<ol class="timeline"><li class="timeline__item"><span class="timeline__mark">Wed 14</span><p class="t-body">…</p></li>…</ol> */
|
|
@@ -206,4 +206,4 @@
|
|
|
206
206
|
cursor: pointer;
|
|
207
207
|
}
|
|
208
208
|
.link--muted { color: var(--link-muted-ink, var(--card-muted)); }
|
|
209
|
-
.card--3 .link--muted { color:
|
|
209
|
+
.card--3 .link--muted { color: var(--card-3-muted); }
|
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(--
|
|
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
|
|
@@ -89,7 +93,9 @@
|
|
|
89
93
|
.step > :is(.base-content, .pages) {
|
|
90
94
|
flex: 0 1 auto;
|
|
91
95
|
}
|
|
92
|
-
|
|
96
|
+
/* 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
|
|
97
|
+
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) */
|
|
98
|
+
.step > .pager__at { margin-top: auto; align-self: center; padding-block: var(--sp-3); flex: none; }
|
|
93
99
|
.step > :is(.base-content, .pages) {
|
|
94
100
|
min-height: 0;
|
|
95
101
|
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 */
|
package/css/tokens.css
CHANGED
|
@@ -155,6 +155,7 @@
|
|
|
155
155
|
--_muted-on-paper: color-mix(in srgb, var(--color-ink) 66%, var(--color-paper, #ffffff));
|
|
156
156
|
--_tone-2: color-mix(in srgb, var(--color-brand) 14%, var(--color-surface, #f2f2f0));
|
|
157
157
|
--_muted-on-tone-2: color-mix(in srgb, var(--color-ink, #141414) 72%, var(--_tone-2));
|
|
158
|
+
--_muted-on-brand: color-mix(in srgb, var(--color-on-brand) 70%, var(--color-brand)); /* the tone-3 card's secondary words: on-brand faded into brand — was opacity 0.7, which no theme could set (timebank #132, 2026-09-22) */
|
|
158
159
|
|
|
159
160
|
/* app background ("earth-toned surface or shiny color, up to the app") */
|
|
160
161
|
--surface: var(--color-surface, #f2f2f0);
|
|
@@ -168,6 +169,7 @@
|
|
|
168
169
|
|
|
169
170
|
--card-border: #141414; /* wireframe: visible outline; palettes may set transparent */
|
|
170
171
|
--card-muted: var(--_muted-on-paper, #6b6b6b); /* secondary text inside cards (the default, tone-1 card) */
|
|
172
|
+
--card-3-muted: var(--_muted-on-brand, #b9b9b9); /* the same text on the tone-3 card — a real colour, like the two above: a theme whose brand is not near-black sets it to reach its contrast (the wireframe's #b9b9b9 = white at 0.7 on #141414, what the opacity gave) */
|
|
171
173
|
--card-2-muted: var(--_muted-on-tone-2, #5f5f5f); /* the same text on the tone-2 card — 5.1:1 on its grey (#6b6b6b is 4.3:1, under AA);
|
|
172
174
|
a palette whose tone-2 ground is strong
|
|
173
175
|
sets this to its tone-2 ink — a muted shade cannot reach AA there (clay) */
|
|
@@ -209,6 +211,7 @@
|
|
|
209
211
|
falls back to when its own declaration cannot resolve.
|
|
210
212
|
============================================================ */
|
|
211
213
|
@property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
212
|
-
@property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; }
|
|
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 */
|
|
213
216
|
@property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
|
|
214
217
|
@property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
|
package/dist/cardds.css
CHANGED
|
@@ -196,6 +196,7 @@
|
|
|
196
196
|
--_muted-on-paper: color-mix(in srgb, var(--color-ink) 66%, var(--color-paper, #ffffff));
|
|
197
197
|
--_tone-2: color-mix(in srgb, var(--color-brand) 14%, var(--color-surface, #f2f2f0));
|
|
198
198
|
--_muted-on-tone-2: color-mix(in srgb, var(--color-ink, #141414) 72%, var(--_tone-2));
|
|
199
|
+
--_muted-on-brand: color-mix(in srgb, var(--color-on-brand) 70%, var(--color-brand)); /* the tone-3 card's secondary words: on-brand faded into brand — was opacity 0.7, which no theme could set (timebank #132, 2026-09-22) */
|
|
199
200
|
|
|
200
201
|
/* app background ("earth-toned surface or shiny color, up to the app") */
|
|
201
202
|
--surface: var(--color-surface, #f2f2f0);
|
|
@@ -209,6 +210,7 @@
|
|
|
209
210
|
|
|
210
211
|
--card-border: #141414; /* wireframe: visible outline; palettes may set transparent */
|
|
211
212
|
--card-muted: var(--_muted-on-paper, #6b6b6b); /* secondary text inside cards (the default, tone-1 card) */
|
|
213
|
+
--card-3-muted: var(--_muted-on-brand, #b9b9b9); /* the same text on the tone-3 card — a real colour, like the two above: a theme whose brand is not near-black sets it to reach its contrast (the wireframe's #b9b9b9 = white at 0.7 on #141414, what the opacity gave) */
|
|
212
214
|
--card-2-muted: var(--_muted-on-tone-2, #5f5f5f); /* the same text on the tone-2 card — 5.1:1 on its grey (#6b6b6b is 4.3:1, under AA);
|
|
213
215
|
a palette whose tone-2 ground is strong
|
|
214
216
|
sets this to its tone-2 ink — a muted shade cannot reach AA there (clay) */
|
|
@@ -250,7 +252,8 @@
|
|
|
250
252
|
falls back to when its own declaration cannot resolve.
|
|
251
253
|
============================================================ */
|
|
252
254
|
@property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
253
|
-
@property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; }
|
|
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 */
|
|
254
257
|
@property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
|
|
255
258
|
@property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
|
|
256
259
|
|
|
@@ -454,6 +457,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
454
457
|
height: 100dvh;
|
|
455
458
|
overflow: clip;
|
|
456
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 */
|
|
457
461
|
}
|
|
458
462
|
/* min-height: 0 — a flex item's floor is its content, and the stack's content
|
|
459
463
|
is its own full-height sheets, so without it the stack grew to the whole
|
|
@@ -493,15 +497,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
493
497
|
}
|
|
494
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) */
|
|
495
499
|
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
496
|
-
the
|
|
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); }
|
|
497
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 */
|
|
498
505
|
.base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
499
|
-
.base-content > .pager__at { justify-self: center; }
|
|
500
|
-
/* the dots at the FOOT of the block (Lh 2026-09-22): a block that ends on .pager__at becomes a flex column so the dots
|
|
501
|
-
ride its bottom edge — the words keep the top, the free room opens between. The block must fill its cell for that:
|
|
502
|
-
in .pages it is stretched to the cell; a lone .step > .base-content grows for it (step.css). */
|
|
503
|
-
.base-content:has(> .pager__at:last-child) { display: flex; flex-direction: column; }
|
|
504
|
-
.base-content > .pager__at:last-child { margin-top: auto; align-self: center; }
|
|
505
506
|
.base-content .cta-pack {
|
|
506
507
|
display: flex;
|
|
507
508
|
align-items: start; /* buttons keep their own height, never the row's */
|
|
@@ -735,7 +736,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
735
736
|
and updates inside it; a browser without them (the floor: Chrome 105 / iOS 16)
|
|
736
737
|
simply switches. Only the card is named; the rest of the page must not
|
|
737
738
|
cross-fade. */
|
|
738
|
-
|
|
739
|
+
/* the name is set by slideTo() on the moving element for the move only (2026-09-22): a static name on every walk's current
|
|
740
|
+
page made two walks on one page — the gallery — share it, and the browser aborts a transition with a duplicate name */
|
|
739
741
|
|
|
740
742
|
/* ---- pages: the cards of a walk, all one height ----
|
|
741
743
|
A sequence the member walks (an introduction) reads steadier when every card
|
|
@@ -748,7 +750,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
748
750
|
<article class="card" aria-current="step">…</article>
|
|
749
751
|
<article class="card" inert>…</article>
|
|
750
752
|
</div><div class="pager__at">…</div></div> */
|
|
751
|
-
.pages {
|
|
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
|
+
}
|
|
752
758
|
.pages > :is(.card, .base-content) { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
|
|
753
759
|
.pages > [inert] { visibility: hidden; }
|
|
754
760
|
/* pages of WORDS (Lh, 2026-09-20): the same walk with .base-content pages, on the base — in a .step the
|
|
@@ -1508,6 +1514,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
1508
1514
|
that thing. */
|
|
1509
1515
|
.topbar + .step { margin-top: calc(-1 * var(--screen-gap)); --_lead: var(--screen-gap); }
|
|
1510
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); }
|
|
1511
1521
|
|
|
1512
1522
|
/* the bar's room — read from actions.css, never recomputed. Margin, not
|
|
1513
1523
|
padding, so the clip cuts at the box edge and nothing shows behind the
|
|
@@ -1543,7 +1553,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
1543
1553
|
.step > :is(.base-content, .pages) {
|
|
1544
1554
|
flex: 0 1 auto;
|
|
1545
1555
|
}
|
|
1546
|
-
|
|
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; }
|
|
1547
1559
|
.step > :is(.base-content, .pages) {
|
|
1548
1560
|
min-height: 0;
|
|
1549
1561
|
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 */
|
|
@@ -2675,7 +2687,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2675
2687
|
.row__body { flex: 1; min-width: 0; display: grid; gap: var(--row-body-gap, var(--sp-0)); }
|
|
2676
2688
|
.row__title { font: var(--row-title-font, var(--type-title)); overflow-wrap: anywhere; } /* wraps freely — no clip, so no padding trick for Thai marks (Lh 2026-09-16, same call as the card heading) */
|
|
2677
2689
|
.row__meta { font: var(--row-meta-font, var(--type-caption)); color: var(--row-meta-ink, var(--card-muted)); display: flex; align-items: center; gap: var(--row-meta-gap, var(--sp-1)); }
|
|
2678
|
-
.card--3 .row__meta { color:
|
|
2690
|
+
.card--3 .row__meta { color: var(--card-3-muted); }
|
|
2679
2691
|
.row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
|
|
2680
2692
|
.row--lg { min-height: var(--row-lg-min-h, var(--fab)); } /* menu rows: a taller target, round arrow at the end; .rows gives them more air */
|
|
2681
2693
|
.row--lg .row__title { font: 700 var(--fs-lg)/1.2 var(--font-display); }
|
|
@@ -2694,7 +2706,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2694
2706
|
at --tap-sm, but not an input: nothing to press. */
|
|
2695
2707
|
.row--done { pointer-events: none; cursor: default; }
|
|
2696
2708
|
.row--done .row__title { color: var(--card-muted); }
|
|
2697
|
-
.card--3 .row--done .row__title { color:
|
|
2709
|
+
.card--3 .row--done .row__title { color: var(--card-3-muted); }
|
|
2698
2710
|
.row__done {
|
|
2699
2711
|
width: var(--row-done-size, var(--tap-sm));
|
|
2700
2712
|
height: var(--row-done-size, var(--tap-sm));
|
|
@@ -2736,7 +2748,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2736
2748
|
.kv-cell > .icon { margin-bottom: var(--sp-1); }
|
|
2737
2749
|
.kv-cell__val { font: var(--kv-cell-val-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
|
|
2738
2750
|
.kv-cell__key { font: var(--kv-cell-key-font, var(--type-caption)); color: var(--kv-cell-key-ink, var(--card-muted)); }
|
|
2739
|
-
.card--3 .kv-cell__key { color:
|
|
2751
|
+
.card--3 .kv-cell__key { color: var(--card-3-muted); }
|
|
2740
2752
|
|
|
2741
2753
|
/* ---- bubble: a chat message — theirs on the left, mine on the right (Lh 2026-09-18, replaces the chip's tail) ----
|
|
2742
2754
|
No tail: a rounded block whose corner nearest the sender is square-ish
|
|
@@ -2786,7 +2798,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2786
2798
|
.bubble__meta { display: flex; align-items: center; gap: var(--bubble-meta-gap, var(--sp-1)); font: var(--bubble-meta-font, var(--type-caption)); color: var(--bubble-meta-ink, var(--on-surface-muted)); }
|
|
2787
2799
|
.bubble--right > .bubble__meta { justify-self: end; }
|
|
2788
2800
|
.bubble__meta > .icon { color: var(--accent); }
|
|
2789
|
-
.card--3 .bubble__meta { color:
|
|
2801
|
+
.card--3 .bubble__meta { color: var(--card-3-muted); }
|
|
2790
2802
|
|
|
2791
2803
|
/* ---- timeline: a hairline with date chips, entries to the right ----
|
|
2792
2804
|
<ol class="timeline"><li class="timeline__item"><span class="timeline__mark">Wed 14</span><p class="t-body">…</p></li>…</ol> */
|
|
@@ -2849,7 +2861,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2849
2861
|
cursor: pointer;
|
|
2850
2862
|
}
|
|
2851
2863
|
.link--muted { color: var(--link-muted-ink, var(--card-muted)); }
|
|
2852
|
-
.card--3 .link--muted { color:
|
|
2864
|
+
.card--3 .link--muted { color: var(--card-3-muted); }
|
|
2853
2865
|
|
|
2854
2866
|
/* ---- css/media.css — cover card, quote, tile grid, fold, mosaic, wave ---- */
|
|
2855
2867
|
/* ============================================================
|
|
@@ -2970,7 +2982,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2970
2982
|
border-color: transparent;
|
|
2971
2983
|
border-bottom-right-radius: 0;
|
|
2972
2984
|
background-image: linear-gradient(to top left,
|
|
2973
|
-
var(--
|
|
2985
|
+
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 */
|
|
2974
2986
|
color-mix(in srgb, currentColor 18%, var(--card-bg)) 50% 100%);
|
|
2975
2987
|
background-repeat: no-repeat;
|
|
2976
2988
|
background-position: bottom right;
|
|
@@ -29,4 +29,4 @@ export type SlideDirection = 'next' | 'back';
|
|
|
29
29
|
* (card.css, `--motion-slide`); the heading and the dots stay still. Without view transitions (the browser
|
|
30
30
|
* floor) it just runs the update.
|
|
31
31
|
*/
|
|
32
|
-
export declare function slideTo(direction: SlideDirection, update: () => void): void;
|
|
32
|
+
export declare function slideTo(direction: SlideDirection, update: () => void, scope?: ParentNode): void;
|
package/dist/scaffold/Centre.js
CHANGED
|
@@ -29,15 +29,25 @@ export function Pages({ className, ...rest }) {
|
|
|
29
29
|
* (card.css, `--motion-slide`); the heading and the dots stay still. Without view transitions (the browser
|
|
30
30
|
* floor) it just runs the update.
|
|
31
31
|
*/
|
|
32
|
-
export function slideTo(direction, update) {
|
|
32
|
+
export function slideTo(direction, update, scope = document) {
|
|
33
33
|
const root = document.documentElement;
|
|
34
34
|
if (typeof document.startViewTransition !== 'function') {
|
|
35
35
|
update();
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
+
/* the moving thing is NAMED for the transition only while it moves, and only inside `scope` (the walk's Step or Centre —
|
|
39
|
+
an app has one walk per screen and can leave it out; the gallery shows several on one page, and two elements with the
|
|
40
|
+
same name abort the transition). CSS names nothing (2026-09-22). */
|
|
41
|
+
const moving = () => scope.querySelector('.pages > [aria-current], .screen__centre > .card');
|
|
42
|
+
const name = (el, v) => { if (el)
|
|
43
|
+
el.style.viewTransitionName = v; };
|
|
44
|
+
const before = moving();
|
|
45
|
+
name(before, 'centre-card');
|
|
38
46
|
root.dataset.slide = direction;
|
|
39
|
-
|
|
47
|
+
let after = null;
|
|
48
|
+
const transition = document.startViewTransition(() => { flushSync(update); name(before, ''); after = moving(); name(after, 'centre-card'); });
|
|
40
49
|
void transition.finished.finally(() => {
|
|
41
50
|
delete root.dataset.slide;
|
|
51
|
+
name(after, '');
|
|
42
52
|
});
|
|
43
53
|
}
|
|
@@ -7,5 +7,5 @@ export interface PagerAtProps extends Omit<ComponentProps<'span'>, 'children'> {
|
|
|
7
7
|
/** accessible name; defaults to "Page {at} of {of}" */
|
|
8
8
|
label?: string;
|
|
9
9
|
}
|
|
10
|
-
/** PagerAt — the "where am I" dots
|
|
10
|
+
/** PagerAt — the "where am I" dots: one accent dot at the current page. Under the card that changes (a child of Centre), or a Step's child after its Pages — pinned to the base's foot. One per walk, never inside an ActionBar, never inside a page. */
|
|
11
11
|
export declare function PagerAt({ at, of, label, className, ...rest }: PagerAtProps): import("react").JSX.Element;
|
package/dist/scaffold/PagerAt.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../cx.js';
|
|
3
|
-
/** PagerAt — the "where am I" dots
|
|
3
|
+
/** PagerAt — the "where am I" dots: one accent dot at the current page. Under the card that changes (a child of Centre), or a Step's child after its Pages — pinned to the base's foot. One per walk, never inside an ActionBar, never inside a page. */
|
|
4
4
|
export function PagerAt({ at, of, label, className, ...rest }) {
|
|
5
5
|
return (_jsx("span", { className: cx('pager__at', className), role: "img", "aria-label": label ?? `Page ${at} of ${of}`, ...rest, children: Array.from({ length: of }, (_, i) => _jsx("i", { className: i + 1 === at ? 'is-at' : undefined }, i)) }));
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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.",
|