@usableapp/cardds 0.7.52 → 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 +18 -0
- package/README.md +19 -9
- package/css/sheet.css +44 -14
- package/css/stack.css +29 -1
- package/css/theme.template.css +1 -1
- package/css/tokens.css +5 -5
- package/dist/cardds.css +78 -20
- 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 +6 -5
- package/dist/sheets/SheetStack.js +60 -410
- 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/dist/templates/Home.d.ts +3 -1
- package/dist/templates/Home.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
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
|
+
|
|
12
|
+
## 0.7.53 — 2026-10-05
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **`SheetStack` / `Home`: the pull down that asks `onRefresh` works with a finger** (Lh 2026-10-05). In 0.7.51 it only worked with a mouse: on a phone the browser took the touch for its own scroll and the content only jerked. A pull down from the top of the open content now follows the finger and asks; any other move still scrolls. Nothing to change in a screen.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`SheetStack` and `Home` take `refreshing`** — the index of the sheet you are reloading, or `null`. Set it in `onRefresh`, clear it when your data is in: `onRefresh={(i) => { setRefreshing(i); load(i).finally(() => setRefreshing(null)); }}` with `refreshing={refreshing}`. While it is the open sheet, its grip dots (below) fade up and down, the card is `aria-busy`, and a pull there does not ask again. Without `refreshing` nothing shows that a reload is running. Reload the pulled sheet alone or everything — that is yours; the stack does not fetch.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **Every sheet of a tap stack wears a grip: four small grey dots at the middle of its top edge** (Lh 2026-10-05) — a sign that the card slides up and down. A mark, not a control; nothing is added to the markup. Every `SheetStack` (tap mode) and `Home` shows it after the update — nothing to change in a screen, and there is no knob for it yet: if your screen needs it tuned or off, report it.
|
|
22
|
+
|
|
5
23
|
## 0.7.52 — 2026-10-05
|
|
6
24
|
|
|
7
25
|
### Changed
|
package/README.md
CHANGED
|
@@ -181,12 +181,14 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
181
181
|
A tap, or a pull up, opens any sheet over it. The open title or its back
|
|
182
182
|
button, tapped or pulled down, drops it home. A pull down in the open
|
|
183
183
|
content, already at the top, asks `onRefresh` — omit it and that pull only
|
|
184
|
-
scrolls. A tab in the head stays a click.
|
|
184
|
+
scrolls. A tab in the head stays a click. Every sheet wears a grip — four
|
|
185
|
+
small dots at the middle of its top edge, a mark that it slides, not a
|
|
186
|
+
control.
|
|
185
187
|
- **Five sheets max.** Slots are declared per `:nth-child` in `stack.css`; a
|
|
186
188
|
sixth would render at slot 0, on top of the first.
|
|
187
189
|
- **Every head needs its own `.sheet-stack__back`** if the stack is `--tap`. It stays
|
|
188
190
|
hidden until that sheet is open, then slides into a reserved header slot.
|
|
189
|
-
- **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.
|
|
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.
|
|
190
192
|
- **`.card-stack` is gone.** That was this component's first name and the wrong
|
|
191
193
|
one — the members are sheets and the stack is the menu, not a list of cards.
|
|
192
194
|
Never bring it back, not even as an alias.
|
|
@@ -218,8 +220,9 @@ of `Screen`:
|
|
|
218
220
|
- **Checked:** `validateScreen` (`scripts/validate.mjs`, run on every story and template by
|
|
219
221
|
`tests/composition.spec.js`) fails a bar after `</Screen>` or a pinned bar, warns on a Sheet + ActionBar.
|
|
220
222
|
- **Templates** are `Screen`s with the three parts filled (`Walkthrough` = top `lead` · content `Pages` · bottom
|
|
221
|
-
`ActionBar`); the gallery draws those parts on a template's page. The gallery's nav groups
|
|
222
|
-
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.
|
|
223
226
|
|
|
224
227
|
## Step markup
|
|
225
228
|
|
|
@@ -269,7 +272,10 @@ whose markup has rules, because the sheet's box is the step itself:
|
|
|
269
272
|
**handle · head · body**: the handle, then a `.card__head` title row (that
|
|
270
273
|
row is what `peek` shows — heading left, the contextual thing right), then
|
|
271
274
|
`.sheet__body` with what the member acts on. The handle and the head are
|
|
272
|
-
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
|
|
273
279
|
sheet with no head (the dim page sheet) is a body alone.
|
|
274
280
|
- **The step follows its top bar directly, and touches it.** `full` means
|
|
275
281
|
the sheet's top edge at the bar's bottom edge — nothing above it but the
|
|
@@ -530,9 +536,13 @@ Live: every screen in the gallery (`npm run dev`).
|
|
|
530
536
|
get covered. Closing reverses it in two phases: the after-pack returns
|
|
531
537
|
while the card drops into its slot, then the before-pack slides back up
|
|
532
538
|
from behind. Slots, offsets and paint order are all CSS `:nth-child`, so
|
|
533
|
-
a stack renders correctly with no script
|
|
534
|
-
|
|
535
|
-
|
|
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.
|
|
536
546
|
7. **Everything shrinks to 320px.** `.screen > *`, `.card` and `.card > *`
|
|
537
547
|
carry `min-width: 0`, so no child's min-content (an input, a fixed row)
|
|
538
548
|
can widen a card's grid track past its padding; topbar titles ellipsize;
|
|
@@ -627,7 +637,7 @@ That file is where a project's look lives. Bases and roles:
|
|
|
627
637
|
|---|---|---|
|
|
628
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 |
|
|
629
639
|
| type | `--fs-base` (1.375rem = 22 — the body) | every `--fs-*`, `--badge`, `--icon-xs` |
|
|
630
|
-
| corners | `--r-base` (
|
|
640
|
+
| corners | `--r-base` (1.3rem = 20.8) | every `--r-*`; `0` = square. `--r-chip` stays fully round |
|
|
631
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) |
|
|
632
642
|
| faces | `--font-ui`, `--font-display` | |
|
|
633
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,6 +183,34 @@
|
|
|
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 dots at the middle of every sheet's top edge — a sign that this card
|
|
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). 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 {
|
|
194
|
+
--_grip-dot: var(--sp-1);
|
|
195
|
+
--_grip-step: calc(var(--sp-1) * 2.5);
|
|
196
|
+
--_grip-y: calc((var(--card-pad) - var(--_grip-dot)) / 2);
|
|
197
|
+
--_grip-ink: color-mix(in srgb, currentColor 35%, transparent);
|
|
198
|
+
--_grip-img: radial-gradient(circle closest-side, var(--_grip-ink) 85%, transparent);
|
|
199
|
+
background-image: var(--_grip-img), var(--_grip-img), var(--_grip-img), var(--_grip-img);
|
|
200
|
+
background-repeat: no-repeat;
|
|
201
|
+
background-size: var(--_grip-dot) var(--_grip-dot);
|
|
202
|
+
background-position:
|
|
203
|
+
calc(50% - 1.5 * var(--_grip-step)) var(--_grip-y), calc(50% - 0.5 * var(--_grip-step)) var(--_grip-y),
|
|
204
|
+
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
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; } }
|
|
211
|
+
@media (prefers-reduced-motion: reduce) {
|
|
212
|
+
.stack-card.is-open.is-loading { animation: none; --_grip-ink: currentColor; }
|
|
213
|
+
}
|
|
186
214
|
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
187
215
|
tappable), so its resting offset would leave its last n steps below the
|
|
188
216
|
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
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,6 +1184,34 @@ 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 dots at the middle of every sheet's top edge — a sign that this card
|
|
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). 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 {
|
|
1195
|
+
--_grip-dot: var(--sp-1);
|
|
1196
|
+
--_grip-step: calc(var(--sp-1) * 2.5);
|
|
1197
|
+
--_grip-y: calc((var(--card-pad) - var(--_grip-dot)) / 2);
|
|
1198
|
+
--_grip-ink: color-mix(in srgb, currentColor 35%, transparent);
|
|
1199
|
+
--_grip-img: radial-gradient(circle closest-side, var(--_grip-ink) 85%, transparent);
|
|
1200
|
+
background-image: var(--_grip-img), var(--_grip-img), var(--_grip-img), var(--_grip-img);
|
|
1201
|
+
background-repeat: no-repeat;
|
|
1202
|
+
background-size: var(--_grip-dot) var(--_grip-dot);
|
|
1203
|
+
background-position:
|
|
1204
|
+
calc(50% - 1.5 * var(--_grip-step)) var(--_grip-y), calc(50% - 0.5 * var(--_grip-step)) var(--_grip-y),
|
|
1205
|
+
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
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; } }
|
|
1212
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1213
|
+
.stack-card.is-open.is-loading { animation: none; --_grip-ink: currentColor; }
|
|
1214
|
+
}
|
|
1187
1215
|
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
1188
1216
|
tappable), so its resting offset would leave its last n steps below the
|
|
1189
1217
|
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
|
@@ -1414,10 +1442,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1414
1442
|
============================================================ */
|
|
1415
1443
|
|
|
1416
1444
|
.sheet {
|
|
1417
|
-
/* the strip a peeked sheet shows: the card's own top padding
|
|
1418
|
-
|
|
1419
|
-
least that tall) and the gap under it, so the cut lands exactly
|
|
1420
|
-
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
|
|
1421
1449
|
(and registered in tokens.css) so a sheet whose head is taller can
|
|
1422
1450
|
retune it: .my-sheet { --sheet-peek: … } */
|
|
1423
1451
|
--sheet-half: 0.5; /* a share of the sheet's own height */
|
|
@@ -1458,7 +1486,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1458
1486
|
/* --sheet-peek is declared on the BOX the sheet slides in, not on the sheet: the words behind a peeked sheet
|
|
1459
1487
|
have to read it too (step.css keeps that much room under them), and a token only flows down. The sheet
|
|
1460
1488
|
inherits it. A sheet whose head is taller retunes it on the box. */
|
|
1461
|
-
: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 */
|
|
1462
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
|
|
1463
1491
|
setting on a narrow phone) — the strip and the head read the same number, so the title row is always inside the strip */
|
|
1464
1492
|
:root { --sheet-head-h: max(var(--tap), calc(var(--fs-h2) * 1.25 + var(--sp-2))); }
|
|
@@ -1542,25 +1570,55 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1542
1570
|
.sheet__body > * { min-width: 0; } /* as .card > *: children shrink to the track, never widen it */
|
|
1543
1571
|
.sheet__body:last-child { margin-bottom: calc(-1 * var(--card-pad)); padding-bottom: calc(var(--card-pad) + var(--_cover) + var(--_safe-b-open)); }
|
|
1544
1572
|
|
|
1545
|
-
/* grab handle — first child of the sheet
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
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. */
|
|
1550
1579
|
.sheet__handle {
|
|
1551
|
-
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;
|
|
1552
1585
|
border: 0;
|
|
1553
|
-
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 */
|
|
1554
1587
|
background: currentColor;
|
|
1555
1588
|
opacity: 0.3;
|
|
1556
|
-
width: var(--
|
|
1589
|
+
width: calc(var(--sp-base) * 18); /* 72: the line. The grab is the ::before, not this width */
|
|
1557
1590
|
height: var(--_handle);
|
|
1558
1591
|
border-radius: var(--sheet-handle-radius, var(--r-chip));
|
|
1559
|
-
margin: 0
|
|
1592
|
+
margin: 0;
|
|
1560
1593
|
padding: 0;
|
|
1561
1594
|
touch-action: none;
|
|
1562
1595
|
cursor: grab;
|
|
1563
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
|
+
}
|
|
1564
1622
|
.sheet__handle::before {
|
|
1565
1623
|
content: "";
|
|
1566
1624
|
position: absolute;
|