@usableapp/cardds 0.7.53 → 0.7.54
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 +7 -0
- package/README.md +16 -8
- package/css/sheet.css +44 -14
- package/css/stack.css +15 -10
- package/css/theme.template.css +1 -1
- package/css/tokens.css +5 -5
- package/dist/cardds.css +64 -29
- package/dist/cards/Card.d.ts +868 -6
- package/dist/cards/Card.js +43 -7
- package/dist/cards/CardHead.d.ts +1 -6
- package/dist/cards/CardHead.js +26 -1
- package/dist/choice/Slider.js +9 -13
- package/dist/forms/Composer.js +3 -1
- package/dist/forms/Field.js +3 -1
- package/dist/forms/FileBtn.js +3 -1
- package/dist/forms/Pin.js +3 -1
- package/dist/sheets/Sheet.d.ts +7 -1
- package/dist/sheets/Sheet.js +2 -2
- package/dist/sheets/SheetStack.d.ts +4 -5
- package/dist/sheets/SheetStack.js +60 -442
- package/dist/sheets/SheetSteps.js +9 -13
- package/dist/sheets/StackSheet.d.ts +4 -1
- package/dist/sheets/StackSheet.js +2 -2
- package/dist/sheets/stackCard.d.ts +31 -0
- package/dist/sheets/stackCard.js +389 -0
- package/dist/sheets/stepCursor.d.ts +12 -0
- package/dist/sheets/stepCursor.js +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
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.7.54 — 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **A stacked sheet renders its own place.** `SheetStack` still takes `open` and `refreshing` as indexes: which sheet is open, and whether its data is in, stay yours. The card renders what those mean (`is-open`, `is-before`, `is-after`, `is-loading`, the grip, the pull). Nothing to change in a screen that already passes those two. Do not stamp those classes onto the card after it has rendered.
|
|
9
|
+
- **The open step's cursor mark comes from the field.** `Field`, `Pin` (its first cell), `Composer`, `FileBtn`, and `Slider` render `data-autofocus`, and `data-cursor` while the steps' holder is parked. A raw `<input>` written by hand in a step does not get the mark. Nothing to change when the step's first control is one of those.
|
|
10
|
+
- **`Slider` publishes `--slider-at` from the value it renders.** Nothing to change in a screen.
|
|
11
|
+
|
|
5
12
|
## 0.7.53 — 2026-10-05
|
|
6
13
|
|
|
7
14
|
### Fixed
|
package/README.md
CHANGED
|
@@ -188,7 +188,7 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
188
188
|
sixth would render at slot 0, on top of the first.
|
|
189
189
|
- **Every head needs its own `.sheet-stack__back`** if the stack is `--tap`. It stays
|
|
190
190
|
hidden until that sheet is open, then slides into a reserved header slot.
|
|
191
|
-
- **No ids, no wiring.** `SheetStack` holds which sheet is open (`open` + `onOpenChange`, or `defaultOpen`). A pull down in the open content asks `onRefresh(index)`. The stack does not fetch: the app reloads (that sheet alone, or more — its call) and says so with `refreshing` (that index, then `null` when the data is in) —
|
|
191
|
+
- **No ids, no wiring.** `SheetStack` holds which sheet is open (`open` + `onOpenChange`, or `defaultOpen`). A pull down in the open content asks `onRefresh(index)`. The stack does not fetch: the app reloads (that sheet alone, or more — its call) and says so with `refreshing` (that index, then `null` when the data is in) — that sheet's grip dots dissolve white to black meanwhile, and a pull there does not ask again.
|
|
192
192
|
- **`.card-stack` is gone.** That was this component's first name and the wrong
|
|
193
193
|
one — the members are sheets and the stack is the menu, not a list of cards.
|
|
194
194
|
Never bring it back, not even as an alias.
|
|
@@ -220,8 +220,9 @@ of `Screen`:
|
|
|
220
220
|
- **Checked:** `validateScreen` (`scripts/validate.mjs`, run on every story and template by
|
|
221
221
|
`tests/composition.spec.js`) fails a bar after `</Screen>` or a pinned bar, warns on a Sheet + ActionBar.
|
|
222
222
|
- **Templates** are `Screen`s with the three parts filled (`Walkthrough` = top `lead` · content `Pages` · bottom
|
|
223
|
-
`ActionBar`); the gallery draws those parts on a template's page. The gallery's nav groups
|
|
224
|
-
the
|
|
223
|
+
`ActionBar`); the gallery draws those parts on a template's page. The gallery's nav groups a screen's
|
|
224
|
+
*top* and *bottom*, then each frame with the pieces that fill it: *card* (Card, its head, body and foot),
|
|
225
|
+
*sheet*, *stack*, *step*. A piece that also fits another frame says so on its page.
|
|
225
226
|
|
|
226
227
|
## Step markup
|
|
227
228
|
|
|
@@ -271,7 +272,10 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
271
272
|
**handle · head · body**: the handle, then a `.card__head` title row (that
|
|
272
273
|
row is what `peek` shows — heading left, the contextual thing right), then
|
|
273
274
|
`.sheet__body` with what the member acts on. The handle and the head are
|
|
274
|
-
pinned; the body is the one thing that scrolls, to the card's own edges.
|
|
275
|
+
pinned; the body is the one thing that scrolls, to the card's own edges. The
|
|
276
|
+
handle is a line, or four dots (`mark="dots"`): both sit in the card's top
|
|
277
|
+
padding, the same distance from the edge, so the title stays put. `paper` makes
|
|
278
|
+
the content white while the head keeps the sheet's colour (`tone`, as a card). A
|
|
275
279
|
sheet with no head (the dim page sheet) is a body alone.
|
|
276
280
|
- **The step follows its top bar directly, and touches it.** `full` means
|
|
277
281
|
the sheet's top edge at the bar's bottom edge — nothing above it but the
|
|
@@ -532,9 +536,13 @@ Live: every screen in the gallery (`npm run dev`).
|
|
|
532
536
|
get covered. Closing reverses it in two phases: the after-pack returns
|
|
533
537
|
while the card drops into its slot, then the before-pack slides back up
|
|
534
538
|
from behind. Slots, offsets and paint order are all CSS `:nth-child`, so
|
|
535
|
-
a stack renders correctly with no script
|
|
536
|
-
|
|
537
|
-
|
|
539
|
+
a stack renders correctly with no script. The app owns which index is
|
|
540
|
+
open and whether that sheet's data is in. The card renders what those
|
|
541
|
+
mean (`is-open`, `is-before`, `is-after`, `is-loading`, the grip, the
|
|
542
|
+
pull) from the `stack` prop `SheetStack` passes it. Don't reintroduce
|
|
543
|
+
height, `display` or z-index switching here — every past bug in this
|
|
544
|
+
animation came from exactly that. Don't stamp those classes onto the
|
|
545
|
+
card after it has rendered.
|
|
538
546
|
7. **Everything shrinks to 320px.** `.screen > *`, `.card` and `.card > *`
|
|
539
547
|
carry `min-width: 0`, so no child's min-content (an input, a fixed row)
|
|
540
548
|
can widen a card's grid track past its padding; topbar titles ellipsize;
|
|
@@ -629,7 +637,7 @@ That file is where a project's look lives. Bases and roles:
|
|
|
629
637
|
|---|---|---|
|
|
630
638
|
| room **between** things | `--sp-base` (0.25rem = 4) | every `--sp-N` = base × N → paddings, gaps, the screen's inset, the bar's reserve, the sheet's peek |
|
|
631
639
|
| type | `--fs-base` (1.375rem = 22 — the body) | every `--fs-*`, `--badge`, `--icon-xs` |
|
|
632
|
-
| corners | `--r-base` (
|
|
640
|
+
| corners | `--r-base` (1.3rem = 20.8) | every `--r-*`; `0` = square. `--r-chip` stays fully round |
|
|
633
641
|
| the size **of** things | `--tap-base` (3rem = 48) | `--tap*`, `--btn-h`, `--chip-h`, `--bar-h`, `--header-h`, `--fab`, `--icon*`, `--avatar*`, `--dot*`, `--ring*`, `--tile-*`. `--tap*` never under `--tap-floor` (44px) |
|
|
634
642
|
| faces | `--font-ui`, `--font-display` | |
|
|
635
643
|
| colour | `--color-surface`, `--color-ink`, `--color-paper`, `--color-brand`, `--color-on-brand` — no default: unset = the wireframe | every role: surface / words, the three card tones (paper · the brand washed 14% into the surface · the brand), the muted inks (the ink washed into their ground), accent, neutral actions, `--dot-a` |
|
package/css/sheet.css
CHANGED
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
============================================================ */
|
|
47
47
|
|
|
48
48
|
.sheet {
|
|
49
|
-
/* the strip a peeked sheet shows: the card's own top padding
|
|
50
|
-
|
|
51
|
-
least that tall) and the gap under it, so the cut lands exactly
|
|
52
|
-
the first control starts and nothing of it leaks through. Published
|
|
49
|
+
/* the strip a peeked sheet shows: the card's own top padding (the handle
|
|
50
|
+
sits inside it), one title row (--tap — the head rule below makes every
|
|
51
|
+
head at least that tall) and the gap under it, so the cut lands exactly
|
|
52
|
+
where the first control starts and nothing of it leaks through. Published
|
|
53
53
|
(and registered in tokens.css) so a sheet whose head is taller can
|
|
54
54
|
retune it: .my-sheet { --sheet-peek: … } */
|
|
55
55
|
--sheet-half: 0.5; /* a share of the sheet's own height */
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
91
91
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
92
92
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
93
|
-
:is(.step, .sheet-stage) { --_handle: var(--sp-
|
|
93
|
+
:is(.step, .sheet-stage) { --_handle: var(--sp-0); --sheet-peek: calc(var(--card-pad-rest) + var(--sheet-head-h) + var(--card-gap-rest) + var(--safe-bottom)); } /* + the gesture bar: the peeked head stands above it. The handle sits IN the card's top padding, so the strip is that padding, the title row and the gap under it */
|
|
94
94
|
/* the head's floor: a tap, or an h2 line with its Thai-mark padding when the type is bigger than that (a large text-size
|
|
95
95
|
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
96
96
|
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
@@ -174,25 +174,55 @@
|
|
|
174
174
|
.sheet__body > * { min-width: 0; } /* as .card > *: children shrink to the track, never widen it */
|
|
175
175
|
.sheet__body:last-child { margin-bottom: calc(-1 * var(--card-pad)); padding-bottom: calc(var(--card-pad) + var(--_cover) + var(--_safe-b-open)); }
|
|
176
176
|
|
|
177
|
-
/* grab handle — first child of the sheet
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
/* grab handle — first child of the sheet, but it does not take a row: it sits in the
|
|
178
|
+
card's top padding, the same air as a stack sheet's four dots (stack.css --_grip-y),
|
|
179
|
+
so a line and the dots leave the title in the same place (Lh 2026-10-05). The line
|
|
180
|
+
is the default; .sheet__handle--dots is the four dots. Either way it is the thumb's
|
|
181
|
+
grab: a pseudo widens its hit box to --tap tall and wider than the mark.
|
|
182
|
+
touch-action: none hands a touch drag to the Sheet component instead of to the scroller. */
|
|
182
183
|
.sheet__handle {
|
|
183
|
-
position:
|
|
184
|
+
position: absolute;
|
|
185
|
+
z-index: 1;
|
|
186
|
+
top: calc((var(--card-pad) - var(--sp-1)) / 2); /* the dots' top: (pad − 4) / 2. The line starts there too */
|
|
187
|
+
left: 50%;
|
|
188
|
+
translate: -50% 0;
|
|
184
189
|
border: 0;
|
|
185
|
-
color: inherit; /* a <button> has its own UA colour; the
|
|
190
|
+
color: inherit; /* a <button> has its own UA colour; the mark is the card's ink */
|
|
186
191
|
background: currentColor;
|
|
187
192
|
opacity: 0.3;
|
|
188
|
-
width: var(--
|
|
193
|
+
width: calc(var(--sp-base) * 18); /* 72: the line. The grab is the ::before, not this width */
|
|
189
194
|
height: var(--_handle);
|
|
190
195
|
border-radius: var(--sheet-handle-radius, var(--r-chip));
|
|
191
|
-
margin: 0
|
|
196
|
+
margin: 0;
|
|
192
197
|
padding: 0;
|
|
193
198
|
touch-action: none;
|
|
194
199
|
cursor: grab;
|
|
195
200
|
}
|
|
201
|
+
/* paper: the head keeps the sheet's own colour, the content is white (Lh 2026-10-05).
|
|
202
|
+
The body and the foot read as paper; the handle and the head stay on the card. */
|
|
203
|
+
.sheet--paper > .sheet__body {
|
|
204
|
+
--card-bg: var(--color-paper, #fff);
|
|
205
|
+
--card-ink: var(--color-ink);
|
|
206
|
+
--card-muted: var(--_muted-on-paper);
|
|
207
|
+
background: var(--card-bg);
|
|
208
|
+
color: var(--card-ink);
|
|
209
|
+
}
|
|
210
|
+
.sheet--paper > .card__foot { --card-bg: var(--color-paper, #fff); color: var(--color-ink); }
|
|
211
|
+
.sheet__handle--dots {
|
|
212
|
+
--_dot: var(--sp-1);
|
|
213
|
+
--_step: calc(var(--_dot) * 2.5);
|
|
214
|
+
--_ink: color-mix(in srgb, currentColor 35%, transparent);
|
|
215
|
+
--_img: radial-gradient(circle closest-side, var(--_ink) 85%, transparent);
|
|
216
|
+
width: calc(3 * var(--_step) + var(--_dot));
|
|
217
|
+
height: var(--_dot);
|
|
218
|
+
opacity: 1;
|
|
219
|
+
border-radius: 0;
|
|
220
|
+
background-color: transparent;
|
|
221
|
+
background-image: var(--_img), var(--_img), var(--_img), var(--_img);
|
|
222
|
+
background-repeat: no-repeat;
|
|
223
|
+
background-size: var(--_dot) var(--_dot);
|
|
224
|
+
background-position: 0 0, var(--_step) 0, calc(2 * var(--_step)) 0, calc(3 * var(--_step)) 0;
|
|
225
|
+
}
|
|
196
226
|
.sheet__handle::before {
|
|
197
227
|
content: "";
|
|
198
228
|
position: absolute;
|
package/css/stack.css
CHANGED
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
overflow: clip;
|
|
163
163
|
transition: translate var(--motion-sheet) ease; /* the only animated property here */
|
|
164
164
|
transition-delay: calc(var(--stagger, 0) * 70ms); /* 0 for everything
|
|
165
|
-
except a staggered is-before card
|
|
165
|
+
except a staggered is-before card: the card renders --stagger as it tucks or un-tucks */
|
|
166
166
|
--_back-size: var(--stack-back-size, calc(var(--tap-base) * 2 / 3)); /* read once, shared by the back button and the head's padding below */
|
|
167
167
|
}
|
|
168
168
|
.sheet-stack--tap > .card.is-open {
|
|
@@ -183,16 +183,18 @@
|
|
|
183
183
|
}
|
|
184
184
|
.sheet-stack > .card > .sheet__body.is-refreshing,
|
|
185
185
|
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
186
|
-
/* the GRIP (Lh 2026-10-05): four small
|
|
186
|
+
/* the GRIP (Lh 2026-10-05): four small dots at the middle of every sheet's top edge — a sign that this card
|
|
187
187
|
slides up and down. A mark, not a control: nothing to press, nothing added to the sheet (the card's own background, in the
|
|
188
|
-
room above its head).
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
.
|
|
188
|
+
room above its head). The card renders `.stack-card` when it is a sheet of a tap stack, so the dots are its own.
|
|
189
|
+
At rest they are the card's own ink, faint. Only while THAT sheet reloads (`.is-loading`, from its `refreshing` prop,
|
|
190
|
+
and only while it is open) they dissolve white → black, so a light card and a dark one each get a moment the dots stand out.
|
|
191
|
+
Nothing travels. --_grip is the mix, 0 white → 1 black, private. */
|
|
192
|
+
@property --_grip { syntax: "<number>"; inherits: false; initial-value: 0; }
|
|
193
|
+
.stack-card {
|
|
192
194
|
--_grip-dot: var(--sp-1);
|
|
193
195
|
--_grip-step: calc(var(--sp-1) * 2.5);
|
|
194
196
|
--_grip-y: calc((var(--card-pad) - var(--_grip-dot)) / 2);
|
|
195
|
-
--_grip-ink: color-mix(in srgb, currentColor
|
|
197
|
+
--_grip-ink: color-mix(in srgb, currentColor 35%, transparent);
|
|
196
198
|
--_grip-img: radial-gradient(circle closest-side, var(--_grip-ink) 85%, transparent);
|
|
197
199
|
background-image: var(--_grip-img), var(--_grip-img), var(--_grip-img), var(--_grip-img);
|
|
198
200
|
background-repeat: no-repeat;
|
|
@@ -201,10 +203,13 @@
|
|
|
201
203
|
calc(50% - 1.5 * var(--_grip-step)) var(--_grip-y), calc(50% - 0.5 * var(--_grip-step)) var(--_grip-y),
|
|
202
204
|
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
203
205
|
}
|
|
204
|
-
.
|
|
205
|
-
|
|
206
|
+
.stack-card.is-open.is-loading {
|
|
207
|
+
--_grip-ink: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
|
|
208
|
+
animation: stack-grip-dissolve 0.4s ease-in-out infinite alternate;
|
|
209
|
+
}
|
|
210
|
+
@keyframes stack-grip-dissolve { from { --_grip: 0; } to { --_grip: 1; } }
|
|
206
211
|
@media (prefers-reduced-motion: reduce) {
|
|
207
|
-
.
|
|
212
|
+
.stack-card.is-open.is-loading { animation: none; --_grip-ink: currentColor; }
|
|
208
213
|
}
|
|
209
214
|
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
210
215
|
tappable), so its resting offset would leave its last n steps below the
|
package/css/theme.template.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
/* ---- size: four bases, the multipliers stay the system's ---- */
|
|
25
25
|
--sp-base: 0.25rem; /* 4 the room BETWEEN things: every --sp-N = base × N, so paddings and gaps */
|
|
26
26
|
--fs-base: 1.375rem; /* 22 the type — the body size; every --fs-* is a multiplier of it (17 · 22 · 26 · 32 · 40 · 55 · 64), the count badge, the inline icon */
|
|
27
|
-
--r-base:
|
|
27
|
+
--r-base: 1.3rem; /* 20.8 the corners: every --r-* (0 = square; chips and buttons stay fully round) */
|
|
28
28
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets, control heights, icons, avatars, ring, tiles
|
|
29
29
|
(a touch target never goes under 44px, whatever this says) */
|
|
30
30
|
/* a member's text-size setting is this same --fs-base, written on <html> by the app at run time (1.375rem = 100%, 2.2rem = 160%) — the words grow, the things keep their size */
|
package/css/tokens.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
--sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
|
|
25
25
|
--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.
|
|
26
26
|
Also the member's text-size setting: the app writes it on <html> at run time (2.2rem = 160%) — words grow, things don't */
|
|
27
|
-
--r-base:
|
|
27
|
+
--r-base: 1.3rem; /* 20.8 radius, 130% of 1rem (Lh 2026-10-05): --r-* (--r-chip stays fully round) */
|
|
28
28
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
|
|
29
29
|
--header-h, --fab), icons, avatars, dots, the ring and the tiles. --sp-base is the room BETWEEN things.
|
|
30
30
|
Text-holders follow the type instead: --badge and --icon-xs (inline with captions) are on --fs-base.
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
|
|
80
80
|
/* ---- radius ---- */
|
|
81
81
|
--r-chip: 999px; /* pills, chips, buttons — always fully round */
|
|
82
|
-
--r-card: calc(var(--r-base) * 2); /*
|
|
83
|
-
--r-media: var(--r-base); /*
|
|
84
|
-
--r-control: calc(var(--r-base) * 0.875); /*
|
|
85
|
-
--r-badge: calc(var(--r-base) * 0.625); /*
|
|
82
|
+
--r-card: calc(var(--r-base) * 2); /* 41.6 the one card radius: every card, row, tile, menu, sheet, fab */
|
|
83
|
+
--r-media: var(--r-base); /* 20.8 images inside cards */
|
|
84
|
+
--r-control: calc(var(--r-base) * 0.875); /* 18.2 fields, thumbs, menu rows */
|
|
85
|
+
--r-badge: calc(var(--r-base) * 0.625); /* 13 tile badge, bars */
|
|
86
86
|
|
|
87
87
|
/* ---- borders / rules / elevation (px on purpose: crisp at any scale) ---- */
|
|
88
88
|
--border-w: 1.5px;
|
package/dist/cardds.css
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
--sp-base: 0.25rem; /* 4 spacing: --sp-N = base × N */
|
|
66
66
|
--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.
|
|
67
67
|
Also the member's text-size setting: the app writes it on <html> at run time (2.2rem = 160%) — words grow, things don't */
|
|
68
|
-
--r-base:
|
|
68
|
+
--r-base: 1.3rem; /* 20.8 radius, 130% of 1rem (Lh 2026-10-05): --r-* (--r-chip stays fully round) */
|
|
69
69
|
--tap-base: 3rem; /* 48 the size of THINGS: touch targets and control heights (--tap*, --btn-h, --chip-h, --bar-h,
|
|
70
70
|
--header-h, --fab), icons, avatars, dots, the ring and the tiles. --sp-base is the room BETWEEN things.
|
|
71
71
|
Text-holders follow the type instead: --badge and --icon-xs (inline with captions) are on --fs-base.
|
|
@@ -120,10 +120,10 @@
|
|
|
120
120
|
|
|
121
121
|
/* ---- radius ---- */
|
|
122
122
|
--r-chip: 999px; /* pills, chips, buttons — always fully round */
|
|
123
|
-
--r-card: calc(var(--r-base) * 2); /*
|
|
124
|
-
--r-media: var(--r-base); /*
|
|
125
|
-
--r-control: calc(var(--r-base) * 0.875); /*
|
|
126
|
-
--r-badge: calc(var(--r-base) * 0.625); /*
|
|
123
|
+
--r-card: calc(var(--r-base) * 2); /* 41.6 the one card radius: every card, row, tile, menu, sheet, fab */
|
|
124
|
+
--r-media: var(--r-base); /* 20.8 images inside cards */
|
|
125
|
+
--r-control: calc(var(--r-base) * 0.875); /* 18.2 fields, thumbs, menu rows */
|
|
126
|
+
--r-badge: calc(var(--r-base) * 0.625); /* 13 tile badge, bars */
|
|
127
127
|
|
|
128
128
|
/* ---- borders / rules / elevation (px on purpose: crisp at any scale) ---- */
|
|
129
129
|
--border-w: 1.5px;
|
|
@@ -1163,7 +1163,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1163
1163
|
overflow: clip;
|
|
1164
1164
|
transition: translate var(--motion-sheet) ease; /* the only animated property here */
|
|
1165
1165
|
transition-delay: calc(var(--stagger, 0) * 70ms); /* 0 for everything
|
|
1166
|
-
except a staggered is-before card
|
|
1166
|
+
except a staggered is-before card: the card renders --stagger as it tucks or un-tucks */
|
|
1167
1167
|
--_back-size: var(--stack-back-size, calc(var(--tap-base) * 2 / 3)); /* read once, shared by the back button and the head's padding below */
|
|
1168
1168
|
}
|
|
1169
1169
|
.sheet-stack--tap > .card.is-open {
|
|
@@ -1184,16 +1184,18 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1184
1184
|
}
|
|
1185
1185
|
.sheet-stack > .card > .sheet__body.is-refreshing,
|
|
1186
1186
|
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
1187
|
-
/* the GRIP (Lh 2026-10-05): four small
|
|
1187
|
+
/* the GRIP (Lh 2026-10-05): four small dots at the middle of every sheet's top edge — a sign that this card
|
|
1188
1188
|
slides up and down. A mark, not a control: nothing to press, nothing added to the sheet (the card's own background, in the
|
|
1189
|
-
room above its head).
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
.
|
|
1189
|
+
room above its head). The card renders `.stack-card` when it is a sheet of a tap stack, so the dots are its own.
|
|
1190
|
+
At rest they are the card's own ink, faint. Only while THAT sheet reloads (`.is-loading`, from its `refreshing` prop,
|
|
1191
|
+
and only while it is open) they dissolve white → black, so a light card and a dark one each get a moment the dots stand out.
|
|
1192
|
+
Nothing travels. --_grip is the mix, 0 white → 1 black, private. */
|
|
1193
|
+
@property --_grip { syntax: "<number>"; inherits: false; initial-value: 0; }
|
|
1194
|
+
.stack-card {
|
|
1193
1195
|
--_grip-dot: var(--sp-1);
|
|
1194
1196
|
--_grip-step: calc(var(--sp-1) * 2.5);
|
|
1195
1197
|
--_grip-y: calc((var(--card-pad) - var(--_grip-dot)) / 2);
|
|
1196
|
-
--_grip-ink: color-mix(in srgb, currentColor
|
|
1198
|
+
--_grip-ink: color-mix(in srgb, currentColor 35%, transparent);
|
|
1197
1199
|
--_grip-img: radial-gradient(circle closest-side, var(--_grip-ink) 85%, transparent);
|
|
1198
1200
|
background-image: var(--_grip-img), var(--_grip-img), var(--_grip-img), var(--_grip-img);
|
|
1199
1201
|
background-repeat: no-repeat;
|
|
@@ -1202,10 +1204,13 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1202
1204
|
calc(50% - 1.5 * var(--_grip-step)) var(--_grip-y), calc(50% - 0.5 * var(--_grip-step)) var(--_grip-y),
|
|
1203
1205
|
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
1204
1206
|
}
|
|
1205
|
-
.
|
|
1206
|
-
|
|
1207
|
+
.stack-card.is-open.is-loading {
|
|
1208
|
+
--_grip-ink: color-mix(in srgb, black calc(var(--_grip) * 100%), white);
|
|
1209
|
+
animation: stack-grip-dissolve 0.4s ease-in-out infinite alternate;
|
|
1210
|
+
}
|
|
1211
|
+
@keyframes stack-grip-dissolve { from { --_grip: 0; } to { --_grip: 1; } }
|
|
1207
1212
|
@media (prefers-reduced-motion: reduce) {
|
|
1208
|
-
.
|
|
1213
|
+
.stack-card.is-open.is-loading { animation: none; --_grip-ink: currentColor; }
|
|
1209
1214
|
}
|
|
1210
1215
|
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
1211
1216
|
tappable), so its resting offset would leave its last n steps below the
|
|
@@ -1437,10 +1442,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1437
1442
|
============================================================ */
|
|
1438
1443
|
|
|
1439
1444
|
.sheet {
|
|
1440
|
-
/* the strip a peeked sheet shows: the card's own top padding
|
|
1441
|
-
|
|
1442
|
-
least that tall) and the gap under it, so the cut lands exactly
|
|
1443
|
-
the first control starts and nothing of it leaks through. Published
|
|
1445
|
+
/* the strip a peeked sheet shows: the card's own top padding (the handle
|
|
1446
|
+
sits inside it), one title row (--tap — the head rule below makes every
|
|
1447
|
+
head at least that tall) and the gap under it, so the cut lands exactly
|
|
1448
|
+
where the first control starts and nothing of it leaks through. Published
|
|
1444
1449
|
(and registered in tokens.css) so a sheet whose head is taller can
|
|
1445
1450
|
retune it: .my-sheet { --sheet-peek: … } */
|
|
1446
1451
|
--sheet-half: 0.5; /* a share of the sheet's own height */
|
|
@@ -1481,7 +1486,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1481
1486
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
1482
1487
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
1483
1488
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
1484
|
-
:is(.step, .sheet-stage) { --_handle: var(--sp-
|
|
1489
|
+
:is(.step, .sheet-stage) { --_handle: var(--sp-0); --sheet-peek: calc(var(--card-pad-rest) + var(--sheet-head-h) + var(--card-gap-rest) + var(--safe-bottom)); } /* + the gesture bar: the peeked head stands above it. The handle sits IN the card's top padding, so the strip is that padding, the title row and the gap under it */
|
|
1485
1490
|
/* the head's floor: a tap, or an h2 line with its Thai-mark padding when the type is bigger than that (a large text-size
|
|
1486
1491
|
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
1487
1492
|
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
@@ -1565,25 +1570,55 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1565
1570
|
.sheet__body > * { min-width: 0; } /* as .card > *: children shrink to the track, never widen it */
|
|
1566
1571
|
.sheet__body:last-child { margin-bottom: calc(-1 * var(--card-pad)); padding-bottom: calc(var(--card-pad) + var(--_cover) + var(--_safe-b-open)); }
|
|
1567
1572
|
|
|
1568
|
-
/* grab handle — first child of the sheet
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
+
/* grab handle — first child of the sheet, but it does not take a row: it sits in the
|
|
1574
|
+
card's top padding, the same air as a stack sheet's four dots (stack.css --_grip-y),
|
|
1575
|
+
so a line and the dots leave the title in the same place (Lh 2026-10-05). The line
|
|
1576
|
+
is the default; .sheet__handle--dots is the four dots. Either way it is the thumb's
|
|
1577
|
+
grab: a pseudo widens its hit box to --tap tall and wider than the mark.
|
|
1578
|
+
touch-action: none hands a touch drag to the Sheet component instead of to the scroller. */
|
|
1573
1579
|
.sheet__handle {
|
|
1574
|
-
position:
|
|
1580
|
+
position: absolute;
|
|
1581
|
+
z-index: 1;
|
|
1582
|
+
top: calc((var(--card-pad) - var(--sp-1)) / 2); /* the dots' top: (pad − 4) / 2. The line starts there too */
|
|
1583
|
+
left: 50%;
|
|
1584
|
+
translate: -50% 0;
|
|
1575
1585
|
border: 0;
|
|
1576
|
-
color: inherit; /* a <button> has its own UA colour; the
|
|
1586
|
+
color: inherit; /* a <button> has its own UA colour; the mark is the card's ink */
|
|
1577
1587
|
background: currentColor;
|
|
1578
1588
|
opacity: 0.3;
|
|
1579
|
-
width: var(--
|
|
1589
|
+
width: calc(var(--sp-base) * 18); /* 72: the line. The grab is the ::before, not this width */
|
|
1580
1590
|
height: var(--_handle);
|
|
1581
1591
|
border-radius: var(--sheet-handle-radius, var(--r-chip));
|
|
1582
|
-
margin: 0
|
|
1592
|
+
margin: 0;
|
|
1583
1593
|
padding: 0;
|
|
1584
1594
|
touch-action: none;
|
|
1585
1595
|
cursor: grab;
|
|
1586
1596
|
}
|
|
1597
|
+
/* paper: the head keeps the sheet's own colour, the content is white (Lh 2026-10-05).
|
|
1598
|
+
The body and the foot read as paper; the handle and the head stay on the card. */
|
|
1599
|
+
.sheet--paper > .sheet__body {
|
|
1600
|
+
--card-bg: var(--color-paper, #fff);
|
|
1601
|
+
--card-ink: var(--color-ink);
|
|
1602
|
+
--card-muted: var(--_muted-on-paper);
|
|
1603
|
+
background: var(--card-bg);
|
|
1604
|
+
color: var(--card-ink);
|
|
1605
|
+
}
|
|
1606
|
+
.sheet--paper > .card__foot { --card-bg: var(--color-paper, #fff); color: var(--color-ink); }
|
|
1607
|
+
.sheet__handle--dots {
|
|
1608
|
+
--_dot: var(--sp-1);
|
|
1609
|
+
--_step: calc(var(--_dot) * 2.5);
|
|
1610
|
+
--_ink: color-mix(in srgb, currentColor 35%, transparent);
|
|
1611
|
+
--_img: radial-gradient(circle closest-side, var(--_ink) 85%, transparent);
|
|
1612
|
+
width: calc(3 * var(--_step) + var(--_dot));
|
|
1613
|
+
height: var(--_dot);
|
|
1614
|
+
opacity: 1;
|
|
1615
|
+
border-radius: 0;
|
|
1616
|
+
background-color: transparent;
|
|
1617
|
+
background-image: var(--_img), var(--_img), var(--_img), var(--_img);
|
|
1618
|
+
background-repeat: no-repeat;
|
|
1619
|
+
background-size: var(--_dot) var(--_dot);
|
|
1620
|
+
background-position: 0 0, var(--_step) 0, calc(2 * var(--_step)) 0, calc(3 * var(--_step)) 0;
|
|
1621
|
+
}
|
|
1587
1622
|
.sheet__handle::before {
|
|
1588
1623
|
content: "";
|
|
1589
1624
|
position: absolute;
|