@usableapp/cardds 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -0
- package/README.md +3 -3
- package/css/base.css +5 -0
- package/css/layover.css +12 -15
- package/css/lists.css +9 -0
- package/css/media.css +67 -31
- package/css/numbers.css +16 -0
- package/css/sheet.css +8 -4
- package/css/stack.css +3 -3
- package/dist/cardds.css +120 -53
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lists/Row.d.ts +11 -283
- package/dist/lists/Row.js +10 -3
- package/dist/media/Postcard.d.ts +15 -5
- package/dist/media/Postcard.js +8 -5
- package/dist/numbers/Progress.d.ts +9 -0
- package/dist/numbers/Progress.js +7 -0
- package/dist/sheets/Drawer.d.ts +2 -2
- package/dist/sheets/Drawer.js +1 -1
- package/dist/sheets/Sheet.d.ts +1 -3
- package/dist/sheets/Sheet.js +2 -2
- package/dist/sheets/SheetStack.d.ts +3 -3
- package/dist/sheets/SheetStack.js +3 -3
- package/dist/sheets/SheetStage.d.ts +3 -5
- package/dist/sheets/SheetStage.js +3 -3
- package/dist/sheets/StackBack.d.ts +7 -0
- package/dist/sheets/StackBack.js +7 -0
- package/package.json +3 -2
- package/dist/sheets/CardBack.d.ts +0 -7
- package/dist/sheets/CardBack.js +0 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## 0.4.2 — 2026-09-22
|
|
6
|
+
|
|
7
|
+
### Words on the base: a picture and the dots
|
|
8
|
+
Screens that only tell (onboarding, a walk of pages) live on the base — `Step › BaseContent` — never in a `Card centre` on an empty base. Two things the base could not hold well are fixed:
|
|
9
|
+
- an `<img>` (or `Placeholder`) among the words is capped by `--base-content-media-h` (14rem) so a tall picture never pushes the words under the bar.
|
|
10
|
+
- `PagerAt` as the last child of a `BaseContent` is centred under the words (it used to sit left).
|
|
11
|
+
Story: `Step · WalksWithArt` — pages of words with a picture and the dots, a slider in the bar's upper tier, the round pager below.
|
|
12
|
+
|
|
13
|
+
Where things go on such a screen (answers given to timebank): a `Postcard` shown with the words is a child of `BaseContent` after them (the picture's slot; never wrapped in a Card) · a summary the system hands over (an address, a ticket) stays a `Card centre` · a walk's progress is the `CardHead` overline ("ขั้นที่ 2 จาก 5") or a `Track` as the first child of `BaseContent` — nothing sits between the TopBar and the Step.
|
|
14
|
+
|
|
15
|
+
## 0.4.1 — 2026-09-22
|
|
16
|
+
|
|
17
|
+
Breaking renames and removals. No aliases (nobody had installed 0.4.0).
|
|
18
|
+
|
|
19
|
+
### `CardBack` → `StackBack`
|
|
20
|
+
The back button a tapped stack sheet reveals belongs to the stack, not the card.
|
|
21
|
+
- import: `CardBack` → `StackBack`
|
|
22
|
+
- class (if you styled it): `.card__back` → `.sheet-stack__back`
|
|
23
|
+
- where it goes: unchanged — first child of the open stack sheet's `CardHead`.
|
|
24
|
+
|
|
25
|
+
### `SheetStage`: `ask` prop removed
|
|
26
|
+
The stage only does the ground; the thing in front places itself.
|
|
27
|
+
- `<SheetStage ask>` → `<SheetStage dim>` — the `Modal` inside centres itself now.
|
|
28
|
+
- A Modal on a clean ground (no scrim) is allowed: `<SheetStage>` with no `dim`.
|
|
29
|
+
- CSS: `.sheet-stage--ask` is gone; `--drawer-h` now lives on `.modal`.
|
|
30
|
+
|
|
31
|
+
### `Sheet`: `raised` prop removed
|
|
32
|
+
A sheet always stands on the floor and never has a dim behind it.
|
|
33
|
+
- `<SheetStage dim><Sheet raised …>` → that screen is a `Modal`: `<SheetStage dim><Modal><Lift>…</Lift><Drawer>…</Drawer></Modal>`
|
|
34
|
+
- `.sheet--raised`, `--sheet-raised-radius` are gone. `SheetStage foot` (the footnote strip) stays.
|
|
35
|
+
|
|
36
|
+
### `Postcard` reshaped
|
|
37
|
+
The shape of `Card cover`: the photo fills it, a band at the bottom names it. Not a 3:2 card inside a card any more.
|
|
38
|
+
- props kept: `photo` `alt` `name` `caption` `flipped` `defaultFlipped` `onFlip` `frontLabel` `backLabel`
|
|
39
|
+
- new: `line` (the words under the name, `t-body`) · `liked` + `onLike(liked)` (the member's own heart in the band; no heart without them) · `likeLabel` · `voice` (a voice note instead of words: whatever you put there stands at the middle of the back — the app plays it; see Progress)
|
|
40
|
+
- the band (`name` · `line` · heart) turns WITH the face. The back: `name` a fifth of the way down, `line`, then `caption` (or `voice`).
|
|
41
|
+
- a tap on the photo still turns it. The turn is `.postcard__turn` (a button filling the face); the faces are divs now.
|
|
42
|
+
- Don't wrap a Postcard in a `Card`. Its min-height is the cover's (`--postcard-min-h` 22.5rem); as a `SheetStage hero` it fills the band the sheet leaves open, with the screen's inset at both ends.
|
|
43
|
+
- Who liked it is YOUR data, shown outside the card — see `Row more`.
|
|
44
|
+
|
|
45
|
+
### `Row`: `more` (new)
|
|
46
|
+
A row that expands: it becomes the summary of a `<details class="row-expand">`, `more` comes out under it.
|
|
47
|
+
- `<Row lead={…} meta="4 คนถูกใจ" end={<Icon name="chevron-down" />} open={open} onOpenChange={setOpen} more={<Rows>…one Row per person…</Rows>} />`
|
|
48
|
+
- `open` + `onOpenChange`, or `defaultOpen`. An `end` chevron turns when open. You fetch the list; the row shows it.
|
|
49
|
+
|
|
50
|
+
### `Progress` (new)
|
|
51
|
+
One straight line, the done share solid, the rest faint.
|
|
52
|
+
- `<Progress at={0.4} label="0:05 of 0:12" />` — `at` 0–1; you keep the value (a timer, a download).
|
|
53
|
+
- knobs: `--progress-h` · `--progress-radius` · `--progress-done-ink` · `--progress-rest-bg`.
|
|
54
|
+
|
|
55
|
+
### Stage hero
|
|
56
|
+
`.sheet-stage__hero` is now as tall as the band a half sheet leaves open, less `--screen-pad` at both ends, and what it holds fills it (a child's `min-height` gives way). If your hero relied on its own height, check it.
|
|
57
|
+
|
|
58
|
+
### Gallery
|
|
59
|
+
One page per thing you name (Card, Sheet, Btn…); inner parts (CardHead, CardBody, SheetBody, MenuItem, BtnRow…) are variations on the parent's page. Nav groups by use. `/Parent/Part` URLs are gone.
|
|
60
|
+
|
|
61
|
+
## 0.4.0 — 2026-09-21
|
|
62
|
+
|
|
63
|
+
Knobs, open icons, theme bases, gallery. Nothing removed from the component API.
|
|
64
|
+
- **Knobs**: every component reads its look through `--<component>-<what>` custom properties with the default as fallback (363, listed in `docs/knobs.md`). Set one on `:root` (your `theme.css`) to retune everywhere, on any element to retune what is inside it. Declared nowhere: unset = nothing moved.
|
|
65
|
+
- **Icons are open**: `registerIcons({ name: '<path…/>' })` → usable by name on every component that takes an icon; `<Icon>` draws a one-off from children; an unknown name warns once instead of throwing.
|
|
66
|
+
- **Theme bases** (`css/theme.template.css`): four size bases (`--sp-base` `--fs-base` `--r-base` `--tap-base`) and five colour bases (`--color-surface` `--color-ink` `--color-paper` `--color-brand` `--color-on-brand`) on `:root`. A project sets a base, never a multiplier. `<html data-palette="own">` for own colours.
|
|
67
|
+
- `Stat centre`; the pager places what sits between its two buttons in the middle; every component types its `ref`.
|
|
68
|
+
|
|
69
|
+
## 0.3.0 — 2026-09-21
|
|
70
|
+
|
|
71
|
+
**Components only — the app wires them.** The biggest change since 0.1: every component with a state is controlled like an input (`x` + `onXChange`, or `defaultX`), keeps its own behaviour inside, and none knows another. `cardds.js` no longer does anything between components — it is `--kb` (the keyboard's height) and `.bleed`'s scrollbar, 35 lines. If your screen relied on cardds.js to connect two things, the app connects them now.
|
|
72
|
+
|
|
73
|
+
### Gone
|
|
74
|
+
- `aria-controls` on a button calling a sheet → the app: `useState` of the sheet's state, the button sets it, `<Sheet state onStateChange>`.
|
|
75
|
+
- events `cardds:sheet` · `cardds:pin` · `cardds:modal` · `cardds:flip`, and the Dropdown's native `change` → the components' own `onXChange` callbacks.
|
|
76
|
+
- `window.carddsSheetSet` / `carddsSheetReveal` / `carddsStepSync`, `--sheet-room`, the bar guard → the Sheet does all of it itself.
|
|
77
|
+
- `data-answer` on Modal buttons → plain `onClick`s; `<Modal open onClosed>`.
|
|
78
|
+
- `IconBtn menu` / `menuId` / `menuStart` → any button opens a popover `Menu` with the platform's `popoverTarget={id}`.
|
|
79
|
+
- `SheetStack mode="tap"` + hand-set `is-open` / `is-before` / `is-after` → `open` (index | null) + `onOpenChange`, or `defaultOpen`; the component paints the classes.
|
|
80
|
+
|
|
81
|
+
### Each component, before → after
|
|
82
|
+
- **Sheet**: `state="half"` alone used to be a starting point the sheet then moved from; now a fixed `state` PINS it (the sheet asks through `onStateChange` and stays if you don't answer). A sheet the member drags or taps the handle of: `defaultState="half"` (it keeps its own), or `state` + `onStateChange` (you keep it). Own behaviour, inside: handle tap ↔ peek, drag-snap to `states`, the raise when a field in a peeked sheet takes focus, the climb when the keyboard comes up. The `away` state (a called sheet, off-screen) is set-only.
|
|
83
|
+
- **SheetStack**: `open` + `onOpenChange` / `defaultOpen`. A peeked head's tap and the back button only ask.
|
|
84
|
+
- **Modal**: `open` + `onClosed` (it plays its opening backwards, then says so; you unmount it). Buttons are yours.
|
|
85
|
+
- **Menu**: `actions` (plain onClicks) or `value` + `onChange`; picking closes it. **Dropdown**: one component like `<select>`, `value` + `onChange`, the pill named by the chosen item.
|
|
86
|
+
- **Pin**: `value` + `onChange(code, complete)` or `defaultValue`; auto-advance, Backspace, arrows, paste inside. Tests that listened for `cardds:pin` type into the cells instead.
|
|
87
|
+
- **Slider**: publishes its own `--slider-at`. **Postcard**: `flipped` + `onFlip` or `defaultFlipped`.
|
|
88
|
+
- **Calendar**: `selected` + `onSelect` or `defaultSelected`; `onPrev` / `onNext`; label props (its buttons had no handlers before).
|
|
89
|
+
- **Picker**: `value` + `onChange` or `defaultValue` — the value IS the item at the rail's centre (it had no way to report one).
|
|
90
|
+
|
|
91
|
+
Guidance, no longer enforced: "pager or sheet", "a sheet finishes on its foot", "a form lives whole in one sheet".
|
|
92
|
+
|
|
93
|
+
## 0.2.2 — 2026-09-21
|
|
94
|
+
|
|
95
|
+
One sheet shape: the head stays pinned, the body scrolls without limit, the foot's button stands at the bottom above the keyboard — at every state, on a step and on a stage. Stage sheets ride `--kb` too. (`carddsSheetSet` / `carddsSheetReveal` on `window` — removed again in 0.3.0.)
|
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
105
105
|
<div class="sheet-stack sheet-stack--tap"> <!-- drop --tap for a static stack -->
|
|
106
106
|
<article class="card card--2">
|
|
107
107
|
<div class="card__head">
|
|
108
|
-
<button class="icon-btn icon-btn--sm
|
|
108
|
+
<button class="icon-btn icon-btn--sm sheet-stack__back" aria-label="Back">…</button>
|
|
109
109
|
<h2 class="t-h2 t-caps">Shopping</h2>
|
|
110
110
|
<span class="icon-row">…</span> <!-- optional trailing icons -->
|
|
111
111
|
</div>
|
|
@@ -125,7 +125,7 @@ the card is the material, the sheet is what it is in the stack.
|
|
|
125
125
|
A tap opens any sheet over it; back drops it home.
|
|
126
126
|
- **Five sheets max.** Slots are declared per `:nth-child` in `stack.css`; a
|
|
127
127
|
sixth would render at slot 0, on top of the first.
|
|
128
|
-
- **Every head needs its own `.
|
|
128
|
+
- **Every head needs its own `.sheet-stack__back`** if the stack is `--tap`. It stays
|
|
129
129
|
hidden until that sheet is open, then slides into a reserved header slot.
|
|
130
130
|
- **No ids, no wiring.** `cardds.js` finds any `.sheet-stack--tap` on the page.
|
|
131
131
|
- **`.card-stack` is gone.** That was this component's first name and the wrong
|
|
@@ -433,7 +433,7 @@ Demo: the *skeleton* block at the top of `elements.html`.
|
|
|
433
433
|
padding. Two boxes hold sheets and the states
|
|
434
434
|
mean the same in both: `.step` (an app screen, the bar outside — Anatomy)
|
|
435
435
|
and `.sheet-stage` (a page-level overlay: a sheet over a map, the dim page
|
|
436
|
-
|
|
436
|
+
stage `--dim` + `.modal` = `__lift` + `__drawer` — a sheet always stands on the floor, never raised).
|
|
437
437
|
A stage keeps the top bar's line free (`--_top`), so `--full` stops under
|
|
438
438
|
`--header-h` and keeps its corners. Both sheet tokens are published — a
|
|
439
439
|
sheet with a taller head retunes `--sheet-peek` — and registered in
|
package/css/base.css
CHANGED
|
@@ -118,6 +118,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
118
118
|
padding-block: var(--base-content-pad-y, var(--sp-4));
|
|
119
119
|
}
|
|
120
120
|
.base-content > .t-body { color: var(--on-surface-muted); max-width: 34ch; }
|
|
121
|
+
/* an illustration among the words (Lh 2026-09-22): capped, so a tall picture never pushes the words under the bar;
|
|
122
|
+
the "where am I" dots under the words, centred — the same dots the centred card has under it */
|
|
123
|
+
.base-content > :is(img, .ph) { max-height: var(--base-content-media-h, 14rem); width: auto; max-width: 100%; object-fit: contain; justify-self: start; }
|
|
124
|
+
.base-content > .ph { width: 100%; height: var(--base-content-media-h, 14rem); }
|
|
125
|
+
.base-content > .pager__at { justify-self: center; }
|
|
121
126
|
.base-content .cta-pack {
|
|
122
127
|
display: flex;
|
|
123
128
|
align-items: start; /* buttons keep their own height, never the row's */
|
package/css/layover.css
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
lives in sheet.css; these compose with .sheet-stage.
|
|
6
6
|
============================================================ */
|
|
7
7
|
|
|
8
|
-
/* ----
|
|
9
|
-
<div class="sheet-stage sheet-stage--
|
|
8
|
+
/* ---- the Modal — the case drops in, its drawer opens below (Lh 2026-09-16: ONE card, two parts)
|
|
9
|
+
<div class="sheet-stage sheet-stage--dim"> (dim = the page under a scrim; a Modal on a clean ground is the stage without it — Lh 2026-09-22, `--ask` retired: the stage does the ground, the modal centres itself)
|
|
10
10
|
<article class="card modal">
|
|
11
11
|
<div class="modal__lift">…the card's content (a head, rows, a timeline)…</div>
|
|
12
12
|
<div class="modal__drawer"><h2 class="t-h2">Take the case?</h2><p class="t-caption">This cannot be undone.</p>
|
|
@@ -17,30 +17,27 @@
|
|
|
17
17
|
.modal is the card — no border (the shadow is its edge on the scrim);
|
|
18
18
|
.modal__lift the light part that holds the thing being asked about (it
|
|
19
19
|
scrolls when tall); .modal__drawer the dark end that asks. The ground is
|
|
20
|
-
the
|
|
20
|
+
whatever the stage shows (dim it or not). Two beats (2 × --motion-sheet) when
|
|
21
21
|
the stage appears, and the same two backwards when it closes:
|
|
22
22
|
1. the card drops in from above the stage to the screen's centre — its
|
|
23
23
|
drawer still inside it, height 0: nothing of the ask shows yet
|
|
24
24
|
2. the drawer comes out of the card's bottom edge while the lift moves
|
|
25
25
|
up, both at once: the card grows from its centre, which never moves
|
|
26
|
-
(the
|
|
26
|
+
(the modal centres itself — absolute, margin auto; a growing card keeps its middle where it is)
|
|
27
27
|
Closing: a click on any button in the drawer plays the same animations in
|
|
28
28
|
reverse (cardds.js: Animation.reverse() on each — nothing duplicated), then
|
|
29
29
|
fires "cardds:modal" on the .sheet-stage (bubbles, detail.answer from the
|
|
30
30
|
button's data-answer) for the app to remove the stage.
|
|
31
31
|
Reduced motion: the finished state, no beats; closing fires at once. */
|
|
32
|
-
.sheet-stage--ask {
|
|
33
|
-
--drawer-h: 14rem;
|
|
34
|
-
display: grid;
|
|
35
|
-
align-content: center; /* the card at the screen's centre — and kept there as it grows */
|
|
36
|
-
padding-inline: var(--screen-pad);
|
|
37
|
-
}
|
|
38
|
-
.sheet-stage--ask::after { content: ""; position: absolute; inset: 0; background: var(--scrim); } /* the page dimmed */
|
|
39
|
-
.sheet-stage--ask > .sheet-stage__bg { filter: blur(var(--sp-2)); }
|
|
40
32
|
.modal {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
--drawer-h: 14rem;
|
|
34
|
+
position: absolute; /* at the stage's centre, and kept there as it grows: absolute + margin auto re-centres on every height */
|
|
35
|
+
inset: 0;
|
|
36
|
+
margin: auto;
|
|
37
|
+
height: fit-content; /* inset 0 alone would stretch it to the stage: its own height, then the margins share the rest */
|
|
38
|
+
width: calc(100% - 2 * var(--screen-pad));
|
|
39
|
+
z-index: 2; /* over the dim stage's scrim (z 1) */
|
|
40
|
+
max-height: calc(100cqh - 2 * var(--sp-5)); /* cqh: the stage (a size container) */
|
|
44
41
|
padding: 0; /* the parts pad themselves */
|
|
45
42
|
border: 0; /* no frame on the modal (Lh 2026-09-16) — its edge is the shadow */
|
|
46
43
|
display: grid;
|
package/css/lists.css
CHANGED
|
@@ -36,6 +36,15 @@
|
|
|
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); }
|
|
39
|
+
/* a row that EXPANDS (Lh 2026-09-22): the row is a <details>' summary, what it shows comes out under it —
|
|
40
|
+
who liked a postcard, the rest of a list. An end chevron turns when open. The app owns the list.
|
|
41
|
+
<details class="row-expand"><summary class="row">…lead · body · end…</summary><div class="row-expand__more">…</div></details> */
|
|
42
|
+
.row-expand { display: block; }
|
|
43
|
+
.row-expand > summary { list-style: none; cursor: pointer; }
|
|
44
|
+
.row-expand > summary::-webkit-details-marker { display: none; }
|
|
45
|
+
.row-expand > summary > .icon:last-child { transition: rotate var(--motion-slide); }
|
|
46
|
+
.row-expand[open] > summary > .icon:last-child { rotate: 180deg; }
|
|
47
|
+
.row-expand__more { padding-top: var(--row-expand-gap, var(--sp-3)); }
|
|
39
48
|
/* a finished item in a checklist (a to-do list, an onboarding "complete your introduction" list):
|
|
40
49
|
the words go muted, a filled tick sits at the end, and the row is no longer a tap target —
|
|
41
50
|
pointer-events off here, aria-disabled from the markup. The tick is the .check's checked look
|
package/css/media.css
CHANGED
|
@@ -152,59 +152,75 @@
|
|
|
152
152
|
.mosaic > .ph { opacity: 0.4; border-width: var(--border-w); }
|
|
153
153
|
.mosaic > img { width: 100%; height: 100%; object-fit: cover; }
|
|
154
154
|
|
|
155
|
-
/* ---- postcard: a member's introduction
|
|
156
|
-
|
|
155
|
+
/* ---- postcard: a member's introduction in the shape of a cover card — the photo fills it,
|
|
156
|
+
the band at the bottom names it; tap the photo to turn it over (Lh 2026-09-12; reshaped 2026-09-22) ----
|
|
157
157
|
<div class="postcard">
|
|
158
|
-
<
|
|
159
|
-
<img src="…" alt=""> — or <div class="ph"></div>
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
<div class="postcard__face postcard__front">
|
|
159
|
+
<button class="postcard__turn" type="button" aria-label="พลิกดูด้านหลัง"><img src="…" alt=""> — or <div class="ph"></div> —</button>
|
|
160
|
+
<div class="postcard__band">
|
|
161
|
+
<div class="postcard__words"><span class="t-h1">ป้าแจ๋ว</span><span class="t-body">บ้านสวนผัก</span></div>
|
|
162
|
+
<button class="icon-btn icon-btn--invert" aria-label="ถูกใจ" aria-pressed="false">♥</button>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="postcard__face postcard__back">
|
|
166
|
+
<button class="postcard__turn" type="button" aria-label="พลิกดูด้านหน้า">
|
|
167
|
+
<div class="postcard__words">…</div><p class="postcard__caption">ชอบชงกาแฟดริปตอนเช้า</p>
|
|
168
|
+
</button>
|
|
169
|
+
<div class="postcard__voice"><div class="progress"></div><span class="t-caption">0:07 / 0:12</span><button class="icon-btn icon-btn--invert" aria-label="ฟัง">▶</button></div> (a voice note: no caption — the line, its time, the control under them, at the middle)
|
|
170
|
+
</div>
|
|
166
171
|
</div>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
The two faces share one box and turn (a Y rotation at --motion-flip, a plain
|
|
173
|
+
swap under prefers-reduced-motion). A face is a div: the TURN is the button
|
|
174
|
+
that fills it (the photo, or the caption), the band lies over its foot and
|
|
175
|
+
turns with it — words and the heart; the back sets the name a fifth of the way down, the words after it; a
|
|
176
|
+
button in the band is never inside the turn button. The size is the cover
|
|
177
|
+
card's (--postcard-min-h = the cover's min-height, the card radius). Who
|
|
178
|
+
liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
|
|
173
179
|
.postcard {
|
|
174
180
|
position: relative;
|
|
175
|
-
|
|
181
|
+
min-height: var(--postcard-min-h, 22.5rem);
|
|
176
182
|
perspective: 60rem;
|
|
177
|
-
border-radius: var(--postcard-radius, var(--r-
|
|
183
|
+
border-radius: var(--postcard-radius, var(--r-card));
|
|
184
|
+
isolation: isolate;
|
|
178
185
|
}
|
|
179
186
|
.postcard__face {
|
|
180
187
|
position: absolute;
|
|
181
188
|
inset: 0;
|
|
182
189
|
display: grid;
|
|
183
190
|
align-content: end;
|
|
184
|
-
padding: var(--postcard-face-pad, var(--sp-4));
|
|
185
|
-
gap: var(--postcard-face-gap, var(--sp-2));
|
|
186
191
|
border: var(--border-w) solid var(--postcard-face-border, var(--card-border));
|
|
187
192
|
border-radius: inherit;
|
|
188
193
|
overflow: hidden;
|
|
189
194
|
isolation: isolate;
|
|
190
|
-
color: inherit;
|
|
191
|
-
text-align: left;
|
|
192
195
|
backface-visibility: hidden;
|
|
193
196
|
-webkit-backface-visibility: hidden;
|
|
194
197
|
transition: transform var(--motion-flip) ease-in-out;
|
|
195
198
|
box-shadow: var(--postcard-face-shadow, var(--shadow-float));
|
|
196
199
|
}
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
/* the turn: the button that fills the face, under the band */
|
|
201
|
+
.postcard__turn {
|
|
202
|
+
position: absolute;
|
|
203
|
+
inset: 0;
|
|
204
|
+
display: grid;
|
|
205
|
+
align-content: start;
|
|
206
|
+
padding: var(--postcard-face-pad, var(--card-pad-rest));
|
|
207
|
+
border: 0;
|
|
208
|
+
border-radius: inherit;
|
|
209
|
+
background: transparent;
|
|
210
|
+
color: inherit;
|
|
211
|
+
text-align: left;
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
}
|
|
214
|
+
.postcard__turn:focus-visible { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
|
|
215
|
+
/* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
|
|
199
216
|
.postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); color: var(--postcard-front-ink, var(--card-3-ink)); }
|
|
200
|
-
.postcard__front > img,
|
|
201
|
-
.postcard__front > .ph {
|
|
217
|
+
.postcard__front .postcard__turn > img,
|
|
218
|
+
.postcard__front .postcard__turn > .ph {
|
|
202
219
|
position: absolute;
|
|
203
220
|
inset: 0;
|
|
204
221
|
width: 100%;
|
|
205
222
|
height: 100%;
|
|
206
223
|
object-fit: cover;
|
|
207
|
-
z-index: -2;
|
|
208
224
|
border: 0;
|
|
209
225
|
border-radius: 0;
|
|
210
226
|
min-height: 0;
|
|
@@ -213,20 +229,40 @@
|
|
|
213
229
|
content: "";
|
|
214
230
|
position: absolute;
|
|
215
231
|
inset: 0;
|
|
216
|
-
z-index:
|
|
232
|
+
z-index: 1;
|
|
233
|
+
pointer-events: none;
|
|
217
234
|
background: linear-gradient(to top, var(--scrim), transparent 45%);
|
|
218
235
|
}
|
|
219
|
-
/* the back: paper — the
|
|
236
|
+
/* the back: paper — the caption and the name packed at the middle, big enough to read at arm's length */
|
|
220
237
|
.postcard__back {
|
|
221
238
|
background: var(--postcard-back-bg, var(--card-2-bg));
|
|
222
239
|
color: var(--postcard-back-ink, var(--card-2-ink));
|
|
223
|
-
align-content: space-between;
|
|
224
240
|
transform: rotateY(180deg);
|
|
225
241
|
}
|
|
242
|
+
.postcard__back .postcard__turn { align-content: start; padding-top: var(--postcard-back-top, 20%); gap: var(--postcard-back-gap, var(--sp-5)); } /* the name a fifth down the paper, the words after it */
|
|
226
243
|
.postcard__caption { margin: 0; font: var(--postcard-caption-font, var(--type-title)); text-wrap: pretty; }
|
|
227
|
-
|
|
244
|
+
/* the band: the face's foot — name and line left, the heart right; in flow at the face's end, over the turn.
|
|
245
|
+
Taps between its words fall through to the turn; its buttons take their own. */
|
|
246
|
+
.postcard__band {
|
|
247
|
+
position: relative;
|
|
248
|
+
z-index: 2;
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: end;
|
|
251
|
+
gap: var(--postcard-band-gap, var(--sp-3));
|
|
252
|
+
padding: var(--postcard-face-pad, var(--card-pad-rest));
|
|
253
|
+
pointer-events: none;
|
|
254
|
+
}
|
|
255
|
+
.postcard__band > * { pointer-events: auto; }
|
|
256
|
+
.postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
|
|
257
|
+
.postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-3-ink)); --on-accent: var(--postcard-front-bg, var(--card-3-bg)); }
|
|
258
|
+
.postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
|
|
228
259
|
.postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
|
|
229
260
|
.postcard.is-flipped > .postcard__back { transform: rotateY(0); }
|
|
261
|
+
/* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
|
|
262
|
+
under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
|
|
263
|
+
.postcard__voice { position: absolute; inset: 0; z-index: 2; display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
|
|
264
|
+
.postcard__voice > * { pointer-events: auto; }
|
|
265
|
+
.postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
|
|
230
266
|
@media (prefers-reduced-motion: reduce) {
|
|
231
267
|
.postcard__face { transition: none; }
|
|
232
268
|
}
|
package/css/numbers.css
CHANGED
|
@@ -256,3 +256,19 @@
|
|
|
256
256
|
}
|
|
257
257
|
.bar--on::after { background: var(--accent); }
|
|
258
258
|
.bar--on > span { background: var(--accent); color: var(--on-accent); padding: var(--sp-0) var(--sp-2); border-radius: var(--r-chip); }
|
|
259
|
+
|
|
260
|
+
/* ---- progress: one straight line, the done share solid, the rest faint (Lh 2026-09-22) — a voice note's
|
|
261
|
+
timeline, a download, a step count as a line. <div class="progress" role="progressbar" style="--progress-at: 0.4"></div>
|
|
262
|
+
--progress-at is DATA the component writes (0–1), not a knob (scripts/knobs.mjs lists it). The done part is a
|
|
263
|
+
gradient of the line's ink sized by the share; the rest is the background colour. */
|
|
264
|
+
.progress {
|
|
265
|
+
display: block;
|
|
266
|
+
width: 100%;
|
|
267
|
+
height: var(--progress-h, var(--sp-1));
|
|
268
|
+
border-radius: var(--progress-radius, var(--r-chip));
|
|
269
|
+
color: var(--progress-done-ink, inherit);
|
|
270
|
+
background-color: var(--progress-rest-bg, color-mix(in srgb, currentColor 25%, transparent));
|
|
271
|
+
background-image: linear-gradient(currentColor, currentColor);
|
|
272
|
+
background-size: calc(var(--progress-at, 0) * 100%) 100%;
|
|
273
|
+
background-repeat: no-repeat;
|
|
274
|
+
}
|
package/css/sheet.css
CHANGED
|
@@ -250,8 +250,13 @@
|
|
|
250
250
|
inset-inline: var(--screen-pad);
|
|
251
251
|
top: var(--_band-centre);
|
|
252
252
|
translate: 0 -50%;
|
|
253
|
-
/*
|
|
253
|
+
/* as tall as the band less the screen's inset at both ends (Lh 2026-09-22: the room under the hero = the room at its sides);
|
|
254
|
+
what it holds fills it (a postcard's min-height gives way) */
|
|
255
|
+
height: calc((100cqh - var(--_top, 0rem)) / 2 - 2 * var(--screen-pad));
|
|
256
|
+
display: grid;
|
|
254
257
|
}
|
|
258
|
+
.sheet-stage > .sheet-stage__hero > * { min-height: 0; } /* outranks a component's own min-height (the postcard's) */
|
|
259
|
+
/* no z-index on the hero: it follows the background in DOM order and the sheet, later, covers it at full */
|
|
255
260
|
|
|
256
261
|
/* ---- map: the box a map engine draws in, behind a stage sheet ----
|
|
257
262
|
<div class="sheet-stage__bg"><div class="map"><div>…the engine's canvas…</div>
|
|
@@ -327,10 +332,9 @@
|
|
|
327
332
|
/* EVERY sheet has one shape (Lh, 2026-09-21): the handle and the head stay at the top and never scroll · the
|
|
328
333
|
foot's button stands at the bottom, and ABOVE the keyboard when there is one · everything between them
|
|
329
334
|
scrolls, as far as it needs to. In a .step the keyboard's room is the step's (--kb, step.css); on a stage
|
|
330
|
-
the sheet gives it up itself, as --_lift
|
|
335
|
+
the sheet gives it up itself, as --_lift. (A sheet ALWAYS stands on the floor — `.sheet--raised`, the one that
|
|
336
|
+
floated above a footnote strip on a dim stage, was retired 2026-09-22: that is a Modal's job.) */
|
|
331
337
|
.sheet-stage > .sheet { --_lift: var(--kb, 0rem); bottom: var(--_lift); }
|
|
332
|
-
.sheet-stage > .sheet--raised { --_lift: calc(var(--tap-lg) + var(--kb, 0rem)); }
|
|
333
|
-
.sheet--raised { --_lift: var(--tap-lg); bottom: var(--_lift); border-radius: var(--sheet-raised-radius, var(--r-card)); border-bottom: var(--border-w) solid var(--card-border); }
|
|
334
338
|
.sheet-stage__foot {
|
|
335
339
|
position: absolute;
|
|
336
340
|
inset-inline: 0;
|
package/css/stack.css
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<div class="sheet-stack sheet-stack--tap"> (drop --tap for a static stack)
|
|
20
20
|
<article class="card card--2">
|
|
21
21
|
<div class="card__head">
|
|
22
|
-
<button class="icon-btn icon-btn--sm
|
|
22
|
+
<button class="icon-btn icon-btn--sm sheet-stack__back" aria-label="Back">…</button>
|
|
23
23
|
<h2 class="t-h2 t-caps">Shopping</h2>
|
|
24
24
|
<span class="icon-row">…</span> (optional trailing icons)
|
|
25
25
|
</div>
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
.sheet-stack--tap > .card.is-open > .card__head {
|
|
244
244
|
padding-left: calc(var(--tap-sm) + var(--sp-3));
|
|
245
245
|
}
|
|
246
|
-
.sheet-stack--tap .
|
|
246
|
+
.sheet-stack--tap .sheet-stack__back {
|
|
247
247
|
position: absolute;
|
|
248
248
|
left: 0;
|
|
249
249
|
top: 50%;
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
visibility: hidden;
|
|
253
253
|
transition: opacity 0.35s ease, visibility 0.35s;
|
|
254
254
|
}
|
|
255
|
-
.sheet-stack--tap > .card.is-open .
|
|
255
|
+
.sheet-stack--tap > .card.is-open .sheet-stack__back {
|
|
256
256
|
opacity: 1;
|
|
257
257
|
visibility: visible;
|
|
258
258
|
}
|