@usableapp/cardds 0.7.103 → 0.7.105
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 +43 -0
- package/css/actions.css +112 -37
- package/css/base.css +139 -15
- package/css/card.css +331 -86
- package/css/choice.css +25 -33
- package/css/finish.css +29 -28
- package/css/forms.css +6 -14
- package/css/journey.css +8 -6
- package/css/layover.css +26 -24
- package/css/lists.css +8 -6
- package/css/media.css +13 -74
- package/css/numbers.css +37 -32
- package/css/palette.css +2 -2
- package/css/people.css +3 -8
- package/css/post.css +23 -19
- package/css/sheet.css +84 -56
- package/css/stack.css +40 -216
- package/css/stamp.css +41 -29
- package/css/step.css +1 -107
- package/css/sticky.css +43 -41
- package/css/theme.template.css +12 -0
- package/css/tokens.css +101 -26
- package/dist/actions/Chip.d.ts +1 -1
- package/dist/actions/Chip.js +3 -2
- package/dist/actions/IconBtn.d.ts +12 -3
- package/dist/actions/IconBtn.js +9 -3
- package/dist/cardds.css +1094 -881
- package/dist/cards/Card.d.ts +8 -6
- package/dist/cards/Card.js +4 -4
- package/dist/cards/CardFeed.js +7 -9
- package/dist/cards/CardFoot.d.ts +7 -2
- package/dist/cards/CardFoot.js +4 -3
- package/dist/catalog.json +1 -1
- package/dist/journey/JourneyBadge.d.ts +1 -1
- package/dist/journey/JourneyBadge.js +1 -1
- package/dist/layover/Snackbar.js +2 -2
- package/dist/lists/Bubble.d.ts +1 -1
- package/dist/lists/Bubble.js +1 -1
- package/dist/lists/Row.js +5 -7
- package/dist/numbers/Picker.js +11 -18
- package/dist/people/Avatar.d.ts +1 -1
- package/dist/people/Avatar.js +3 -2
- package/dist/post/PostDesk.js +6 -9
- package/dist/post/PostLetter.js +4 -4
- package/dist/pressable.d.ts +9 -0
- package/dist/pressable.js +6 -0
- package/dist/room.d.ts +5 -0
- package/dist/room.js +9 -0
- package/dist/scaffold/Panes.js +8 -14
- package/dist/sheets/Sheet.d.ts +1 -1
- package/dist/sheets/Sheet.js +2 -3
- package/dist/sheets/SheetSteps.js +3 -3
- package/dist/sheets/StackSheet.d.ts +1 -1
- package/dist/sheets/sheetStepsPile.js +2 -3
- package/dist/sheets/stackCard.js +67 -115
- package/dist/state.d.ts +21 -0
- package/dist/state.js +28 -1
- package/dist/sticky/Sticky.js +3 -7
- package/dist/templates/Home.d.ts +1 -1
- package/dist/templates/Splash.js +2 -2
- package/dist/templates/Walkthrough.js +4 -5
- package/docs/reference.md +76 -12
- package/lib/catalog.mjs +23 -7
- package/lint/rules.mjs +7 -5
- package/lint/values.mjs +144 -0
- package/package.json +6 -4
package/dist/cardds.css
CHANGED
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
|
|
47
47
|
Every length is rem so the whole system scales with the root
|
|
48
48
|
font size (base.css steps it down on narrow phones and it follows
|
|
49
|
-
the user's text-size setting).
|
|
50
|
-
|
|
49
|
+
the user's text-size setting). Borders, rules and focus rings
|
|
50
|
+
are rem too (Lh 2026-10-20: "units are always rem").
|
|
51
51
|
4pt grid: 1rem = 16px → --sp-1 = 4px.
|
|
52
52
|
============================================================ */
|
|
53
53
|
|
|
@@ -69,7 +69,7 @@
|
|
|
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.
|
|
72
|
-
Not on a base: --screen-w (the column), borders / rules / focus (
|
|
72
|
+
Not on a base: --screen-w (the column), borders / rules / focus (fixed rem), shadows, motion. */
|
|
73
73
|
|
|
74
74
|
/* ---- spacing (4pt grid): the number in the name is the multiplier ---- */
|
|
75
75
|
--sp-0: calc(var(--sp-base) * 0.5); /* 2 hairline gaps (title→meta) */
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
--tap: max(var(--tap-floor), var(--tap-base)); /* 48 primary touch target */
|
|
89
89
|
--tap-sm: max(var(--tap-floor), calc(var(--tap-base) * 11 / 12)); /* 44 minimum touch target */
|
|
90
90
|
--tap-lg: max(var(--tap-floor), calc(var(--tap-base) * 7 / 6)); /* 56 hero controls: send, field with action */
|
|
91
|
+
--tap-xl: max(var(--tap-floor), calc(var(--tap-base) * 17 / 12)); /* 68 the biggest round control: a foot of a few big round buttons (IconBtn xl) */
|
|
91
92
|
--btn-h: calc(var(--tap-base) * 13 / 12); /* 52 button height */
|
|
92
93
|
--btn-xl-h: calc(var(--tap-base) * 11 / 6); /* 88 the one giant CTA */
|
|
93
94
|
--chip-h: calc(var(--tap-base) * 19 / 24); /* 38 chip height */
|
|
@@ -119,7 +120,10 @@
|
|
|
119
120
|
--tile-h: calc(var(--tap-base) * 11 / 4); /* 132 tile / band height */
|
|
120
121
|
|
|
121
122
|
/* ---- radius ---- */
|
|
122
|
-
|
|
123
|
+
/* TILT (Lh 2026-10-20: no unit a component writes for itself): every paper laid at an angle (a note, a stamp, a letter, a card
|
|
124
|
+
sent away) turns by a multiple of this. A theme that wants everything straight sets it to 0deg */
|
|
125
|
+
--tilt-base: 1deg;
|
|
126
|
+
--r-chip: 62.4375rem; /* pills, chips, buttons — always fully round */
|
|
123
127
|
--r-card: calc(var(--r-base) * 2); /* 41.6 the one card radius: every card, tile, menu, sheet, fab — but a row card (--r-card-row) */
|
|
124
128
|
--r-card-row: calc(var(--r-base) * 0.625); /* 13 a row card's corner (Lh 2026-10-17: a small card's corner shrinks to keep the big card's PROPORTION). Measured at 375:
|
|
125
129
|
whole cards stand 186–437 tall, corner/height 0.095–0.224 (Card · Skeleton 0.122); a two-line row stands 88, so its
|
|
@@ -128,13 +132,19 @@
|
|
|
128
132
|
--r-control: calc(var(--r-base) * 0.875); /* 18.2 fields, thumbs, menu rows */
|
|
129
133
|
--r-badge: calc(var(--r-base) * 0.625); /* 13 tile badge, bars */
|
|
130
134
|
|
|
131
|
-
/* ---- borders / rules / elevation (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
--
|
|
135
|
-
--
|
|
136
|
-
|
|
137
|
-
--
|
|
135
|
+
/* ---- borders / rules / elevation (rem, like every length) ---- */
|
|
136
|
+
/* one device-independent pixel in rem: what the platform needs to keep a hidden word in the accessibility tree, and the half-width
|
|
137
|
+
of a drawn line. The browser's own text size: a field under it makes iOS zoom in on focus */
|
|
138
|
+
--px: 0.0625rem;
|
|
139
|
+
--browser-text: 1rem;
|
|
140
|
+
/* the wireframe map's two street directions (.map) */
|
|
141
|
+
--street-1: 75deg; --street-2: -18deg;
|
|
142
|
+
--border-w: 0.09375rem;
|
|
143
|
+
--hairline-w: 0.125rem; /* the thinnest mark: a sheet's grab line (Lh 2026-10-06: 1px, then 2px the same day) */
|
|
144
|
+
--rule-w: 0.125rem; /* dotted connectors, picker rules */
|
|
145
|
+
--scroll-w: 0.0625rem; /* the scrollbar of a .bleed track: a hairline of ink */
|
|
146
|
+
--focus: 0.125rem solid currentColor; /* :focus-visible ring */
|
|
147
|
+
--shadow-card: 0 0.0625rem 0.125rem rgb(0 0 0 / 0.06), 0 0.5rem 1.5rem rgb(0 0 0 / 0.08);
|
|
138
148
|
--shadow-float: 0 0.25rem 0.75rem rgb(0 0 0 / 0.14);
|
|
139
149
|
--shadow-sheet: 0 -0.5rem 2rem rgb(0 0 0 / 0.16); /* sheets, lifted cards */
|
|
140
150
|
|
|
@@ -144,11 +154,44 @@
|
|
|
144
154
|
--motion-quick: 0.2s; /* a small state flip that isn't a press: a chevron turning, a popover/banner opening */
|
|
145
155
|
--motion-slide: 0.22s; /* a centred card sliding out and the next in (card.css) — quick, a page turn (Lh, 2026-09-12) */
|
|
146
156
|
--motion-flip: 0.45s; /* a postcard turning over (media.css) — slow enough to read as a card in the hand */
|
|
157
|
+
--motion-spin: 0.9s; /* a busy ring's one turn (Spinner, a busy FormNote) */
|
|
158
|
+
--motion-blink: 1.1s; /* a code cell's caret, on and off once */
|
|
159
|
+
--motion-flash: 0.5s; /* a secret code's digit seen before its dot takes it */
|
|
160
|
+
--motion-shake: 0.4s; /* a wrong code shaking once */
|
|
161
|
+
--motion-pulse: 0.4s; /* a sheet's grip, ink to ink and back, while the app reloads */
|
|
162
|
+
--motion-run: 6s; /* a long title running sideways, until its own length is measured (--_run) */
|
|
163
|
+
--motion-stagger: 70ms; /* one stacked sheet after another as they tuck or untuck */
|
|
164
|
+
/* LAYERS (Lh 2026-10-20: no number a component writes for itself): the order of things INSIDE one piece, which keeps its own
|
|
165
|
+
stacking context. Under its ground, flat, over it, over that, the picked one on top */
|
|
166
|
+
--z-under-2: -2; --z-under: -1; --z-flat: 0; --z-over: 1; --z-over-2: 2; --z-over-3: 3;
|
|
167
|
+
--z-pile: 7; /* a sticky pile's top note: one over the six it can show, each note under it one lower (StickyPile) */
|
|
168
|
+
/* EASING (Lh 2026-10-20: a component writes no curve of its own): the six ways a thing moves, read by every transition and animation.
|
|
169
|
+
--ease-settle is the one a moved row or card lands with (Row, CardFeed: Web Animations read it) */
|
|
170
|
+
--ease: ease;
|
|
171
|
+
--ease-in: ease-in;
|
|
172
|
+
--ease-out: ease-out;
|
|
173
|
+
--ease-in-out: ease-in-out;
|
|
174
|
+
--ease-linear: linear;
|
|
175
|
+
--ease-settle: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
176
|
+
--ease-step: steps(1); /* on or off with nothing between: a code cell's caret blinking, a secret digit before its dot */
|
|
147
177
|
--motion-press: 0.12s; /* a control giving under the finger and springing back (actions.css PRESS) */
|
|
148
178
|
--press-scale: 0.96; /* how far a finger-sized control gives — a share of its size */
|
|
179
|
+
--scale-lift: 1.06; /* a stamp lifted off the paper while the finger holds it */
|
|
180
|
+
--scale-pick: 1.7; /* a mood's dot, the one picked */
|
|
181
|
+
--scale-arrive: 0.6; /* a stamp's remove button, the size it grows from */
|
|
149
182
|
--press-dim: 0.6; /* how far a card-wide one (a row, a summary line, a card) fades under the finger */
|
|
150
183
|
--press-shade: 0.93; /* how far something with a thing behind it (a peeked sheet, a postcard) darkens under the finger */
|
|
151
184
|
--off: 0.4; /* a disabled control's fade */
|
|
185
|
+
/* FADES (Lh 2026-10-20: no opacity a component writes for itself): what each lets through. Nine uses had eight values; one name each
|
|
186
|
+
until the system settles on fewer */
|
|
187
|
+
--fade-faint: 0.25; /* a halo behind an avatar */
|
|
188
|
+
--fade-dim: 0.3; /* a sheet's grip at rest, a day that cannot be picked */
|
|
189
|
+
--fade-soft: 0.4; /* the dots between a trio's answers */
|
|
190
|
+
--fade-half: 0.5; /* a step's button while the step is busy */
|
|
191
|
+
--fade-hint: 0.55; /* a field's placeholder */
|
|
192
|
+
--fade-mild: 0.65; /* a route's line after its code */
|
|
193
|
+
--fade-near: 0.85; /* an icon row */
|
|
194
|
+
--fade-most: 0.9; /* a quote's mark */
|
|
152
195
|
|
|
153
196
|
/* ---- z-index: the few layers that stack ABOVE ordinary content, low to high ---- */
|
|
154
197
|
--z-bar: 30; /* .action-bar, .float-bar — above .sheet-stack sheets (--level 1-5) */
|
|
@@ -184,18 +227,31 @@
|
|
|
184
227
|
--fs-hero: calc(var(--fs-base) * 2.5); /* 55 the one number on a sheet */
|
|
185
228
|
--fs-giant: calc(var(--fs-base) * 2.9); /* 64 data bands */
|
|
186
229
|
|
|
187
|
-
/*
|
|
188
|
-
|
|
189
|
-
--
|
|
190
|
-
--
|
|
191
|
-
--
|
|
192
|
-
--
|
|
193
|
-
--
|
|
194
|
-
--
|
|
195
|
-
--
|
|
196
|
-
--
|
|
197
|
-
--
|
|
198
|
-
--
|
|
230
|
+
/* TYPE (Lh 2026-10-20: every kind and every size has its tokens; a component writes no type value of its own).
|
|
231
|
+
THE SCALES: weight and leading, each step named once. A kind below picks a step of each; a piece that needs a step alone reads it. */
|
|
232
|
+
--fw-regular: 400; --fw-medium: 500; --fw-semibold: 600; --fw-strong: 650; --fw-bold: 700; --fw-heavy: 800; --fw-black: 900;
|
|
233
|
+
--lh-mark: 0.55; /* a quotation mark three lines tall, set into the first line */
|
|
234
|
+
--lh-band: 0.9; /* a data band's giant number */
|
|
235
|
+
--lh-badge: 0.95; /* a date box's two lines (JourneyBadge) */
|
|
236
|
+
--lh-solid: 1; /* a line as tall as its letters: a number in a disc, an icon's glyph, one big number */
|
|
237
|
+
--lh-tight: 1.1; /* a day's number, a tile's value */
|
|
238
|
+
--lh-field: 1.4; /* the words in a field */
|
|
239
|
+
--lh-hand: 1.6; /* a letter written by hand */
|
|
240
|
+
--lh-ruled: 1.7; /* a ruled sheet: the line under every line of writing */
|
|
241
|
+
--track-loose: 0.02em; /* initials in an avatar */
|
|
242
|
+
--track-caps: 0.08em; /* a kicker in capitals */
|
|
243
|
+
/* THE EIGHT KINDS: each has its face, weight, size (--fs-* above), leading and tracking, and its style --type-<kind> is made of them */
|
|
244
|
+
--fw-display: var(--fw-heavy); --lh-display: 1.15; --track-display: -0.02em; /* leading: Thai marks stack to 1.1em up and 0.3em down in FC Pride — 1.02 clipped them (2026-09-10) */
|
|
245
|
+
--fw-h1: var(--fw-black); --lh-h1: 1.2; --track-h1: -0.015em;
|
|
246
|
+
--fw-h2: var(--fw-bold); --lh-h2: 1.25; --track-h2: -0.01em;
|
|
247
|
+
--fw-title: var(--fw-strong); --lh-title: 1.3; --track-title: normal;
|
|
248
|
+
--fw-body: var(--fw-regular); --lh-body: 1.5; --track-body: normal;
|
|
249
|
+
--fw-label: var(--fw-semibold); --lh-label: 1.2; --track-label: normal; /* chips, buttons */
|
|
250
|
+
--fw-caption: var(--fw-medium); --lh-caption: 1.35; --track-caption: normal;
|
|
251
|
+
--fw-stat: var(--fw-heavy); --lh-stat: 1.05; --track-stat: normal; /* big numbers */
|
|
252
|
+
--face-display: var(--font-display); --face-h1: var(--font-display); --face-h2: var(--font-display); --face-stat: var(--font-display);
|
|
253
|
+
--face-title: var(--font-ui); --face-body: var(--font-ui); --face-label: var(--font-ui); --face-caption: var(--font-ui);
|
|
254
|
+
/* --type-<kind> is set where a palette or a mode is (below), so a weight or a face tuned there reaches the words inside */
|
|
199
255
|
|
|
200
256
|
/* ============================================================
|
|
201
257
|
COLOR ROLES — wireframe defaults (grayscale).
|
|
@@ -278,6 +334,12 @@
|
|
|
278
334
|
--ink-rail: color-mix(in srgb, currentColor 30%, transparent); /* todo steps, ghost edges */
|
|
279
335
|
--accent-soft: color-mix(in srgb, var(--accent) 22%, transparent); /* calendar marks */
|
|
280
336
|
--accent-mid: color-mix(in srgb, var(--accent) 50%, transparent);
|
|
337
|
+
/* PASTELS (Lh 2026-10-20: a component never brings a colour of its own; every colour is the theme's): the nine pastel papers of a
|
|
338
|
+
sticky note, a note chooser's swatch and a stamp's picture ground (src/sticky/tones.ts names them), and a stamp's paper. Light
|
|
339
|
+
grounds in every palette: their words are the onlight set. A theme retunes them here; a knob (--sticky-<tone>-bg) retunes one place */
|
|
340
|
+
--pastel-cotton: #f3efe2; --pastel-blush: #f6d5da; --pastel-tea-leaf: #d5e8c5; --pastel-custard: #f6e9a0; --pastel-salmon: #f3c7b3;
|
|
341
|
+
--pastel-iceberg: #cfe0ee; --pastel-cocoa: #dccbbd; --pastel-apricot: #f6d49a; --pastel-water: #bcd6f3;
|
|
342
|
+
--stamp-paper: #fffdf8;
|
|
281
343
|
--scrim: color-mix(in srgb, #000 50%, transparent); /* dim stage, cover gradient — ALWAYS black, never the brand (Lh 2026-09-24) */
|
|
282
344
|
}
|
|
283
345
|
|
|
@@ -302,6 +364,19 @@
|
|
|
302
364
|
sit on <body>: a custom property is resolved where it is declared. A signal's SOFT ground (what a Snackbar stands on) is its
|
|
303
365
|
onlight shade washed 14% into the surface (tone 2's formula) */
|
|
304
366
|
:root, [data-palette], [data-mode] {
|
|
367
|
+
/* each kind's style, from its tokens: set here so a palette or a mode that tunes a kind's weight, size, leading or face reaches .t-<kind> */
|
|
368
|
+
--type-display: var(--fw-display) var(--fs-display) / var(--lh-display) var(--face-display);
|
|
369
|
+
--type-h1: var(--fw-h1) var(--fs-h1) / var(--lh-h1) var(--face-h1);
|
|
370
|
+
--type-h2: var(--fw-h2) var(--fs-h2) / var(--lh-h2) var(--face-h2);
|
|
371
|
+
--type-title: var(--fw-title) var(--fs-title) / var(--lh-title) var(--face-title);
|
|
372
|
+
--type-body: var(--fw-body) var(--fs-body) / var(--lh-body) var(--face-body);
|
|
373
|
+
--type-label: var(--fw-label) var(--fs-label) / var(--lh-label) var(--face-label);
|
|
374
|
+
--type-caption: var(--fw-caption) var(--fs-caption) / var(--lh-caption) var(--face-caption);
|
|
375
|
+
--type-stat: var(--fw-stat) var(--fs-stat) / var(--lh-stat) var(--face-stat);
|
|
376
|
+
/* shadows of paper laid on paper: mixed from the onlight ink, so re-derived where a palette is set */
|
|
377
|
+
--shadow-paper: 0 var(--sp-1) var(--sp-2) color-mix(in srgb, var(--ink-onlight) 16%, transparent); /* a stamp lying on paper: follows its teeth (drop-shadow) */
|
|
378
|
+
--shadow-lift: 0 var(--sp-0) var(--sp-1) color-mix(in srgb, var(--ink-onlight) 35%, transparent); /* a small disc lifted over a picture */
|
|
379
|
+
--shadow-fold: calc(-1 * var(--sp-0)) calc(-1 * var(--sp-0)) var(--sp-1) color-mix(in srgb, var(--ink-onlight) 8%, transparent); /* a note's folded corner */
|
|
305
380
|
--on-surface: color-mix(in srgb, var(--ink-ondark) calc(var(--surface-dark) * 100%), var(--ink-onlight));
|
|
306
381
|
--on-surface-muted: color-mix(in srgb, var(--ink-ondark-muted) calc(var(--surface-dark) * 100%), var(--ink-onlight-muted));
|
|
307
382
|
--action-ink: color-mix(in srgb, var(--ink-ondark) calc(var(--action-dark) * 100%), var(--ink-onlight));
|
|
@@ -322,7 +397,7 @@
|
|
|
322
397
|
surface turns everything on it to ondark with no rule per piece. A piece with no fill that stands in for a surface (a stat
|
|
323
398
|
tile) takes its ground's side: `--dark: inherit`. inkGrounds() measures it; docs/reference.md "Ink follows its ground" */
|
|
324
399
|
:where(body, [data-palette], [data-mode], .card, .tile, .modal__drawer, .postcard__front, .postcard__back, .sticky, .post-paper,
|
|
325
|
-
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .map__pin, .sheet--paper > .sheet__body,
|
|
400
|
+
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .map__pin, .sheet--paper > :is(.sheet__body, .card__foot),
|
|
326
401
|
.postcard__band, .track__step, .toggle, .chip, .card__face, .card--bands > :is(.card__head, .card__foot), .card--bands > .card__face > :is(.card__head, .card__foot)) {
|
|
327
402
|
--card-ink: color-mix(in srgb, var(--ink-ondark) calc(var(--dark) * 100%), var(--ink-onlight));
|
|
328
403
|
--card-muted: color-mix(in srgb, var(--ink-ondark-muted) calc(var(--dark) * 100%), var(--ink-onlight-muted));
|
|
@@ -335,8 +410,8 @@
|
|
|
335
410
|
}
|
|
336
411
|
:where(body, [data-palette], [data-mode]) { --dark: var(--surface-dark); }
|
|
337
412
|
|
|
338
|
-
:root { --safe-top: calc(env(safe-area-inset-top,
|
|
339
|
-
* { --_safe-b-open: max(
|
|
413
|
+
:root { --safe-top: calc(env(safe-area-inset-top, 0rem) + var(--vv-top, 0rem)); /* + the phone's pan while a keyboard is up (cardds.js) */ --safe-bottom: env(safe-area-inset-bottom, 0rem); }
|
|
414
|
+
* { --_safe-b-open: max(0rem, var(--safe-bottom) - var(--kb, 0rem)); }
|
|
340
415
|
|
|
341
416
|
@property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
342
417
|
@property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; } /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
|
|
@@ -352,7 +427,7 @@
|
|
|
352
427
|
|
|
353
428
|
/* ---- css/palette.css — color settings — the file you tune ---- */
|
|
354
429
|
/* ============================================================
|
|
355
|
-
cardds/palette.css —
|
|
430
|
+
cardds/palette.css — the palettes cardds ships. A project tunes its own theme.css (theme.template.css), never this file.
|
|
356
431
|
Apply with <html data-palette="clay"> (or <body>).
|
|
357
432
|
No attribute = wireframe grayscale from tokens.css.
|
|
358
433
|
Each palette overrides role tokens only — components never change.
|
|
@@ -451,7 +526,7 @@
|
|
|
451
526
|
[data-palette="orchard"] .field { background: var(--field-bg, #ffffff); color: var(--ink-onlight); }
|
|
452
527
|
[data-palette="orchard"] .pin__cell { background: var(--pin-cell-bg, #ffffff); color: var(--ink-onlight); }
|
|
453
528
|
/* the vivid ones, tried on the live parts (Lh 2026-09-25): primary = yellow · a user avatar = fuchsia · ring = sky · a switch on = sky · maple only on small dots (the count, dot a); the chosen stack tab stays moss */
|
|
454
|
-
[data-palette="orchard"] .btn--primary { background: var(--btn-primary-bg, var(--highlight-2)); color: var(--btn-primary-ink, var(--ink-onlight)); }
|
|
529
|
+
[data-palette="orchard"] :is(.btn--primary, .icon-btn--primary) { background: var(--btn-primary-bg, var(--highlight-2)); color: var(--btn-primary-ink, var(--ink-onlight)); }
|
|
455
530
|
[data-palette="orchard"] .avatar:not(.avatar--outline, .avatar--add, .avatar--on) { background: var(--avatar-bg, var(--highlight-4)); --dark: 1; }
|
|
456
531
|
[data-palette="orchard"] .ring::before { background: conic-gradient(var(--highlight-3) calc(var(--value) * 1%), var(--ink-tint) 0); }
|
|
457
532
|
[data-palette="orchard"] .toggle:checked { background: var(--highlight-3); --accent-dark: 0; } /* sky is a light ground: its knob is onlight */
|
|
@@ -568,6 +643,13 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
568
643
|
|
|
569
644
|
/* icon from icons.svg sprite: <svg class="icon"><use href="icons.svg#bell"/></svg> */
|
|
570
645
|
.icon { width: var(--icon-size, var(--icon)); height: var(--icon-size, var(--icon)); flex: none; }
|
|
646
|
+
/* what a component that holds an icon may set on it (#62: a holder adjusts its child only through the child's knobs): unset, each
|
|
647
|
+
is the icon as it is. No fill: a drawing's own fill="none" is an attribute any declared fill would beat */
|
|
648
|
+
.icon {
|
|
649
|
+
display: var(--icon-display, block); vertical-align: var(--icon-align, baseline); margin: var(--icon-margin, 0);
|
|
650
|
+
padding: var(--icon-pad, 0); border-radius: var(--icon-radius, 0); background: var(--icon-bg, none); color: var(--icon-ink, inherit);
|
|
651
|
+
rotate: var(--icon-turn, none); translate: var(--icon-shift, none); transition: var(--icon-motion, all 0s); animation: var(--icon-spin, none);
|
|
652
|
+
}
|
|
571
653
|
.icon--sm { width: var(--icon-sm-size, var(--icon-sm)); height: var(--icon-sm-size, var(--icon-sm)); }
|
|
572
654
|
.icon--xs { width: var(--icon-xs-size, var(--icon-xs)); height: var(--icon-xs-size, var(--icon-xs)); } /* inside captions, chips, links */
|
|
573
655
|
.icon--lg { width: var(--icon-lg-size, var(--icon-lg)); height: var(--icon-lg-size, var(--icon-lg)); } /* empty states */
|
|
@@ -616,12 +698,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
616
698
|
position: relative; /* the box a .card--centre or a .screen__centre floats in (card.css) */
|
|
617
699
|
container-type: size; /* a share of the SCREEN's height (a no-bar step's lead, step.css) reads it as cqh — a real phone's height, or the gallery's frame */
|
|
618
700
|
}
|
|
619
|
-
/* min-height: 0 — a flex item's floor is its content, and the stack's content
|
|
620
|
-
is its own full-height sheets, so without it the stack grew to the whole
|
|
621
|
-
screen and its open sheet ran past the bottom edge by the bars' height,
|
|
622
|
-
unreachable by scrolling (timebank home, 2026-09-13). The stack IS the room
|
|
623
|
-
left under the bars; every sheet fills exactly that. */
|
|
624
|
-
.screen--fill > .sheet-stack { flex: 1; min-height: 0; }
|
|
625
701
|
|
|
626
702
|
/* ---- typography levels ---- */
|
|
627
703
|
/* --fs-self: each level publishes its own size, for a consumer that scales a level without knowing which */
|
|
@@ -631,7 +707,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
631
707
|
.t-title { font: var(--type-title); margin: 0; --fs-self: var(--fs-title); }
|
|
632
708
|
.t-body { font: var(--type-body); margin: 0; }
|
|
633
709
|
.t-label { font: var(--type-label); }
|
|
634
|
-
.t-caption { font: var(--type-caption); }
|
|
710
|
+
.t-caption { font: var(--type-caption); color: var(--t-caption-ink, inherit); }
|
|
635
711
|
.t-stat { font: var(--type-stat); letter-spacing: var(--track-display); margin: 0; font-variant-numeric: tabular-nums; }
|
|
636
712
|
.t-stat--mono { font-family: var(--font-mono); letter-spacing: 0; } /* clocks, ETAs, codes */
|
|
637
713
|
.t-stat--xl { font-size: var(--fs-hero); } /* the one number on a sheet */
|
|
@@ -648,9 +724,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
648
724
|
--_shape: 0.75em 0.3em 0.9em 0.35em / 0.4em 0.95em 0.35em 0.8em; /* four unlike corners: a stroke drawn by hand has no two ends alike */
|
|
649
725
|
background-color: var(--mark-bg, var(--_bg));
|
|
650
726
|
background-image:
|
|
651
|
-
linear-gradient(
|
|
652
|
-
linear-gradient(
|
|
653
|
-
linear-gradient(
|
|
727
|
+
linear-gradient(calc(90deg + var(--tilt-base) * 10), var(--_pool), transparent 0.55em), /* ink pools where the pen lands */
|
|
728
|
+
linear-gradient(calc(270deg + var(--tilt-base) * 10), var(--_pool), transparent 0.4em), /* and a little where it lifts */
|
|
729
|
+
linear-gradient(calc(180deg + var(--tilt-base)), transparent 18%, var(--_pool) 52%, transparent 88%); /* the nib presses harder along its middle */
|
|
654
730
|
color: var(--mark-ink, inherit);
|
|
655
731
|
padding-inline: var(--mark-pad-x, 0.3em);
|
|
656
732
|
border-radius: var(--mark-radius, var(--_shape));
|
|
@@ -664,7 +740,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
664
740
|
/* read, not shown (Lh 2026-09-23): words a screen reader needs — a page's h1 the design leaves out — kept in the page, taking no room.
|
|
665
741
|
The 1px is the one the platform needs to keep it in the accessibility tree, not a length of the design. */
|
|
666
742
|
.screen :is(input, textarea, [contenteditable="true"]), .screen .t-select { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
|
|
667
|
-
.t-sr { position: absolute; width:
|
|
743
|
+
.t-sr { position: absolute; width: var(--px); height: var(--px); margin: calc(-1 * var(--px)); padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
668
744
|
|
|
669
745
|
/* ---- page: words straight on the base, no card around them — a
|
|
670
746
|
heading, a paragraph, maybe a .cta-pack. Was .section until 2026-09-16
|
|
@@ -683,8 +759,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
683
759
|
/* the page's ONE heading on the base (a screen with no TopBar: CardHead level="h1") reads at the card heading's size —
|
|
684
760
|
h1 is the semantic level, not a bigger face; --fs-h1 is the bar title's size (Lh 2026-09-22) */
|
|
685
761
|
.page > .card__head > .t-h1 { font-size: var(--fs-h2); }
|
|
686
|
-
.page > :is(img, .ph) { max-height: var(--page-media-h, 14rem); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; align-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
|
|
687
|
-
.page > .ph { width: 100%; height: var(--page-media-h, 14rem); }
|
|
688
762
|
.page .cta-pack {
|
|
689
763
|
display: flex;
|
|
690
764
|
align-items: start; /* buttons keep their own height, never the row's */
|
|
@@ -709,12 +783,12 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
709
783
|
/* ---- the turn: one flip mechanic for everything with two faces (a Postcard, a Card's `back`).
|
|
710
784
|
The faces share one grid cell and turn on Y at --motion-flip; where motion is off they swap at once.
|
|
711
785
|
The owner sets the box's size and the faces' look; .is-flipped on .flip shows the back. */
|
|
712
|
-
.flip { perspective:
|
|
786
|
+
.flip { perspective: calc(var(--tap-base) * 20); display: grid; }
|
|
713
787
|
.flip__face {
|
|
714
788
|
grid-area: 1 / 1;
|
|
715
789
|
backface-visibility: hidden;
|
|
716
790
|
-webkit-backface-visibility: hidden;
|
|
717
|
-
transition: transform var(--motion-flip) ease-in-out;
|
|
791
|
+
transition: transform var(--motion-flip) var(--ease-in-out);
|
|
718
792
|
}
|
|
719
793
|
.flip__face--back { transform: rotateY(180deg); }
|
|
720
794
|
.flip.is-flipped > .flip__face--front { transform: rotateY(-180deg); }
|
|
@@ -723,6 +797,131 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
723
797
|
.flip__face { transition: none; }
|
|
724
798
|
}
|
|
725
799
|
|
|
800
|
+
/* ==== the screen's parts and the room it keeps, once written in the files of what they hold (#62: a block is written in its own file) ==== */
|
|
801
|
+
/* the appbar is fixed over the top of the screen: the screen reserves its height, from the same header-h token. The breathing under it is published:
|
|
802
|
+
a .step cancels it to touch the bar (actions.css) */
|
|
803
|
+
.screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
|
|
804
|
+
.screen.screen--fill:has(> .appbar) { padding-top: var(--safe-top); --header-gap: 0rem; }
|
|
805
|
+
/* a plain screen is a column at least one phone tall when it ends on an .action-bar or holds a .screen__centre (step.css, card.css) */
|
|
806
|
+
.screen:not(.screen--fill):has(> .action-bar:last-child, > .screen__centre) { display: flex; flex-direction: column; min-height: 100dvh; }
|
|
807
|
+
/* the centred group: the place of a .card--centre (card.css), for a small group of things */
|
|
808
|
+
.screen--fill > .screen__centre {
|
|
809
|
+
position: absolute;
|
|
810
|
+
top: var(--screen-centre);
|
|
811
|
+
translate: 0 -50%;
|
|
812
|
+
/* the cap: as a .card--centre's (card.css, which says why) */
|
|
813
|
+
--_lead: 0rem;
|
|
814
|
+
max-height: calc(2 * min(var(--screen-centre) - var(--_lead) - var(--screen-gap), 100% - var(--screen-centre) - var(--bar-reserve)));
|
|
815
|
+
overflow-y: auto;
|
|
816
|
+
overscroll-behavior: contain;
|
|
817
|
+
}
|
|
818
|
+
/* ---- centred block: the same place, for one thing to read that is not a
|
|
819
|
+
lone card — an empty state, a card with its quiet second button and the
|
|
820
|
+
pager under it. The block runs edge to
|
|
821
|
+
edge and keeps the screen's inset as padding, and
|
|
822
|
+
its children stack at the screen's own rhythm (Lh, 2026-09-11):
|
|
823
|
+
<div class="screen screen--fill"><header class="topbar">…</header>
|
|
824
|
+
<div class="screen__centre"><article class="card">…</article><button class="btn btn--quiet btn--block">…</button></div>
|
|
825
|
+
</div> */
|
|
826
|
+
.screen__centre {
|
|
827
|
+
inset-inline: 0;
|
|
828
|
+
padding-inline: var(--screen-pad);
|
|
829
|
+
display: grid;
|
|
830
|
+
gap: var(--screen-gap);
|
|
831
|
+
align-content: start;
|
|
832
|
+
}
|
|
833
|
+
.screen__centre > * { min-width: 0; }
|
|
834
|
+
|
|
835
|
+
/* ---- the same group on a plain .screen: centred when short, as long as it
|
|
836
|
+
likes when not (Lh, 2026-09-12) ----
|
|
837
|
+
The member sees one content area — whatever is between the top bar and the
|
|
838
|
+
bar — and reads it as a whole page: a short card sits at its middle, a long
|
|
839
|
+
one simply makes the page longer, runs under the bar (a sticky last row,
|
|
840
|
+
actions.css) and scrolls up like any page — nothing is capped, nothing
|
|
841
|
+
shrinks, no scroll box inside a box.
|
|
842
|
+
So on a .screen that is NOT --fill the group is in flow: the column is at
|
|
843
|
+
least one phone tall (step.css), the group takes the room after the top bar and
|
|
844
|
+
centres its children in it. The screen's own inset stays the inset.
|
|
845
|
+
<div class="screen"><header class="topbar">…</header>
|
|
846
|
+
<div class="screen__centre"><article class="card">…</article><div class="pager__at">…</div></div>
|
|
847
|
+
</div><div class="action-bar">…</div> */
|
|
848
|
+
.screen:not(.screen--fill) > .screen__centre { flex: 1; padding-inline: 0; align-content: center; }
|
|
849
|
+
|
|
850
|
+
.screen__centre:has(> :is(.stamp, .stamp-place)) { justify-items: center; text-align: center; }
|
|
851
|
+
|
|
852
|
+
/* ---- where it sits in what card.css draws (docs/reference.md rule 15) ---- */
|
|
853
|
+
/* flat head markup (Lh 2026-09-16 — no <p>, no wrapper, only padding and gap tokens):
|
|
854
|
+
<div class="card__head"><h2 class="t-h2">…</h2><span class="t-caption t-muted">…</span><button class="icon-btn icon-btn--sm">…</button></div>
|
|
855
|
+
The heading and the far-right thing share the first row; a caption sits on
|
|
856
|
+
a row below (order 1), whatever the DOM order — so an author writes the
|
|
857
|
+
words in reading order. */
|
|
858
|
+
.card__head > :is(.t-caption, .t-body) { flex: 1 1 100%; order: 1; margin: 0; }
|
|
859
|
+
.card__head:has(> .stamp) > :is(.t-caption, .t-body) { grid-column: 1; grid-row: 2; align-self: start; }
|
|
860
|
+
/* ---- where it sits in what stack.css draws (docs/reference.md rule 15) ---- */
|
|
861
|
+
.sheet-stack__base > .screen__centre { padding-inline: 0; }
|
|
862
|
+
/* ---- where it sits in what sheet.css draws (docs/reference.md rule 15) ---- */
|
|
863
|
+
/* the lucide tip stops 2.2 of 24 short of the box's bottom: lowered so the tip IS the bottom-centre; filled with the surface so the
|
|
864
|
+
street pattern never shows through the pin's body */
|
|
865
|
+
.map__pin > .icon { fill: var(--surface); }
|
|
866
|
+
/* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
|
|
867
|
+
/* the reading block: a .page (base.css) — the same words-on-the-base
|
|
868
|
+
block as anywhere else (it was .step__card, and a .card before 2026-09-16;
|
|
869
|
+
Lh: a step reads like a page, not a card in a page — so it IS the page's
|
|
870
|
+
block, one name). Here it keeps a card's inner rhythm (a head, --card-gap
|
|
871
|
+
between things, the tokens .card publishes for the head and .bleed) and
|
|
872
|
+
gets the flex rule: it hugs its content, and when the column runs out (a
|
|
873
|
+
short phone, large text) it shrinks and scrolls inside itself instead of
|
|
874
|
+
pushing the bar or the sheet */
|
|
875
|
+
.step > .page, .step > .pages > .page {
|
|
876
|
+
gap: var(--card-gap);
|
|
877
|
+
padding-block: 0; /* --_lead carries the top; the bar's room is the step's */
|
|
878
|
+
color: var(--on-surface); /* align-content: start is .page's own (base.css) — not restated here */
|
|
879
|
+
}
|
|
880
|
+
/* a lone .page carries the margin a walk's .pages does not (its own .page children do, below) —
|
|
881
|
+
the screen's gap, moved in here from above the step; the inset, taken back from the bleed.
|
|
882
|
+
It ends with the bottom layer's room, as a walk's page does: it scrolls only when the layer would cover its words
|
|
883
|
+
(timebank onboard-04, 2026-09-27 — its last Link lay under the bar; Lh: same rule as the walk) */
|
|
884
|
+
.step > .page { margin: var(--_lead, 0) var(--screen-pad) 0; padding-bottom: var(--bottom-h, 0rem); }
|
|
885
|
+
/* --bleed: the page's box runs to the screen's edges, the inset is its own padding — what leans, slides or casts a shadow past
|
|
886
|
+
the inset (a postcard) is cut at the glass, not at the inset; a little room on top for a turned corner. Nothing pans sideways. */
|
|
887
|
+
.step > .page--bleed { margin-inline: 0; padding-inline: var(--screen-pad); padding-block-start: var(--sp-2); overflow-x: hidden; }
|
|
888
|
+
.step > .pages > .page {
|
|
889
|
+
padding-inline: var(--screen-pad); /* the words keep the inset INSIDE it */
|
|
890
|
+
margin-top: var(--_lead, 0rem); /* the lead is each PAGE's margin (15% with no top, the screen's gap under a bar): the words start there and scroll up to the box's edge */
|
|
891
|
+
padding-bottom: var(--bottom-h, 0rem); /* exactly the bottom layer (measured by Screen): a page scrolls ONLY when the layer would cover its words, and then just far enough to clear them — at 100% nothing scrolls (Lh 2026-09-23) */
|
|
892
|
+
}
|
|
893
|
+
/* a walk page that OPENS on a picture (Lh 2026-09-23): the picture takes the lead's room — as wide as the content (edge to edge), from the top edge, as tall as
|
|
894
|
+
the lead + the picture's own height (--page-media-h), square — so the words start where they always did */
|
|
895
|
+
.step > .pages > .page:has(> :is(img, .ph):first-child) { margin-top: 0; }
|
|
896
|
+
.step > .empty-state { flex: none; }
|
|
897
|
+
.sheet__body > .empty-state:only-child { flex: none; }
|
|
898
|
+
/* ---- where it sits in what actions.css draws (docs/reference.md rule 15) ---- */
|
|
899
|
+
/* ---- top bar: back + screen title ---- */
|
|
900
|
+
.topbar .t-h1 {
|
|
901
|
+
text-transform: uppercase;
|
|
902
|
+
flex: 1;
|
|
903
|
+
min-width: 0;
|
|
904
|
+
overflow: hidden;
|
|
905
|
+
text-overflow: ellipsis;
|
|
906
|
+
white-space: nowrap;
|
|
907
|
+
padding-block: 0.15em; /* Thai marks survive the clip — see card.css, the head rule */
|
|
908
|
+
margin-block: -0.15em;
|
|
909
|
+
}
|
|
910
|
+
/* ---- where it sits in what media.css draws (docs/reference.md rule 15) ---- */
|
|
911
|
+
.postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
|
|
912
|
+
|
|
913
|
+
/* ---- where it sits in what card.css draws: a walk's page (docs/reference.md rule 15) ---- */
|
|
914
|
+
.pages > .page { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
|
|
915
|
+
|
|
916
|
+
/* ---- where it sits in what step.css draws: the reading block (docs/reference.md rule 15) ---- */
|
|
917
|
+
.step:has(> .sheet--peek) > .page { padding-bottom: var(--sheet-peek); }
|
|
918
|
+
.step > .page {
|
|
919
|
+
flex: 0 1 auto;
|
|
920
|
+
min-height: 0;
|
|
921
|
+
overflow-y: auto;
|
|
922
|
+
overscroll-behavior: contain;
|
|
923
|
+
}
|
|
924
|
+
|
|
726
925
|
/* ---- css/card.css — card boundary + list variants ---- */
|
|
727
926
|
/* ============================================================
|
|
728
927
|
cardds/card.css — the card boundary + list variants
|
|
@@ -755,10 +954,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
755
954
|
card once `sent` and a `Card` with `leave` read the same two knobs; the time is --motion-sheet. `.card--leaver` is a card whose app may ask it
|
|
756
955
|
to go (it holds the transition, so it comes back as it went); `.card--away` is one that has been asked. A knob of the time would read dead:
|
|
757
956
|
the knob walk switches motion off */
|
|
758
|
-
|
|
759
|
-
|
|
957
|
+
.card--leaver { transition: translate var(--motion-sheet) var(--ease), rotate var(--motion-sheet) var(--ease); }
|
|
958
|
+
.card--away { --_y: calc(var(--leave-rise, 160%) * -1); rotate: var(--leave-turn, calc(var(--tilt-base) * -5)); }
|
|
760
959
|
.card--away { translate: 0 var(--_y); }
|
|
761
|
-
@media (prefers-reduced-motion: reduce) {
|
|
960
|
+
@media (prefers-reduced-motion: reduce) { .card--leaver { transition: none; } }
|
|
762
961
|
|
|
763
962
|
/* each tone is a fill and a side; its words and glyphs follow from the side (tokens.css) */
|
|
764
963
|
.card--2 { --card-bg: var(--card-2-bg); --dark: var(--card-2-dark); }
|
|
@@ -775,18 +974,13 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
775
974
|
multi-line title can opt back out with .card__head--top. */
|
|
776
975
|
.card__head {
|
|
777
976
|
display: flex;
|
|
778
|
-
flex-wrap: wrap;
|
|
977
|
+
flex-wrap: var(--card-head-wrap, wrap); /* the caption takes a line of its own — no wrapper div */
|
|
779
978
|
align-items: center;
|
|
780
979
|
justify-content: space-between;
|
|
781
980
|
column-gap: var(--card-head-col-gap, var(--sp-3));
|
|
782
981
|
row-gap: var(--card-head-row-gap, var(--sp-0));
|
|
982
|
+
min-height: var(--card-head-min-h, auto);
|
|
783
983
|
}
|
|
784
|
-
/* flat head markup (Lh 2026-09-16 — no <p>, no wrapper, only padding and gap tokens):
|
|
785
|
-
<div class="card__head"><h2 class="t-h2">…</h2><span class="t-caption t-muted">…</span><button class="icon-btn icon-btn--sm">…</button></div>
|
|
786
|
-
The heading and the far-right thing share the first row; a caption sits on
|
|
787
|
-
a row below (order 1), whatever the DOM order — so an author writes the
|
|
788
|
-
words in reading order. */
|
|
789
|
-
.card__head > :is(.t-caption, .t-body) { flex: 1 1 100%; order: 1; margin: 0; }
|
|
790
984
|
.card__head--top { align-items: flex-start; }
|
|
791
985
|
|
|
792
986
|
.card__media { border-radius: var(--card-media-radius, var(--r-media)); overflow: hidden; position: relative; } /* relative: corner badges */
|
|
@@ -806,12 +1000,23 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
806
1000
|
.card__head > :is(h1, h2, h3, div, p):first-child { flex: 1; min-width: 0; }
|
|
807
1001
|
|
|
808
1002
|
/* Skeleton head — the heading in a card's OWN head, and only there.
|
|
809
|
-
The heading wraps
|
|
810
|
-
2026-
|
|
811
|
-
|
|
1003
|
+
The heading wraps, no squeeze, up to three lines and cut there with an
|
|
1004
|
+
ellipsis (Lh 2026-10-21: "มันควรจะ max สามบรรทัด" — a request's title ran to
|
|
1005
|
+
four lines on a phone; reverses 2026-09-16's "no clamp"). The count is the knob
|
|
1006
|
+
--card-head-title-lines. The group that holds heading ·
|
|
812
1007
|
caption is the head's own wrapping row (above) — no wrapper element, no
|
|
813
1008
|
height, only the row gap. */
|
|
814
|
-
.card__head :is(h1, h2, h3) {
|
|
1009
|
+
.card__head :is(h1, h2, h3) {
|
|
1010
|
+
overflow-wrap: anywhere;
|
|
1011
|
+
display: -webkit-box;
|
|
1012
|
+
-webkit-box-orient: vertical;
|
|
1013
|
+
-webkit-line-clamp: var(--card-head-title-lines, 3);
|
|
1014
|
+
line-clamp: var(--card-head-title-lines, 3);
|
|
1015
|
+
overflow: hidden;
|
|
1016
|
+
padding-block-start: var(--_mark-room); /* a stacked Thai mark rises over the first line's box: room inside the clip, given back outside */
|
|
1017
|
+
margin-block-start: calc(-1 * var(--_mark-room));
|
|
1018
|
+
--_mark-room: 0.2em;
|
|
1019
|
+
}
|
|
815
1020
|
|
|
816
1021
|
/* a Stamp in a card's head (`Card end={<Stamp …/>}`): the words stand at the left, the stamp at the right BESIDE BOTH LINES — the heading and the
|
|
817
1022
|
caption pair up beside it, not the stamp beside the heading alone with the caption left under it. The stamp is its own size (--stamp-size,
|
|
@@ -819,15 +1024,13 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
819
1024
|
<div class="card__head"><h2 class="t-h2">…</h2><span class="t-caption t-muted">…</span><span class="stamp">…</span></div> */
|
|
820
1025
|
.card__head:has(> .stamp) { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
|
|
821
1026
|
.card__head:has(> .stamp) > :is(h1, h2, h3) { grid-column: 1; grid-row: 1; align-self: end; }
|
|
822
|
-
.card__head:has(> .stamp) > :is(.t-caption, .t-body) { grid-column: 1; grid-row: 2; align-self: start; }
|
|
823
|
-
.card__head > .stamp { grid-column: 2; grid-row: 1 / span 2; margin-inline: 0; }
|
|
824
1027
|
|
|
825
1028
|
/* a JourneyBadge at the edge of the card's own first head (the date box in the top-left or top-right corner) keeps its corner concentric
|
|
826
1029
|
with the card's: the card's corner (--r-card) less the room the padding leaves round it. journey.css reads it behind the theme's --tile-badge-radius,
|
|
827
1030
|
so a theme that sets that knob still wins. */
|
|
828
1031
|
.card > .card__head:first-child > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); }
|
|
829
1032
|
|
|
830
|
-
/* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are
|
|
1033
|
+
/* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are paper bands edge to
|
|
831
1034
|
edge over the card's padding, no hairline; the middle between them keeps the card's tone, lightened (--card-middle-bg). The card
|
|
832
1035
|
clips, so the bands keep its corners. A two-faced card (below) carries the look on each face. */
|
|
833
1036
|
:is(.card--bands, .card--bands > .card__face) {
|
|
@@ -848,12 +1051,14 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
848
1051
|
|
|
849
1052
|
/* ---- two faces: the WHOLE card turns (`Card back`, Lh 2026-10-19). The article is the turning box (.flip, base.css) and only that;
|
|
850
1053
|
each face is the card's paper — head · middle · foot — and both lie in one grid cell. The head and the foot are the same on both
|
|
851
|
-
faces; the
|
|
1054
|
+
faces; the card is as tall as its taller face and each face fills it, the middle taking what the head and the foot leave (2026-10-21,
|
|
1055
|
+
timebank: a fixed middle left a short front a tall empty band), so the card never changes height as it turns; --card-middle-h fixes
|
|
1056
|
+
the middle's height instead, the theme's call. The hidden
|
|
852
1057
|
face is inert. Each face holds an unseen turn button: the keyboard's way to turn it; its ring is drawn on the face. */
|
|
853
1058
|
.card--turns { background: none; border: 0; padding: 0; cursor: pointer; touch-action: manipulation; }
|
|
854
1059
|
.card__face {
|
|
855
|
-
display:
|
|
856
|
-
|
|
1060
|
+
display: flex;
|
|
1061
|
+
flex-direction: column;
|
|
857
1062
|
gap: var(--card-gap);
|
|
858
1063
|
min-width: 0;
|
|
859
1064
|
padding: var(--card-pad);
|
|
@@ -864,13 +1069,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
864
1069
|
position: relative;
|
|
865
1070
|
}
|
|
866
1071
|
.card__face > * { min-width: 0; }
|
|
867
|
-
.card__middle { display: grid; align-content: start; gap: var(--card-gap); height: var(--card-middle-h,
|
|
868
|
-
.card__turn { position: absolute; inset: 0; width:
|
|
1072
|
+
.card__middle { flex: 1 0 auto; display: grid; align-content: start; gap: var(--card-gap); height: var(--card-middle-h, auto); overflow: hidden; }
|
|
1073
|
+
.card__turn { position: absolute; inset: 0; width: var(--px); height: var(--px); padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); }
|
|
869
1074
|
.card__face:has(> .card__turn:focus-visible) { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
|
|
870
|
-
|
|
871
|
-
.card__head > .chip-row { flex: 1 1 100%; order: 2; max-height: calc(2 * (var(--fs-sm) * 1.5 + 2 * var(--chip-sm-pad-y, var(--sp-0)) + 2 * var(--border-w)) + var(--sp-2)); overflow: hidden; }
|
|
872
|
-
.card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; }
|
|
873
|
-
.card__head > .chip-row > .chip > .icon { display: inline-block; vertical-align: -0.15em; margin-inline-end: var(--chip-sm-gap, var(--sp-1)); }
|
|
1075
|
+
.card__head > .chip-row > .chip > .icon { --icon-display: inline-block; --icon-align: -0.15em; --icon-margin: 0 var(--chip-sm-gap, var(--sp-1)) 0 0; }
|
|
874
1076
|
|
|
875
1077
|
/* ---- bleed: a scrolling track that runs to the container's edges ----
|
|
876
1078
|
For a horizontal strip inside a padded container — .avatar-pick, .chip-row,
|
|
@@ -891,15 +1093,15 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
891
1093
|
nothing jumps. Chromium/Safari paint it through the ::-webkit-scrollbar
|
|
892
1094
|
parts; Firefox gets its thin bar in the same ink. */
|
|
893
1095
|
.bleed {
|
|
894
|
-
margin-inline: calc(-1 * var(--card-pad,
|
|
895
|
-
padding-inline: var(--card-pad,
|
|
1096
|
+
margin-inline: calc(-1 * var(--card-pad, 0rem));
|
|
1097
|
+
padding-inline: var(--card-pad, 0rem);
|
|
896
1098
|
padding-block-end: var(--sp-2);
|
|
897
|
-
scroll-padding-inline: var(--card-pad,
|
|
1099
|
+
scroll-padding-inline: var(--card-pad, 0rem);
|
|
898
1100
|
flex-wrap: nowrap;
|
|
899
1101
|
overflow-x: auto;
|
|
900
1102
|
}
|
|
901
1103
|
.bleed::-webkit-scrollbar { height: var(--scroll-w); }
|
|
902
|
-
.bleed::-webkit-scrollbar-track { background: transparent; margin-inline: var(--card-pad,
|
|
1104
|
+
.bleed::-webkit-scrollbar-track { background: transparent; margin-inline: var(--card-pad, 0rem); }
|
|
903
1105
|
.bleed::-webkit-scrollbar-thumb { background: transparent; }
|
|
904
1106
|
.bleed:is(:hover, .is-scrolling)::-webkit-scrollbar-thumb { background: currentColor; }
|
|
905
1107
|
@supports not selector(::-webkit-scrollbar) {
|
|
@@ -913,29 +1115,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
913
1115
|
/* foot: the card's action buttons on one line. One button takes the width,
|
|
914
1116
|
up to four share it in even parts — so the gaps read even too — and from
|
|
915
1117
|
three on they drop their side padding so the labels keep the room. */
|
|
916
|
-
.card__foot { display: flex; gap: var(--card-foot-gap, var(--sp-2)); align-items: center; }
|
|
917
|
-
.card__foot > .btn {
|
|
918
|
-
/* …unless the button says its own size: `Btn form` is as wide as its words in a foot too, centred (Lh 2026-10-10: one button filling
|
|
919
|
-
a card's foot read too big — which button a card wears is the project's choice, so the foot must not undo it) */
|
|
920
|
-
.card__foot > .btn--form { flex: 0 1 auto; }
|
|
921
|
-
.card__foot:has(> :nth-child(3)) > .btn { padding-inline: var(--sp-2); }
|
|
1118
|
+
.card__foot { display: flex; gap: var(--card-foot-gap, var(--sp-2)); align-items: center; color: var(--card-foot-ink, inherit); }
|
|
1119
|
+
.card__foot:has(> :nth-child(3)) > .btn { --btn-pad-x: var(--sp-2); }
|
|
922
1120
|
/* a small link under the buttons (CardFoot link — Lh 2026-09-23, as the ActionBar's ctaLink): the row wraps, the link takes a
|
|
923
1121
|
whole short row of its own, centred — it never shares the buttons' line */
|
|
924
1122
|
.card__foot--link { flex-wrap: wrap; align-content: end; row-gap: var(--card-foot-link-gap, var(--sp-1)); }
|
|
925
1123
|
.card__foot-link { flex: 1 0 100%; display: flex; justify-content: center; }
|
|
1124
|
+
/* round (CardFoot round): the foot holds a few round IconBtns, each its own size (they never share the line), as ONE group in the middle with
|
|
1125
|
+
a gap that does not follow the card's width. The gap is wider than the Btns' (a miss between two round targets): --card-foot-gap sets it */
|
|
1126
|
+
.card__foot--round { justify-content: center; gap: var(--card-foot-gap, var(--sp-6)); }
|
|
926
1127
|
|
|
927
1128
|
/* media placeholder for wireframes — currentColor so it survives any palette */
|
|
928
1129
|
.ph {
|
|
929
1130
|
background:
|
|
930
|
-
linear-gradient(to top right, transparent calc(50% -
|
|
931
|
-
linear-gradient(to top left, transparent calc(50% -
|
|
1131
|
+
linear-gradient(to top right, transparent calc(50% - var(--px)), currentColor calc(50% - var(--px)), currentColor calc(50% + var(--px)), transparent calc(50% + var(--px))),
|
|
1132
|
+
linear-gradient(to top left, transparent calc(50% - var(--px)), currentColor calc(50% - var(--px)), currentColor calc(50% + var(--px)), transparent calc(50% + var(--px)));
|
|
932
1133
|
border: var(--border-w) solid currentColor;
|
|
933
1134
|
border-radius: var(--r-media);
|
|
934
1135
|
opacity: var(--off);
|
|
935
|
-
min-height:
|
|
1136
|
+
min-height: calc(var(--tap-base) * 10 / 3);
|
|
936
1137
|
}
|
|
937
|
-
.ph--sm { min-height:
|
|
938
|
-
.ph--md { min-height:
|
|
1138
|
+
.ph--sm { min-height: calc(var(--tap-base) * 5 / 2); }
|
|
1139
|
+
.ph--md { min-height: calc(var(--tap-base) * 3); }
|
|
939
1140
|
/* ---- centred card: one thing to read, at the human centre of a fill screen ----
|
|
940
1141
|
(.screen__centre below is the same placement for a small GROUP of things)
|
|
941
1142
|
Where the centre of a phone IS, to the person holding it: the bottom fifth is
|
|
@@ -948,8 +1149,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
948
1149
|
The card hugs its words and keeps the screen's inset. It is capped so that it
|
|
949
1150
|
never reaches the title bar above it nor the bar's room below it — past that it
|
|
950
1151
|
scrolls inside — from the same tokens those bars reserve their room with. */
|
|
951
|
-
.card--centre
|
|
952
|
-
.screen--fill > .screen__centre {
|
|
1152
|
+
.card--centre {
|
|
953
1153
|
position: absolute;
|
|
954
1154
|
top: var(--screen-centre);
|
|
955
1155
|
translate: 0 -50%;
|
|
@@ -966,38 +1166,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
966
1166
|
:is(.topbar, .appbar) ~ :is(.card--centre, .screen__centre) { --_lead: var(--header-h); }
|
|
967
1167
|
.card--centre { inset-inline: var(--screen-pad); }
|
|
968
1168
|
|
|
969
|
-
/* ---- centred block: the same place, for one thing to read that is not a
|
|
970
|
-
lone card — an empty state, a card with its quiet second button and the
|
|
971
|
-
pager under it. The block runs edge to
|
|
972
|
-
edge and keeps the screen's inset as padding, and
|
|
973
|
-
its children stack at the screen's own rhythm (Lh, 2026-09-11):
|
|
974
|
-
<div class="screen screen--fill"><header class="topbar">…</header>
|
|
975
|
-
<div class="screen__centre"><article class="card">…</article><button class="btn btn--quiet btn--block">…</button></div>
|
|
976
|
-
</div> */
|
|
977
|
-
.screen__centre {
|
|
978
|
-
inset-inline: 0;
|
|
979
|
-
padding-inline: var(--screen-pad);
|
|
980
|
-
display: grid;
|
|
981
|
-
gap: var(--screen-gap);
|
|
982
|
-
align-content: start;
|
|
983
|
-
}
|
|
984
|
-
.screen__centre > * { min-width: 0; }
|
|
985
|
-
|
|
986
|
-
/* ---- the same group on a plain .screen: centred when short, as long as it
|
|
987
|
-
likes when not (Lh, 2026-09-12) ----
|
|
988
|
-
The member sees one content area — whatever is between the top bar and the
|
|
989
|
-
bar — and reads it as a whole page: a short card sits at its middle, a long
|
|
990
|
-
one simply makes the page longer, runs under the bar (a sticky last row,
|
|
991
|
-
actions.css) and scrolls up like any page — nothing is capped, nothing
|
|
992
|
-
shrinks, no scroll box inside a box.
|
|
993
|
-
So on a .screen that is NOT --fill the group is in flow: the column is at
|
|
994
|
-
least one phone tall (step.css), the group takes the room after the top bar and
|
|
995
|
-
centres its children in it. The screen's own inset stays the inset.
|
|
996
|
-
<div class="screen"><header class="topbar">…</header>
|
|
997
|
-
<div class="screen__centre"><article class="card">…</article><div class="pager__at">…</div></div>
|
|
998
|
-
</div><div class="action-bar">…</div> */
|
|
999
|
-
.screen:not(.screen--fill) > .screen__centre { flex: 1; padding-inline: 0; align-content: center; }
|
|
1000
|
-
|
|
1001
1169
|
/* ---- the card that changes slides: out one side, the next in from the other ----
|
|
1002
1170
|
A .screen__centre group that walks pages (data-slide="next" | "back" on the
|
|
1003
1171
|
root while a step changes) turns its card like a page: the old card slides
|
|
@@ -1026,7 +1194,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1026
1194
|
user-select: none; /* a walk is dragged, not read-and-selected — without this, a finger crossing the words highlights them instead of turning the page (timebank #59) */
|
|
1027
1195
|
touch-action: pan-y; /* leaves vertical scroll to the browser; horizontal is the app's own drag, never a native pan */
|
|
1028
1196
|
}
|
|
1029
|
-
.pages >
|
|
1197
|
+
.pages > .card { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
|
|
1030
1198
|
.pages > [inert] { visibility: hidden; }
|
|
1031
1199
|
/* pages of WORDS (Lh, 2026-09-20): the same walk with .page pages, on the base — in a .step the
|
|
1032
1200
|
.pages is the reading block (step.css), the round pager in the bar turns it, the same slideTo().
|
|
@@ -1038,7 +1206,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1038
1206
|
::view-transition-old(centre-card-out), ::view-transition-new(centre-card-in),
|
|
1039
1207
|
::view-transition-old(screen-out), ::view-transition-new(screen-in) { /* screen-*: slideScreen — one whole screen to the next (Lh 2026-09-25) */
|
|
1040
1208
|
animation-duration: var(--motion-slide);
|
|
1041
|
-
animation-timing-function: ease-out;
|
|
1209
|
+
animation-timing-function: var(--ease-out);
|
|
1042
1210
|
mix-blend-mode: normal;
|
|
1043
1211
|
}
|
|
1044
1212
|
:root[data-slide="next"]::view-transition-old(centre-card-out), :root[data-slide="next"]::view-transition-old(screen-out) { animation-name: centre-card-out-left; }
|
|
@@ -1053,9 +1221,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1053
1221
|
::view-transition-old(centre-card-out), ::view-transition-new(centre-card-in),
|
|
1054
1222
|
::view-transition-old(screen-out), ::view-transition-new(screen-in) { animation: none; }
|
|
1055
1223
|
}
|
|
1056
|
-
/* the page dots under the card sit centred under it — the indicator belongs
|
|
1057
|
-
with the page that changes, never in the bar (Lh, 2026-09-12) */
|
|
1058
|
-
.screen__centre > .pager__at { justify-self: center; }
|
|
1059
1224
|
|
|
1060
1225
|
/* compact card inside a card (nested note, promo) */
|
|
1061
1226
|
.card--sm { padding: var(--card-sm-pad, var(--sp-4)); --card-gap: var(--sp-2); border-radius: var(--card-sm-radius, var(--r-card)); }
|
|
@@ -1064,7 +1229,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1064
1229
|
stack.css; a stacked sheet is a plain .card, the material */
|
|
1065
1230
|
|
|
1066
1231
|
/* trailing icon cluster in a card header */
|
|
1067
|
-
.icon-row { display: flex; align-items: center; gap: var(--icon-row-gap, var(--sp-4)); opacity:
|
|
1232
|
+
.icon-row { display: flex; align-items: center; gap: var(--icon-row-gap, var(--sp-4)); opacity: var(--fade-near); }
|
|
1068
1233
|
|
|
1069
1234
|
/* ---- card list: rows, each row its own boundary ---- */
|
|
1070
1235
|
.card-list { display: grid; gap: var(--card-list-gap, var(--sp-3)); }
|
|
@@ -1100,7 +1265,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1100
1265
|
.card__remove {
|
|
1101
1266
|
--_size: var(--card-remove-size, calc(var(--tap-base) / 2));
|
|
1102
1267
|
position: relative;
|
|
1103
|
-
z-index:
|
|
1268
|
+
z-index: var(--z-over);
|
|
1104
1269
|
flex: none;
|
|
1105
1270
|
align-self: flex-start;
|
|
1106
1271
|
display: grid;
|
|
@@ -1118,7 +1283,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1118
1283
|
cursor: pointer;
|
|
1119
1284
|
}
|
|
1120
1285
|
.card__remove::after { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; width: max(var(--tap-floor), var(--_size)); aspect-ratio: 1; border-radius: 50%; }
|
|
1121
|
-
.card__remove > .icon {
|
|
1286
|
+
.card__remove > .icon { --icon-size: 62%; }
|
|
1122
1287
|
.card__remove:focus-visible { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
1123
1288
|
|
|
1124
1289
|
/* leading thumb/icon in a row */
|
|
@@ -1161,7 +1326,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1161
1326
|
.feed-card__rest > :first-child { margin-top: var(--card-gap); }
|
|
1162
1327
|
.feed-card--small .feed-card__rest { overflow: clip; visibility: hidden; }
|
|
1163
1328
|
@media (prefers-reduced-motion: no-preference) {
|
|
1164
|
-
.feed-card__more { transition: grid-template-rows var(--motion-sheet) ease; }
|
|
1329
|
+
.feed-card__more { transition: grid-template-rows var(--motion-sheet) var(--ease); }
|
|
1165
1330
|
.feed-card--small .feed-card__rest { transition: visibility 0s var(--motion-sheet); }
|
|
1166
1331
|
}
|
|
1167
1332
|
|
|
@@ -1172,7 +1337,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1172
1337
|
|
|
1173
1338
|
.delta {
|
|
1174
1339
|
font: var(--delta-font, var(--type-caption));
|
|
1175
|
-
font-weight:
|
|
1340
|
+
font-weight: var(--fw-bold);
|
|
1176
1341
|
padding: var(--delta-pad-y, var(--sp-1)) var(--delta-pad-x, var(--sp-2));
|
|
1177
1342
|
border-radius: var(--delta-radius, var(--r-chip));
|
|
1178
1343
|
background: var(--delta-bg, var(--card-3-bg));
|
|
@@ -1199,7 +1364,286 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1199
1364
|
scroll-snap-align: start; scroll-snap-stop: always;
|
|
1200
1365
|
display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--card-gap); align-content: start;
|
|
1201
1366
|
}
|
|
1202
|
-
|
|
1367
|
+
|
|
1368
|
+
/* ==== the card's variants another family draws: Cover and the fold (media), Band (numbers) (#62: a block is written in its own file) ==== */
|
|
1369
|
+
|
|
1370
|
+
/* ---- cover: the image IS the card; text over a scrim ----
|
|
1371
|
+
<article class="card card--cover"><div class="ph" role="img"></div>
|
|
1372
|
+
<div class="card__head"><button class="icon-btn icon-btn--sm">♥</button></div>
|
|
1373
|
+
<div><h2 class="t-h1">Art Institute</h2><p class="t-body">111 South Michigan Ave</p></div>
|
|
1374
|
+
<div class="row">…meta…</div></article> */
|
|
1375
|
+
.card--cover {
|
|
1376
|
+
--card-bg: var(--card-3-bg);
|
|
1377
|
+
--dark: var(--card-3-dark);
|
|
1378
|
+
--accent: var(--card-ink);
|
|
1379
|
+
--accent-dark: calc(1 - var(--dark));
|
|
1380
|
+
min-height: var(--_cover-min-h);
|
|
1381
|
+
align-content: space-between;
|
|
1382
|
+
overflow: hidden;
|
|
1383
|
+
isolation: isolate;
|
|
1384
|
+
}
|
|
1385
|
+
.card--cover > img,
|
|
1386
|
+
.card--cover > .ph {
|
|
1387
|
+
position: absolute;
|
|
1388
|
+
inset: 0;
|
|
1389
|
+
width: 100%;
|
|
1390
|
+
height: 100%;
|
|
1391
|
+
object-fit: cover;
|
|
1392
|
+
z-index: var(--z-under-2);
|
|
1393
|
+
border: 0;
|
|
1394
|
+
border-radius: 0;
|
|
1395
|
+
min-height: 0;
|
|
1396
|
+
}
|
|
1397
|
+
/* scrim: dark at both ends so the meta stays readable */
|
|
1398
|
+
.card--cover::before {
|
|
1399
|
+
content: "";
|
|
1400
|
+
position: absolute;
|
|
1401
|
+
inset: 0;
|
|
1402
|
+
z-index: var(--z-under);
|
|
1403
|
+
background: linear-gradient(to bottom, var(--scrim), transparent 40% 60%, var(--scrim));
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/* ---- fold: a sticky-note corner on any card (decorative) ----
|
|
1407
|
+
No border. The card's outline follows its border box, so it ran straight
|
|
1408
|
+
across the squared bottom-right corner and cut through the fold's diagonal —
|
|
1409
|
+
the corner read as a notch with a line through it rather than a lifted flap.
|
|
1410
|
+
The COLOUR goes transparent rather than the border going to 0, so the box
|
|
1411
|
+
keeps its geometry and a fold card still measures the same as the cards
|
|
1412
|
+
beside it. */
|
|
1413
|
+
.card--fold {
|
|
1414
|
+
border-color: transparent;
|
|
1415
|
+
border-bottom-right-radius: 0;
|
|
1416
|
+
background-image: linear-gradient(to top left,
|
|
1417
|
+
var(--card-bg) 0 50%, /* the card's own background, not --surface (the page's) — a mismatch here drew a seam at the flap's edge */
|
|
1418
|
+
color-mix(in srgb, currentColor 18%, var(--card-bg)) 50% 100%);
|
|
1419
|
+
background-repeat: no-repeat;
|
|
1420
|
+
background-position: bottom right;
|
|
1421
|
+
background-size: var(--sp-8) var(--sp-8);
|
|
1422
|
+
background-origin: border-box; /* the flap sits on the true corner, not inside the (transparent) border */
|
|
1423
|
+
/* the corner is CUT: the triangle under the flap shows what is behind the card. Painted in the card's own colour it
|
|
1424
|
+
read as the square corner of a second card underneath (Lh 2026-09-23). The polygon reaches --sp-12 past the other
|
|
1425
|
+
edges so a coloured palette's shadow is not clipped there. */
|
|
1426
|
+
clip-path: polygon(calc(-1 * var(--sp-12)) calc(-1 * var(--sp-12)), calc(100% + var(--sp-12)) calc(-1 * var(--sp-12)), calc(100% + var(--sp-12)) calc(100% - var(--sp-8) - var(--sp-12)),
|
|
1427
|
+
calc(100% - var(--sp-8) - var(--sp-12)) calc(100% + var(--sp-12)), calc(-1 * var(--sp-12)) calc(100% + var(--sp-12)));
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/* ---- band: data card; numbers.css draws its parts and the band stack ---- */
|
|
1431
|
+
.card--band {
|
|
1432
|
+
grid-template-columns: 1fr auto;
|
|
1433
|
+
grid-template-areas: "label num" "foot num";
|
|
1434
|
+
align-items: end;
|
|
1435
|
+
gap: var(--sp-2) var(--sp-4);
|
|
1436
|
+
min-height: var(--card-band-min-h, var(--tile-h));
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/* ---- where it sits in what base.css draws (docs/reference.md rule 15) ---- */
|
|
1440
|
+
.page > :is(img, .ph) { max-height: var(--page-media-h, calc(var(--tap-base) * 14 / 3)); width: auto; height: auto; max-width: 100%; object-fit: contain; justify-self: start; align-self: start; } /* height auto: an <img> with width/height attributes keeps its ratio inside the cap — no band under the picture */
|
|
1441
|
+
.page > .ph { width: 100%; height: var(--page-media-h, calc(var(--tap-base) * 14 / 3)); }
|
|
1442
|
+
/* ---- where it sits in what stack.css draws (docs/reference.md rule 15) ---- */
|
|
1443
|
+
.sheet-stack > .card {
|
|
1444
|
+
grid-area: 1 / 1; /* every card shares the stack's cell; later ones offset down */
|
|
1445
|
+
border-bottom-left-radius: 0;
|
|
1446
|
+
border-bottom-right-radius: 0;
|
|
1447
|
+
border-bottom: 0;
|
|
1448
|
+
align-content: start;
|
|
1449
|
+
align-self: start; /* own natural height (padding + head) by default */
|
|
1450
|
+
z-index: var(--level, var(--z-over)); /* explicit paint order, 1 = back of the stack */
|
|
1451
|
+
}
|
|
1452
|
+
/* THE FRONT SHEET is the last .card, not the last child: a --closed stack
|
|
1453
|
+
may carry a .sheet-stack__base after its sheets (below). Written once here
|
|
1454
|
+
as "a card no card follows" so every rule about the front sheet agrees. */
|
|
1455
|
+
.sheet-stack > .card:not(:has(~ .card)) {
|
|
1456
|
+
align-self: stretch; /* the front card fills the remaining stack height */
|
|
1457
|
+
}
|
|
1458
|
+
.sheet-stack > .card:nth-child(2) { margin-top: var(--stack-step); }
|
|
1459
|
+
.sheet-stack > .card:nth-child(3) { margin-top: calc(2 * var(--stack-step)); }
|
|
1460
|
+
.sheet-stack > .card:nth-child(4) { margin-top: calc(3 * var(--stack-step)); }
|
|
1461
|
+
.sheet-stack > .card:nth-child(5) { margin-top: calc(4 * var(--stack-step)); }
|
|
1462
|
+
/* stack headers: uniform — [back?] title left, icons right, one height,
|
|
1463
|
+
same gap to content on every card */
|
|
1464
|
+
.sheet-stack > .card > .card__head {
|
|
1465
|
+
align-items: center;
|
|
1466
|
+
justify-content: flex-start;
|
|
1467
|
+
min-height: var(--tap);
|
|
1468
|
+
}
|
|
1469
|
+
.sheet-stack--display > .card > .card__head { align-items: start; min-height: 0; }
|
|
1470
|
+
/* ============================================================
|
|
1471
|
+
.sheet-stack--tap — tap a peeked sheet open; a back button returns it.
|
|
1472
|
+
|
|
1473
|
+
A sheet (a .card — the material, sheet.css's single sheet is the same
|
|
1474
|
+
stuff) is rigid: it can move in, move out, or be covered — it can
|
|
1475
|
+
never change shape. So every sheet here is the SAME full height,
|
|
1476
|
+
always, set once and never touched again by anything. A "peek" is
|
|
1477
|
+
not a short sheet; it's a full sheet the next one happens to cover,
|
|
1478
|
+
showing only the strip above it, exactly like a sheet of paper part
|
|
1479
|
+
way out of a real stack.
|
|
1480
|
+
|
|
1481
|
+
Because of that, the whole animation is one property: translate.
|
|
1482
|
+
Nothing resizes, nothing re-stacks — z-index is the plain resting
|
|
1483
|
+
--level order and is never touched, since the order already says
|
|
1484
|
+
what has to happen. Cards after the tapped one sit IN FRONT of it,
|
|
1485
|
+
so they're what's covering it, so they're what must leave. Cards
|
|
1486
|
+
before it are already BEHIND it, so they need do nothing at all
|
|
1487
|
+
except let themselves be covered as it rises over them.
|
|
1488
|
+
|
|
1489
|
+
.is-open — the tapped card, moved up to the top of the stack. Its
|
|
1490
|
+
own resting offset is margin-top (below); opening just
|
|
1491
|
+
translates that offset back off, landing it flush at
|
|
1492
|
+
the stack's top with its full height exactly filling
|
|
1493
|
+
to the bottom.
|
|
1494
|
+
.is-before — an earlier card, nudged one step down so it slips
|
|
1495
|
+
under the next card's resting strip and is covered
|
|
1496
|
+
there. Each carries its own --stagger (set in JS: 0
|
|
1497
|
+
for the one nearest the tapped card, counting up
|
|
1498
|
+
further back) so they tuck away, and later un-tuck,
|
|
1499
|
+
one at a time.
|
|
1500
|
+
.is-after — a later card, moved out below, together, no stagger.
|
|
1501
|
+
============================================================ */
|
|
1502
|
+
.sheet-stack--tap > .card {
|
|
1503
|
+
height: 100%; /* every card identical and full length, always — the one
|
|
1504
|
+
size in this whole component, never toggled, never transitioned. At a
|
|
1505
|
+
resting margin-top of n steps it simply overhangs the stack's bottom
|
|
1506
|
+
by n steps (clipped by .screen--fill), so translating it up by exactly
|
|
1507
|
+
that much lands it filling the stack precisely. */
|
|
1508
|
+
overflow: clip;
|
|
1509
|
+
transition: translate var(--motion-sheet) var(--ease); /* the only animated property here */
|
|
1510
|
+
transition-delay: calc(var(--stagger, 0) * var(--motion-stagger)); /* 0 for everything
|
|
1511
|
+
except a staggered is-before card: the card renders --stagger as it tucks or un-tucks */
|
|
1512
|
+
}
|
|
1513
|
+
.sheet-stack--tap > .card.is-open {
|
|
1514
|
+
/* up by its own resting offset — pure movement, nothing resizes */
|
|
1515
|
+
translate: 0 calc(-1 * var(--slot, 0) * var(--stack-step) + var(--_pull, 0rem));
|
|
1516
|
+
cursor: default;
|
|
1517
|
+
overflow-y: auto;
|
|
1518
|
+
overflow-x: clip;
|
|
1519
|
+
overscroll-behavior: contain;
|
|
1520
|
+
}
|
|
1521
|
+
.sheet-stack--tap > .card.is-open.is-dragging { transition: none; }
|
|
1522
|
+
.sheet-stack--tap > .card > .card__head { touch-action: none; }
|
|
1523
|
+
.sheet-stack--tap > .card.is-open:not(:has(> .sheet__body)) {
|
|
1524
|
+
padding-top: calc(var(--card-pad) + var(--_refresh, 0rem));
|
|
1525
|
+
transition: translate var(--motion-sheet) var(--ease), padding-top var(--motion-sheet) var(--ease);
|
|
1526
|
+
}
|
|
1527
|
+
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
1528
|
+
tappable), so its resting offset would leave its last n steps below the
|
|
1529
|
+
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
|
1530
|
+
alone is cut to the room it actually has, and it scrolls like an opened
|
|
1531
|
+
sheet does. Every other sheet keeps the one full height. */
|
|
1532
|
+
.sheet-stack--tap:not(.sheet-stack--closed) > .card:not(:has(~ .card)) {
|
|
1533
|
+
height: calc(100% - var(--slot, 0) * var(--stack-step));
|
|
1534
|
+
overflow-y: auto;
|
|
1535
|
+
overflow-x: clip;
|
|
1536
|
+
overscroll-behavior: contain;
|
|
1537
|
+
}
|
|
1538
|
+
/* a scrolling sheet shows no scrollbar (a desktop with a mouse would paint one
|
|
1539
|
+
down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself
|
|
1540
|
+
(the hiding rule is base.css's, shared with every other scrolling track) */
|
|
1541
|
+
.sheet-stack--tap > .card.is-before { translate: 0 var(--stack-step); }
|
|
1542
|
+
.sheet-stack--tap > .card.is-after { translate: 0 100vh; }
|
|
1543
|
+
.sheet-stack--closed > .card { translate: 0 calc(var(--_headroom) - var(--_lift, 0rem)); }
|
|
1544
|
+
.sheet-stack--closed > .card.is-dragging { transition: none; }
|
|
1545
|
+
.sheet-stack--tap:not(.sheet-stack--closed) > .card.is-dragging:not(.is-open) {
|
|
1546
|
+
translate: 0 calc(-1 * var(--_lift, 0rem));
|
|
1547
|
+
transition: none;
|
|
1548
|
+
}
|
|
1549
|
+
.sheet-stack--closed > .card.is-before { translate: 0 calc(var(--_headroom) + var(--stack-step)); }
|
|
1550
|
+
.sheet-stack--closed > .card.is-after { translate: 0 100vh; }
|
|
1551
|
+
/* a card standing in this room is FLAT (Lh 2026-10-09): the room clips at its own edges, and a card's shadow cut there drew a straight
|
|
1552
|
+
line under the bar and along the first sheet. Its ground and its radius are what say "card" here. */
|
|
1553
|
+
:is([data-palette], [data-finish]) .sheet-stack__base .card { box-shadow: none; }
|
|
1554
|
+
/* at the screen's top the open sheet's top corners are SQUARE (Lh 2026-10-08): round, they left two small pockets of the
|
|
1555
|
+
bar's ground at the glass. They square as the sheet rises and round again as it folds, in the slide's own time. Paint
|
|
1556
|
+
only, the sheet's size is untouched. A pull on the head keeps them square until the finger lets go. */
|
|
1557
|
+
.sheet-stack--flood > .card { transition: translate var(--motion-sheet) var(--ease), border-radius var(--motion-sheet) var(--ease); }
|
|
1558
|
+
.sheet-stack--flood > .card.is-open { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
1559
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head {
|
|
1560
|
+
position: relative;
|
|
1561
|
+
padding-left: 0;
|
|
1562
|
+
transition: padding-left var(--motion-sheet) var(--ease);
|
|
1563
|
+
}
|
|
1564
|
+
/* ---- a stacked sheet with a SheetBody has the ONE sheet shape (Lh 2026-09-23, the home layout): sheet head
|
|
1565
|
+
pinned at the top · sheet body scrolls, as long as it needs · sheet foot (a CardFoot, the CTA) at the bottom.
|
|
1566
|
+
The card becomes a flex column and stops scrolling itself — only the body does. A sheet with no SheetBody
|
|
1567
|
+
keeps the old way: the whole open card scrolls. ---- */
|
|
1568
|
+
.sheet-stack--tap > .card:has(> .sheet__body) { display: flex; flex-direction: column; gap: var(--card-gap); }
|
|
1569
|
+
.sheet-stack--tap > .card:has(> .sheet__body).is-open { overflow: clip; }
|
|
1570
|
+
/* a paper stack's foot is paper to the card's edges, and it covers the tinted flex gap above it so the
|
|
1571
|
+
button does not sit on a colour stripe. The head's gap stays: it is outside this foot. The button stays
|
|
1572
|
+
where it was — the padding gives back what the bleed takes. */
|
|
1573
|
+
.sheet-stack > .card.sheet--paper > .card__foot {
|
|
1574
|
+
background: var(--action-bg);
|
|
1575
|
+
color: var(--card-ink);
|
|
1576
|
+
margin-top: calc(-1 * var(--card-gap));
|
|
1577
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
1578
|
+
margin-bottom: calc(-1 * var(--card-pad));
|
|
1579
|
+
padding-top: var(--card-gap);
|
|
1580
|
+
padding-inline: var(--card-pad);
|
|
1581
|
+
padding-bottom: var(--card-pad);
|
|
1582
|
+
}
|
|
1583
|
+
.stack-sheet > .card__head { flex-wrap: nowrap; }
|
|
1584
|
+
/* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
|
|
1585
|
+
tappable"): a SMALL DISC — its ring kept, 32px instead of 44, the chevron in it at 20 — so the head gives it 32px + a gap
|
|
1586
|
+
(was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
|
|
1587
|
+
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back:not(.sheet-stack__back--end)) { padding-left: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
1588
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) var(--ease); }
|
|
1589
|
+
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) { padding-right: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
1590
|
+
/* a stacked sheet is a control until it is open — tapped, never read into: no text selection (a drag, a double tap, a long press),
|
|
1591
|
+
no grey flash, no iOS callout; its head stays one when open. Only the OPEN sheet's body is text to select (Lh 2026-09-23) */
|
|
1592
|
+
.sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
1593
|
+
/* ---- where it sits in what sheet.css draws (docs/reference.md rule 15) ---- */
|
|
1594
|
+
.sheet > .card__foot {
|
|
1595
|
+
position: absolute; inset: calc(-1 * var(--border-w)) 0 0; z-index: var(--z-over); /* the sheet's border box: the same height the sheet's own % reads */
|
|
1596
|
+
align-items: end;
|
|
1597
|
+
align-content: end;
|
|
1598
|
+
padding: 0 var(--card-pad) calc(var(--card-pad) + var(--_safe-b-open));
|
|
1599
|
+
background: linear-gradient(var(--card-bg), var(--card-bg)) bottom / 100% var(--_foot) no-repeat;
|
|
1600
|
+
pointer-events: none;
|
|
1601
|
+
translate: 0 calc(-1 * var(--_y) - var(--_drag, 0rem));
|
|
1602
|
+
transition: translate var(--motion-sheet) var(--ease), visibility 0s;
|
|
1603
|
+
}
|
|
1604
|
+
.sheet.is-dragging > .card__foot { transition: none; }
|
|
1605
|
+
/* at peek and away the foot is NOT held on the box's edge: it stands on the sheet's own bottom edge (no counter-slide), so it
|
|
1606
|
+
LEAVES AND ARRIVES WITH ITS SHEET — solid the whole way, hidden only once the slide is over. It used to stay in place and
|
|
1607
|
+
fade: the body slid through the strip behind a half-transparent button (timebank, Lh 2026-10-01: "ตัวหนังสือลอดใต้ปุ่ม"). */
|
|
1608
|
+
:is(.sheet--peek, .sheet--away) > .card__foot { translate: 0 0; visibility: hidden; transition: translate var(--motion-sheet) var(--ease), visibility 0s var(--ease-linear) var(--motion-sheet); }
|
|
1609
|
+
/* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
|
|
1610
|
+
/* a WALK (Pages of Page) — Lh 2026-09-23, third call: no top; the picture and the words are ONE run that scrolls up together
|
|
1611
|
+
(the picture is never pinned). Each page starts at the screen's lead and ends with the same room under it — the lead as
|
|
1612
|
+
padding above AND below — so the words begin near the middle and, when a big text size runs them long, scroll up clear.
|
|
1613
|
+
The walk shows no scrollbar. */
|
|
1614
|
+
.step > .pages { touch-action: pan-y; } /* pan-y: the walk scrolls itself up and down; a sideways swipe is the Walkthrough's (it turns the page) */ /* the scrolling box is the whole content: from the step's top edge, edge to edge — no margin, the default (Lh 2026-09-23) */
|
|
1615
|
+
.step > .pages > .page > :is(img, .ph):first-child {
|
|
1616
|
+
width: calc(100% + 2 * var(--screen-pad)); /* the content's width: the page's inset given back */
|
|
1617
|
+
max-width: none;
|
|
1618
|
+
margin-inline: calc(-1 * var(--screen-pad));
|
|
1619
|
+
height: calc(var(--_lead, 0rem) + var(--page-media-h, calc(var(--tap-base) * 14 / 3)));
|
|
1620
|
+
max-height: none;
|
|
1621
|
+
border-radius: 0;
|
|
1622
|
+
border-inline: 0;
|
|
1623
|
+
border-top: 0;
|
|
1624
|
+
object-fit: cover;
|
|
1625
|
+
}
|
|
1626
|
+
/* ---- where it sits in what numbers.css draws (docs/reference.md rule 15) ---- */
|
|
1627
|
+
.band-stack > .card--band { border: 0; border-radius: 0; box-shadow: none; }
|
|
1628
|
+
.band-stack > .card--band + .card--band { border-top: var(--border-w) solid var(--hairline); }
|
|
1629
|
+
/* ---- where it sits in what sticky.css draws (docs/reference.md rule 15) ---- */
|
|
1630
|
+
.sticky__head > .ph { flex: 1 1 0; min-width: 0; } /* a picture in the head takes the note's width */
|
|
1631
|
+
.sticky__head:has(> .sticky-date) > .ph { flex: none; width: var(--sticky-pic-size, calc(var(--tap) * 1.5)); min-height: 0; aspect-ratio: 1; } /* beside a date the picture is a small square at the start */
|
|
1632
|
+
|
|
1633
|
+
/* ---- where it sits in what media.css draws: a postcard's picture (docs/reference.md rule 15) ---- */
|
|
1634
|
+
.postcard__front .postcard__turn > .ph { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border: 0; border-radius: 0; min-height: 0; }
|
|
1635
|
+
|
|
1636
|
+
/* ---- where it sits in what stack.css draws: a sheet pulled to refresh holds still (docs/reference.md rule 15) ---- */
|
|
1637
|
+
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
1638
|
+
|
|
1639
|
+
/* ---- where it sits in what step.css draws: the reading block, a walk of pages (docs/reference.md rule 15) ---- */
|
|
1640
|
+
.step:has(> .sheet--peek) > .pages { padding-bottom: var(--sheet-peek); }
|
|
1641
|
+
.step > .pages {
|
|
1642
|
+
flex: 0 1 auto;
|
|
1643
|
+
min-height: 0;
|
|
1644
|
+
overflow-y: auto;
|
|
1645
|
+
overscroll-behavior: contain;
|
|
1646
|
+
}
|
|
1203
1647
|
|
|
1204
1648
|
/* ---- css/stack.css — sheet stack: .sheet-stack — the menu, sheets stacked, heads peek, tap opens ---- */
|
|
1205
1649
|
/* ============================================================
|
|
@@ -1257,22 +1701,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1257
1701
|
Lh, 2026-09-11). --_n: how many sheets the stack holds, read from the DOM
|
|
1258
1702
|
by :has below so the closed geometry needs no script and no per-stack
|
|
1259
1703
|
declaration. */
|
|
1260
|
-
.sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step:
|
|
1704
|
+
.sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step: calc(var(--tap-base) * 11 / 6); margin-inline: calc(-1 * var(--screen-pad)); --_n: 1; }
|
|
1261
1705
|
.sheet-stack > .card {
|
|
1262
|
-
grid-area: 1 / 1; /* every card shares the stack's cell; later ones offset down */
|
|
1263
|
-
border-bottom-left-radius: 0;
|
|
1264
|
-
border-bottom-right-radius: 0;
|
|
1265
|
-
border-bottom: 0;
|
|
1266
|
-
align-content: start;
|
|
1267
|
-
align-self: start; /* own natural height (padding + head) by default */
|
|
1268
1706
|
--card-gap: var(--sp-5); /* head→content gap = the strip's 20px rhythm */
|
|
1269
|
-
z-index: var(--level, 1); /* explicit paint order, 1 = back of the stack */
|
|
1270
|
-
}
|
|
1271
|
-
/* THE FRONT SHEET is the last .card, not the last child: a --closed stack
|
|
1272
|
-
may carry a .sheet-stack__base after its sheets (below). Written once here
|
|
1273
|
-
as "a card no card follows" so every rule about the front sheet agrees. */
|
|
1274
|
-
.sheet-stack > .card:not(:has(~ .card)) {
|
|
1275
|
-
align-self: stretch; /* the front card fills the remaining stack height */
|
|
1276
1707
|
}
|
|
1277
1708
|
.sheet-stack:has(> .card:nth-child(2)) { --_n: 2; }
|
|
1278
1709
|
.sheet-stack:has(> .card:nth-child(3)) { --_n: 3; }
|
|
@@ -1283,119 +1714,53 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1283
1714
|
script at all. --slot is that place in steps; the tap animation reads it
|
|
1284
1715
|
to know how far up to travel. Five is the practical peek depth. */
|
|
1285
1716
|
.sheet-stack > .card:nth-child(1) { --slot: 0; --level: 1; }
|
|
1286
|
-
.sheet-stack > .card:nth-child(2) { --slot: 1; --level: 2;
|
|
1287
|
-
.sheet-stack > .card:nth-child(3) { --slot: 2; --level: 3;
|
|
1288
|
-
.sheet-stack > .card:nth-child(4) { --slot: 3; --level: 4;
|
|
1289
|
-
.sheet-stack > .card:nth-child(5) { --slot: 4; --level: 5;
|
|
1717
|
+
.sheet-stack > .card:nth-child(2) { --slot: 1; --level: 2; }
|
|
1718
|
+
.sheet-stack > .card:nth-child(3) { --slot: 2; --level: 3; }
|
|
1719
|
+
.sheet-stack > .card:nth-child(4) { --slot: 3; --level: 4; }
|
|
1720
|
+
.sheet-stack > .card:nth-child(5) { --slot: 4; --level: 5; }
|
|
1290
1721
|
|
|
1291
|
-
/* stack headers: uniform — [back?] title left, icons right, one height,
|
|
1292
|
-
same gap to content on every card */
|
|
1293
|
-
.sheet-stack > .card > .card__head {
|
|
1294
|
-
align-items: center;
|
|
1295
|
-
justify-content: flex-start;
|
|
1296
|
-
min-height: var(--tap);
|
|
1297
|
-
}
|
|
1298
1722
|
/* NOT a skeleton head: a peek is one line at its own size, and the display
|
|
1299
1723
|
stack deliberately runs a second line under the next card. */
|
|
1300
1724
|
.sheet-stack > .card > .card__head :is(h1, h2, h3) {
|
|
1301
1725
|
font-size: var(--fs-h1);
|
|
1302
|
-
line-height:
|
|
1303
|
-
letter-spacing: -
|
|
1726
|
+
line-height: var(--lh-solid);
|
|
1727
|
+
letter-spacing: var(--track-h2);
|
|
1304
1728
|
margin-right: auto; /* pins the icon cluster right */
|
|
1305
1729
|
display: block;
|
|
1306
|
-
overflow-wrap: normal;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
/* display stack: the giant title IS the peek — line one shows, line two is
|
|
1310
|
-
clipped by the next card (recents / library lists) */
|
|
1311
|
-
.sheet-stack--display { --stack-step: 6.5rem; }
|
|
1312
|
-
.sheet-stack--display > .card > .card__head { align-items: start; min-height: 0; }
|
|
1313
|
-
.sheet-stack--display > .card > .card__head :is(h1, h2, h3) {
|
|
1314
|
-
font: var(--type-display);
|
|
1315
|
-
letter-spacing: var(--track-display);
|
|
1316
|
-
/* the FIRST line sits on the centre of the peek strip (--stack-step tall,
|
|
1317
|
-
measured from the card's top edge); line two runs under the next card */
|
|
1318
|
-
margin-top: calc((var(--stack-step) - 1lh) / 2 - var(--sp-5));
|
|
1319
|
-
}
|
|
1320
|
-
/* fanned stack: a toggle opens the stack wider so every peek shows more */
|
|
1321
|
-
.sheet-stack--fanned { --stack-step: 9.5rem; }
|
|
1322
|
-
|
|
1323
|
-
/* a peeked card in a plain stack hides its own body. A --tap stack does
|
|
1324
|
-
NOT: there, every card is full height and full content always, and a
|
|
1325
|
-
peek is simply the strip of it the next card doesn't cover — the way a
|
|
1326
|
-
card in a real deck is never a shorter card, just a mostly hidden one. */
|
|
1327
|
-
.sheet-stack:not(.sheet-stack--tap) > .card:has(~ .card):not(.is-open) > :not(.card__head) { display: none; }
|
|
1328
|
-
|
|
1329
|
-
/* ============================================================
|
|
1330
|
-
.sheet-stack--tap — tap a peeked sheet open; a back button returns it.
|
|
1331
|
-
|
|
1332
|
-
A sheet (a .card — the material, sheet.css's single sheet is the same
|
|
1333
|
-
stuff) is rigid: it can move in, move out, or be covered — it can
|
|
1334
|
-
never change shape. So every sheet here is the SAME full height,
|
|
1335
|
-
always, set once and never touched again by anything. A "peek" is
|
|
1336
|
-
not a short sheet; it's a full sheet the next one happens to cover,
|
|
1337
|
-
showing only the strip above it, exactly like a sheet of paper part
|
|
1338
|
-
way out of a real stack.
|
|
1339
|
-
|
|
1340
|
-
Because of that, the whole animation is one property: translate.
|
|
1341
|
-
Nothing resizes, nothing re-stacks — z-index is the plain resting
|
|
1342
|
-
--level order and is never touched, since the order already says
|
|
1343
|
-
what has to happen. Cards after the tapped one sit IN FRONT of it,
|
|
1344
|
-
so they're what's covering it, so they're what must leave. Cards
|
|
1345
|
-
before it are already BEHIND it, so they need do nothing at all
|
|
1346
|
-
except let themselves be covered as it rises over them.
|
|
1347
|
-
|
|
1348
|
-
.is-open — the tapped card, moved up to the top of the stack. Its
|
|
1349
|
-
own resting offset is margin-top (below); opening just
|
|
1350
|
-
translates that offset back off, landing it flush at
|
|
1351
|
-
the stack's top with its full height exactly filling
|
|
1352
|
-
to the bottom.
|
|
1353
|
-
.is-before — an earlier card, nudged one step down so it slips
|
|
1354
|
-
under the next card's resting strip and is covered
|
|
1355
|
-
there. Each carries its own --stagger (set in JS: 0
|
|
1356
|
-
for the one nearest the tapped card, counting up
|
|
1357
|
-
further back) so they tuck away, and later un-tuck,
|
|
1358
|
-
one at a time.
|
|
1359
|
-
.is-after — a later card, moved out below, together, no stagger.
|
|
1360
|
-
============================================================ */
|
|
1361
|
-
.sheet-stack--tap > .card {
|
|
1362
|
-
height: 100%; /* every card identical and full length, always — the one
|
|
1363
|
-
size in this whole component, never toggled, never transitioned. At a
|
|
1364
|
-
resting margin-top of n steps it simply overhangs the stack's bottom
|
|
1365
|
-
by n steps (clipped by .screen--fill), so translating it up by exactly
|
|
1366
|
-
that much lands it filling the stack precisely. */
|
|
1367
|
-
overflow: clip;
|
|
1368
|
-
transition: translate var(--motion-sheet) ease; /* the only animated property here */
|
|
1369
|
-
transition-delay: calc(var(--stagger, 0) * 70ms); /* 0 for everything
|
|
1370
|
-
except a staggered is-before card: the card renders --stagger as it tucks or un-tucks */
|
|
1371
|
-
--_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 */
|
|
1730
|
+
overflow-wrap: normal;
|
|
1372
1731
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1732
|
+
|
|
1733
|
+
/* display stack: the giant title IS the peek — line one shows, line two is
|
|
1734
|
+
clipped by the next card (recents / library lists) */
|
|
1735
|
+
.sheet-stack--display { --stack-step: calc(var(--tap-base) * 13 / 6); }
|
|
1736
|
+
.sheet-stack--display > .card > .card__head :is(h1, h2, h3) {
|
|
1737
|
+
font: var(--type-display);
|
|
1738
|
+
letter-spacing: var(--track-display);
|
|
1739
|
+
/* the FIRST line sits on the centre of the peek strip (--stack-step tall,
|
|
1740
|
+
measured from the card's top edge); line two runs under the next card */
|
|
1741
|
+
margin-top: calc((var(--stack-step) - 1lh) / 2 - var(--sp-5) - var(--_mark-room)); /* --_mark-room: card.css, the room for the marks over line one */
|
|
1380
1742
|
}
|
|
1381
|
-
|
|
1382
|
-
.sheet-stack--
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1743
|
+
/* fanned stack: a toggle opens the stack wider so every peek shows more */
|
|
1744
|
+
.sheet-stack--fanned { --stack-step: calc(var(--tap-base) * 19 / 6); }
|
|
1745
|
+
|
|
1746
|
+
/* a peeked card in a plain stack hides its own body. A --tap stack does
|
|
1747
|
+
NOT: there, every card is full height and full content always, and a
|
|
1748
|
+
peek is simply the strip of it the next card doesn't cover — the way a
|
|
1749
|
+
card in a real deck is never a shorter card, just a mostly hidden one. */
|
|
1750
|
+
.sheet-stack:not(.sheet-stack--tap) > .card:has(~ .card):not(.is-open) > :not(.card__head) { display: none; }
|
|
1751
|
+
|
|
1752
|
+
/* ---- .sheet-stack--tap — tap a peeked sheet open; a back button returns it. How its sheets move (one property: translate) is
|
|
1753
|
+
told over their rules, where they sit, in card.css. ---- */
|
|
1754
|
+
.sheet-stack--tap > .card {
|
|
1755
|
+
--_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 */
|
|
1390
1756
|
}
|
|
1391
|
-
|
|
1392
|
-
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
1757
|
+
|
|
1393
1758
|
/* the GRIP (Lh 2026-10-05): four small dots at the middle of every sheet's top edge — a sign that this card
|
|
1394
1759
|
slides up and down. A mark, not a control: nothing to press, nothing added to the sheet (the card's own background, in the
|
|
1395
1760
|
room above its head). The card renders `.stack-card` when it is a sheet of a tap stack, so the dots are its own.
|
|
1396
1761
|
At rest they are the card's own ink, faint. Only while THAT sheet reloads (`.is-loading`, from its `refreshing` prop,
|
|
1397
|
-
and only while it is open) they dissolve
|
|
1398
|
-
Nothing travels. --_grip is the mix, 0
|
|
1762
|
+
and only while it is open) they dissolve from the ondark ink to the onlight ink, so a light card and a dark one each get a moment the dots stand out.
|
|
1763
|
+
Nothing travels. --_grip is the mix, 0 the ondark ink → 1 the onlight ink, private. */
|
|
1399
1764
|
@property --_grip { syntax: "<number>"; inherits: false; initial-value: 0; }
|
|
1400
1765
|
.stack-card {
|
|
1401
1766
|
--_grip-dot: var(--sp-1);
|
|
@@ -1411,8 +1776,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1411
1776
|
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
1412
1777
|
}
|
|
1413
1778
|
.stack-card.is-open.is-loading {
|
|
1414
|
-
--_grip-ink: color-mix(in srgb,
|
|
1415
|
-
animation: stack-grip-dissolve
|
|
1779
|
+
--_grip-ink: color-mix(in srgb, var(--ink-onlight) calc(var(--_grip) * 100%), var(--ink-ondark));
|
|
1780
|
+
animation: stack-grip-dissolve var(--motion-pulse) var(--ease-in-out) infinite alternate;
|
|
1416
1781
|
}
|
|
1417
1782
|
@keyframes stack-grip-dissolve { from { --_grip: 0; } to { --_grip: 1; } }
|
|
1418
1783
|
/* the grip as ONE LINE (`mark="line"`, Lh 2026-10-06): the single Sheet's line, at the same place and the same length. It reads the
|
|
@@ -1432,22 +1797,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1432
1797
|
@media (prefers-reduced-motion: reduce) {
|
|
1433
1798
|
.stack-card.is-open.is-loading { animation: none; --_grip-ink: currentColor; }
|
|
1434
1799
|
}
|
|
1435
|
-
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
1436
|
-
tappable), so its resting offset would leave its last n steps below the
|
|
1437
|
-
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
|
1438
|
-
alone is cut to the room it actually has, and it scrolls like an opened
|
|
1439
|
-
sheet does. Every other sheet keeps the one full height. */
|
|
1440
|
-
.sheet-stack--tap:not(.sheet-stack--closed) > .card:not(:has(~ .card)) {
|
|
1441
|
-
height: calc(100% - var(--slot, 0) * var(--stack-step));
|
|
1442
|
-
overflow-y: auto;
|
|
1443
|
-
overflow-x: clip;
|
|
1444
|
-
overscroll-behavior: contain;
|
|
1445
|
-
}
|
|
1446
|
-
/* a scrolling sheet shows no scrollbar (a desktop with a mouse would paint one
|
|
1447
|
-
down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself
|
|
1448
|
-
(the hiding rule is base.css's, shared with every other scrolling track) */
|
|
1449
|
-
.sheet-stack--tap > .card.is-before { translate: 0 var(--stack-step); }
|
|
1450
|
-
.sheet-stack--tap > .card.is-after { translate: 0 100vh; }
|
|
1451
1800
|
|
|
1452
1801
|
/* ============================================================
|
|
1453
1802
|
.sheet-stack--closed — nothing open on arrival. Every sheet, the front
|
|
@@ -1464,14 +1813,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1464
1813
|
lands flush at the top. is-before tucks one step below its resting place.
|
|
1465
1814
|
============================================================ */
|
|
1466
1815
|
.sheet-stack--closed { --_headroom: calc(100% - var(--_n) * var(--stack-step)); }
|
|
1467
|
-
.sheet-stack--closed > .card { translate: 0 calc(var(--_headroom) - var(--_lift, 0px)); }
|
|
1468
|
-
.sheet-stack--closed > .card.is-dragging { transition: none; }
|
|
1469
|
-
.sheet-stack--tap:not(.sheet-stack--closed) > .card.is-dragging:not(.is-open) {
|
|
1470
|
-
translate: 0 calc(-1 * var(--_lift, 0px));
|
|
1471
|
-
transition: none;
|
|
1472
|
-
}
|
|
1473
|
-
.sheet-stack--closed > .card.is-before { translate: 0 calc(var(--_headroom) + var(--stack-step)); }
|
|
1474
|
-
.sheet-stack--closed > .card.is-after { translate: 0 100vh; }
|
|
1475
1816
|
|
|
1476
1817
|
/* .sheet-stack__base — the base's content, in the headroom of a closed stack:
|
|
1477
1818
|
behind every sheet (level 0), the stack's inset put back (the stack bleeds),
|
|
@@ -1481,9 +1822,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1481
1822
|
.sheet-stack > .sheet-stack__base {
|
|
1482
1823
|
grid-area: 1 / 1;
|
|
1483
1824
|
align-self: start;
|
|
1484
|
-
z-index:
|
|
1485
|
-
--_under:
|
|
1486
|
-
height: calc(var(--_headroom,
|
|
1825
|
+
z-index: var(--z-flat);
|
|
1826
|
+
--_under: 0rem;
|
|
1827
|
+
height: calc(var(--_headroom, 0rem) + var(--_under));
|
|
1487
1828
|
padding-inline: var(--screen-pad);
|
|
1488
1829
|
--_gap: var(--card-gap, var(--sp-5));
|
|
1489
1830
|
display: grid;
|
|
@@ -1500,7 +1841,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1500
1841
|
/* a base that is ONE Centre is one thing handed over (a note, an empty state): it stands in the MIDDLE of the room above the peeks
|
|
1501
1842
|
(Lh 2026-10-06). The base already keeps the screen's inset, so the centre block gives its own back. */
|
|
1502
1843
|
.sheet-stack__base:has(> .screen__centre:only-child) { align-content: center; }
|
|
1503
|
-
.sheet-stack__base > .screen__centre { padding-inline: 0; }
|
|
1504
1844
|
/* loose things rest against the peeks, so the room under the last one is the room between two of them (Lh 2026-10-09: a card's
|
|
1505
1845
|
bottom edge lay on the first sheet's top edge). A lone Page reads from the top and a lone Centre stands in the middle: neither rests there. */
|
|
1506
1846
|
.sheet-stack__base:not(:has(> .page:only-child, > .screen__centre:only-child)) {
|
|
@@ -1523,9 +1863,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1523
1863
|
overscroll-behavior: contain;
|
|
1524
1864
|
touch-action: pan-y;
|
|
1525
1865
|
}
|
|
1526
|
-
/* a card standing in this room is FLAT (Lh 2026-10-09): the room clips at its own edges, and a card's shadow cut there drew a straight
|
|
1527
|
-
line under the bar and along the first sheet. Its ground and its radius are what say "card" here. */
|
|
1528
|
-
:is([data-palette], [data-finish]) .sheet-stack__base .card { box-shadow: none; }
|
|
1529
1866
|
|
|
1530
1867
|
/* ============================================================
|
|
1531
1868
|
.sheet-stack--flood — a closed stack whose open sheet FLOODS the screen: it rises over the top bar and fills the whole screen
|
|
@@ -1547,38 +1884,17 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1547
1884
|
============================================================ */
|
|
1548
1885
|
.screen--fill:has(> .sheet-stack--flood) { --_bar-gap: var(--sp-2); }
|
|
1549
1886
|
.screen--fill:has(> .sheet-stack--flood):has(> .topbar:first-child) { --_bar-gap: var(--screen-gap); }
|
|
1550
|
-
.screen--fill:has(> .sheet-stack--flood) > :is(.appbar, .topbar):first-child {
|
|
1551
|
-
position: absolute;
|
|
1552
|
-
inset: var(--safe-top) 0 auto;
|
|
1553
|
-
max-width: none;
|
|
1554
|
-
margin: 0;
|
|
1555
|
-
z-index: 0; /* under every sheet (--level 1-5); the base clears it by margin */
|
|
1556
|
-
}
|
|
1557
1887
|
.sheet-stack--flood { --_bar: calc(var(--top-h, var(--header-h)) + var(--_bar-gap)); }
|
|
1558
1888
|
.sheet-stack--flood > .sheet-stack__base {
|
|
1559
1889
|
margin-top: var(--_bar);
|
|
1560
|
-
height: calc(var(--_headroom,
|
|
1890
|
+
height: calc(var(--_headroom, 0rem) - var(--_bar) + var(--_under));
|
|
1561
1891
|
}
|
|
1562
|
-
.screen--fill:has(> .sheet-stack--flood > .stack-card.is-open, > .sheet-stack--flood > :not(.stack-card) > .stack-card.is-open) > :is(.appbar, .topbar):first-child {
|
|
1563
|
-
visibility: hidden;
|
|
1564
|
-
transition: visibility 0s linear var(--motion-sheet); /* gone once the sheet has covered it; back at once on a fold (the rule above has no delay) */
|
|
1565
|
-
}
|
|
1566
|
-
/* at the screen's top the open sheet's top corners are SQUARE (Lh 2026-10-08): round, they left two small pockets of the
|
|
1567
|
-
bar's ground at the glass. They square as the sheet rises and round again as it folds, in the slide's own time. Paint
|
|
1568
|
-
only, the sheet's size is untouched. A pull on the head keeps them square until the finger lets go. */
|
|
1569
|
-
.sheet-stack--flood > .card { transition: translate var(--motion-sheet) ease, border-radius var(--motion-sheet) ease; }
|
|
1570
|
-
.sheet-stack--flood > .card.is-open { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
1571
1892
|
|
|
1572
1893
|
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
1573
1894
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
1574
1895
|
slide read as a second thing moving while the sheet itself moved; Lh,
|
|
1575
1896
|
2026-09-11). Only the sheet travels; the button appears where it is. */
|
|
1576
1897
|
/* a sheet's OWN head and back: the card is the stack's child or, with one host wrapper, the wrapper's. A card standing in the sheet's body wears its own head */
|
|
1577
|
-
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head {
|
|
1578
|
-
position: relative;
|
|
1579
|
-
padding-left: 0;
|
|
1580
|
-
transition: padding-left var(--motion-sheet) ease;
|
|
1581
|
-
}
|
|
1582
1898
|
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head > .sheet-stack__back {
|
|
1583
1899
|
position: absolute;
|
|
1584
1900
|
left: 0;
|
|
@@ -1586,7 +1902,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1586
1902
|
translate: 0 -50%;
|
|
1587
1903
|
opacity: 0;
|
|
1588
1904
|
visibility: hidden;
|
|
1589
|
-
transition: opacity var(--motion-sheet) ease, visibility var(--motion-sheet);
|
|
1905
|
+
transition: opacity var(--motion-sheet) var(--ease), visibility var(--motion-sheet);
|
|
1590
1906
|
/* SMALL (see "the back button, SMALL" below) */
|
|
1591
1907
|
width: var(--_back-size);
|
|
1592
1908
|
height: var(--_back-size);
|
|
@@ -1599,63 +1915,16 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1599
1915
|
visibility: visible;
|
|
1600
1916
|
}
|
|
1601
1917
|
|
|
1602
|
-
/* ---- a stacked sheet with a SheetBody has the ONE sheet shape (Lh 2026-09-23, the home layout): sheet head
|
|
1603
|
-
pinned at the top · sheet body scrolls, as long as it needs · sheet foot (a CardFoot, the CTA) at the bottom.
|
|
1604
|
-
The card becomes a flex column and stops scrolling itself — only the body does. A sheet with no SheetBody
|
|
1605
|
-
keeps the old way: the whole open card scrolls. ---- */
|
|
1606
|
-
.sheet-stack--tap > .card:has(> .sheet__body) { display: flex; flex-direction: column; gap: var(--card-gap); }
|
|
1607
|
-
.sheet-stack--tap > .card:has(> .sheet__body).is-open { overflow: clip; }
|
|
1608
1918
|
.sheet-stack > .card:has(> .sheet__body) > :not(.sheet__body) { flex-shrink: 0; }
|
|
1609
1919
|
.sheet-stack > .card > .sheet__body { --_cover: 0rem; } /* nothing slid under here: the stack's sheet is always whole */
|
|
1610
|
-
/* a
|
|
1611
|
-
|
|
1612
|
-
shows in that pocket. Open, the body is white, and the first row sits --sp-6 down, the same white
|
|
1613
|
-
inset a single paper sheet keeps: the flex gap under the head is the card's colour, so the row
|
|
1614
|
-
would otherwise sit on that seam. A pull adds --_refresh on top of the inset. On the way back
|
|
1615
|
-
into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
|
|
1616
|
-
sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
|
|
1617
|
-
at the moment the sheet stops. Rising, the body is white at once. */
|
|
1618
|
-
/* the head's band is as slim as it can be (Lh 2026-10-06: 20 over · 48 head · 20 under read as a heavy bar): the white starts
|
|
1619
|
-
--sp-3 under the head, not a whole --card-gap (--sp-2 at first; Lh added 4 the same day). The body is pulled up by the difference — the card's gap is left alone, the
|
|
1620
|
-
stack's step and the body's own rows read it. */
|
|
1621
|
-
.sheet-stack > .card.sheet--paper > .sheet__body {
|
|
1622
|
-
margin-top: calc(var(--sp-3) - var(--card-gap));
|
|
1623
|
-
padding-top: calc(var(--sp-6) + var(--_refresh, 0px));
|
|
1624
|
-
transition: padding-top var(--motion-sheet) ease, background-color 0s linear;
|
|
1625
|
-
}
|
|
1626
|
-
/* a pull follows the finger at once, on paper too: the rule above is as strong as the stack's own `is-refreshing` one and comes later */
|
|
1627
|
-
.sheet-stack > .card.sheet--paper > .sheet__body.is-refreshing { transition: none; }
|
|
1628
|
-
.sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
1629
|
-
background-color: transparent;
|
|
1630
|
-
transition:
|
|
1631
|
-
padding-top var(--motion-sheet) ease,
|
|
1632
|
-
background-color var(--motion-quick) ease calc(var(--stagger, 0) * 70ms + var(--motion-sheet) - var(--motion-quick));
|
|
1633
|
-
}
|
|
1634
|
-
@media (prefers-reduced-motion: reduce) {
|
|
1635
|
-
.sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
1636
|
-
transition: padding-top var(--motion-sheet) ease, background-color 0s linear;
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
/* a paper stack's foot is white to the card's edges, and it covers the tinted flex gap above it so the
|
|
1640
|
-
button does not sit on a colour stripe. The head's gap stays: it is outside this foot. The button stays
|
|
1641
|
-
where it was — the padding gives back what the bleed takes. */
|
|
1642
|
-
.sheet-stack > .card.sheet--paper > .card__foot {
|
|
1643
|
-
background: var(--color-paper, #fff);
|
|
1644
|
-
color: var(--color-ink, #141414);
|
|
1645
|
-
margin-top: calc(-1 * var(--card-gap));
|
|
1646
|
-
margin-inline: calc(-1 * var(--card-pad));
|
|
1647
|
-
margin-bottom: calc(-1 * var(--card-pad));
|
|
1648
|
-
padding-top: var(--card-gap);
|
|
1649
|
-
padding-inline: var(--card-pad);
|
|
1650
|
-
padding-bottom: var(--card-pad);
|
|
1651
|
-
}
|
|
1920
|
+
/* a paper stack's foot is paper (card.css paints it): its ink reads the action's side */
|
|
1921
|
+
.sheet-stack > .card.sheet--paper > .card__foot { --dark: var(--action-dark); }
|
|
1652
1922
|
|
|
1653
1923
|
/* ---- StackSheet's tabs (Lh 2026-09-23): the head's end is a row of small icon buttons, the sheet's pages — the chosen one
|
|
1654
1924
|
filled (.icon-btn--invert) only while the sheet is open; a count is a small circle over the icon's top right corner ---- */
|
|
1655
1925
|
.stack-sheet__tabs { display: flex; gap: var(--stack-sheet-tabs-gap, var(--sp-2)); flex: none; } /* room between the tabs: with none, the chosen tab's disc touched its neighbour's (Lh 2026-09-24) */
|
|
1656
1926
|
/* the head is ONE line, always (Lh 2026-09-24): the tabs keep their size, the TITLE gives — it never wraps, it runs sideways
|
|
1657
1927
|
(StackSheet measures --_over, the width it overflows by, and sets .is-long), one way: it waits, runs to the end, waits, and starts again from the first letter — like a song title */
|
|
1658
|
-
.stack-sheet > .card__head { flex-wrap: nowrap; }
|
|
1659
1928
|
.stack-sheet__title {
|
|
1660
1929
|
flex: 1; min-width: 0; overflow-x: clip; overflow-y: visible; white-space: nowrap; overflow-wrap: normal; /* clip SIDEWAYS only:
|
|
1661
1930
|
hidden clipped the Thai tone marks above a line-height-1 line (เพื่อน lost its ไม้เอก — Lh 2026-09-24) */
|
|
@@ -1664,7 +1933,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1664
1933
|
}
|
|
1665
1934
|
.stack-sheet__run { display: inline-block; }
|
|
1666
1935
|
.stack-sheet__title.is-long .stack-sheet__run {
|
|
1667
|
-
animation: stack-sheet-run var(--stack-sheet-run-time, var(--_run,
|
|
1936
|
+
animation: stack-sheet-run var(--stack-sheet-run-time, var(--_run, var(--motion-run))) var(--ease-in-out) infinite; /* ONE way: start → end, then back to the start at once (Lh 2026-09-24) */ /* --_run: from the overflow, 30px a second + the pauses */
|
|
1668
1937
|
}
|
|
1669
1938
|
@keyframes stack-sheet-run { 0%, 20% { translate: 0; } 85%, 100% { translate: calc(-1 * var(--_over)); } }
|
|
1670
1939
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1677,11 +1946,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1677
1946
|
/* the disc fades with the sheet: in as it rises, out as it folds, and across when another tab is pressed.
|
|
1678
1947
|
scale stays on the press (actions.css) so the give under a finger stays quick */
|
|
1679
1948
|
transition:
|
|
1680
|
-
scale var(--motion-press) ease-out,
|
|
1681
|
-
background-color var(--motion-sheet) ease,
|
|
1682
|
-
color var(--motion-sheet) ease,
|
|
1683
|
-
border-color var(--motion-sheet) ease,
|
|
1684
|
-
box-shadow var(--motion-sheet) ease;
|
|
1949
|
+
scale var(--motion-press) var(--ease-out),
|
|
1950
|
+
background-color var(--motion-sheet) var(--ease),
|
|
1951
|
+
color var(--motion-sheet) var(--ease),
|
|
1952
|
+
border-color var(--motion-sheet) var(--ease),
|
|
1953
|
+
box-shadow var(--motion-sheet) var(--ease);
|
|
1685
1954
|
}
|
|
1686
1955
|
.stack-sheet__tab > .stack-sheet__count { /* outranks .badge (numbers.css loads after this file) */
|
|
1687
1956
|
position: absolute;
|
|
@@ -1694,7 +1963,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1694
1963
|
padding: 0 var(--stack-sheet-count-pad-x, 0.25em) var(--stack-sheet-count-lift, 0.1em); /* "99+" keeps its room at every text size;
|
|
1695
1964
|
the room UNDER the number lifts it onto the circle's middle: a digit has no descender, so a centred line box sets it low (Lh 2026-10-01) */
|
|
1696
1965
|
font: var(--type-caption);
|
|
1697
|
-
line-height:
|
|
1966
|
+
line-height: var(--lh-solid);
|
|
1698
1967
|
pointer-events: none;
|
|
1699
1968
|
}
|
|
1700
1969
|
/* a tab not chosen is the icon alone — no ring, no ground (Lh 2026-09-23: "the circle around is not pretty"); the chosen one turns
|
|
@@ -1710,26 +1979,25 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1710
1979
|
margin around it reaches into the card's padding — and an open head (back · title · three tabs) keeps one line (Lh 2026-09-23) */
|
|
1711
1980
|
.card__head > .stack-sheet__tabs { margin-right: calc(-1 * var(--sp-2)); }
|
|
1712
1981
|
|
|
1713
|
-
/* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
|
|
1714
|
-
tappable"): a SMALL DISC — its ring kept, 32px instead of 44, the chevron in it at 20 — so the head gives it 32px + a gap
|
|
1715
|
-
(was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
|
|
1716
|
-
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back:not(.sheet-stack__back--end)) { padding-left: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
1717
1982
|
/* AT THE END (Lh 2026-09-29, the home): after the tabs, chevron down — the open sheet folds down. The head's end gives it its room
|
|
1718
1983
|
while open; the last tab then sits on the inset (no reach into the padding) */
|
|
1719
1984
|
.sheet-stack--tap .sheet-stack__back--end { left: auto; right: 0; }
|
|
1720
|
-
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) ease; }
|
|
1721
|
-
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) { padding-right: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
1722
1985
|
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) > .stack-sheet__tabs { margin-right: 0; }
|
|
1723
|
-
.sheet-stack--tap .sheet-stack__back > .icon {
|
|
1986
|
+
.sheet-stack--tap .sheet-stack__back > .icon { --icon-size: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
|
|
1724
1987
|
.sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--_back-size) - var(--tap-sm)) / 2); }
|
|
1725
1988
|
|
|
1726
1989
|
/* the open sheet's title folds it (SheetStack.tsx): it says so to a pointer */
|
|
1727
1990
|
.sheet-stack--tap > .card.is-open > .card__head > :is(h1, h2, h3) { cursor: pointer; }
|
|
1728
|
-
/* a stacked sheet is a control until it is open — tapped, never read into: no text selection (a drag, a double tap, a long press),
|
|
1729
|
-
no grey flash, no iOS callout; its head stays one when open. Only the OPEN sheet's body is text to select (Lh 2026-09-23) */
|
|
1730
|
-
.sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
1731
1991
|
.sheet-stack--tap > .card.is-open > :not(.card__head) { -webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); } /* the open sheet reads as the rest of the screen: not selectable (Lh 2026-09-25; it was text) */
|
|
1732
1992
|
|
|
1993
|
+
/* ---- where it sits in what base.css draws (docs/reference.md rule 15) ---- */
|
|
1994
|
+
/* min-height: 0 — a flex item's floor is its content, and the stack's content
|
|
1995
|
+
is its own full-height sheets, so without it the stack grew to the whole
|
|
1996
|
+
screen and its open sheet ran past the bottom edge by the bars' height,
|
|
1997
|
+
unreachable by scrolling (timebank home, 2026-09-13). The stack IS the room
|
|
1998
|
+
left under the bars; every sheet fills exactly that. */
|
|
1999
|
+
.screen--fill > .sheet-stack { flex: 1; min-height: 0; }
|
|
2000
|
+
|
|
1733
2001
|
/* ---- css/sheet.css — sheet single: .sheet — one card as a bottom sheet, peek / half / full ---- */
|
|
1734
2002
|
/* ============================================================
|
|
1735
2003
|
cardds/sheet.css — sheet single: one card as a bottom sheet.
|
|
@@ -1804,7 +2072,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1804
2072
|
inset: 0;
|
|
1805
2073
|
top: var(--_top, 0); /* a stage keeps its top bar's line free — see .sheet-stage */
|
|
1806
2074
|
translate: 0 calc(var(--_y) + var(--_drag, 0rem)); /* --_drag: the Sheet component, only while the handle is held */
|
|
1807
|
-
transition: translate var(--motion-sheet) ease;
|
|
2075
|
+
transition: translate var(--motion-sheet) var(--ease);
|
|
1808
2076
|
|
|
1809
2077
|
/* handle · head · body: a column, the body takes what is left and scrolls
|
|
1810
2078
|
(below) — so the handle and the head never move, whatever the content */
|
|
@@ -1837,7 +2105,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1837
2105
|
that holds that way's WHOLE form, its submit on the sheet's foot, leading to the next page. The base
|
|
1838
2106
|
carries no control. It is NOT for a form taken apart — one sheet per field, a "send" left on the base:
|
|
1839
2107
|
a form lives whole in one sheet. */
|
|
1840
|
-
.sheet--away { --_y: 100%; --_cover: var(--_box); visibility: hidden; transition: translate var(--motion-sheet) ease, visibility 0s linear var(--motion-sheet); }
|
|
2108
|
+
.sheet--away { --_y: 100%; --_cover: var(--_box); visibility: hidden; transition: translate var(--motion-sheet) var(--ease), visibility 0s var(--ease-linear) var(--motion-sheet); }
|
|
1841
2109
|
.sheet.is-dragging { transition: none; } /* follows the finger; the snap gets the ease back */
|
|
1842
2110
|
|
|
1843
2111
|
/* the body — everything under the head, the one thing in a sheet that
|
|
@@ -1882,23 +2150,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1882
2150
|
One row, ≤ 2 buttons. <div class="card__foot"><button class="btn btn--primary btn--block">…</button></div> */
|
|
1883
2151
|
/* …and the link row under the button, when there is one (its line + the gap over it) */
|
|
1884
2152
|
.sheet:has(> .card__foot--link) { --_foot: calc(var(--card-gap) + var(--sheet-foot-h, calc(var(--btn-h) + var(--sp-1) + var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)))) + var(--card-pad) + var(--_safe-b-open)); } /* --sheet-foot-h: the foot's rows as the Sheet measured them (a button grows with the text size) */
|
|
1885
|
-
.sheet > .card__foot {
|
|
1886
|
-
position: absolute; inset: calc(-1 * var(--border-w)) 0 0; z-index: 1; /* the sheet's border box: the same height the sheet's own % reads */
|
|
1887
|
-
align-items: end;
|
|
1888
|
-
align-content: end;
|
|
1889
|
-
padding: 0 var(--card-pad) calc(var(--card-pad) + var(--_safe-b-open));
|
|
1890
|
-
background: linear-gradient(var(--card-bg), var(--card-bg)) bottom / 100% var(--_foot) no-repeat;
|
|
1891
|
-
pointer-events: none;
|
|
1892
|
-
translate: 0 calc(-1 * var(--_y) - var(--_drag, 0rem));
|
|
1893
|
-
transition: translate var(--motion-sheet) ease, visibility 0s;
|
|
1894
|
-
}
|
|
1895
2153
|
.sheet > .card__foot > * { pointer-events: auto; }
|
|
1896
2154
|
.sheet:has(> .card__foot) > .sheet__body { margin-bottom: calc(var(--_foot) - var(--card-pad)); } /* the body stops above the strip (the card's own padding is the rest) */
|
|
1897
|
-
.sheet.is-dragging > .card__foot { transition: none; }
|
|
1898
|
-
/* at peek and away the foot is NOT held on the box's edge: it stands on the sheet's own bottom edge (no counter-slide), so it
|
|
1899
|
-
LEAVES AND ARRIVES WITH ITS SHEET — solid the whole way, hidden only once the slide is over. It used to stay in place and
|
|
1900
|
-
fade: the body slid through the strip behind a half-transparent button (timebank, Lh 2026-10-01: "ตัวหนังสือลอดใต้ปุ่ม"). */
|
|
1901
|
-
:is(.sheet--peek, .sheet--away) > .card__foot { translate: 0 0; visibility: hidden; transition: translate var(--motion-sheet) ease, visibility 0s linear var(--motion-sheet); }
|
|
1902
2155
|
/* that padding can be taller than the body's own box (a short box: a small phone with the keyboard up) and a
|
|
1903
2156
|
box is never smaller than its padding — so the pinned parts must not give way to it: the body runs past
|
|
1904
2157
|
the sheet's clipped edge instead, where nothing shows anyway. At peek and away no part of the body shows. */
|
|
@@ -1915,14 +2168,14 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1915
2168
|
touch-action: none hands a touch drag to the Sheet component instead of to the scroller. */
|
|
1916
2169
|
.sheet__handle {
|
|
1917
2170
|
position: absolute;
|
|
1918
|
-
z-index:
|
|
2171
|
+
z-index: var(--z-over);
|
|
1919
2172
|
top: calc((var(--card-pad) - var(--sp-1)) / 2); /* the dots' top: (pad − 4) / 2. The line starts there too */
|
|
1920
2173
|
left: 50%;
|
|
1921
2174
|
translate: -50% 0;
|
|
1922
2175
|
border: 0;
|
|
1923
2176
|
color: inherit; /* a <button> has its own UA colour; the mark is the card's ink */
|
|
1924
2177
|
background: currentColor;
|
|
1925
|
-
opacity:
|
|
2178
|
+
opacity: var(--fade-dim);
|
|
1926
2179
|
width: calc(var(--sp-base) * 18); /* 72: the line. The grab is the ::before, not this width */
|
|
1927
2180
|
height: var(--_handle);
|
|
1928
2181
|
border-radius: var(--sheet-handle-radius, var(--r-chip));
|
|
@@ -1931,22 +2184,22 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1931
2184
|
touch-action: none;
|
|
1932
2185
|
cursor: grab;
|
|
1933
2186
|
}
|
|
1934
|
-
/* paper: the head keeps the sheet's own colour, the content is
|
|
2187
|
+
/* paper: the head keeps the sheet's own colour, the content is the theme's paper, --action-bg, the ground of every field (Lh 2026-10-05: white; Lh 2026-10-20: the theme's, never a fixed white).
|
|
1935
2188
|
The body and the foot read as paper; the handle and the head stay on the card.
|
|
1936
2189
|
The flex gap under the head is outside the body, so it stays the sheet's tint.
|
|
1937
|
-
The first field would sit on that colour seam. The
|
|
2190
|
+
The first field would sit on that colour seam. The paper's inset is --sp-6 (24px at
|
|
1938
2191
|
the default spacing), the body's own top padding, and only when the sheet is paper.
|
|
1939
2192
|
A plain sheet keeps padding-top 0. A sheet in a stack wears the same class and the same
|
|
1940
|
-
inset: the stack's own gap under the head is the card's colour, so the row still needs this
|
|
1941
|
-
The paper
|
|
2193
|
+
inset: the stack's own gap under the head is the card's colour, so the row still needs this paper.
|
|
2194
|
+
The paper takes the side of the controls' paper (--action-dark): on a dark sheet its words still turn to onlight. */
|
|
1942
2195
|
.sheet--paper > .sheet__body {
|
|
1943
|
-
--card-bg: var(--
|
|
1944
|
-
--dark: var(--
|
|
2196
|
+
--card-bg: var(--action-bg);
|
|
2197
|
+
--dark: var(--action-dark);
|
|
1945
2198
|
background: var(--card-bg);
|
|
1946
2199
|
color: var(--card-ink);
|
|
1947
2200
|
padding-top: var(--sp-6);
|
|
1948
2201
|
}
|
|
1949
|
-
.sheet--paper > .card__foot { --card-bg: var(--
|
|
2202
|
+
.sheet--paper > .card__foot { --card-bg: var(--action-bg); --dark: var(--action-dark); --card-foot-ink: var(--card-ink); }
|
|
1950
2203
|
.sheet__handle--dots {
|
|
1951
2204
|
--_dot: var(--sp-1);
|
|
1952
2205
|
--_step: calc(var(--_dot) * 2.5);
|
|
@@ -1963,15 +2216,15 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1963
2216
|
background-position: 0 0, var(--_step) 0, calc(2 * var(--_step)) 0, calc(3 * var(--_step)) 0;
|
|
1964
2217
|
}
|
|
1965
2218
|
/* the app reloads what the sheet shows (`refreshing`, off unless set): the mark changes colour exactly as a stack sheet's grip
|
|
1966
|
-
does —
|
|
2219
|
+
does — the ondark ink → the onlight ink and back, so a light sheet and a dark one each get a moment it stands out. --_grip and the keyframes are
|
|
1967
2220
|
the stack's (stack.css): one sign for both. Nothing travels. */
|
|
1968
2221
|
.sheet.is-loading > .sheet__handle {
|
|
1969
2222
|
opacity: 1;
|
|
1970
|
-
background-color: color-mix(in srgb,
|
|
1971
|
-
animation: stack-grip-dissolve
|
|
2223
|
+
background-color: color-mix(in srgb, var(--ink-onlight) calc(var(--_grip) * 100%), var(--ink-ondark));
|
|
2224
|
+
animation: stack-grip-dissolve var(--motion-pulse) var(--ease-in-out) infinite alternate;
|
|
1972
2225
|
}
|
|
1973
2226
|
.sheet.is-loading > .sheet__handle--dots {
|
|
1974
|
-
--_ink: color-mix(in srgb,
|
|
2227
|
+
--_ink: color-mix(in srgb, var(--ink-onlight) calc(var(--_grip) * 100%), var(--ink-ondark));
|
|
1975
2228
|
background-color: transparent;
|
|
1976
2229
|
}
|
|
1977
2230
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1995,18 +2248,18 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1995
2248
|
- a label .row with its .check / .toggle may be the head when the sheet's
|
|
1996
2249
|
whole ask is that switch: peek shows it (a row is --tap tall plus its own
|
|
1997
2250
|
padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
|
|
1998
|
-
.sheet > .card__head { min-
|
|
2251
|
+
.sheet > .card__head { --card-head-min-h: var(--sheet-head-h); }
|
|
1999
2252
|
/* a sheet's title is ONE line, always (Lh 2026-09-28, timebank #153: "ข้อกำหนดและเงื่อนไขการใช้งาน" wrapped to two lines at 390px) — the
|
|
2000
2253
|
thing at its end keeps its place beside it; a caption still takes its own row. A title too long for the line RUNS sideways
|
|
2001
2254
|
like a song title (Lh 2026-10-03, as the StackSheet's — it was cut with …, and the end of it could never be read): the Sheet
|
|
2002
2255
|
measures --_over, the width it overflows by, and sets .is-long; one way — it waits, runs to its end, waits, and starts again
|
|
2003
2256
|
from the first letter. The move is text-indent: the heading is the app's own element, there is no inner box to slide.
|
|
2004
2257
|
Before the Sheet has measured, and where motion is off, it is cut with …. */
|
|
2005
|
-
.sheet > .card__head:not(:has(> :is(.t-caption, .t-body))) {
|
|
2258
|
+
.sheet > .card__head:not(:has(> :is(.t-caption, .t-body))) { --card-head-wrap: nowrap; }
|
|
2006
2259
|
.sheet > .card__head > :is(h1, h2, h3):first-child, .sheet__title { white-space: nowrap; overflow-x: clip; overflow-y: visible; text-overflow: ellipsis; min-width: 0; } /* clip SIDEWAYS only: hidden cut the Thai tone marks over the line */
|
|
2007
2260
|
.sheet > .card__head > :is(h1, h2, h3):first-child.is-long, .sheet__title.is-long {
|
|
2008
2261
|
text-overflow: clip;
|
|
2009
|
-
animation: sheet-title-run var(--sheet-title-run-time, var(--_run,
|
|
2262
|
+
animation: sheet-title-run var(--sheet-title-run-time, var(--_run, var(--motion-run))) var(--ease-in-out) infinite; /* --_run: from the overflow, 30px a second + the pauses */
|
|
2010
2263
|
}
|
|
2011
2264
|
@keyframes sheet-title-run { 0%, 20% { text-indent: 0; } 85%, 100% { text-indent: calc(-1 * var(--_over)); } }
|
|
2012
2265
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -2097,11 +2350,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2097
2350
|
isolation: isolate; /* the ring and the hint stack inside the map only — a sheet over the map covers them */
|
|
2098
2351
|
color: var(--map-ink, var(--on-surface));
|
|
2099
2352
|
background:
|
|
2100
|
-
repeating-linear-gradient(
|
|
2101
|
-
repeating-linear-gradient(-
|
|
2353
|
+
repeating-linear-gradient(var(--street-1), transparent 0 calc(var(--tap-base) * 11 / 6), var(--hairline) calc(var(--tap-base) * 11 / 6) calc(var(--tap-base) * 25 / 12)),
|
|
2354
|
+
repeating-linear-gradient(var(--street-2), transparent 0 calc(var(--tap-base) * 35 / 12), var(--ink-tint) calc(var(--tap-base) * 35 / 12) calc(var(--tap-base) * 37 / 12)),
|
|
2102
2355
|
var(--card-2-bg);
|
|
2103
2356
|
}
|
|
2104
|
-
.map > :first-child { position: absolute; inset: 0; z-index:
|
|
2357
|
+
.map > :first-child { position: absolute; inset: 0; z-index: var(--z-flat); isolation: isolate; background: transparent; } /* its own stacking context: an engine's layered panes stay under the ring; an engine's own grey ground would hide the pattern while tiles load */
|
|
2105
2358
|
.map__ring {
|
|
2106
2359
|
position: absolute;
|
|
2107
2360
|
left: 50%;
|
|
@@ -2116,7 +2369,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2116
2369
|
background: var(--map-ring-bg, var(--accent-soft));
|
|
2117
2370
|
box-shadow: var(--map-ring-shadow, var(--shadow-float));
|
|
2118
2371
|
pointer-events: none;
|
|
2119
|
-
z-index:
|
|
2372
|
+
z-index: var(--z-over); /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
|
|
2120
2373
|
}
|
|
2121
2374
|
.map__pin { /* mark="pin": an exact point — the pin's TIP is where the ring's centre sits, so the engine reads the element's bottom-centre */
|
|
2122
2375
|
position: absolute;
|
|
@@ -2131,11 +2384,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2131
2384
|
color: var(--map-pin-ink, var(--card-ink));
|
|
2132
2385
|
filter: drop-shadow(var(--map-pin-drop, var(--shadow-float)));
|
|
2133
2386
|
pointer-events: none;
|
|
2134
|
-
z-index:
|
|
2387
|
+
z-index: var(--z-over);
|
|
2135
2388
|
}
|
|
2136
2389
|
/* the lucide tip stops 2.2 of 24 short of the box's bottom: lowered so the tip IS the bottom-centre; filled with the surface so the
|
|
2137
2390
|
street pattern never shows through the pin's body */
|
|
2138
|
-
.map__pin > .icon {
|
|
2391
|
+
.map__pin > .icon { --icon-size: 100%; --icon-shift: 0 9%; }
|
|
2139
2392
|
.map__credit { /* the tile provider's credit — required by OSM; a caption, not the engine's own control */
|
|
2140
2393
|
position: absolute;
|
|
2141
2394
|
right: var(--sp-2);
|
|
@@ -2146,7 +2399,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2146
2399
|
color: var(--map-credit-ink, var(--on-surface-muted));
|
|
2147
2400
|
font: var(--map-credit-font, var(--type-caption));
|
|
2148
2401
|
pointer-events: none;
|
|
2149
|
-
z-index:
|
|
2402
|
+
z-index: var(--z-over);
|
|
2150
2403
|
}
|
|
2151
2404
|
.map__hint {
|
|
2152
2405
|
position: absolute;
|
|
@@ -2164,7 +2417,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2164
2417
|
text-align: center;
|
|
2165
2418
|
box-shadow: var(--map-hint-shadow, var(--shadow-float));
|
|
2166
2419
|
pointer-events: none;
|
|
2167
|
-
z-index:
|
|
2420
|
+
z-index: var(--z-over);
|
|
2168
2421
|
}
|
|
2169
2422
|
|
|
2170
2423
|
/* ---- the map IN FLOW (Lh 2026-10-17): `.map--inline` — a box in a card, or the whole of a sheet's body ----
|
|
@@ -2194,7 +2447,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2194
2447
|
name; the picked one fills with accent. Above the ring: the ring takes no taps, the places do. */
|
|
2195
2448
|
.map-pick {
|
|
2196
2449
|
position: absolute;
|
|
2197
|
-
z-index: 2;
|
|
2450
|
+
z-index: var(--z-over-2);
|
|
2198
2451
|
width: var(--tap);
|
|
2199
2452
|
height: var(--tap);
|
|
2200
2453
|
translate: -50% -50%;
|
|
@@ -2225,7 +2478,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2225
2478
|
background: var(--map-pick-name-bg, color-mix(in srgb, var(--surface) 85%, transparent));
|
|
2226
2479
|
font: var(--map-pick-font, var(--type-caption));
|
|
2227
2480
|
}
|
|
2228
|
-
.map-pick:has(:checked) { z-index: 3; } /* the picked place's name lies over a neighbour's */
|
|
2481
|
+
.map-pick:has(:checked) { z-index: var(--z-over-3); } /* the picked place's name lies over a neighbour's */
|
|
2229
2482
|
.map-pick:has(:checked) > :is(.map-pick__mark, .map-pick__name) { background: var(--map-pick-on-bg, var(--accent)); color: var(--map-pick-on-ink, var(--on-accent)); }
|
|
2230
2483
|
.map-pick:has(:focus-visible) > .map-pick__mark { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
2231
2484
|
.map-pick:has(:disabled) { opacity: var(--off); cursor: default; }
|
|
@@ -2250,8 +2503,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2250
2503
|
/* words on the scrim: white on a strip of the scrim over it. The scrim alone is half black, so over a white page it is #808080, where white
|
|
2251
2504
|
is 3.9:1; doubled, #404040, 10:1. It read --card-3-ink, a brand's light ink, on a black scrim */
|
|
2252
2505
|
background: var(--sheet-stage-foot-bg, var(--scrim));
|
|
2253
|
-
color: var(--sheet-stage-foot-ink,
|
|
2254
|
-
z-index: 2;
|
|
2506
|
+
color: var(--sheet-stage-foot-ink, var(--ink-ondark));
|
|
2507
|
+
z-index: var(--z-over-2);
|
|
2255
2508
|
}
|
|
2256
2509
|
|
|
2257
2510
|
/* dim stage: blur + darken whatever sits behind the sheet */
|
|
@@ -2261,9 +2514,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2261
2514
|
position: absolute;
|
|
2262
2515
|
inset: 0;
|
|
2263
2516
|
background: var(--scrim);
|
|
2264
|
-
z-index:
|
|
2517
|
+
z-index: var(--z-over);
|
|
2265
2518
|
}
|
|
2266
|
-
.sheet-stage--dim .sheet { z-index: 2; }
|
|
2519
|
+
.sheet-stage--dim .sheet { z-index: var(--z-over-2); }
|
|
2267
2520
|
|
|
2268
2521
|
/* ---- sheet steps: one task in steps inside one sheet (Lh 2026-09-24) ----
|
|
2269
2522
|
<div class="sheet-steps"> › <ol class="sheet-steps__track"> of <li class="sheet-step sheet-step--done|now|next">. The done steps
|
|
@@ -2274,14 +2527,14 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2274
2527
|
falls — no height is written. Nothing fades: done and next read at full ink. */
|
|
2275
2528
|
.sheet__body:has(> .sheet-steps) { display: flex; flex-direction: column; }
|
|
2276
2529
|
.sheet-steps { flex-shrink: 0; min-height: calc(100% + var(--kb, 0rem)); /* the keyboard's height more: the pile of next steps stays under the keyboard, it never rises with it */ display: flex; flex-direction: column; touch-action: pan-y; position: relative; }
|
|
2277
|
-
.sheet-steps__holder { position: absolute; top: 0; left: 0; width:
|
|
2530
|
+
.sheet-steps__holder { position: absolute; top: 0; left: 0; width: var(--browser-text); height: var(--browser-text); padding: 0; border: 0; opacity: 0; pointer-events: none; font-size: var(--browser-text); } /* holds the cursor (and the phone's keyboard) while a step folds; font 1rem = the browser's own size, so iOS does not zoom to it */
|
|
2278
2531
|
.sheet-steps__track { list-style: none; margin: 0; padding: 0 0 var(--sheet-steps-end, var(--sp-6)); flex: 1; /* room under the LAST step: scrolled to the end it never touches the foot's button (Lh 2026-09-28, #153) */ display: flex; flex-direction: column; }
|
|
2279
2532
|
|
|
2280
2533
|
.sheet-step {
|
|
2281
2534
|
position: relative; min-width: 0;
|
|
2282
2535
|
display: grid; grid-template-columns: minmax(0, 1fr); /* the column is the step's width, never its words' — a long summary is cut with …, it never pushes the row past the sheet's edge (Lh 2026-09-28, #153) */ grid-template-rows: auto 0fr; align-content: start; /* mid-fold the rows sum to less than the box: the spare room stays BELOW, never spread into the title row (Lh: it looked like a spring) */
|
|
2283
2536
|
flex: 0 0 auto;
|
|
2284
|
-
transition: grid-template-rows var(--motion-sheet) ease, flex-grow var(--motion-sheet) ease;
|
|
2537
|
+
transition: grid-template-rows var(--motion-sheet) var(--ease), flex-grow var(--motion-sheet) var(--ease);
|
|
2285
2538
|
--_row-h: var(--sheet-steps-row-h, calc(var(--tap-base) * 2 / 3)); /* the bar's own floor, read once (__head and __go below share it) */
|
|
2286
2539
|
}
|
|
2287
2540
|
/* the open step GROWS into the room left, so the steps not done yet wait in a pile at the bottom — and a move is an ACCORDION:
|
|
@@ -2289,7 +2542,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2289
2542
|
with them, nothing jumps (Lh 2026-09-24) */
|
|
2290
2543
|
.sheet-step--now { grid-template-rows: auto 1fr; flex-grow: 1; }
|
|
2291
2544
|
.sheet-step + .sheet-step { margin-top: var(--sheet-steps-gap, var(--sp-1)); } /* the folded bars read as a pile, a little ground between them (Lh 2026-09-25) */
|
|
2292
|
-
.sheet-step__head { /* every step's title line is a quiet BAR (a tint of the ink, not the brand — a brand bar read as a button, Lh 2026-09-25), the same height open or folded — opening shows the
|
|
2545
|
+
.sheet-step__head { /* every step's title line is a quiet BAR (a tint of the ink, not the brand — a brand bar read as a button, Lh 2026-09-25), the same height open or folded — opening shows the card's room under it, never a taller bar (Lh 2026-09-25; was a thin rule on top) */
|
|
2293
2546
|
display: flex; align-items: center; gap: var(--sp-3); min-height: var(--_row-h); min-width: 0; /* THIN: 32px, a summary line, not a button (Lh 2026-09-25) — the tap stays 44, see __go */
|
|
2294
2547
|
padding-inline: var(--sheet-step-bar-pad-x, var(--sp-3)); border-radius: var(--sheet-step-bar-radius, var(--r-chip));
|
|
2295
2548
|
background: var(--sheet-step-bar-bg, var(--ink-tint)); color: var(--sheet-step-bar-ink, inherit);
|
|
@@ -2297,11 +2550,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2297
2550
|
border-top: var(--sheet-step-border-w, 0rem) solid var(--sheet-step-border, currentColor); /* a rule instead of the bar: set this, the bar's bg transparent, radius 0 */
|
|
2298
2551
|
}
|
|
2299
2552
|
.sheet-step__title { font: var(--sheet-step-title-font, var(--type-label)); margin: 0; flex: 0 1 auto; min-width: 0; outline: none; /* the label size, not a body title: a pile of rows must stay small (Lh 2026-09-24) */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2300
|
-
.sheet-step__num { /* `numbered`: the step's number in a disc BEFORE its title (Lh 2026-09-25) — a
|
|
2553
|
+
.sheet-step__num { /* `numbered`: the step's number in a disc BEFORE its title (Lh 2026-09-25) — a disc of the card's colour on the bar, the number in the ink */
|
|
2301
2554
|
flex: none; display: grid; place-items: center; box-sizing: border-box; inline-size: var(--sheet-step-num-size, 1.408em); block-size: var(--sheet-step-num-size, 1.408em); /* em, not rem (Lh 2026-10-03, as the StackSheet's count): the disc is cut from ITS OWN number's size —
|
|
2302
2555
|
at a large text size the number grew and a 24px disc did not, so the digit sat on its bottom edge; 1.408em of a 17px caption = the 24px it always was */
|
|
2303
2556
|
padding-bottom: var(--sheet-step-num-lift, 0.1em); /* the room UNDER the number lifts it onto the disc's middle: a digit has no descender, so a centred line box sets it low */
|
|
2304
|
-
border-radius: 50%; font: var(--sheet-step-num-font, var(--type-caption)); line-height:
|
|
2557
|
+
border-radius: 50%; font: var(--sheet-step-num-font, var(--type-caption)); line-height: var(--lh-solid); font-variant-numeric: tabular-nums;
|
|
2305
2558
|
background: var(--sheet-step-num-bg, var(--card-bg)); color: var(--sheet-step-num-ink, inherit);
|
|
2306
2559
|
}
|
|
2307
2560
|
.sheet-step__summary { color: var(--sheet-step-summary-ink, var(--card-muted)); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -2309,14 +2562,57 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2309
2562
|
.sheet-step__body { min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
|
|
2310
2563
|
.sheet-step__inner { display: grid; gap: var(--card-gap); padding-block: var(--card-gap); padding-inline: var(--sheet-step-inner-pad-x, var(--sheet-step-bar-pad-x, var(--sp-3))); } /* the open step's content in the frame of its title's words (the bar's own inset), so it reads as that step's (Lh 2026-09-28, #153) */
|
|
2311
2564
|
.sheet-step__inner > * { min-width: 0; }
|
|
2312
|
-
.sheet-
|
|
2313
|
-
.sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s linear var(--motion-sheet); }
|
|
2565
|
+
.sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s var(--ease-linear) var(--motion-sheet); }
|
|
2314
2566
|
.sheet-step__go { position: absolute; inset: min(0rem, (var(--_row-h) - var(--tap-sm)) / 2) 0 auto; min-height: max(100%, var(--tap-sm)); /* a thin bar still takes a 44px tap */ margin: 0; cursor: pointer; }
|
|
2315
2567
|
/* the last step's finish, when the app gives one: under the steps */
|
|
2316
2568
|
.sheet-steps__finish { display: grid; padding-top: var(--card-gap); }
|
|
2317
2569
|
@media (prefers-reduced-motion: reduce) { .sheet-step { transition: none; } }
|
|
2318
|
-
|
|
2319
|
-
|
|
2570
|
+
|
|
2571
|
+
/* ==== the step bar answers a press, as PRESS in actions.css answers every other control (#62: a block is written in its own file) ==== */
|
|
2572
|
+
.sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
|
|
2573
|
+
|
|
2574
|
+
/* ==== a body that holds only an EmptyState stands it at --empty-state-at of its room, as a Step does (step.css) (#62: a block is written in its own file) ==== */
|
|
2575
|
+
.sheet__body:has(> .empty-state:only-child) { display: flex; flex-direction: column; gap: 0; }
|
|
2576
|
+
.sheet__body:has(> .empty-state:only-child)::before { content: ""; flex: calc(var(--empty-state-at, 0) * 5) 1 0; }
|
|
2577
|
+
.sheet__body:has(> .empty-state:only-child)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0) * 5) 1 0; }
|
|
2578
|
+
|
|
2579
|
+
/* ---- where it sits in what stack.css draws (docs/reference.md rule 15) ---- */
|
|
2580
|
+
/* a pull in the content, already at the top: the words move down, the card stays. --_refresh is the finger, private.
|
|
2581
|
+
A paper body adds its own --sp-6 on top of this (the rule below): the stack's gap under the head is the
|
|
2582
|
+
card's colour, so without the paper's inset the first row sits on that seam. */
|
|
2583
|
+
.sheet-stack > .card > .sheet__body { padding-top: var(--_refresh, 0rem); transition: padding-top var(--motion-sheet) var(--ease); }
|
|
2584
|
+
/* the head's band is as slim as it can be (Lh 2026-10-06: 20 over · 48 head · 20 under read as a heavy bar): the paper starts
|
|
2585
|
+
--sp-3 under the head, not a whole --card-gap (--sp-2 at first; Lh added 4 the same day). The body is pulled up by the difference — the card's gap is left alone, the
|
|
2586
|
+
stack's step and the body's own rows read it. */
|
|
2587
|
+
.sheet-stack > .card.sheet--paper > .sheet__body {
|
|
2588
|
+
margin-top: calc(var(--sp-3) - var(--card-gap));
|
|
2589
|
+
padding-top: calc(var(--sp-6) + var(--_refresh, 0rem));
|
|
2590
|
+
transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
|
|
2591
|
+
}
|
|
2592
|
+
/* a pull follows the finger at once, on paper too: the rule above is as strong as the stack's own `is-refreshing` one and comes later */
|
|
2593
|
+
.sheet-stack > .card.sheet--paper > .sheet__body.is-refreshing { transition: none; }
|
|
2594
|
+
/* a closed paper sheet's body stays clear (Lh 2026-10-06). The next sheet's round top does not
|
|
2595
|
+
cover the corners, and a paper body showed there beside the head. Clear, the card's own colour
|
|
2596
|
+
shows in that pocket. Open, the body is paper, and the first row sits --sp-6 down, the same paper
|
|
2597
|
+
inset a single paper sheet keeps: the flex gap under the head is the card's colour, so the row
|
|
2598
|
+
would otherwise sit on that seam. A pull adds --_refresh on top of the inset. On the way back
|
|
2599
|
+
into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
|
|
2600
|
+
sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
|
|
2601
|
+
at the moment the sheet stops. Rising, the body is paper at once. */
|
|
2602
|
+
.sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
2603
|
+
background-color: transparent;
|
|
2604
|
+
transition:
|
|
2605
|
+
padding-top var(--motion-sheet) var(--ease),
|
|
2606
|
+
background-color var(--motion-quick) var(--ease) calc(var(--stagger, 0) * var(--motion-stagger) + var(--motion-sheet) - var(--motion-quick));
|
|
2607
|
+
}
|
|
2608
|
+
@media (prefers-reduced-motion: reduce) { .sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
2609
|
+
transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
|
|
2610
|
+
} }
|
|
2611
|
+
/* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
|
|
2612
|
+
.screen--fill > .appbar + .sheet-stage { margin-top: 0; }
|
|
2613
|
+
|
|
2614
|
+
/* ---- where it sits in what stack.css draws: a body pulled to refresh holds still (docs/reference.md rule 15) ---- */
|
|
2615
|
+
.sheet-stack > .card > .sheet__body.is-refreshing { transition: none; }
|
|
2320
2616
|
|
|
2321
2617
|
/* ---- css/step.css — .step: one screen, one task — card, optional sheet, bar outside ---- */
|
|
2322
2618
|
/* ============================================================
|
|
@@ -2386,135 +2682,29 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2386
2682
|
own screens are composed (the guide); the framework does not police it — a component never changes
|
|
2387
2683
|
because another one is there. */
|
|
2388
2684
|
|
|
2389
|
-
/* the reading block
|
|
2390
|
-
block as anywhere else (it was .step__card, and a .card before 2026-09-16;
|
|
2391
|
-
Lh: a step reads like a page, not a card in a page — so it IS the page's
|
|
2392
|
-
block, one name). Here it keeps a card's inner rhythm (a head, --card-gap
|
|
2393
|
-
between things, the tokens .card publishes for the head and .bleed) and
|
|
2394
|
-
gets the flex rule: it hugs its content, and when the column runs out (a
|
|
2395
|
-
short phone, large text) it shrinks and scrolls inside itself instead of
|
|
2396
|
-
pushing the bar or the sheet */
|
|
2685
|
+
/* the reading block (base.css writes where it sits and why): a card's inner rhythm, set here */
|
|
2397
2686
|
.step > .page, .step > .pages > .page {
|
|
2398
2687
|
--card-pad: 0rem; /* published for .bleed: nothing to cancel */
|
|
2399
2688
|
--card-gap: var(--sp-4);
|
|
2400
|
-
gap: var(--card-gap);
|
|
2401
|
-
padding-block: 0; /* --_lead carries the top; the bar's room is the step's */
|
|
2402
|
-
color: var(--on-surface); /* align-content: start is .page's own (base.css) — not restated here */
|
|
2403
2689
|
}
|
|
2404
2690
|
/* the reading block is the .page — or the .pages that holds several of them, a walk of words
|
|
2405
2691
|
(card.css). Behind a PEEKED sheet the words keep the strip's room under them, so whatever is down there
|
|
2406
2692
|
(the buttons that call the other sheets) scrolls clear of it — CSS alone: the box declares --sheet-peek. */
|
|
2407
|
-
.step:has(> .sheet--peek) > :is(.page, .pages) { padding-bottom: var(--sheet-peek); }
|
|
2408
|
-
.step > :is(.page, .pages) {
|
|
2409
|
-
flex: 0 1 auto;
|
|
2410
|
-
min-height: 0;
|
|
2411
|
-
overflow-y: auto;
|
|
2412
|
-
overscroll-behavior: contain;
|
|
2413
|
-
}
|
|
2414
|
-
/* a lone .page carries the margin a walk's .pages does not (its own .page children do, below) —
|
|
2415
|
-
the screen's gap, moved in here from above the step; the inset, taken back from the bleed.
|
|
2416
|
-
It ends with the bottom layer's room, as a walk's page does: it scrolls only when the layer would cover its words
|
|
2417
|
-
(timebank onboard-04, 2026-09-27 — its last Link lay under the bar; Lh: same rule as the walk) */
|
|
2418
|
-
.step > .page { margin: var(--_lead, 0) var(--screen-pad) 0; padding-bottom: var(--bottom-h, 0rem); }
|
|
2419
|
-
/* --bleed: the page's box runs to the screen's edges, the inset is its own padding — what leans, slides or casts a shadow past
|
|
2420
|
-
the inset (a postcard) is cut at the glass, not at the inset; a little room on top for a turned corner. Nothing pans sideways. */
|
|
2421
|
-
.step > .page--bleed { margin-inline: 0; padding-inline: var(--screen-pad); padding-block-start: var(--sp-2); overflow-x: hidden; }
|
|
2422
2693
|
|
|
2423
|
-
/* a WALK (Pages of Page) — Lh 2026-09-23, third call: no top; the picture and the words are ONE run that scrolls up together
|
|
2424
|
-
(the picture is never pinned). Each page starts at the screen's lead and ends with the same room under it — the lead as
|
|
2425
|
-
padding above AND below — so the words begin near the middle and, when a big text size runs them long, scroll up clear.
|
|
2426
|
-
The walk shows no scrollbar. */
|
|
2427
|
-
.step > .pages { touch-action: pan-y; } /* pan-y: the walk scrolls itself up and down; a sideways swipe is the Walkthrough's (it turns the page) */ /* the scrolling box is the whole content: from the step's top edge, edge to edge — no margin, the default (Lh 2026-09-23) */
|
|
2428
|
-
.step > .pages > .page {
|
|
2429
|
-
padding-inline: var(--screen-pad); /* the words keep the inset INSIDE it */
|
|
2430
|
-
margin-top: var(--_lead, 0rem); /* the lead is each PAGE's margin (15% with no top, the screen's gap under a bar): the words start there and scroll up to the box's edge */
|
|
2431
|
-
padding-bottom: var(--bottom-h, 0rem); /* exactly the bottom layer (measured by Screen): a page scrolls ONLY when the layer would cover its words, and then just far enough to clear them — at 100% nothing scrolls (Lh 2026-09-23) */
|
|
2432
|
-
}
|
|
2433
2694
|
|
|
2434
2695
|
/* a SCROLLING (not fill) screen becomes a column at least one phone tall when it holds a bottom or a centre:
|
|
2435
2696
|
an .action-bar as its last child (Lh 2026-09-24, 0.7.0: the bar is the column's last row — at the foot when the words are short,
|
|
2436
|
-
sticky to the viewport's foot while they scroll; actions.css) or a .screen__centre (Lh 2026-09-12: the group is in flow, takes the
|
|
2437
|
-
room after the top bar and centres its children in it; a long one makes the page longer and runs under the bar; card.css) */
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
/* ============================================================
|
|
2441
|
-
TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
|
|
2442
|
-
in a .screen--fill the content runs to the screen's bottom edge, and the bar lies over it at the foot — it covers what is
|
|
2443
|
-
under it, on purpose; nothing is added to the content to make room. The keyboard lifts the layer (--kb).
|
|
2444
|
-
(On a scrolling screen the bar is a sticky last row instead, actions.css.)
|
|
2445
|
-
============================================================ */
|
|
2446
|
-
.screen--fill > .action-bar {
|
|
2447
|
-
position: absolute; inset-inline: 0; top: auto; bottom: var(--kb, 0rem);
|
|
2448
|
-
max-width: none;
|
|
2449
|
-
margin: 0;
|
|
2450
|
-
/* room between its top edge and its first row (the walk's dots): since it has a ground, a 0 here put the dots against the words
|
|
2451
|
-
running under it (Lh 2026-09-25) — the bar's own top padding */
|
|
2452
|
-
padding-top: var(--action-bar-fill-pad-top, var(--sp-3));
|
|
2453
|
-
/* its GROUND (Lh 2026-09-24, timebank card 141 — was "no ground of its own"): words that run under the layer mid-scroll
|
|
2454
|
-
showed through the dots, the controls and the buttons. The layer lies on the screen's own surface now; a project sets
|
|
2455
|
-
another (or `transparent`, the old see-through) with the knob */
|
|
2456
|
-
background: var(--action-bar-bg, var(--surface));
|
|
2457
|
-
}
|
|
2458
|
-
/* the walk's dots are the BOTTOM layer's too (Lh 2026-09-23): a row of the bar, over its buttons, centred */
|
|
2459
|
-
.action-bar > .pager__at, .action-bar__tier > .pager__at { align-self: center; }
|
|
2460
|
-
/* the TOP is in the column too (Lh 2026-09-23): an .appbar in a fill screen is its first row, not fixed over it — the screen
|
|
2461
|
-
reserves nothing for it; the breathing under it stays the one it had (--sp-2), not the screen's gap */
|
|
2462
|
-
.screen--fill > .appbar {
|
|
2463
|
-
position: relative; inset: auto; top: auto;
|
|
2464
|
-
flex: none;
|
|
2465
|
-
max-width: none;
|
|
2466
|
-
margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge; its own padding is the inset */
|
|
2467
|
-
margin-bottom: calc(var(--sp-2) - var(--screen-gap));
|
|
2468
|
-
}
|
|
2469
|
-
.screen.screen--fill:has(> .appbar) { padding-top: var(--safe-top); --header-gap: 0rem; }
|
|
2470
|
-
/* …and a STEP touches it, as it touches a TopBar: a full sheet's top edge on the bar's bottom edge (0.6.5 left 8px of base between
|
|
2471
|
-
them — timebank #137); the words keep the screen's gap as their lead (--_lead, .appbar + .step above) */
|
|
2472
|
-
.screen--fill > .appbar:has(+ .step) { margin-bottom: calc(-1 * var(--screen-gap)); }
|
|
2473
|
-
.screen--fill > .appbar + .sheet-stage { margin-top: 0; }
|
|
2474
|
-
/* a TRACK as the screen's TOP (Lh 2026-10-03): the walk's progress stands where a bar would, a row of the column, in the screen's
|
|
2475
|
-
inset — and it never touches what comes up under it: the step (and so a sheet at full) starts --track-top-gap below it. That
|
|
2476
|
-
room is the track's own, not the screen's gap; --track-top-lead is the air over it, under the status bar. Both = the screen's side inset (16): the pill
|
|
2477
|
-
stands the same distance from everything around it (Lh 2026-10-03: 8 over was too little, 24 under too much). */
|
|
2478
|
-
.screen--fill > .track:first-child {
|
|
2479
|
-
flex: none;
|
|
2480
|
-
margin-top: var(--track-top-lead, var(--sp-4));
|
|
2481
|
-
margin-bottom: calc(var(--track-top-gap, var(--sp-4)) - var(--screen-gap));
|
|
2482
|
-
}
|
|
2483
|
-
/* …and over a STAGE (a map behind a sheet, Lh 2026-10-03): the stage is the whole screen, so the track is a LAYER on it — the map
|
|
2484
|
-
shows around the pill — and the stage keeps the track's line free itself: a sheet at full stops --track-top-gap under the
|
|
2485
|
-
track, as in a step. --top-h is the track's own height, measured by Screen (a numbered track grows with the text). */
|
|
2486
|
-
.screen--fill > .track:first-child:has(+ .sheet-stage) {
|
|
2487
|
-
position: absolute; z-index: 1;
|
|
2488
|
-
inset: calc(var(--safe-top) + var(--track-top-lead, var(--sp-4))) var(--screen-pad) auto;
|
|
2489
|
-
margin: 0;
|
|
2490
|
-
}
|
|
2697
|
+
sticky to the viewport's foot while they scroll; actions.css) or a .screen__centre (Lh 2026-09-12: the group is in flow, takes the
|
|
2698
|
+
room after the top bar and centres its children in it; a long one makes the page longer and runs under the bar; card.css) */
|
|
2699
|
+
|
|
2491
2700
|
.screen--fill > .track:first-child + .sheet-stage {
|
|
2492
2701
|
--_top: calc(var(--track-top-lead, var(--sp-4)) + var(--top-h, var(--header-h)) + var(--track-top-gap, var(--sp-4)));
|
|
2493
2702
|
}
|
|
2494
|
-
/* a walk page that OPENS on a picture (Lh 2026-09-23): the picture takes the lead's room — as wide as the content (edge to edge), from the top edge, as tall as
|
|
2495
|
-
the lead + the picture's own height (--page-media-h), square — so the words start where they always did */
|
|
2496
|
-
.step > .pages > .page:has(> :is(img, .ph):first-child) { margin-top: 0; }
|
|
2497
|
-
.step > .pages > .page > :is(img, .ph):first-child {
|
|
2498
|
-
width: calc(100% + 2 * var(--screen-pad)); /* the content's width: the page's inset given back */
|
|
2499
|
-
max-width: none;
|
|
2500
|
-
margin-inline: calc(-1 * var(--screen-pad));
|
|
2501
|
-
height: calc(var(--_lead, 0rem) + var(--page-media-h, 14rem));
|
|
2502
|
-
max-height: none;
|
|
2503
|
-
border-radius: 0;
|
|
2504
|
-
border-inline: 0;
|
|
2505
|
-
border-top: 0;
|
|
2506
|
-
object-fit: cover;
|
|
2507
|
-
}
|
|
2508
2703
|
/* an EMPTY STATE is a component in the content box too (Lh 2026-09-23 — it no longer floats in a Centre over the screen): alone in a
|
|
2509
2704
|
Step it stands at the human centre of the content, 40% down (two parts of room above it, three below). --empty-state-at is the share of the
|
|
2510
2705
|
free room that stands above it (a theme's 0.2 puts it a fifth down); a sheet's body holds an only EmptyState by the same share, but defaults to 0: at the top, as it always stood */
|
|
2511
2706
|
.step:has(> .empty-state)::before { content: ""; flex: calc(var(--empty-state-at, 0.4) * 5) 1 0; }
|
|
2512
2707
|
.step:has(> .empty-state)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0.4) * 5) 1 0; }
|
|
2513
|
-
.step > .empty-state { flex: none; }
|
|
2514
|
-
.sheet__body:has(> .empty-state:only-child) { display: flex; flex-direction: column; gap: 0; }
|
|
2515
|
-
.sheet__body:has(> .empty-state:only-child)::before { content: ""; flex: calc(var(--empty-state-at, 0) * 5) 1 0; }
|
|
2516
|
-
.sheet__body:has(> .empty-state:only-child)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0) * 5) 1 0; }
|
|
2517
|
-
.sheet__body > .empty-state:only-child { flex: none; }
|
|
2518
2708
|
|
|
2519
2709
|
/* ---- css/forms.css — outlined fields, add-row ---- */
|
|
2520
2710
|
/* ============================================================
|
|
@@ -2542,7 +2732,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2542
2732
|
.field select,
|
|
2543
2733
|
.field textarea,
|
|
2544
2734
|
.field--rows {
|
|
2545
|
-
font: var(--field-font,
|
|
2735
|
+
font: var(--field-font, var(--fw-regular) var(--fs-lg) / var(--lh-field) var(--font-ui)); /* 26: the words being typed read a size up from body (Lh 2026-09-22); the Composer already does. .field--rows too, so 1lh is the textarea's line for its icon */
|
|
2546
2736
|
}
|
|
2547
2737
|
.field input,
|
|
2548
2738
|
.field select,
|
|
@@ -2556,17 +2746,16 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2556
2746
|
caret-color: currentColor;
|
|
2557
2747
|
}
|
|
2558
2748
|
.field input::placeholder,
|
|
2559
|
-
.field textarea::placeholder { color: inherit; opacity:
|
|
2749
|
+
.field textarea::placeholder { color: inherit; opacity: var(--fade-hint); font-size: calc(1em * var(--_ph-fit, 1)); } /* --_ph-fit: Field's measure — a placeholder longer than the box shrinks to show whole (Lh 2026-09-25) */
|
|
2560
2750
|
/* several lines in the same frame (Field rows): the textarea is as tall as its `rows` × line, the frame grows round it, the icon and the action stay at the top (Lh 2026-10-19) */
|
|
2561
2751
|
.field--rows { align-items: flex-start; padding-block: var(--field-rows-pad-y, var(--sp-3)); }
|
|
2562
2752
|
.field textarea { flex: 1; resize: none; display: block; }
|
|
2563
|
-
.field--rows > .icon { margin
|
|
2753
|
+
.field--rows > .icon { --icon-margin: calc((1lh - var(--icon-sm)) / 2) 0 0; } /* centred on the first line */
|
|
2564
2754
|
/* the field the cursor is in: its own border goes full ink, in every palette (the coloured ones fade it to 25% at rest) */
|
|
2565
2755
|
.field:has(:focus, [data-cursor]) { border-color: var(--field-active-border, currentColor); } /* [data-cursor]: SheetSteps holds the phone's keyboard for this field */
|
|
2566
2756
|
|
|
2567
2757
|
/* field with its action inside: date pick, send, go — the button sits flush right */
|
|
2568
2758
|
.field--action { padding-right: var(--sp-1); min-height: var(--field-action-min-h, var(--tap-lg)); }
|
|
2569
|
-
.field--action > .icon-btn { margin-left: auto; }
|
|
2570
2759
|
|
|
2571
2760
|
/* colored palettes drop the visible outline elsewhere — fields keep one */
|
|
2572
2761
|
[data-palette] .field:not(:has(:focus)) { border-color: color-mix(in srgb, currentColor 25%, transparent); }
|
|
@@ -2591,25 +2780,18 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2591
2780
|
display: grid;
|
|
2592
2781
|
place-items: center;
|
|
2593
2782
|
font-size: var(--add-row-plus-font-size, var(--fs-md));
|
|
2594
|
-
line-height:
|
|
2783
|
+
line-height: var(--lh-solid);
|
|
2595
2784
|
}
|
|
2596
|
-
.add-row__plus .icon {
|
|
2785
|
+
.add-row__plus .icon { --icon-size: var(--icon-xs); }
|
|
2597
2786
|
|
|
2598
2787
|
/* ---- a wrong value, and the line that says so (Lh 2026-09-24) ----
|
|
2599
2788
|
--signal-error is the one colour the wireframe has; it never speaks alone — the line carries an icon and words.
|
|
2600
2789
|
<p class="form-note form-note--error" role="alert"><svg class="icon icon--xs">…</svg>Not a phone number</p>
|
|
2601
2790
|
<p class="form-note form-note--busy" role="status"><svg …/>Sending the code…</p> (the ring turns) */
|
|
2602
2791
|
.field.field--error, [data-palette] .field.field--error { border-color: var(--field-error-border, var(--signal-error)); }
|
|
2603
|
-
.pin.pin--error .pin__cell, .pin.pin--error .pin__cell:focus { border-color: var(--pin-error-border, var(--signal-error)); color: var(--pin-error-ink, var(--signal-error)); } /* ALL the cells, edge and digit — the one the cursor waits in too (Lh 2026-09-24); the wrong digits stay in red until the member types again */
|
|
2604
|
-
/* a code that does not match (Pin match): the row shakes once, sideways, then the Pin empties itself for a new try (Lh 2026-09-25) */
|
|
2605
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
2606
|
-
.pin--shake { animation: pin-shake var(--pin-shake-time, 0.4s) ease-in-out; }
|
|
2607
|
-
}
|
|
2608
|
-
@keyframes pin-shake { 0%, 100% { translate: 0; } 20%, 60% { translate: calc(-1 * var(--pin-shake-x, var(--sp-2))); } 40%, 80% { translate: var(--pin-shake-x, var(--sp-2)); } }
|
|
2609
2792
|
.form-note { display: flex; align-items: center; justify-content: center; gap: var(--sp-2); margin: 0; font: var(--form-note-font, var(--type-caption)); color: var(--form-note-ink, var(--card-muted)); text-align: center; }
|
|
2610
|
-
.form-note > .icon { flex: none; }
|
|
2611
2793
|
.form-note--error { color: var(--form-note-error-ink, var(--signal-error)); }
|
|
2612
|
-
@media (prefers-reduced-motion: no-preference) { .form-note--busy > .icon {
|
|
2794
|
+
@media (prefers-reduced-motion: no-preference) { .form-note--busy > .icon { --icon-spin: turn var(--motion-spin) var(--ease-linear) infinite; } } /* `turn`: tokens.css */
|
|
2613
2795
|
|
|
2614
2796
|
/* ---- css/journey.css — route, tile badge, note row ---- */
|
|
2615
2797
|
/* ============================================================
|
|
@@ -2636,7 +2818,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2636
2818
|
.route__time { grid-row: 3; align-self: start; }
|
|
2637
2819
|
|
|
2638
2820
|
.route__code { font: var(--route-code-font, var(--type-h2)); letter-spacing: var(--track-h2); display: flex; align-items: center; gap: var(--route-code-gap, var(--sp-3)); }
|
|
2639
|
-
.route__code::after, .route__point--end .route__code::before { content: ""; flex: 1; border-top: var(--rule-w) dotted currentColor; opacity:
|
|
2821
|
+
.route__code::after, .route__point--end .route__code::before { content: ""; flex: 1; border-top: var(--rule-w) dotted currentColor; opacity: var(--fade-mild); }
|
|
2640
2822
|
.route__point--end .route__code::after { content: none; }
|
|
2641
2823
|
.route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
|
|
2642
2824
|
.route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
|
|
@@ -2657,17 +2839,19 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2657
2839
|
/* the label is the badge's own ink, smaller: a badge is a small fill of any colour (a palette paints it yellow), and only its own ink is
|
|
2658
2840
|
sure to hold 4.5:1 on it; --dim faded it to 2.4:1 */
|
|
2659
2841
|
.tile-badge__label { font: var(--tile-badge-label-font, var(--type-caption)); color: var(--tile-badge-label-ink, inherit); }
|
|
2660
|
-
.tile-badge__value { font:
|
|
2842
|
+
.tile-badge__value { font: var(--fw-heavy) var(--fs-lg) / var(--lh-tight) var(--font-display); font-variant-numeric: tabular-nums; }
|
|
2661
2843
|
|
|
2662
2844
|
/* plain: no fill — a date box beside a title (Jun / 18) */
|
|
2663
2845
|
.tile-badge--plain { background: transparent; --dark: inherit; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
|
|
2664
|
-
.tile-badge--plain .tile-badge__value { font:
|
|
2846
|
+
.tile-badge--plain .tile-badge__value { font: var(--fw-heavy) var(--fs-h1) / var(--lh-solid) var(--font-display); }
|
|
2665
2847
|
|
|
2666
2848
|
/* stretch: a date in a head (Lh 2026-10-19) — as tall as the title block beside it and no taller (the head stretches it; size
|
|
2667
2849
|
containment keeps its own words from adding height). Its height picks the form: short (a one-line title) = two lines, the day name
|
|
2668
|
-
over the number and the month on one line; tall (a two-line title) = three lines, day · number · month.
|
|
2850
|
+
over the number and the month on one line; tall (a two-line title) = three lines, day · number · month. It grows no taller than
|
|
2851
|
+
its three lines (2026-10-21, timebank: beside a four-line title it was a tall empty block); held there, it stands at the head's top. */
|
|
2669
2852
|
.tile-badge--stretch {
|
|
2670
2853
|
align-self: stretch;
|
|
2854
|
+
max-block-size: calc((2 * var(--fs-caption) + var(--fs-lg)) * var(--tile-badge-line, var(--lh-badge)) + 2 * var(--sp-3));
|
|
2671
2855
|
container-type: size;
|
|
2672
2856
|
inline-size: calc(var(--fs-lg) * 3.25);
|
|
2673
2857
|
grid-template-columns: auto auto;
|
|
@@ -2677,11 +2861,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2677
2861
|
column-gap: var(--sp-1);
|
|
2678
2862
|
row-gap: 0;
|
|
2679
2863
|
padding-block: var(--sp-3);
|
|
2680
|
-
line-height: var(--tile-badge-line,
|
|
2864
|
+
line-height: var(--tile-badge-line, var(--lh-badge));
|
|
2681
2865
|
}
|
|
2682
2866
|
.tile-badge--stretch > * { line-height: inherit; }
|
|
2683
2867
|
.tile-badge--stretch > :first-child { grid-column: 1 / -1; }
|
|
2684
|
-
@container (min-height:
|
|
2868
|
+
@container (min-height: 3.5rem) { /* just under the three lines' own height, the most the badge grows to */
|
|
2685
2869
|
.tile-badge--stretch > * { grid-column: 1 / -1; }
|
|
2686
2870
|
}
|
|
2687
2871
|
|
|
@@ -2726,17 +2910,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2726
2910
|
min-height: var(--header-h);
|
|
2727
2911
|
}
|
|
2728
2912
|
|
|
2729
|
-
/* ---- top bar: back + screen title ---- */
|
|
2730
|
-
.topbar .t-h1 {
|
|
2731
|
-
text-transform: uppercase;
|
|
2732
|
-
flex: 1;
|
|
2733
|
-
min-width: 0;
|
|
2734
|
-
overflow: hidden;
|
|
2735
|
-
text-overflow: ellipsis;
|
|
2736
|
-
white-space: nowrap;
|
|
2737
|
-
padding-block: 0.15em; /* Thai marks survive the clip — see card.css, the head rule */
|
|
2738
|
-
margin-block: -0.15em;
|
|
2739
|
-
}
|
|
2740
2913
|
.topbar__actions { margin-left: auto; display: flex; gap: var(--sp-2); }
|
|
2741
2914
|
/* at most TWO trailing controls (Lh 2026-09-22): the title must keep its line; a third and
|
|
2742
2915
|
beyond is not drawn — more moves belong in a Menu behind one button, or on the base */
|
|
@@ -2761,7 +2934,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2761
2934
|
/* fixed, so it covers the top of the screen it sits in — reserve its height
|
|
2762
2935
|
there, from the same header-h token. The breathing under it is published:
|
|
2763
2936
|
a .step cancels it to touch the bar. */
|
|
2764
|
-
.screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
|
|
2765
2937
|
.appbar__brand { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
|
|
2766
2938
|
.appbar__text { display: grid; min-width: 0; flex: 1; } /* pushes trailing button to the right edge */
|
|
2767
2939
|
/* the mark + the words as ONE button (AppBar brandButton — the member's name opens their menu, Lh 2026-09-23): the same row, a tap target */
|
|
@@ -2771,7 +2943,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2771
2943
|
margin: 0; text-align: start;
|
|
2772
2944
|
border-radius: var(--appbar-who-radius, var(--r-chip));
|
|
2773
2945
|
}
|
|
2774
|
-
.appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height:
|
|
2946
|
+
.appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: var(--lh-caption); } /* clipped lines: room for Thai marks */
|
|
2775
2947
|
.appbar__title { font: var(--type-label); }
|
|
2776
2948
|
.appbar__sub { font: var(--type-caption); color: var(--on-surface-muted); }
|
|
2777
2949
|
/* the bar's trailing controls (one or two: a labelled switch chip, a wallet button — home,
|
|
@@ -2786,13 +2958,22 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2786
2958
|
border: var(--border-w) solid var(--icon-btn-border, var(--outline)); /* see .btn — same reason */
|
|
2787
2959
|
background: var(--icon-btn-bg, var(--action-bg));
|
|
2788
2960
|
color: var(--icon-btn-ink, var(--action-ink));
|
|
2961
|
+
box-shadow: var(--icon-btn-shadow, none);
|
|
2789
2962
|
display: grid;
|
|
2790
2963
|
place-items: center;
|
|
2791
2964
|
font-size: var(--fs-lg);
|
|
2792
|
-
line-height:
|
|
2965
|
+
line-height: var(--lh-solid);
|
|
2793
2966
|
}
|
|
2794
2967
|
.icon-btn--invert { background: var(--icon-btn-invert-bg, var(--accent)); color: var(--icon-btn-invert-ink, var(--on-accent)); border-color: transparent; }
|
|
2795
2968
|
.icon-btn--sm { width: var(--icon-btn-sm-size, var(--tap-sm)); height: var(--icon-btn-sm-size, var(--tap-sm)); font-size: var(--fs-title); }
|
|
2969
|
+
/* the bigger round sizes (`IconBtn size`): lg is the hero control's size (--tap-lg), xl the biggest (--tap-xl), both on --tap-base like every size.
|
|
2970
|
+
What stands inside is a share of the circle (`--_ib`, its side), so it grows with it: the ICON is --icon-btn-icon of the side (7/12, the share of the default 48px
|
|
2971
|
+
button's 28px icon), and a WORD (a string child) keeps to --icon-btn-word-max of the side (0.45: a word of four letters stays inside) — it still
|
|
2972
|
+
grows with the member's text size, to that ceiling and no further. The default and the sm button keep their icon tokens and their word as before. */
|
|
2973
|
+
.icon-btn--lg { --_ib: var(--icon-btn-lg-size, var(--tap-lg)); width: var(--_ib); height: var(--_ib); }
|
|
2974
|
+
.icon-btn--xl { --_ib: var(--icon-btn-xl-size, var(--tap-xl)); width: var(--_ib); height: var(--_ib); }
|
|
2975
|
+
:is(.icon-btn--lg, .icon-btn--xl) { font-size: min(var(--fs-lg), calc(var(--_ib) * var(--icon-btn-word-max, 0.45))); }
|
|
2976
|
+
:is(.icon-btn--lg, .icon-btn--xl) > .icon { --icon-size: calc(var(--_ib) * var(--icon-btn-icon, 7 / 12)); --icon-sm-size: var(--icon-size); --icon-lg-size: var(--icon-size); } /* the Icon's own knobs */
|
|
2796
2977
|
/* ---- icon button group: up to four icon-only commands in ONE frame (Lh 2026-09-29) ----
|
|
2797
2978
|
<div class="icon-btn-group" role="group"><button class="icon-btn" aria-label="…">…</button>…</div>
|
|
2798
2979
|
One pill as tall as a button, the buttons share its width, a hairline between them; in a foot it takes the row. */
|
|
@@ -2850,15 +3031,16 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2850
3031
|
min-height: var(--btn-h);
|
|
2851
3032
|
text-decoration: none; /* .btn works as <a> too */
|
|
2852
3033
|
}
|
|
2853
|
-
|
|
3034
|
+
/* primary: a Btn and a round IconBtn fill alike, from the same tokens (the palette's primary — orchard's yellow — retunes both) */
|
|
3035
|
+
:is(.btn--primary, .icon-btn--primary) { background: var(--btn-primary-bg, var(--accent)); color: var(--btn-primary-ink, var(--on-accent)); border-color: transparent; }
|
|
2854
3036
|
/* quiet keeps a faint outline so it still reads as a button on colored palettes;
|
|
2855
3037
|
it is ink on whatever it sits on — inherit, or on the dark slot (card--3) it
|
|
2856
3038
|
would take the light-card action ink and vanish */
|
|
2857
|
-
.btn--quiet { background: transparent; color: inherit; border-color: color-mix(in srgb, currentColor 35%, transparent); }
|
|
3039
|
+
.btn--quiet { background: transparent; color: inherit; border-color: var(--btn-quiet-border, color-mix(in srgb, currentColor 35%, transparent)); }
|
|
2858
3040
|
.btn--block { width: 100%; }
|
|
2859
3041
|
.btn--form { justify-self: center; align-self: center; margin-inline: auto; padding-inline: var(--btn-form-pad-x, calc(var(--sp-base) * 14.5)); /* 58px: "ส่งรหัส OTP" at 213px, 20% longer than at 40px sides (Lh 2026-09-25) */ } /* a form's button: its words + wide sides, centred in a grid (a step) or a block (Lh 2026-09-25) */
|
|
2860
3042
|
/* xl: the one giant call to action in a tinted card ("Taxi!") */
|
|
2861
|
-
.btn--xl { min-height: var(--btn-xl-h); font:
|
|
3043
|
+
.btn--xl { min-height: var(--btn-xl-h); font: var(--fw-heavy) var(--fs-3xl) / var(--lh-solid) var(--font-display); letter-spacing: var(--track-h1); border-radius: var(--r-card); padding-inline: var(--sp-8); }
|
|
2862
3044
|
/* disabled keeps the slot — a control that vanishes moves everything else,
|
|
2863
3045
|
and a pager whose "previous" disappeared on page one would shift the bar
|
|
2864
3046
|
under the thumb on every page */
|
|
@@ -2883,9 +3065,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2883
3065
|
padding: var(--chip-pad-y, var(--sp-2)) var(--chip-pad-x, var(--sp-3));
|
|
2884
3066
|
font: var(--type-label);
|
|
2885
3067
|
font-size: var(--chip-font-size, var(--fs-sm));
|
|
2886
|
-
min-height: var(--chip-h);
|
|
3068
|
+
min-height: var(--chip-min-h, var(--chip-h));
|
|
2887
3069
|
text-decoration: none;
|
|
2888
|
-
justify-
|
|
3070
|
+
justify-content: var(--chip-justify, normal);
|
|
3071
|
+
justify-self: var(--chip-self, start); /* as a grid child it hugs its text, never stretches */
|
|
2889
3072
|
}
|
|
2890
3073
|
.dot { width: var(--dot); height: var(--dot); flex: none; border-radius: 50%; background: var(--dot-a); translate: 0 var(--dot-drop, 0.09em); } /* signal dot: chips, legends.
|
|
2891
3074
|
The drop: a line box is centred on its ascender room (Thai marks), the letters beside the dot sit lower — the dot rides down to the
|
|
@@ -2902,8 +3085,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2902
3085
|
/* muted: an untouched default — the words wash out, the edge stays; emphasis is the difference from the neighbours (docs/reference.md 13) */
|
|
2903
3086
|
.chip--muted { color: var(--chip-muted-ink, var(--card-muted)); } /* the chip's own fill's muted words: a muted chip on the dark card is still a light chip */
|
|
2904
3087
|
|
|
2905
|
-
.chip-row { display: flex; gap: var(--sp-2);
|
|
2906
|
-
.chip-row.bleed { flex-wrap:
|
|
3088
|
+
.chip-row { display: flex; gap: var(--sp-2); }
|
|
3089
|
+
.chip-row:not(.bleed) { flex-wrap: wrap; } /* a strip (.bleed, card.css) stays one line */
|
|
2907
3090
|
.chip-row.bleed > .chip { flex: none; white-space: nowrap; } /* a chip of the strip keeps its words on one line at its own width: the strip scrolls, a chip never shrinks to fit it */
|
|
2908
3091
|
|
|
2909
3092
|
/* ---- dropdown: the standard for every select — a pill trigger + .menu popover ----
|
|
@@ -2932,10 +3115,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2932
3115
|
/* muted: the value is still the default (docs/reference.md 13) */
|
|
2933
3116
|
.dropdown--muted .dropdown__btn { color: var(--dropdown-muted-ink, var(--action-muted)); }
|
|
2934
3117
|
.dropdown__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2935
|
-
.dropdown__btn > .icon {
|
|
2936
|
-
.dropdown:has(.menu:popover-open) .dropdown__btn > .icon {
|
|
2937
|
-
.dropdown:has(.menu.\:popover-open) .dropdown__btn > .icon {
|
|
2938
|
-
.dropdown--end .menu[popover] { position-area: block-end span-inline-start; } /* right-aligned trigger: panel grows leftwards (= .menu--end, layover.css) */
|
|
3118
|
+
.dropdown__btn > .icon { --icon-motion: rotate var(--motion-quick) var(--ease); }
|
|
3119
|
+
.dropdown:has(.menu:popover-open) .dropdown__btn > .icon { --icon-turn: 180deg; }
|
|
3120
|
+
.dropdown:has(.menu.\:popover-open) .dropdown__btn > .icon { --icon-turn: 180deg; } /* the polyfill's open (layover.css) */
|
|
2939
3121
|
.filter-row { display: flex; gap: var(--sp-2); }
|
|
2940
3122
|
.filter-row > .dropdown { flex: 1; min-width: 0; }
|
|
2941
3123
|
|
|
@@ -3081,7 +3263,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3081
3263
|
cursor: default;
|
|
3082
3264
|
}
|
|
3083
3265
|
.action-note__words { display: block; } /* ONE run of words: an icon inside sits in the line, the spaces around it kept */
|
|
3084
|
-
.action-note__words > .icon { display: inline-block;
|
|
3266
|
+
.action-note__words > .icon { --icon-display: inline-block; --icon-align: -0.2em; --icon-size: 1.25em; --icon-margin: 0 0.15em; --icon-ink: var(--action-note-icon-ink, inherit); } /* a project may colour the icon apart from the words — e.g. the system blue of the control it points at (Lh 2026-09-24) */
|
|
3085
3267
|
|
|
3086
3268
|
/* PRESS (Lh 2026-09-25; every tappable thing, 2026-09-27): every CONTROL answers a tap, INFORMATION never does (no pointer, no
|
|
3087
3269
|
press). Two answers, by size: GIVE — a control the size of a finger shrinks under it (--press-scale) and springs back; DIM —
|
|
@@ -3097,34 +3279,27 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3097
3279
|
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, .stamp-on__remove, .map-pick) {
|
|
3098
3280
|
cursor: pointer;
|
|
3099
3281
|
touch-action: manipulation;
|
|
3100
|
-
transition: scale var(--motion-press) ease-out;
|
|
3282
|
+
transition: scale var(--motion-press) var(--ease-out);
|
|
3101
3283
|
}
|
|
3102
3284
|
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .card--go, .feed-card--small, .toggle-field, .post-box__edge) {
|
|
3103
3285
|
cursor: pointer;
|
|
3104
3286
|
touch-action: manipulation;
|
|
3105
|
-
transition: opacity var(--motion-press) ease-out;
|
|
3287
|
+
transition: opacity var(--motion-press) var(--ease-out);
|
|
3106
3288
|
}
|
|
3107
3289
|
/* a CHOICE's fill fades in as it is picked (a plain button's colour never changes on a tap, so it does not fade) */
|
|
3108
3290
|
:where(.chip--pick, button.chip, .tile--pick, button.tile, .day, .segment__item, .calendar__day, .float-bar__item, .picker__item, .check,
|
|
3109
3291
|
.icon-btn[role="tab"], .avatar-pick__item > .avatar, .map-pick > .map-pick__mark, .map-pick > .map-pick__name) {
|
|
3110
3292
|
transition-property: scale, background-color, color, border-color, opacity;
|
|
3111
3293
|
transition-duration: var(--motion-press);
|
|
3112
|
-
transition-timing-function: ease-out;
|
|
3294
|
+
transition-timing-function: var(--ease-out);
|
|
3113
3295
|
}
|
|
3114
3296
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
3115
3297
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
3116
3298
|
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, .stamp-on__remove, .map-pick):active:not(:disabled, [aria-disabled="true"], :has(:disabled)) { scale: var(--press-scale); }
|
|
3117
3299
|
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .card--go, .feed-card--small, .toggle-field, .post-box__edge):active:not([aria-disabled="true"], :has(:disabled)) { opacity: var(--press-dim); }
|
|
3118
|
-
.sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
|
|
3119
|
-
/* a pile of notes pressed whole (Lh 2026-10-11): the button lies invisible UNDER the notes, so it is the notes that answer — DIM, as a card does (the group dims as one, the
|
|
3120
|
-
notes do not show through one another); the ring is the button's own, round the whole pile */
|
|
3121
|
-
.sticky-pile__press { cursor: pointer; touch-action: manipulation; }
|
|
3122
|
-
.sticky-pile__notes:has(> .sticky-pile__press) { transition: opacity var(--motion-press) ease-out; }
|
|
3123
|
-
.sticky-pile__notes:has(> .sticky-pile__press:active) { opacity: var(--press-dim); }
|
|
3124
|
-
.sticky-pile__press:focus-visible { outline: var(--focus); outline-offset: 0; }
|
|
3125
3300
|
/* SHADE — what has something BEHIND it (a peeked sheet over the pile, a postcard's face over its back, a post letter's sheet over the other) must not turn see-through:
|
|
3126
3301
|
it darkens a touch instead (--press-shade) */
|
|
3127
|
-
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn, .post-letter__sheet:not(:has(.icon-btn:active))) { cursor: pointer; touch-action: manipulation; transition: filter var(--motion-press) ease-out; }
|
|
3302
|
+
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn, .post-letter__sheet:not(:has(.icon-btn:active))) { cursor: pointer; touch-action: manipulation; transition: filter var(--motion-press) var(--ease-out); }
|
|
3128
3303
|
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn, .post-letter__sheet:not(:has(.icon-btn:active))):active { filter: brightness(var(--press-shade)); }
|
|
3129
3304
|
/* the focus ring: on the control, or on the label that wraps a hidden input (chip, tile, file button, a row that is a label, a card that is a label round its radio) */
|
|
3130
3305
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
@@ -3144,6 +3319,90 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3144
3319
|
.sheet-stack--tap > .card > .card__head > :is(h1, h2, h3):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); } /* the keyboard's way into a stack sheet */
|
|
3145
3320
|
@media (prefers-reduced-motion: reduce) { :root { --press-scale: 1; } } /* the give goes; the dim, the shade and the focus ring stay */
|
|
3146
3321
|
|
|
3322
|
+
/* ==== the chip a choice draws: picked (choice.css drives it with its input), and --sign (#62: a block is written in its own file) ==== */
|
|
3323
|
+
.chip--pick { position: relative; } /* the input covers the whole chip, on a row as in a grid */
|
|
3324
|
+
.chip--pick:has(:checked) { background: var(--accent); --dark: var(--accent-dark); color: var(--card-ink); border-color: transparent; }
|
|
3325
|
+
/* --sign: the trailing + that turns 45° into an × when picked — one mask, one rotate, no swap (Lh 2026-09-16) */
|
|
3326
|
+
.chip--sign { padding-right: var(--sp-2); }
|
|
3327
|
+
.chip--sign::after {
|
|
3328
|
+
content: "";
|
|
3329
|
+
width: var(--icon-xs);
|
|
3330
|
+
height: var(--icon-xs);
|
|
3331
|
+
flex: none;
|
|
3332
|
+
background: currentColor;
|
|
3333
|
+
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>') center / contain no-repeat;
|
|
3334
|
+
-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>') center / contain no-repeat;
|
|
3335
|
+
}
|
|
3336
|
+
.chip--sign:has(:checked)::after { rotate: 45deg; }
|
|
3337
|
+
@media (prefers-reduced-motion: no-preference) { .chip--sign::after { transition: rotate var(--motion-slide); } }
|
|
3338
|
+
|
|
3339
|
+
/* ---- where it sits in what card.css draws (docs/reference.md rule 15) ---- */
|
|
3340
|
+
/* a head's ChipRow takes a line of its own under the title: at most two lines of chips, a chip's words cut with an ellipsis */
|
|
3341
|
+
.card__head > .chip-row { flex: 1 1 100%; order: 2; max-height: calc(2 * (var(--fs-sm) * 1.5 + 2 * var(--chip-sm-pad-y, var(--sp-0)) + 2 * var(--border-w)) + var(--sp-2)); overflow: hidden; }
|
|
3342
|
+
.card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: var(--lh-body); }
|
|
3343
|
+
.card__foot > .btn { flex: 1 1 0; min-width: 0; }
|
|
3344
|
+
/* …unless the button says its own size: `Btn form` is as wide as its words in a foot too, centred (Lh 2026-10-10: one button filling
|
|
3345
|
+
a card's foot read too big — which button a card wears is the project's choice, so the foot must not undo it) */
|
|
3346
|
+
.card__foot > .btn--form { flex: 0 1 auto; }
|
|
3347
|
+
/* the page dots under the card sit centred under it — the indicator belongs
|
|
3348
|
+
with the page that changes, never in the bar (Lh, 2026-09-12) */
|
|
3349
|
+
.screen__centre > .pager__at { justify-self: center; }
|
|
3350
|
+
.panes > .pager__at { justify-self: center; }
|
|
3351
|
+
/* ---- where it sits in what stack.css draws (docs/reference.md rule 15) ---- */
|
|
3352
|
+
.screen--fill:has(> .sheet-stack--flood) > :is(.appbar, .topbar):first-child {
|
|
3353
|
+
position: absolute;
|
|
3354
|
+
inset: var(--safe-top) 0 auto;
|
|
3355
|
+
max-width: none;
|
|
3356
|
+
margin: 0;
|
|
3357
|
+
z-index: var(--z-flat); /* under every sheet (--level 1-5); the base clears it by margin */
|
|
3358
|
+
}
|
|
3359
|
+
.screen--fill:has(> .sheet-stack--flood > .stack-card.is-open, > .sheet-stack--flood > :not(.stack-card) > .stack-card.is-open) > :is(.appbar, .topbar):first-child {
|
|
3360
|
+
visibility: hidden;
|
|
3361
|
+
transition: visibility 0s var(--ease-linear) var(--motion-sheet); /* gone once the sheet has covered it; back at once on a fold (the rule above has no delay) */
|
|
3362
|
+
}
|
|
3363
|
+
/* ---- where it sits in what sheet.css draws (docs/reference.md rule 15) ---- */
|
|
3364
|
+
/* the step waiting for its answer: its buttons take no taps (the app guards its own handler too) */
|
|
3365
|
+
.sheet-step--busy .btn { pointer-events: none; opacity: var(--fade-half); }
|
|
3366
|
+
/* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
|
|
3367
|
+
/* ============================================================
|
|
3368
|
+
TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
|
|
3369
|
+
in a .screen--fill the content runs to the screen's bottom edge, and the bar lies over it at the foot — it covers what is
|
|
3370
|
+
under it, on purpose; nothing is added to the content to make room. The keyboard lifts the layer (--kb).
|
|
3371
|
+
(On a scrolling screen the bar is a sticky last row instead, actions.css.)
|
|
3372
|
+
============================================================ */
|
|
3373
|
+
.screen--fill > .action-bar {
|
|
3374
|
+
position: absolute; inset-inline: 0; top: auto; bottom: var(--kb, 0rem);
|
|
3375
|
+
max-width: none;
|
|
3376
|
+
margin: 0;
|
|
3377
|
+
/* room between its top edge and its first row (the walk's dots): since it has a ground, a 0 here put the dots against the words
|
|
3378
|
+
running under it (Lh 2026-09-25) — the bar's own top padding */
|
|
3379
|
+
padding-top: var(--action-bar-fill-pad-top, var(--sp-3));
|
|
3380
|
+
/* its GROUND (Lh 2026-09-24, timebank card 141 — was "no ground of its own"): words that run under the layer mid-scroll
|
|
3381
|
+
showed through the dots, the controls and the buttons. The layer lies on the screen's own surface now; a project sets
|
|
3382
|
+
another (or `transparent`, the old see-through) with the knob */
|
|
3383
|
+
background: var(--action-bar-bg, var(--surface));
|
|
3384
|
+
}
|
|
3385
|
+
/* the walk's dots are the BOTTOM layer's too (Lh 2026-09-23): a row of the bar, over its buttons, centred */
|
|
3386
|
+
.action-bar > .pager__at, .action-bar__tier > .pager__at { align-self: center; }
|
|
3387
|
+
/* the TOP is in the column too (Lh 2026-09-23): an .appbar in a fill screen is its first row, not fixed over it — the screen
|
|
3388
|
+
reserves nothing for it; the breathing under it stays the one it had (--sp-2), not the screen's gap */
|
|
3389
|
+
.screen--fill > .appbar {
|
|
3390
|
+
position: relative; inset: auto; top: auto;
|
|
3391
|
+
flex: none;
|
|
3392
|
+
max-width: none;
|
|
3393
|
+
margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge; its own padding is the inset */
|
|
3394
|
+
margin-bottom: calc(var(--sp-2) - var(--screen-gap));
|
|
3395
|
+
}
|
|
3396
|
+
/* …and a STEP touches it, as it touches a TopBar: a full sheet's top edge on the bar's bottom edge (0.6.5 left 8px of base between
|
|
3397
|
+
them — timebank #137); the words keep the screen's gap as their lead (--_lead, .appbar + .step above) */
|
|
3398
|
+
.screen--fill > .appbar:has(+ .step) { margin-bottom: calc(-1 * var(--screen-gap)); }
|
|
3399
|
+
/* ---- where it sits in what forms.css draws (docs/reference.md rule 15) ---- */
|
|
3400
|
+
.field--action > .icon-btn { margin-left: auto; }
|
|
3401
|
+
/* ---- where it sits in what sticky.css draws (docs/reference.md rule 15) ---- */
|
|
3402
|
+
.sticky__foot > .chip { display: block; flex: none; min-width: 0; max-width: var(--sticky-foot-chip-max-w, 62%); overflow: hidden; text-overflow: ellipsis; text-align: start; }
|
|
3403
|
+
/* ---- where it sits in what layover.css draws (docs/reference.md rule 15) ---- */
|
|
3404
|
+
.modal__drawer > .btn-row { justify-self: stretch; margin-top: var(--modal-drawer-answers-gap, var(--sp-5)); white-space: nowrap; }
|
|
3405
|
+
|
|
3147
3406
|
/* ---- css/numbers.css — ring, track, band, dot grid, picker, badge, bars ---- */
|
|
3148
3407
|
/* ============================================================
|
|
3149
3408
|
cardds/numbers.css — big numbers inside cards:
|
|
@@ -3175,13 +3434,13 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3175
3434
|
border-radius: 50%;
|
|
3176
3435
|
background: conic-gradient(var(--accent) calc(var(--value) * 1%), var(--ink-tint) 0);
|
|
3177
3436
|
/* masks only read alpha: currentColor = "opaque" without naming a color */
|
|
3178
|
-
mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) -
|
|
3179
|
-
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) -
|
|
3437
|
+
mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - var(--px)), currentColor calc(100% - var(--_w)));
|
|
3438
|
+
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--_w) - var(--px)), currentColor calc(100% - var(--_w)));
|
|
3180
3439
|
}
|
|
3181
3440
|
.ring__value { font: var(--ring-value-font, var(--type-stat)); letter-spacing: var(--track-display); }
|
|
3182
3441
|
.ring__label { font: var(--ring-label-font, var(--type-caption)); color: var(--ring-label-ink, var(--card-muted)); }
|
|
3183
3442
|
.ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
|
|
3184
|
-
.ring--sm .ring__value { font:
|
|
3443
|
+
.ring--sm .ring__value { font: var(--fw-heavy) var(--fs-2xl) / var(--lh-solid) var(--font-display); }
|
|
3185
3444
|
.ring--xs { --_size: var(--tap-sm); --_w: var(--dot-xs); } /* inline counter (step 3 of 5) */
|
|
3186
3445
|
.ring--xs .ring__value { font: var(--type-caption); }
|
|
3187
3446
|
|
|
@@ -3222,44 +3481,31 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3222
3481
|
}
|
|
3223
3482
|
.track__step:first-child::before { display: none; }
|
|
3224
3483
|
.track__step.is-done::before { background: var(--accent); }
|
|
3225
|
-
.track__step > .icon
|
|
3226
|
-
.track__step > .track__dot {
|
|
3227
|
-
width: var(--_step);
|
|
3228
|
-
height: var(--_step);
|
|
3484
|
+
.track__step > .icon { --icon-size: var(--_step); --icon-pad: calc(var(--_step) * 7 / 32); --icon-radius: 50%; --icon-bg: var(--ink-rail); --icon-ink: var(--card-ink); }
|
|
3485
|
+
.track__step > .track__dot { /* a number in the round (TrackStep num) */
|
|
3229
3486
|
flex: none;
|
|
3230
|
-
padding: calc(var(--_step) * 7 / 32); /* the glyph = 18 in the 32 round */
|
|
3231
3487
|
border-radius: 50%;
|
|
3232
|
-
background: var(--ink-
|
|
3488
|
+
background: var(--ink-tint); /* a number is words: on the 30% rail it fell under 4.5:1 on a mid-dark track; the 12% tint is what every tone holds its ink on */
|
|
3233
3489
|
color: var(--card-ink);
|
|
3234
|
-
}
|
|
3235
|
-
.track__step > .track__dot { background: var(--ink-tint); } /* a number is words: on the 30% rail it fell under 4.5:1 on a mid-dark track; the 12% tint is what every tone holds its ink on */
|
|
3236
|
-
.track__step > .track__dot { /* a number in the round (TrackStep num) */
|
|
3237
3490
|
display: grid; place-items: center; box-sizing: border-box;
|
|
3238
|
-
font: var(--track-dot-font, var(--type-caption)); line-height:
|
|
3491
|
+
font: var(--track-dot-font, var(--type-caption)); line-height: var(--lh-solid); font-variant-numeric: tabular-nums;
|
|
3239
3492
|
width: var(--track-dot-size, 1.877em); height: var(--track-dot-size, 1.877em); /* em, not rem (Lh 2026-10-03, as the SheetSteps' number and the StackSheet's count): the round is cut from ITS OWN
|
|
3240
3493
|
number's size — 1.877em of a 17px caption = the 32px of an icon's round; at a larger text it grows with its number, and the pill with it */
|
|
3241
3494
|
padding: 0 0 var(--track-dot-lift, 0.1em); /* the room UNDER the number lifts it onto the round's middle: a digit has no descender */
|
|
3242
3495
|
}
|
|
3243
|
-
.track__step.is-done > .icon
|
|
3496
|
+
.track__step.is-done > .icon { --icon-bg: var(--accent); --icon-ink: var(--on-accent); }
|
|
3244
3497
|
.track__step.is-done > .track__dot { background: var(--accent); color: var(--on-accent); }
|
|
3245
3498
|
|
|
3246
3499
|
/* ---- band: data card — 2-line label · giant number · footnote ----
|
|
3247
3500
|
<article class="card card--band"><span class="band__label">Hours<br>banked</span><span class="band__num">18</span><span class="band__foot">this week</span></article> */
|
|
3248
|
-
.
|
|
3249
|
-
grid-template-columns: 1fr auto;
|
|
3250
|
-
grid-template-areas: "label num" "foot num";
|
|
3251
|
-
align-items: end;
|
|
3252
|
-
gap: var(--sp-2) var(--sp-4);
|
|
3253
|
-
min-height: var(--card-band-min-h, var(--tile-h));
|
|
3254
|
-
}
|
|
3255
|
-
.band__label { grid-area: label; align-self: start; font: var(--band-label-font, var(--type-label)); font-size: var(--band-label-font-size, var(--fs-body)); line-height: 1.2; }
|
|
3501
|
+
.band__label { grid-area: label; align-self: start; font: var(--band-label-font, var(--type-label)); font-size: var(--band-label-font-size, var(--fs-body)); line-height: var(--lh-label); }
|
|
3256
3502
|
.band__num {
|
|
3257
3503
|
grid-area: num;
|
|
3258
|
-
font:
|
|
3504
|
+
font: var(--fw-heavy) var(--fs-giant) / var(--lh-band) var(--font-display);
|
|
3259
3505
|
letter-spacing: var(--track-display);
|
|
3260
3506
|
font-variant-numeric: tabular-nums;
|
|
3261
3507
|
}
|
|
3262
|
-
.band__num small { font-size: 0.5em; font-weight:
|
|
3508
|
+
.band__num small { font-size: 0.5em; font-weight: var(--fw-bold); margin-right: 0.1em; } /* unit or % */
|
|
3263
3509
|
.band__foot { grid-area: foot; font: var(--band-foot-font, var(--type-caption)); color: var(--band-foot-ink, var(--card-muted)); }
|
|
3264
3510
|
/* band stack: ONE card boundary; bands are frameless inside it, a hairline
|
|
3265
3511
|
between each. Bands may still carry slot tints (card--2 / card--3). */
|
|
@@ -3269,8 +3515,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3269
3515
|
border-radius: var(--band-stack-radius, var(--r-card));
|
|
3270
3516
|
overflow: hidden;
|
|
3271
3517
|
}
|
|
3272
|
-
.band-stack > .card--band { border: 0; border-radius: 0; box-shadow: none; }
|
|
3273
|
-
.band-stack > .card--band + .card--band { border-top: var(--border-w) solid var(--hairline); }
|
|
3274
3518
|
|
|
3275
3519
|
/* ---- dotgrid: progress over time as dots — --total, --done, --cols ----
|
|
3276
3520
|
one element, no children:
|
|
@@ -3304,7 +3548,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3304
3548
|
);
|
|
3305
3549
|
}
|
|
3306
3550
|
/* no round(): show the grid without the done fill rather than a broken shape */
|
|
3307
|
-
@supports not (width: round(up,
|
|
3551
|
+
@supports not (width: round(up, 0.0625rem, 0.0625rem)) {
|
|
3308
3552
|
.dotgrid::before { display: none; }
|
|
3309
3553
|
}
|
|
3310
3554
|
/* ---- picker: horizontal snap picker, centre value big ----
|
|
@@ -3349,7 +3593,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3349
3593
|
.picker__unit { font: var(--picker-unit-font, var(--type-caption)); color: var(--picker-unit-ink, var(--card-muted)); }
|
|
3350
3594
|
/* the scrolled-to item lights up by position — no JS */
|
|
3351
3595
|
@supports (animation-timeline: view(x)) {
|
|
3352
|
-
.picker__item { animation: picker-focus linear both; animation-timeline: view(x); }
|
|
3596
|
+
.picker__item { animation: picker-focus var(--ease-linear) both; animation-timeline: view(x); }
|
|
3353
3597
|
@keyframes picker-focus {
|
|
3354
3598
|
0%, 100% { color: var(--_rest); }
|
|
3355
3599
|
45%, 55% { color: var(--card-ink); }
|
|
@@ -3375,7 +3619,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3375
3619
|
|
|
3376
3620
|
/* ---- bars: three or four bars with a count on top — the glance chart ----
|
|
3377
3621
|
<div class="bars"><div class="bar" style="--v: 16"><span>+16</span></div>… --max on the parent */
|
|
3378
|
-
.bars { --max: 20; --_h:
|
|
3622
|
+
.bars { --max: 20; --_h: calc(var(--tap-base) * 5 / 3); display: flex; align-items: end; gap: var(--bars-gap, var(--sp-2)); height: calc(var(--_h) + var(--sp-8)); }
|
|
3379
3623
|
.bar {
|
|
3380
3624
|
flex: 1;
|
|
3381
3625
|
min-width: 0;
|
|
@@ -3385,7 +3629,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3385
3629
|
gap: var(--bar-gap, var(--sp-1));
|
|
3386
3630
|
height: 100%;
|
|
3387
3631
|
font: var(--bar-font, var(--type-caption));
|
|
3388
|
-
font-weight:
|
|
3632
|
+
font-weight: var(--fw-bold);
|
|
3389
3633
|
}
|
|
3390
3634
|
.bar::after {
|
|
3391
3635
|
content: "";
|
|
@@ -3401,8 +3645,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3401
3645
|
<span class="spinner" role="status"><svg class="icon">…loader-circle…</svg>Loading…</span>
|
|
3402
3646
|
The ring turns; words beside it are optional and take the caption's look. Still when motion is reduced. */
|
|
3403
3647
|
.spinner { display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2); vertical-align: middle; font: var(--type-caption); color: var(--spinner-ink, currentColor); }
|
|
3404
|
-
.spinner > .icon {
|
|
3405
|
-
@media (prefers-reduced-motion: no-preference) { .spinner > .icon { animation: turn 0.9s linear infinite; } } /* `turn`: tokens.css */
|
|
3648
|
+
@media (prefers-reduced-motion: no-preference) { .spinner > .icon { --icon-spin: turn var(--motion-spin) var(--ease-linear) infinite; } } /* `turn`: tokens.css */
|
|
3406
3649
|
|
|
3407
3650
|
/* ---- progress: one straight line, the done share solid, the rest faint (Lh 2026-09-22) — a voice note's
|
|
3408
3651
|
timeline, a download, a step count as a line. <div class="progress" role="progressbar" style="--progress-at: 0.4"></div>
|
|
@@ -3420,6 +3663,27 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3420
3663
|
background-repeat: no-repeat;
|
|
3421
3664
|
}
|
|
3422
3665
|
|
|
3666
|
+
/* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
|
|
3667
|
+
/* a TRACK as the screen's TOP (Lh 2026-10-03): the walk's progress stands where a bar would, a row of the column, in the screen's
|
|
3668
|
+
inset — and it never touches what comes up under it: the step (and so a sheet at full) starts --track-top-gap below it. That
|
|
3669
|
+
room is the track's own, not the screen's gap; --track-top-lead is the air over it, under the status bar. Both = the screen's side inset (16): the pill
|
|
3670
|
+
stands the same distance from everything around it (Lh 2026-10-03: 8 over was too little, 24 under too much). */
|
|
3671
|
+
.screen--fill > .track:first-child {
|
|
3672
|
+
flex: none;
|
|
3673
|
+
margin-top: var(--track-top-lead, var(--sp-4));
|
|
3674
|
+
margin-bottom: calc(var(--track-top-gap, var(--sp-4)) - var(--screen-gap));
|
|
3675
|
+
}
|
|
3676
|
+
/* …and over a STAGE (a map behind a sheet, Lh 2026-10-03): the stage is the whole screen, so the track is a LAYER on it — the map
|
|
3677
|
+
shows around the pill — and the stage keeps the track's line free itself: a sheet at full stops --track-top-gap under the
|
|
3678
|
+
track, as in a step. --top-h is the track's own height, measured by Screen (a numbered track grows with the text). */
|
|
3679
|
+
.screen--fill > .track:first-child:has(+ .sheet-stage) {
|
|
3680
|
+
position: absolute; z-index: var(--z-over);
|
|
3681
|
+
inset: calc(var(--safe-top) + var(--track-top-lead, var(--sp-4))) var(--screen-pad) auto;
|
|
3682
|
+
margin: 0;
|
|
3683
|
+
}
|
|
3684
|
+
/* ---- where it sits in what media.css draws (docs/reference.md rule 15) ---- */
|
|
3685
|
+
.postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
|
|
3686
|
+
|
|
3423
3687
|
/* ---- css/people.css — avatar, avatar stack, chooser row ---- */
|
|
3424
3688
|
/* ============================================================
|
|
3425
3689
|
cardds/people.css — avatars: single, stacked, chooser row.
|
|
@@ -3444,12 +3708,12 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3444
3708
|
font: var(--avatar-font, var(--type-label));
|
|
3445
3709
|
font-size: calc(var(--_size) * 0.3);
|
|
3446
3710
|
text-transform: uppercase;
|
|
3447
|
-
letter-spacing:
|
|
3711
|
+
letter-spacing: var(--track-loose);
|
|
3448
3712
|
position: relative;
|
|
3449
3713
|
text-decoration: none; /* .avatar works as <a> too */
|
|
3450
3714
|
}
|
|
3451
3715
|
.avatar > img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
|
|
3452
|
-
.avatar > .icon {
|
|
3716
|
+
.avatar > .icon { --icon-size: 50%; }
|
|
3453
3717
|
.avatar--sm { --_size: var(--avatar-sm); }
|
|
3454
3718
|
.avatar--lg { --_size: var(--avatar-lg); }
|
|
3455
3719
|
.avatar--xl { --_size: var(--avatar-xl); } /* sheet head icon ring */
|
|
@@ -3465,7 +3729,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3465
3729
|
inset: calc(-0.55 * var(--_size));
|
|
3466
3730
|
border-radius: 50%;
|
|
3467
3731
|
background: repeating-radial-gradient(circle, transparent 0 calc(var(--_size) * 0.5), currentColor calc(var(--_size) * 0.5) calc(var(--_size) * 0.5 + var(--border-w)), transparent calc(var(--_size) * 0.5 + var(--border-w)) calc(var(--_size) * 0.68));
|
|
3468
|
-
opacity:
|
|
3732
|
+
opacity: var(--fade-faint);
|
|
3469
3733
|
pointer-events: none;
|
|
3470
3734
|
}
|
|
3471
3735
|
|
|
@@ -3474,11 +3738,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3474
3738
|
.avatar-stack { display: inline-flex; align-items: center; }
|
|
3475
3739
|
.avatar-stack > .avatar { box-shadow: 0 0 0 var(--sp-1) var(--card-bg, var(--surface)); }
|
|
3476
3740
|
.avatar-stack > .avatar + .avatar { margin-left: calc(-1 * var(--sp-3)); }
|
|
3477
|
-
/* a stamp in the stack is the stamp's small size (--stamp-sm-size: one rule in stamp.css, 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
|
|
3478
|
-
lead with a title, a meta line and an end button: at 375 the meta line breaks in two, accepted by Lh (a lap of 16 would hold it; the lap stays 8)
|
|
3479
|
-
<span class="avatar-stack"><span class="stamp stamp--bare">…</span><span class="stamp stamp--bare">…</span><span class="avatar-stack__more">+9</span></span> */
|
|
3480
|
-
.avatar-stack > .stamp { flex: none; margin-inline: 0; }
|
|
3481
|
-
.avatar-stack > .stamp + .stamp { margin-left: calc(-1 * var(--sp-2)); }
|
|
3482
3741
|
/* more: the count that trails the stack — plain words on the ground, title size so it reads at arm's length (the accent "+7" avatar is the small, framed way) */
|
|
3483
3742
|
.avatar-stack__more { margin-left: var(--avatar-stack-more-gap, var(--sp-3)); font: var(--avatar-stack-more-font, var(--type-title)); color: var(--avatar-stack-more-ink, inherit); white-space: nowrap; }
|
|
3484
3743
|
|
|
@@ -3558,7 +3817,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3558
3817
|
--dark: var(--action-dark); /* the paper's side: its knob at rest is the paper's muted ink, on the accent the accent's */
|
|
3559
3818
|
color: var(--card-ink);
|
|
3560
3819
|
position: relative;
|
|
3561
|
-
transition: background-color var(--motion-press) ease-out, border-color var(--motion-press) ease-out;
|
|
3820
|
+
transition: background-color var(--motion-press) var(--ease-out), border-color var(--motion-press) var(--ease-out);
|
|
3562
3821
|
}
|
|
3563
3822
|
.toggle::after {
|
|
3564
3823
|
content: "";
|
|
@@ -3569,7 +3828,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3569
3828
|
height: calc(var(--_h) - 2 * var(--_pad) - 2 * var(--border-w));
|
|
3570
3829
|
border-radius: 50%;
|
|
3571
3830
|
background: var(--card-muted); /* a glyph: a shade of its side, never an opacity (0.45 of the ink was 2.4:1 on the paper) */
|
|
3572
|
-
transition: translate var(--motion-quick) ease;
|
|
3831
|
+
transition: translate var(--motion-quick) var(--ease);
|
|
3573
3832
|
}
|
|
3574
3833
|
.toggle:checked { background: var(--accent); border-color: transparent; }
|
|
3575
3834
|
.toggle:checked::after { translate: calc(var(--_w) - var(--_h)) 0; background: var(--on-accent); }
|
|
@@ -3581,34 +3840,18 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3581
3840
|
<div class="chip-row"><label class="chip chip--pick chip--sign"><input type="checkbox">Nature</label>…</div> */
|
|
3582
3841
|
.chip-grid {
|
|
3583
3842
|
display: grid;
|
|
3584
|
-
grid-template-columns: repeat(auto-fill, minmax(min(
|
|
3843
|
+
grid-template-columns: repeat(auto-fill, minmax(min(calc(var(--tap-base) * 11 / 6), 100%), 1fr));
|
|
3585
3844
|
gap: var(--chip-grid-gap, var(--sp-2));
|
|
3586
3845
|
}
|
|
3587
3846
|
/* .chip hugs its text as a loose grid child (justify-self: start); inside a
|
|
3588
3847
|
chip grid the whole point is equal, full-width pick targets, so stretch.
|
|
3589
3848
|
Matters most in scripts without word spaces (Thai): min-content is the
|
|
3590
3849
|
whole string, tracks get wide, and hugging chips leave a ragged grid. */
|
|
3591
|
-
.chip-grid > .chip {
|
|
3592
|
-
.chip--pick { position: relative; } /* the input covers the whole chip, on a row as in a grid */
|
|
3850
|
+
.chip-grid > .chip { --chip-self: stretch; --chip-justify: center; --chip-min-h: var(--btn-h); --chip-pad-x: var(--sp-2); }
|
|
3593
3851
|
/* the hidden input, the same shape wherever a native radio/checkbox drives a picked look:
|
|
3594
3852
|
a chip, a mood dot, a tile (media.css), a sticky's tone swatch (sticky.css), a place on a map (sheet.css), a card that is a label round a radio
|
|
3595
3853
|
(a pickable row card: the app gives the picked one its `tone`) — the native input lies over its whole box, unseen */
|
|
3596
3854
|
:is(.chip--pick, .mood__opt, .tile--pick, .sticky-pick__swatch, .map-pick) > input, label.card > input[type="radio"] { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
|
|
3597
|
-
:is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); --dark: var(--accent-dark); color: var(--card-ink); border-color: transparent; }
|
|
3598
|
-
/* --sign: the trailing + that turns 45° into an × when picked — one mask, one rotate, no swap (Lh 2026-09-16) */
|
|
3599
|
-
.chip--sign { padding-right: var(--sp-2); }
|
|
3600
|
-
.chip--sign::after {
|
|
3601
|
-
content: "";
|
|
3602
|
-
width: var(--icon-xs);
|
|
3603
|
-
height: var(--icon-xs);
|
|
3604
|
-
flex: none;
|
|
3605
|
-
background: currentColor;
|
|
3606
|
-
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>') center / contain no-repeat;
|
|
3607
|
-
-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>') center / contain no-repeat;
|
|
3608
|
-
}
|
|
3609
|
-
.chip--sign:has(:checked)::after { rotate: 45deg; }
|
|
3610
|
-
@media (prefers-reduced-motion: no-preference) { .chip--sign::after { transition: rotate var(--motion-slide); } }
|
|
3611
|
-
|
|
3612
3855
|
/* ---- day strip: a week, one day selected, today a light tone ----
|
|
3613
3856
|
<div class="day-strip" role="tablist"><button class="day" role="tab" aria-selected="true"><span class="day__name">Thu</span><span class="day__num">16</span></button>…</div> */
|
|
3614
3857
|
.day-strip { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--day-strip-gap, var(--sp-1)); }
|
|
@@ -3631,7 +3874,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3631
3874
|
color: var(--day-rest-ink, var(--card-muted)); /* an unpicked day at rest: the muted words of the ground it stands on (an opacity faded it under AA) */
|
|
3632
3875
|
}
|
|
3633
3876
|
.day__name { font: var(--day-name-font, var(--type-caption)); }
|
|
3634
|
-
.day__num { font:
|
|
3877
|
+
.day__num { font: var(--fw-bold) var(--fs-lg) / var(--lh-tight) var(--font-display); font-variant-numeric: tabular-nums; }
|
|
3635
3878
|
/* today: a light tone, no louder than a rest; the selected pill below wins when it is both (it comes after and weighs more) */
|
|
3636
3879
|
.day--today { background: var(--day-today-bg, var(--ink-tint)); color: var(--day-today-ink, var(--card-ink)); }
|
|
3637
3880
|
.day[aria-selected="true"] { background: var(--day-selected-bg, var(--accent)); color: var(--day-selected-ink, var(--on-accent)); }
|
|
@@ -3650,7 +3893,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3650
3893
|
display: grid;
|
|
3651
3894
|
place-items: center;
|
|
3652
3895
|
border-radius: 50%;
|
|
3653
|
-
font:
|
|
3896
|
+
font: var(--fw-medium) var(--fs-title) / var(--lh-solid) var(--font-ui);
|
|
3654
3897
|
font-variant-numeric: tabular-nums;
|
|
3655
3898
|
grid-column-start: var(--start, auto); /* --start on the 1st: weekday column 1–7 */
|
|
3656
3899
|
}
|
|
@@ -3658,8 +3901,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3658
3901
|
strong mark adds a ring of the accent. A stronger fill sat at the middle, where neither the ink nor --on-accent holds 4.5:1 */
|
|
3659
3902
|
.calendar__day--mark { background: var(--calendar-day-mark-bg, color-mix(in srgb, var(--accent) 12%, transparent)); }
|
|
3660
3903
|
.calendar__day--mark2 { background: var(--calendar-day-mark2-bg, color-mix(in srgb, var(--accent) 12%, transparent)); color: var(--calendar-day-mark2-ink, inherit); box-shadow: var(--calendar-day-mark2-shadow, inset 0 0 0 var(--border-w) var(--accent)); }
|
|
3661
|
-
.calendar__day[aria-selected="true"] { background: var(--calendar-day-selected-bg, var(--accent)); color: var(--calendar-day-selected-ink, var(--on-accent)); font-weight:
|
|
3662
|
-
.calendar__day:disabled { opacity:
|
|
3904
|
+
.calendar__day[aria-selected="true"] { background: var(--calendar-day-selected-bg, var(--accent)); color: var(--calendar-day-selected-ink, var(--on-accent)); font-weight: var(--fw-bold); }
|
|
3905
|
+
.calendar__day:disabled { opacity: var(--fade-dim); }
|
|
3663
3906
|
.calendar__day[aria-current="date"] { text-decoration: underline; text-underline-offset: 0.2em; }
|
|
3664
3907
|
|
|
3665
3908
|
/* ---- calendar weeks: a run of week rows that scrolls week by week, `show` rows in sight; the head sticks at the top of the box
|
|
@@ -3672,12 +3915,12 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3672
3915
|
overflow-y: auto;
|
|
3673
3916
|
overscroll-behavior: contain;
|
|
3674
3917
|
scroll-snap-type: y mandatory;
|
|
3675
|
-
scroll-padding-block-start: var(--_weeks-lead,
|
|
3918
|
+
scroll-padding-block-start: var(--_weeks-lead, 0rem);
|
|
3676
3919
|
overflow-anchor: none; /* a pick rerenders the rows; the browser must not shift the scroll to keep an anchor */
|
|
3677
3920
|
scrollbar-width: none;
|
|
3678
3921
|
}
|
|
3679
3922
|
.calendar--weeks::-webkit-scrollbar { display: none; }
|
|
3680
|
-
.calendar__weeks-head { position: sticky; inset-block-start: 0; z-index:
|
|
3923
|
+
.calendar__weeks-head { position: sticky; inset-block-start: 0; z-index: var(--z-over); display: grid; gap: var(--calendar-head-gap, var(--sp-2)); background: var(--calendar-weeks-head-bg, var(--card-bg)); }
|
|
3681
3924
|
.calendar__label { font: var(--calendar-label-font, var(--type-label)); color: var(--calendar-label-ink, inherit); justify-self: start; }
|
|
3682
3925
|
button.calendar__label { appearance: none; border: 0; padding: 0; background: none; cursor: pointer; border-radius: var(--sp-1); }
|
|
3683
3926
|
button.calendar__label:focus-visible { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
@@ -3702,14 +3945,14 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
3702
3945
|
height: var(--mood-dot-size, var(--icon-xs));
|
|
3703
3946
|
border-radius: 50%;
|
|
3704
3947
|
background: var(--_mood, currentColor);
|
|
3705
|
-
transition: scale
|
|
3948
|
+
transition: scale var(--motion-quick) var(--ease), box-shadow var(--motion-quick) var(--ease);
|
|
3706
3949
|
}
|
|
3707
3950
|
.mood__opt:nth-child(1) { --_mood: var(--neg); }
|
|
3708
3951
|
.mood__opt:nth-child(2) { --_mood: color-mix(in srgb, var(--neg) 50%, var(--dot-b)); }
|
|
3709
3952
|
.mood__opt:nth-child(3) { --_mood: var(--dot-b); }
|
|
3710
3953
|
.mood__opt:nth-child(4) { --_mood: color-mix(in srgb, var(--pos) 50%, var(--dot-b)); }
|
|
3711
3954
|
.mood__opt:nth-child(5) { --_mood: var(--pos); }
|
|
3712
|
-
.mood__opt:has(:checked) > .mood__dot { scale:
|
|
3955
|
+
.mood__opt:has(:checked) > .mood__dot { scale: var(--scale-pick); box-shadow: 0 0 0 var(--sp-2) color-mix(in srgb, var(--_mood, currentColor) 25%, transparent); }
|
|
3713
3956
|
.mood__opt:has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-0); border-radius: 50%; }
|
|
3714
3957
|
|
|
3715
3958
|
/* ---- pin: big code entry, one digit per pill ----
|
|
@@ -3749,7 +3992,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
3749
3992
|
background-position: center;
|
|
3750
3993
|
background-repeat: no-repeat;
|
|
3751
3994
|
}
|
|
3752
|
-
.pin--secret .pin__cell.is-fresh:not(:placeholder-shown) { animation: pin-flash var(--pin-flash-time,
|
|
3995
|
+
.pin--secret .pin__cell.is-fresh:not(:placeholder-shown) { animation: pin-flash var(--pin-flash-time, var(--motion-flash)) var(--ease-step); }
|
|
3753
3996
|
@keyframes pin-flash { 0% { -webkit-text-fill-color: currentColor; background-size: 0 0; } }
|
|
3754
3997
|
.pin--secret .pin__cell::selection { background: transparent; } /* its digit is selected when the cell takes the cursor: nothing to show of it */
|
|
3755
3998
|
.pin__cell:focus { outline: none; }
|
|
@@ -3760,12 +4003,12 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
3760
4003
|
.pin--asleep .pin__cell:focus:placeholder-shown { background-image: none; animation: none; }
|
|
3761
4004
|
.pin__cell:is(:focus, [data-cursor]):placeholder-shown {
|
|
3762
4005
|
background-image: linear-gradient(currentColor, currentColor);
|
|
3763
|
-
background-size: var(--pin-caret-w,
|
|
4006
|
+
background-size: var(--pin-caret-w, var(--hairline-w)) var(--pin-caret-h, 1em);
|
|
3764
4007
|
background-position: center;
|
|
3765
4008
|
background-repeat: no-repeat;
|
|
3766
4009
|
}
|
|
3767
4010
|
@media (prefers-reduced-motion: no-preference) {
|
|
3768
|
-
.pin__cell:is(:focus, [data-cursor]):placeholder-shown { animation: pin-caret
|
|
4011
|
+
.pin__cell:is(:focus, [data-cursor]):placeholder-shown { animation: pin-caret var(--motion-blink) var(--ease-step) infinite; }
|
|
3769
4012
|
@keyframes pin-caret { 50% { background-size: 0 0; } }
|
|
3770
4013
|
}
|
|
3771
4014
|
|
|
@@ -3777,13 +4020,13 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
3777
4020
|
.composer { display: grid; gap: var(--composer-gap, var(--sp-3)); }
|
|
3778
4021
|
.composer__input {
|
|
3779
4022
|
width: 100%;
|
|
3780
|
-
min-height: var(--composer-input-min-h,
|
|
4023
|
+
min-height: var(--composer-input-min-h, calc(var(--tap-base) * 11 / 3));
|
|
3781
4024
|
resize: none;
|
|
3782
4025
|
border: 0;
|
|
3783
4026
|
padding: 0;
|
|
3784
4027
|
background: transparent;
|
|
3785
4028
|
color: inherit;
|
|
3786
|
-
font: var(--composer-input-font,
|
|
4029
|
+
font: var(--composer-input-font, var(--fw-bold) var(--fs-2xl) / var(--lh-h2) var(--font-display));
|
|
3787
4030
|
letter-spacing: var(--track-h2);
|
|
3788
4031
|
outline: none;
|
|
3789
4032
|
}
|
|
@@ -3870,6 +4113,14 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
3870
4113
|
flex: none;
|
|
3871
4114
|
}
|
|
3872
4115
|
|
|
4116
|
+
/* ==== the pin's wrong code: red cells and one shake (#62: a block is written in its own file) ==== */
|
|
4117
|
+
.pin.pin--error .pin__cell, .pin.pin--error .pin__cell:focus { border-color: var(--pin-error-border, var(--signal-error)); color: var(--pin-error-ink, var(--signal-error)); } /* ALL the cells, edge and digit — the one the cursor waits in too (Lh 2026-09-24); the wrong digits stay in red until the member types again */
|
|
4118
|
+
/* a code that does not match (Pin match): the row shakes once, sideways, then the Pin empties itself for a new try (Lh 2026-09-25) */
|
|
4119
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
4120
|
+
.pin--shake { animation: pin-shake var(--pin-shake-time, var(--motion-shake)) var(--ease-in-out); }
|
|
4121
|
+
}
|
|
4122
|
+
@keyframes pin-shake { 0%, 100% { translate: 0; } 20%, 60% { translate: calc(-1 * var(--pin-shake-x, var(--sp-2))); } 40%, 80% { translate: var(--pin-shake-x, var(--sp-2)); } }
|
|
4123
|
+
|
|
3873
4124
|
/* ---- css/lists.css — row, rows, key-value, kv grid, timeline, legend, link ---- */
|
|
3874
4125
|
/* ============================================================
|
|
3875
4126
|
cardds/lists.css — frameless rows and pairs INSIDE a card:
|
|
@@ -3907,15 +4158,15 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
3907
4158
|
.row__meta { font: var(--row-meta-font, var(--type-caption)); color: var(--row-meta-ink, var(--card-muted)); display: flex; align-items: center; gap: var(--row-meta-gap, var(--sp-1)); }
|
|
3908
4159
|
.row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
|
|
3909
4160
|
.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 */
|
|
3910
|
-
.row--lg .row__title { font:
|
|
4161
|
+
.row--lg .row__title { font: var(--fw-bold) var(--fs-lg) / var(--lh-h1) var(--font-display); }
|
|
3911
4162
|
/* a row that EXPANDS (Lh 2026-09-22): the row is a <details>' summary, what it shows comes out under it —
|
|
3912
4163
|
who liked a postcard, the rest of a list. An end chevron turns when open. The app owns the list.
|
|
3913
4164
|
<details class="row-expand"><summary class="row">…lead · body · end…</summary><div class="row-expand__more">…</div></details> */
|
|
3914
4165
|
.row-expand { display: block; }
|
|
3915
4166
|
.row-expand > summary { list-style: none; }
|
|
3916
4167
|
.row-expand > summary::-webkit-details-marker { display: none; }
|
|
3917
|
-
.row-expand > summary > .icon:last-child {
|
|
3918
|
-
.row-expand[open] > summary > .icon:last-child {
|
|
4168
|
+
.row-expand > summary > .icon:last-child { --icon-motion: rotate var(--motion-slide); }
|
|
4169
|
+
.row-expand[open] > summary > .icon:last-child { --icon-turn: 180deg; }
|
|
3919
4170
|
.row-expand__more { padding-top: var(--row-expand-gap, var(--sp-3)); }
|
|
3920
4171
|
/* a finished item in a checklist (a to-do list, an onboarding "complete your introduction" list):
|
|
3921
4172
|
the words go muted, a filled tick sits at the end, and the row is no longer a tap target —
|
|
@@ -3964,7 +4215,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
3964
4215
|
.kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(var(--tile-min), 100%), 1fr)); gap: var(--kv-grid-gap, var(--sp-4)); }
|
|
3965
4216
|
.kv-grid > .kv-cell + .kv-cell { border-left: var(--border-w) solid var(--hairline); padding-left: var(--sp-4); }
|
|
3966
4217
|
.kv-cell { display: grid; gap: var(--kv-cell-gap, var(--sp-0)); align-content: start; min-width: 0; }
|
|
3967
|
-
.kv-cell > .icon { margin
|
|
4218
|
+
.kv-cell > .icon { --icon-margin: 0 0 var(--sp-1); }
|
|
3968
4219
|
.kv-cell__val { font: var(--kv-cell-val-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
|
|
3969
4220
|
.kv-cell__key { font: var(--kv-cell-key-font, var(--type-caption)); color: var(--kv-cell-key-ink, var(--card-muted)); }
|
|
3970
4221
|
|
|
@@ -4009,7 +4260,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4009
4260
|
}
|
|
4010
4261
|
.bubble__meta { display: flex; align-items: center; gap: var(--bubble-meta-gap, var(--sp-1)); font: var(--bubble-meta-font, var(--type-caption)); color: var(--bubble-meta-ink, var(--card-muted)); }
|
|
4011
4262
|
.bubble--right > .bubble__meta { justify-self: end; }
|
|
4012
|
-
.bubble__meta > .icon {
|
|
4263
|
+
.bubble__meta > .icon { --icon-ink: var(--bubble-meta-icon-ink, var(--accent-ink)); }
|
|
4013
4264
|
|
|
4014
4265
|
/* ---- timeline: a hairline with date chips, entries to the right ----
|
|
4015
4266
|
<ol class="timeline"><li class="timeline__item"><span class="timeline__mark">Wed 14</span><p class="t-body">…</p></li>…</ol> */
|
|
@@ -4080,12 +4331,14 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4080
4331
|
.bullets { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--bullets-gap, var(--sp-3)); font: var(--bullets-font, var(--type-body)); }
|
|
4081
4332
|
.bullets__item { display: flex; align-items: start; gap: var(--bullets-pic-gap, var(--sp-3)); min-width: 0; }
|
|
4082
4333
|
.bullets__pic { flex: none; inline-size: var(--bullets-pic-size, 1lh); block-size: var(--bullets-pic-size, 1lh); min-height: 0; object-fit: contain; border-radius: var(--bullets-pic-radius, 0rem); display: grid; place-items: center; }
|
|
4083
|
-
.bullets__pic.ph { border-radius: var(--bullets-pic-radius, 0rem); }
|
|
4084
4334
|
.bullets__text { flex: 1; min-width: 0; }
|
|
4085
4335
|
|
|
4336
|
+
/* ---- where it sits in what sheet.css draws (docs/reference.md rule 15) ---- */
|
|
4337
|
+
.sheet-step__inner > .link { justify-self: center; } /* a link of its own row in a step (skip, "change number", "set PIN again"): centred under the step, as the skip (Lh 2026-09-25) */
|
|
4338
|
+
|
|
4086
4339
|
/* ---- css/sticky.css — sticky note, its field, tone chooser, thread of posts, pile ---- */
|
|
4087
4340
|
/* cardds — sticky notes: the paper note, the field written on it, the tone chooser, notes stuck on a page, a thread of posts, the answer note.
|
|
4088
|
-
A note is
|
|
4341
|
+
A note is pastel paper: its tone is one of the theme's nine pastels (--pastel-*, tokens.css), its ink the onlight set, whatever card it lies on. */
|
|
4089
4342
|
|
|
4090
4343
|
/* ---- sticky: a paper note — a flat pastel SQUARE of one size, the bottom-right corner folded over ----
|
|
4091
4344
|
ONE SIZE (Lh 2026-10-10): every note is --sticky-size square (168), whatever it says; words it cannot hold are written one size smaller
|
|
@@ -4125,28 +4378,28 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4125
4378
|
.sticky::before, .sticky::after {
|
|
4126
4379
|
content: "";
|
|
4127
4380
|
position: absolute;
|
|
4128
|
-
z-index: -
|
|
4381
|
+
z-index: var(--z-under);
|
|
4129
4382
|
pointer-events: none;
|
|
4130
4383
|
border-radius: var(--sticky-radius, 0rem);
|
|
4131
4384
|
background-image: linear-gradient(-45deg, transparent calc(var(--_fold) * 0.7071), var(--_ink) 0); /* the folded corner, cut */
|
|
4132
4385
|
}
|
|
4133
|
-
.sticky::after { --_ink: var(--sticky-edge-bg,
|
|
4386
|
+
.sticky::after { --_ink: var(--sticky-edge-bg, color-mix(in srgb, var(--ink-onlight) 14%, transparent)); inset: calc(-1 * var(--border-w)); }
|
|
4134
4387
|
.sticky::before {
|
|
4135
|
-
--_ink: var(--sticky-shadow-bg,
|
|
4388
|
+
--_ink: var(--sticky-shadow-bg, color-mix(in srgb, var(--ink-onlight) 28%, transparent));
|
|
4136
4389
|
inset: var(--sp-1) var(--sp-0) calc(-1 * var(--sp-0)) var(--sp-0);
|
|
4137
4390
|
filter: blur(var(--sticky-shadow-blur, var(--sp-0)));
|
|
4138
4391
|
}
|
|
4139
4392
|
.sticky--flat::before, .sticky--flat::after { background: var(--_ink); }
|
|
4140
4393
|
/* the tones — the names in src/sticky/tones.ts. A swatch of the chooser wears the same class. */
|
|
4141
|
-
.sticky--cotton { background-color: var(--sticky-cotton-bg,
|
|
4142
|
-
.sticky--blush { background-color: var(--sticky-blush-bg,
|
|
4143
|
-
.sticky--tea-leaf { background-color: var(--sticky-tea-leaf-bg,
|
|
4144
|
-
.sticky--custard { background-color: var(--sticky-custard-bg,
|
|
4145
|
-
.sticky--salmon { background-color: var(--sticky-salmon-bg,
|
|
4146
|
-
.sticky--iceberg { background-color: var(--sticky-iceberg-bg,
|
|
4147
|
-
.sticky--cocoa { background-color: var(--sticky-cocoa-bg,
|
|
4148
|
-
.sticky--apricot { background-color: var(--sticky-apricot-bg,
|
|
4149
|
-
.sticky--water { background-color: var(--sticky-water-bg,
|
|
4394
|
+
.sticky--cotton { background-color: var(--sticky-cotton-bg, var(--pastel-cotton)); }
|
|
4395
|
+
.sticky--blush { background-color: var(--sticky-blush-bg, var(--pastel-blush)); }
|
|
4396
|
+
.sticky--tea-leaf { background-color: var(--sticky-tea-leaf-bg, var(--pastel-tea-leaf)); }
|
|
4397
|
+
.sticky--custard { background-color: var(--sticky-custard-bg, var(--pastel-custard)); }
|
|
4398
|
+
.sticky--salmon { background-color: var(--sticky-salmon-bg, var(--pastel-salmon)); }
|
|
4399
|
+
.sticky--iceberg { background-color: var(--sticky-iceberg-bg, var(--pastel-iceberg)); }
|
|
4400
|
+
.sticky--cocoa { background-color: var(--sticky-cocoa-bg, var(--pastel-cocoa)); }
|
|
4401
|
+
.sticky--apricot { background-color: var(--sticky-apricot-bg, var(--pastel-apricot)); }
|
|
4402
|
+
.sticky--water { background-color: var(--sticky-water-bg, var(--pastel-water)); }
|
|
4150
4403
|
|
|
4151
4404
|
.sticky__paper {
|
|
4152
4405
|
position: relative;
|
|
@@ -4163,12 +4416,10 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4163
4416
|
clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--_fold)), calc(100% - var(--_fold)) 100%, 0 100%);
|
|
4164
4417
|
}
|
|
4165
4418
|
.sticky__head { flex: none; display: flex; align-items: center; gap: var(--sticky-head-gap, var(--sp-2)); font: var(--sticky-head-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
|
|
4166
|
-
.sticky__head > .ph { flex: 1 1 0; min-width: 0; } /* a picture in the head takes the note's width */
|
|
4167
4419
|
.sticky__head:has(> .sticky-date) { align-items: start; }
|
|
4168
|
-
.sticky__head:has(> .sticky-date) > .ph { flex: none; width: var(--sticky-pic-size, calc(var(--tap) * 1.5)); min-height: 0; aspect-ratio: 1; } /* beside a date the picture is a small square at the start */
|
|
4169
4420
|
.sticky__body { flex: 1 1 auto; min-width: 0; min-height: 0; overflow: clip; /* words past the note's room are cut, the foot keeps its place */ font: var(--sticky-font, var(--type-title)); overflow-wrap: anywhere; }
|
|
4170
4421
|
.sticky--small .sticky__body { font: var(--sticky-small-font, var(--type-label)); } /* the note measured its words and they did not fit at its size (src/sticky/fit.ts) */
|
|
4171
|
-
.sticky__paper > :not(.sticky__body) > .chip { border
|
|
4422
|
+
.sticky__paper > :not(.sticky__body) > .chip { --chip-border: transparent; --chip-bg: var(--sticky-chip-bg, color-mix(in srgb, var(--stamp-paper) 85%, transparent)); --dark: inherit; --chip-ink: var(--sticky-ink, var(--card-ink)); } /* a chip on a note is a label ON the paper (whose note it is): a pale ground nearly opaque (it may lie over words), no edge, the note's ink */
|
|
4172
4423
|
.sticky__foot { flex: none; font: var(--sticky-foot-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); padding-inline-end: var(--_fold); } /* the line stops before the fold */
|
|
4173
4424
|
/* the flap: a square in the corner — the paper's cut takes its outer half, what is left is the triangle lying on the note */
|
|
4174
4425
|
.sticky__fold {
|
|
@@ -4179,7 +4430,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4179
4430
|
height: var(--_fold);
|
|
4180
4431
|
border-top-left-radius: var(--sp-1);
|
|
4181
4432
|
background-color: var(--sticky-fold-bg, color-mix(in srgb, currentColor 14%, transparent)); /* a darker shade of whatever the paper is */
|
|
4182
|
-
box-shadow:
|
|
4433
|
+
box-shadow: var(--shadow-fold);
|
|
4183
4434
|
}
|
|
4184
4435
|
/* things stuck ON the note (a stamp, placed by hand): a layer over the whole paper. A tap between them still reaches the words under it.
|
|
4185
4436
|
A stamp on a note is the stamp's small size whatever the note's (48 × 60, --stamp-sm-size: stamp.css; `StampOn main` is the main size), and may lie over the words. */
|
|
@@ -4191,8 +4442,8 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4191
4442
|
/* a note that stands alone in a page's column or in a centre stands in its middle */
|
|
4192
4443
|
.page > .sticky, .screen__centre > .sticky { margin-inline: auto; }
|
|
4193
4444
|
.sticky--tilt-left, .sticky--tilt-right { margin-block: var(--sp-4); } /* the turned corners and the shadow need room: a box that scrolls (a centre) would cut them */
|
|
4194
|
-
.sticky--tilt-left { rotate: calc(-1 * var(--sticky-tilt,
|
|
4195
|
-
.sticky--tilt-right { rotate: var(--sticky-tilt,
|
|
4445
|
+
.sticky--tilt-left { rotate: calc(-1 * var(--sticky-tilt, calc(var(--tilt-base) * 5))); }
|
|
4446
|
+
.sticky--tilt-right { rotate: var(--sticky-tilt, calc(var(--tilt-base) * 5)); }
|
|
4196
4447
|
|
|
4197
4448
|
/* ---- sticky date: the day, big, the month under it — at the end of the note's head (its top-right corner) ----
|
|
4198
4449
|
<div class="sticky__head">…<time class="sticky-date" datetime="2026-02-14"><span class="sticky-date__day">14</span><span class="sticky-date__month">ก.พ.</span></time></div> */
|
|
@@ -4234,7 +4485,6 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4234
4485
|
/* a name label in the foot (a Chip, `sm`) stands at the START, what follows it (the time) at the end, on one line: a long name is cut
|
|
4235
4486
|
with an ellipsis past its share of the row and never wraps; the time is never cut (a long one may run a little onto the fold) */
|
|
4236
4487
|
.sticky__paper > .sticky__foot:has(> .chip) { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); white-space: nowrap; text-align: end; }
|
|
4237
|
-
.sticky__foot > .chip { display: block; flex: none; min-width: 0; max-width: var(--sticky-foot-chip-max-w, 62%); overflow: hidden; text-overflow: ellipsis; text-align: start; }
|
|
4238
4488
|
|
|
4239
4489
|
/* ---- sticky field: the words written ON a note — a bare textarea in the note's type and ink, filling it ----
|
|
4240
4490
|
<div class="sticky__body"><textarea class="sticky-field" aria-label="Note" placeholder="Add text to this note"></textarea></div> */
|
|
@@ -4329,12 +4579,12 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4329
4579
|
.sticky-thread--lapped > .sticky-post > .sticky-post__who { display: none; }
|
|
4330
4580
|
.sticky-thread--lapped > .sticky-post > .sticky-post__notes { grid-area: 1 / 1; }
|
|
4331
4581
|
.sticky-thread--lapped > .sticky-post > .sticky-post__head { grid-area: 2 / 1; }
|
|
4332
|
-
.sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__name { position: absolute; width:
|
|
4582
|
+
.sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__name { position: absolute; width: var(--px); height: var(--px); margin: calc(-1 * var(--px)); overflow: hidden; clip-path: inset(50%); white-space: nowrap; } /* as .t-sr: heard, not seen */
|
|
4333
4583
|
.sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__time { margin-inline: 0; }
|
|
4334
4584
|
.sticky-thread--lapped > .sticky-post > .sticky-post__head:not(:has(> .sticky-post__time)) { display: none; }
|
|
4335
4585
|
/* each note leans a little, one way then the other; a post counts among posts only, so a heading before the first changes no note's lean */
|
|
4336
|
-
.sticky-thread--lapped > .sticky-post > .sticky-post__notes > .sticky { rotate: calc(-1 * var(--sticky-thread-tilt,
|
|
4337
|
-
.sticky-thread--lapped > .sticky-post:nth-child(even of .sticky-post) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt,
|
|
4586
|
+
.sticky-thread--lapped > .sticky-post > .sticky-post__notes > .sticky { rotate: calc(-1 * var(--sticky-thread-tilt, calc(var(--tilt-base) * 2))); }
|
|
4587
|
+
.sticky-thread--lapped > .sticky-post:nth-child(even of .sticky-post) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, calc(var(--tilt-base) * 2)); }
|
|
4338
4588
|
/* under an answer note the thread ends with the room that note stands in: the last turn scrolls up clear of it */
|
|
4339
4589
|
.screen:has(> .sticky-answer) .sticky-thread--lapped { padding-block-end: calc(var(--sticky-size, calc(var(--sp-12) * 3.5)) * var(--sticky-answer-show, 0.75) + var(--sp-6)); }
|
|
4340
4590
|
|
|
@@ -4367,11 +4617,12 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4367
4617
|
.sticky-board .sticky__paper > .sticky__foot { position: absolute; inset: auto var(--sticky-board-foot-inset, var(--sp-3)) var(--sticky-board-foot-inset, var(--sp-3)); text-align: end; }
|
|
4368
4618
|
/* how far each is turned: FOUR unlike angles in turn (Lh 2026-10-11). At most three notes meet, so four keep the lean from ever looking the
|
|
4369
4619
|
same: two in a row, a note lies over the one two before it and those lean opposite ways; three in a row, over the one three before it,
|
|
4370
|
-
never the same angle. (A run of seven left one column at 0.75° and −0.5°: its edges read as one straight line and the lean was lost.)
|
|
4371
|
-
|
|
4372
|
-
.sticky-board > :nth-child(4n +
|
|
4373
|
-
.sticky-board > :nth-child(4n +
|
|
4374
|
-
.sticky-board > :nth-child(4n) { rotate: var(--sticky-board-turn-
|
|
4620
|
+
never the same angle. (A run of seven left one column at 0.75° and −0.5°: its edges read as one straight line and the lean was lost.)
|
|
4621
|
+
A pile (below) turns its notes by the same four, by their place among its children. */
|
|
4622
|
+
.sticky-board > :nth-child(4n + 1), .sticky-pile__notes > .sticky:nth-child(4n + 1) { rotate: var(--sticky-board-turn-1, calc(var(--tilt-base) * -3)); }
|
|
4623
|
+
.sticky-board > :nth-child(4n + 2), .sticky-pile__notes > .sticky:nth-child(4n + 2) { rotate: var(--sticky-board-turn-2, calc(var(--tilt-base) * 1.5)); }
|
|
4624
|
+
.sticky-board > :nth-child(4n + 3), .sticky-pile__notes > .sticky:nth-child(4n + 3) { rotate: var(--sticky-board-turn-3, calc(var(--tilt-base) * 2.75)); }
|
|
4625
|
+
.sticky-board > :nth-child(4n), .sticky-pile__notes > .sticky:nth-child(4n) { rotate: var(--sticky-board-turn-4, calc(var(--tilt-base) * -1.75)); }
|
|
4375
4626
|
|
|
4376
4627
|
/* ---- sticky pile: one to six notes lying one over another — the newest on top (Lh 2026-10-11) ----
|
|
4377
4628
|
<div class="sticky-pile sticky-pile--tap" style="--_n: 3">
|
|
@@ -4402,17 +4653,13 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4402
4653
|
grid-template: var(--_size) / var(--_size);
|
|
4403
4654
|
padding: var(--_pad) calc(var(--_pad) + (var(--_n) - 1) * var(--_step-x)) calc(var(--_pad) + (var(--_n) - 1) * var(--_step-y)) var(--_pad);
|
|
4404
4655
|
}
|
|
4405
|
-
.sticky-pile__notes > .sticky { --_depth: 0; grid-area: 1 / 1; z-index: calc(
|
|
4656
|
+
.sticky-pile__notes > .sticky { --_depth: 0; grid-area: 1 / 1; z-index: calc(var(--z-pile) - var(--_depth)); translate: calc(var(--_depth) * var(--_step-x)) calc(var(--_depth) * var(--_step-y)); }
|
|
4406
4657
|
.sticky-pile__notes > .sticky:nth-child(2) { --_depth: 1; }
|
|
4407
4658
|
.sticky-pile__notes > .sticky:nth-child(3) { --_depth: 2; }
|
|
4408
4659
|
.sticky-pile__notes > .sticky:nth-child(4) { --_depth: 3; }
|
|
4409
4660
|
.sticky-pile__notes > .sticky:nth-child(5) { --_depth: 4; }
|
|
4410
4661
|
.sticky-pile__notes > .sticky:nth-child(6) { --_depth: 5; }
|
|
4411
4662
|
.sticky-pile__notes > .sticky:nth-child(n + 2) .sticky__foot { display: none; }
|
|
4412
|
-
.sticky-pile__notes > .sticky:nth-child(4n + 1) { rotate: var(--sticky-board-turn-1, -3deg); }
|
|
4413
|
-
.sticky-pile__notes > .sticky:nth-child(4n + 2) { rotate: var(--sticky-board-turn-2, 1.5deg); }
|
|
4414
|
-
.sticky-pile__notes > .sticky:nth-child(4n + 3) { rotate: var(--sticky-board-turn-3, 2.75deg); }
|
|
4415
|
-
.sticky-pile__notes > .sticky:nth-child(4n) { rotate: var(--sticky-board-turn-4, -1.75deg); }
|
|
4416
4663
|
.sticky-pile__press { position: absolute; inset: 0; margin: 0; padding: 0; border: 0; appearance: none; background: none; -webkit-tap-highlight-color: transparent; }
|
|
4417
4664
|
.sticky-pile--tap .sticky-pile__notes > .sticky { pointer-events: none; }
|
|
4418
4665
|
|
|
@@ -4438,16 +4685,16 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4438
4685
|
pointer-events: none;
|
|
4439
4686
|
}
|
|
4440
4687
|
.screen--fill > .sticky-answer { position: absolute; }
|
|
4441
|
-
.sticky-answer--writing { z-index: calc(var(--z-top) +
|
|
4442
|
-
.sticky-answer__scrim { position: absolute; inset: 0; background: var(--sticky-answer-scrim-bg, color-mix(in srgb, var(--surface) 82%, transparent)); opacity: 0; visibility: hidden; transition: opacity var(--motion-sheet) ease, visibility 0s var(--motion-sheet); }
|
|
4688
|
+
.sticky-answer--writing { z-index: calc(var(--z-top) + var(--z-over)); }
|
|
4689
|
+
.sticky-answer__scrim { position: absolute; inset: 0; background: var(--sticky-answer-scrim-bg, color-mix(in srgb, var(--surface) 82%, transparent)); opacity: 0; visibility: hidden; transition: opacity var(--motion-sheet) var(--ease), visibility 0s var(--motion-sheet); }
|
|
4443
4690
|
.sticky-answer--writing > .sticky-answer__scrim { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s; }
|
|
4444
4691
|
.sticky-answer__note {
|
|
4445
4692
|
position: absolute;
|
|
4446
|
-
top: calc(100% - var(--_size) * var(--sticky-answer-show, 0.75) - max(var(--safe-bottom), var(--kb,
|
|
4693
|
+
top: calc(100% - var(--_size) * var(--sticky-answer-show, 0.75) - max(var(--safe-bottom), var(--kb, 0rem)));
|
|
4447
4694
|
right: var(--sticky-answer-end, var(--screen-pad));
|
|
4448
|
-
rotate: var(--sticky-answer-tilt, -
|
|
4695
|
+
rotate: var(--sticky-answer-tilt, calc(var(--tilt-base) * -3));
|
|
4449
4696
|
pointer-events: auto;
|
|
4450
|
-
transition: top var(--motion-sheet) ease, right var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
|
|
4697
|
+
transition: top var(--motion-sheet) var(--ease), right var(--motion-sheet) var(--ease), rotate var(--motion-sheet) var(--ease);
|
|
4451
4698
|
}
|
|
4452
4699
|
.sticky-answer--writing > .sticky-answer__note { top: calc(var(--safe-top) + var(--header-h) + var(--sticky-answer-top, var(--sp-5))); right: calc(50% - var(--_size) / 2); rotate: 0deg; }
|
|
4453
4700
|
.sticky-answer__under {
|
|
@@ -4455,7 +4702,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4455
4702
|
top: calc(var(--safe-top) + var(--header-h) + var(--sticky-answer-top, var(--sp-5)) + var(--_size) + var(--sticky-answer-gap, var(--sp-4)));
|
|
4456
4703
|
display: grid; gap: var(--sticky-answer-gap, var(--sp-4));
|
|
4457
4704
|
opacity: 0; visibility: hidden;
|
|
4458
|
-
transition: opacity var(--motion-sheet-rest) ease, visibility 0s var(--motion-sheet-rest);
|
|
4705
|
+
transition: opacity var(--motion-sheet-rest) var(--ease), visibility 0s var(--motion-sheet-rest);
|
|
4459
4706
|
}
|
|
4460
4707
|
.sticky-answer--writing > .sticky-answer__under { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: var(--motion-sheet-rest), 0s; }
|
|
4461
4708
|
.sticky-answer__tools { display: flex; align-items: center; justify-content: space-between; gap: var(--sticky-answer-tools-gap, var(--sp-3)); }
|
|
@@ -4479,7 +4726,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4479
4726
|
border-block-end: var(--rule-w) dotted currentColor;
|
|
4480
4727
|
font: inherit;
|
|
4481
4728
|
}
|
|
4482
|
-
.sticky-heading__title { min-width: 0; max-width: 100%; font: var(--sticky-heading-title-font, var(--type-label)); line-height:
|
|
4729
|
+
.sticky-heading__title { min-width: 0; max-width: 100%; font: var(--sticky-heading-title-font, var(--type-label)); line-height: var(--lh-caption); /* Thai marks stack taller than the label's 1.2, and overflow clips them */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4483
4730
|
.sticky-heading__caption { font: var(--sticky-heading-caption-font, var(--type-caption)); color: var(--sticky-heading-caption-ink, var(--card-muted)); white-space: nowrap; } /* the muted words of what it stands on: a note, or the screen */
|
|
4484
4731
|
/* on a note: across the top of its paper, and the note's words start under it */
|
|
4485
4732
|
.sticky__stuck > .sticky-heading { position: absolute; inset: var(--sp-2) var(--sp-4) auto; }
|
|
@@ -4488,9 +4735,17 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4488
4735
|
.sticky-board .sticky:has(> .sticky__stuck > .sticky-heading) .sticky__body { flex: none; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
|
4489
4736
|
/* in a thread: across its top on the screen's ground, staying there while the notes run under it; the room before the first note is the turned corner's, as the thread's own top */
|
|
4490
4737
|
/* the ground is drawn a little wider than the heading each way: a turned note's corner and its shadow stand outside the thread's width */
|
|
4491
|
-
.sticky-thread > .sticky-heading { position: sticky; inset-block-start: 0; z-index: 2; padding-block-start: var(--sp-2); background: var(--surface); color: var(--on-surface); box-shadow: calc(-1 * var(--sp-3)) 0 0 var(--surface), var(--sp-3) 0 0 var(--surface); }
|
|
4738
|
+
.sticky-thread > .sticky-heading { position: sticky; inset-block-start: 0; z-index: var(--z-over-2); padding-block-start: var(--sp-2); background: var(--surface); color: var(--on-surface); box-shadow: calc(-1 * var(--sp-3)) 0 0 var(--surface), var(--sp-3) 0 0 var(--surface); }
|
|
4492
4739
|
.sticky-thread--lapped > .sticky-heading { margin-block-end: var(--sp-4); }
|
|
4493
4740
|
|
|
4741
|
+
/* ==== the pile pressed whole, as PRESS in actions.css answers every other control (#62: a block is written in its own file) ==== */
|
|
4742
|
+
/* a pile of notes pressed whole (Lh 2026-10-11): the button lies invisible UNDER the notes, so it is the notes that answer — DIM, as a card does (the group dims as one, the
|
|
4743
|
+
notes do not show through one another); the ring is the button's own, round the whole pile */
|
|
4744
|
+
.sticky-pile__press { cursor: pointer; touch-action: manipulation; }
|
|
4745
|
+
.sticky-pile__notes:has(> .sticky-pile__press) { transition: opacity var(--motion-press) var(--ease-out); }
|
|
4746
|
+
.sticky-pile__notes:has(> .sticky-pile__press:active) { opacity: var(--press-dim); }
|
|
4747
|
+
.sticky-pile__press:focus-visible { outline: var(--focus); outline-offset: 0; }
|
|
4748
|
+
|
|
4494
4749
|
/* ---- css/stamp.css — stamp, empty place, album page, a stamp stuck on a surface ---- */
|
|
4495
4750
|
/* cardds — stamps: the paper stamp, an empty place for one, the album page, a stamp stuck on a surface.
|
|
4496
4751
|
A stamp is its own paper, like a sticky note: it brings its ground and its ink, whatever card or palette it lies on.
|
|
@@ -4527,42 +4782,44 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4527
4782
|
position: relative;
|
|
4528
4783
|
display: block;
|
|
4529
4784
|
container-type: inline-size;
|
|
4530
|
-
color: var(--stamp-ink,
|
|
4531
|
-
filter: drop-shadow(var(--stamp-drop,
|
|
4785
|
+
color: var(--stamp-ink, var(--ink-onlight));
|
|
4786
|
+
filter: drop-shadow(var(--stamp-drop, var(--shadow-paper))); /* drop-shadow follows the teeth; a box-shadow would draw the rectangle */
|
|
4787
|
+
rotate: var(--stamp-turn, none);
|
|
4788
|
+
animation: var(--stamp-arrive, none);
|
|
4532
4789
|
}
|
|
4533
4790
|
.stamp__paper {
|
|
4534
4791
|
--_pitch: calc(100cqw / 12); /* one tooth: 12 across, 15 down — the holes land whole on every edge. Fewer and larger than a real stamp's: a small stamp's teeth must still read (Lh 2026-10-07; it was 20 × 25) */
|
|
4535
4792
|
--_hole: 2.6cqw;
|
|
4536
4793
|
position: absolute;
|
|
4537
4794
|
inset: 0;
|
|
4538
|
-
background-color: var(--stamp-paper-bg,
|
|
4795
|
+
background-color: var(--stamp-paper-bg, var(--stamp-paper));
|
|
4539
4796
|
-webkit-mask: linear-gradient(#000 0 0) center / calc(100% - var(--_pitch)) calc(100% - var(--_pitch)) no-repeat,
|
|
4540
4797
|
radial-gradient(circle, transparent var(--_hole), #000 calc(var(--_hole) + 0.4cqw)) calc(var(--_pitch) / -2) calc(var(--_pitch) / -2) / var(--_pitch) var(--_pitch);
|
|
4541
4798
|
mask: linear-gradient(#000 0 0) center / calc(100% - var(--_pitch)) calc(100% - var(--_pitch)) no-repeat,
|
|
4542
4799
|
radial-gradient(circle, transparent var(--_hole), #000 calc(var(--_hole) + 0.4cqw)) calc(var(--_pitch) / -2) calc(var(--_pitch) / -2) / var(--_pitch) var(--_pitch);
|
|
4543
4800
|
}
|
|
4544
|
-
.stamp__pic { position: absolute; inset: var(--stamp-frame, 8cqw); overflow: hidden; } /* the
|
|
4801
|
+
.stamp__pic { position: absolute; inset: var(--stamp-frame, 8cqw); overflow: hidden; } /* the paper's frame round the picture; the ground is the tone class's */
|
|
4545
4802
|
|
|
4546
4803
|
/* the pictures, placed by COUNT: where, how wide, how far turned — the layout is the stamp's look, never the app's.
|
|
4547
4804
|
A share of the picture area; the foot stays clear for the words. */
|
|
4548
4805
|
.stamp__art { position: absolute; left: var(--_x); top: var(--_y); width: var(--_w); aspect-ratio: 1; translate: -50% -50%; rotate: var(--_r); }
|
|
4549
4806
|
.stamp__art > * { display: block; width: 100%; height: 100%; }
|
|
4550
4807
|
.stamp--1 .stamp__art { --_x: 50%; --_y: 40%; --_w: 72%; --_r: 0deg; }
|
|
4551
|
-
.stamp--2 .stamp__art:nth-child(1) { --_x: 37%; --_y: 27%; --_w: 58%; --_r: -
|
|
4552
|
-
.stamp--2 .stamp__art:nth-child(2) { --_x: 63%; --_y: 56%; --_w: 54%; --_r:
|
|
4553
|
-
.stamp--3 .stamp__art:nth-child(1) { --_x: 30%; --_y: 21%; --_w: 50%; --_r: -
|
|
4554
|
-
.stamp--3 .stamp__art:nth-child(2) { --_x: 71%; --_y: 31%; --_w: 46%; --_r:
|
|
4555
|
-
.stamp--3 .stamp__art:nth-child(3) { --_x: 47%; --_y: 58%; --_w: 54%; --_r: -
|
|
4556
|
-
.stamp--4 .stamp__art:nth-child(1) { --_x: 29%; --_y: 19%; --_w: 44%; --_r: -
|
|
4557
|
-
.stamp--4 .stamp__art:nth-child(2) { --_x: 71%; --_y: 24%; --_w: 42%; --_r:
|
|
4558
|
-
.stamp--4 .stamp__art:nth-child(3) { --_x: 31%; --_y: 57%; --_w: 42%; --_r:
|
|
4559
|
-
.stamp--4 .stamp__art:nth-child(4) { --_x: 70%; --_y: 60%; --_w: 44%; --_r: -
|
|
4808
|
+
.stamp--2 .stamp__art:nth-child(1) { --_x: 37%; --_y: 27%; --_w: 58%; --_r: calc(var(--tilt-base) * -6); }
|
|
4809
|
+
.stamp--2 .stamp__art:nth-child(2) { --_x: 63%; --_y: 56%; --_w: 54%; --_r: calc(var(--tilt-base) * 8); }
|
|
4810
|
+
.stamp--3 .stamp__art:nth-child(1) { --_x: 30%; --_y: 21%; --_w: 50%; --_r: calc(var(--tilt-base) * -8); }
|
|
4811
|
+
.stamp--3 .stamp__art:nth-child(2) { --_x: 71%; --_y: 31%; --_w: 46%; --_r: calc(var(--tilt-base) * 7); }
|
|
4812
|
+
.stamp--3 .stamp__art:nth-child(3) { --_x: 47%; --_y: 58%; --_w: 54%; --_r: calc(var(--tilt-base) * -2); }
|
|
4813
|
+
.stamp--4 .stamp__art:nth-child(1) { --_x: 29%; --_y: 19%; --_w: 44%; --_r: calc(var(--tilt-base) * -8); }
|
|
4814
|
+
.stamp--4 .stamp__art:nth-child(2) { --_x: 71%; --_y: 24%; --_w: 42%; --_r: calc(var(--tilt-base) * 7); }
|
|
4815
|
+
.stamp--4 .stamp__art:nth-child(3) { --_x: 31%; --_y: 57%; --_w: 42%; --_r: calc(var(--tilt-base) * 5); }
|
|
4816
|
+
.stamp--4 .stamp__art:nth-child(4) { --_x: 70%; --_y: 60%; --_w: 44%; --_r: calc(var(--tilt-base) * -6); }
|
|
4560
4817
|
|
|
4561
4818
|
/* the words on the picture's foot: a small line, then the big name — one line each (clipped sideways only: Thai tone marks stand above the line box) */
|
|
4562
4819
|
.stamp__cap { position: absolute; inset-inline: 5cqw; bottom: 3.5cqw; display: grid; gap: 0.4cqw; }
|
|
4563
4820
|
.stamp__words, .stamp__name { display: block; min-width: 0; white-space: nowrap; overflow-x: clip; text-overflow: ellipsis; }
|
|
4564
|
-
.stamp__words { --_words: 4.8cqw; font:
|
|
4565
|
-
.stamp__name { --_name: 9.5cqw; font:
|
|
4821
|
+
.stamp__words { --_words: 4.8cqw; font: var(--fw-medium) var(--_words) / var(--lh-title) var(--font-ui); font-size: var(--stamp-words-font-size, var(--_words)); }
|
|
4822
|
+
.stamp__name { --_name: 9.5cqw; font: var(--fw-black) var(--_name) / var(--lh-display) var(--font-display); font-size: var(--stamp-name-font-size, var(--_name)); }
|
|
4566
4823
|
/* at the stamp's own size and under (and stuck on a note): the small line goes, the name grows */
|
|
4567
4824
|
/* --bare: the pictures alone, no words — a stamp stuck on something (a note, a postcard). The words belong to the album.
|
|
4568
4825
|
The pictures take the foot's room: each sits a little lower, so the group stands in the middle of the frame. */
|
|
@@ -4576,14 +4833,13 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4576
4833
|
|
|
4577
4834
|
/* --tilt: turned a little on its middle — the one stamp a start screen hands over */
|
|
4578
4835
|
.stamp--tilt-left, .stamp--tilt-right { margin-block: var(--sp-4); } /* the turned corners and the shadow need room: a centre scrolls and would cut them */
|
|
4579
|
-
.stamp--tilt-left { rotate: calc(-1 * var(--stamp-tilt,
|
|
4580
|
-
.stamp--tilt-right { rotate: var(--stamp-tilt,
|
|
4836
|
+
.stamp--tilt-left { rotate: calc(-1 * var(--stamp-tilt, calc(var(--tilt-base) * 5))); }
|
|
4837
|
+
.stamp--tilt-right { rotate: var(--stamp-tilt, calc(var(--tilt-base) * 5)); }
|
|
4581
4838
|
|
|
4582
4839
|
/* --tap: the whole stamp is the button (`onClick`); the press and the ring are the PRESS list's (actions.css) */
|
|
4583
4840
|
.stamp--tap { appearance: none; border: 0; padding: 0; background: none; font: inherit; text-align: start; -webkit-tap-highlight-color: transparent; }
|
|
4584
4841
|
|
|
4585
4842
|
/* a stamp — or the empty place for one — handed over in a Centre: its greeting centred under it. The stamp is its own size here as everywhere */
|
|
4586
|
-
.screen__centre:has(> :is(.stamp, .stamp-place)) { justify-items: center; text-align: center; }
|
|
4587
4843
|
|
|
4588
4844
|
/* ---- stamp place: an empty place for a stamp — an album's gap, "make yours" ----
|
|
4589
4845
|
<span class="stamp-place"><svg class="stamp-place__edge" aria-hidden="true"><rect/></svg>สร้าง</span> · <button class="stamp-place">…</button> · <span class="stamp-place stamp-place--faint">…</span>
|
|
@@ -4677,7 +4933,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4677
4933
|
background-color: var(--stamp-cell-on-bg, var(--accent));
|
|
4678
4934
|
color: var(--stamp-cell-on-ink, var(--on-accent));
|
|
4679
4935
|
}
|
|
4680
|
-
.stamp-cell__on > .icon {
|
|
4936
|
+
.stamp-cell__on > .icon { --icon-size: 70%; }
|
|
4681
4937
|
/* --bare: no ground, no padding, no round — the stamps lie on what holds the book, three across as on the ground. The name is the Stamp's own; free places still draw.
|
|
4682
4938
|
<div class="stamp-book stamp-book--bare"> */
|
|
4683
4939
|
.stamp-book--bare { padding: 0; border-radius: 0; background: none; }
|
|
@@ -4721,24 +4977,24 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4721
4977
|
After the rule above, at the same weight: `main` is the one place a stuck stamp reads --stamp-size. */
|
|
4722
4978
|
.stamp-on--main > .stamp { width: var(--stamp-size, calc(var(--tap) * 2)); }
|
|
4723
4979
|
.stamp-on--move { will-change: transform; } /* its own layer: Safari left the stamp's shadow behind as a trail along the drag when it was painted with the note (seen on the simulator, 2026-10-07) */
|
|
4724
|
-
.stamp-on--move { cursor: grab; touch-action: none; -webkit-user-select: none; user-select: none; transition: scale var(--motion-press) ease-out; }
|
|
4980
|
+
.stamp-on--move { cursor: grab; touch-action: none; -webkit-user-select: none; user-select: none; transition: scale var(--motion-press) var(--ease-out); }
|
|
4725
4981
|
.stamp-on--move:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
4726
|
-
.stamp-on.is-held { cursor: grabbing; z-index:
|
|
4982
|
+
.stamp-on.is-held { cursor: grabbing; z-index: var(--z-over); scale: var(--scale-lift); } /* lifted off the paper while the finger holds it */
|
|
4727
4983
|
@media (prefers-reduced-motion: reduce) { .stamp-on.is-held { scale: 1; } }
|
|
4728
4984
|
|
|
4729
4985
|
/* ---- the cross that takes a stamp off (`StampOn onRemove`): a small dark disc with an x on the stamp's top right corner ----
|
|
4730
4986
|
<div class="stamp-on stamp-on--move">…<button class="stamp-on__remove" aria-label="Remove"><span class="stamp-on__remove-disc"><svg class="icon">x</svg></span></button></div>
|
|
4731
4987
|
The button is the touch target — at least --tap-floor (44) square, invisible, centred on the corner — and the disc inside it is what is seen
|
|
4732
4988
|
(24). It is NOT turned with the stamp: a hand turns a stamp, a button stays square to the screen, so the cross counter-turns by the stamp's own turn. Like the
|
|
4733
|
-
stamp it
|
|
4989
|
+
stamp it takes the theme's two inks, the onlight ink as its disc and the ondark ink as its x and ring: seen on any paper and over any picture. */
|
|
4734
4990
|
.stamp-on__remove-disc {
|
|
4735
4991
|
display: grid;
|
|
4736
4992
|
place-items: center;
|
|
4737
4993
|
width: var(--stamp-on-remove-size, var(--sp-6));
|
|
4738
4994
|
aspect-ratio: 1;
|
|
4739
4995
|
border-radius: 50%;
|
|
4740
|
-
background-color: var(--stamp-on-remove-bg,
|
|
4741
|
-
box-shadow: 0 0 0 var(--sp-0) currentColor,
|
|
4996
|
+
background-color: var(--stamp-on-remove-bg, var(--ink-onlight));
|
|
4997
|
+
box-shadow: 0 0 0 var(--sp-0) currentColor, var(--shadow-lift);
|
|
4742
4998
|
}
|
|
4743
4999
|
.stamp-on__remove {
|
|
4744
5000
|
--_hit: max(var(--tap-floor), var(--stamp-on-remove-size, var(--sp-6)));
|
|
@@ -4755,20 +5011,31 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4755
5011
|
border: 0;
|
|
4756
5012
|
border-radius: 50%;
|
|
4757
5013
|
background: none;
|
|
4758
|
-
color: var(--stamp-on-remove-ink,
|
|
5014
|
+
color: var(--stamp-on-remove-ink, var(--ink-ondark));
|
|
4759
5015
|
font: inherit;
|
|
4760
5016
|
-webkit-tap-highlight-color: transparent;
|
|
4761
5017
|
}
|
|
4762
|
-
.stamp-on__remove-disc > .icon {
|
|
4763
|
-
@media (prefers-reduced-motion: no-preference) { .stamp-on__remove { animation: stamp-on-remove-in var(--motion-press) ease-out; } }
|
|
4764
|
-
@keyframes stamp-on-remove-in { from { opacity: 0; scale:
|
|
5018
|
+
.stamp-on__remove-disc > .icon { --icon-size: 60%; }
|
|
5019
|
+
@media (prefers-reduced-motion: no-preference) { .stamp-on__remove { animation: stamp-on-remove-in var(--motion-press) var(--ease-out); } }
|
|
5020
|
+
@keyframes stamp-on-remove-in { from { opacity: 0; scale: var(--scale-arrive); } }
|
|
4765
5021
|
|
|
4766
5022
|
/* --arrive: the stamp sticks ITSELF (StampOn `arrive`; a Stamp's `arrive` too, where it already stands) — struck onto its place (`strike`: tokens.css).
|
|
4767
5023
|
The component reads the animation's name to tell the app it has landed. `scale` and `opacity` leave a --tilt's `rotate` alone. Knob: --stamp-on-arrive-time */
|
|
4768
5024
|
@media (prefers-reduced-motion: no-preference) {
|
|
4769
|
-
.stamp-on--arrive, .stamp--arrive { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) ease-in both; }
|
|
5025
|
+
.stamp-on--arrive, .stamp--arrive { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) var(--ease-in) both; }
|
|
4770
5026
|
}
|
|
4771
5027
|
|
|
5028
|
+
/* ---- where it sits in what card.css draws (docs/reference.md rule 15) ---- */
|
|
5029
|
+
.card__head > .stamp { grid-column: 2; grid-row: 1 / span 2; margin-inline: 0; }
|
|
5030
|
+
/* ---- where it sits in what people.css draws (docs/reference.md rule 15) ---- */
|
|
5031
|
+
/* a stamp in the stack is the stamp's small size (--stamp-sm-size: one rule in stamp.css, 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
|
|
5032
|
+
lead with a title, a meta line and an end button: at 375 the meta line breaks in two, accepted by Lh (a lap of 16 would hold it; the lap stays 8)
|
|
5033
|
+
<span class="avatar-stack"><span class="stamp stamp--bare">…</span><span class="stamp stamp--bare">…</span><span class="avatar-stack__more">+9</span></span> */
|
|
5034
|
+
.avatar-stack > .stamp { flex: none; margin-inline: 0; }
|
|
5035
|
+
.avatar-stack > .stamp + .stamp { margin-left: calc(-1 * var(--sp-2)); }
|
|
5036
|
+
/* ---- where it sits in what post.css draws (docs/reference.md rule 15) ---- */
|
|
5037
|
+
.post-corner__stamp > :is(.stamp, .stamp-place) { grid-area: 1 / 1; width: 100%; max-width: none; } /* the corner is the postcard's own size (72 × 90), outside the stamp's two sizes, by the owner's decision (2026-10-11); its box sizes the stamp, which fills it */
|
|
5038
|
+
|
|
4772
5039
|
/* ---- css/post.css — the post: postmark, a postcard read (letter), written (desk), kept (box) ---- */
|
|
4773
5040
|
/* cardds — the post: a postcard read (post-letter), written (post-desk) and kept (post-box), the postmark, the card stock.
|
|
4774
5041
|
Not the `Postcard` of media.css (a member's cover card that flips) — nothing here flips, and no class is shared with it.
|
|
@@ -4791,7 +5058,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4791
5058
|
.postmark {
|
|
4792
5059
|
--_base: calc(var(--tap) * 1.75);
|
|
4793
5060
|
position: relative;
|
|
4794
|
-
z-index:
|
|
5061
|
+
z-index: var(--z-over);
|
|
4795
5062
|
flex: none;
|
|
4796
5063
|
display: grid;
|
|
4797
5064
|
place-content: center;
|
|
@@ -4806,7 +5073,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4806
5073
|
color: var(--postmark-ink, color-mix(in srgb, currentColor 72%, transparent));
|
|
4807
5074
|
font: var(--postmark-font, var(--type-label));
|
|
4808
5075
|
text-align: center;
|
|
4809
|
-
rotate: var(--postmark-tilt, -
|
|
5076
|
+
rotate: var(--postmark-tilt, calc(var(--tilt-base) * -10));
|
|
4810
5077
|
}
|
|
4811
5078
|
.postmark::after { /* the cancel lines that run on over the stamp */
|
|
4812
5079
|
content: "";
|
|
@@ -4831,9 +5098,8 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4831
5098
|
display: grid;
|
|
4832
5099
|
width: var(--post-corner-stamp-w, var(--_w));
|
|
4833
5100
|
aspect-ratio: 4 / 5;
|
|
4834
|
-
rotate: var(--post-corner-stamp-tilt,
|
|
5101
|
+
rotate: var(--post-corner-stamp-tilt, calc(var(--tilt-base) * 2));
|
|
4835
5102
|
}
|
|
4836
|
-
.post-corner__stamp > :is(.stamp, .stamp-place) { grid-area: 1 / 1; width: 100%; max-width: none; } /* the corner is the postcard's own size (72 × 90), outside the stamp's two sizes, by the owner's decision (2026-10-11); its box sizes the stamp, which fills it */
|
|
4837
5103
|
|
|
4838
5104
|
/* ============================================================
|
|
4839
5105
|
post-letter — READING one that arrived. The written side stood upright: the address half over the message half.
|
|
@@ -4874,15 +5140,15 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4874
5140
|
min-height: 0;
|
|
4875
5141
|
overflow: clip;
|
|
4876
5142
|
visibility: hidden;
|
|
4877
|
-
rotate: calc(-1 * var(--post-letter-tilt,
|
|
5143
|
+
rotate: calc(-1 * var(--post-letter-tilt, calc(var(--tilt-base) * 2)));
|
|
4878
5144
|
}
|
|
4879
|
-
.post-letter__sheet--back { rotate: var(--post-letter-tilt,
|
|
5145
|
+
.post-letter__sheet--back { rotate: var(--post-letter-tilt, calc(var(--tilt-base) * 2)); } /* turned over, the lean is the other way */
|
|
4880
5146
|
.post-letter--back > .post-letter__sheet--back,
|
|
4881
5147
|
.post-letter--picture > .post-letter__sheet--picture { visibility: visible; }
|
|
4882
5148
|
.post-letter:not(:has(> .post-letter__journey)) { --_room: 0rem; } /* no journey line: its room goes back to the sheet */
|
|
4883
5149
|
.post-letter__journey { grid-area: 2 / 1; margin: var(--sp-3) 0 0; text-align: center; font: var(--type-caption); color: var(--post-letter-journey-ink, var(--on-surface-muted)); }
|
|
4884
5150
|
|
|
4885
|
-
/* the picture side: the print with its
|
|
5151
|
+
/* the picture side: the print with its margin of paper, the place printed under it */
|
|
4886
5152
|
.post-letter__sheet--picture {
|
|
4887
5153
|
display: grid;
|
|
4888
5154
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
@@ -4901,7 +5167,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4901
5167
|
.post-letter__sheet--back { display: flex; flex-direction: column; gap: var(--post-letter-gap, var(--sp-4)); padding: var(--post-letter-pad, var(--sp-5)); }
|
|
4902
5168
|
.post-letter__head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: var(--sp-2); padding-block-end: var(--sp-4); border-block-end: var(--rule-w) solid var(--_rule); }
|
|
4903
5169
|
.post-letter__to { display: grid; gap: var(--sp-2); min-width: 0; align-self: stretch; align-content: space-between; }
|
|
4904
|
-
.post-letter__kicker { font: var(--type-caption); color: var(--_muted); letter-spacing:
|
|
5170
|
+
.post-letter__kicker { font: var(--type-caption); color: var(--_muted); letter-spacing: var(--track-caps); }
|
|
4905
5171
|
.post-letter__lines { display: grid; gap: var(--sp-1); }
|
|
4906
5172
|
.post-letter__line { display: block; padding-block: var(--sp-1); border-block-end: var(--border-w) solid var(--_rule); font: var(--type-caption); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4907
5173
|
.post-letter__line--name { font: var(--type-title); }
|
|
@@ -4914,7 +5180,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4914
5180
|
gap: var(--sp-3);
|
|
4915
5181
|
font: var(--post-letter-hand-font, var(--type-h2));
|
|
4916
5182
|
font-size: var(--post-letter-hand-font-size, var(--fs-lg));
|
|
4917
|
-
line-height: var(--post-letter-hand-leading,
|
|
5183
|
+
line-height: var(--post-letter-hand-leading, var(--lh-hand));
|
|
4918
5184
|
}
|
|
4919
5185
|
.post-letter__message > p { margin: 0; }
|
|
4920
5186
|
.post-letter__sign { align-self: flex-end; }
|
|
@@ -4926,7 +5192,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4926
5192
|
inset-block-start: calc(var(--_peek) / 2);
|
|
4927
5193
|
inset-inline-start: var(--sp-2);
|
|
4928
5194
|
translate: 0 -50%;
|
|
4929
|
-
z-index: 2;
|
|
5195
|
+
z-index: var(--z-over-2);
|
|
4930
5196
|
display: inline-flex;
|
|
4931
5197
|
align-items: center;
|
|
4932
5198
|
gap: var(--sp-2);
|
|
@@ -4987,8 +5253,8 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
4987
5253
|
background: transparent;
|
|
4988
5254
|
color: inherit;
|
|
4989
5255
|
font: var(--post-desk-hand-font, var(--type-h2));
|
|
4990
|
-
line-height: calc(var(--fs-body) * var(--post-desk-leading,
|
|
4991
|
-
--_line: calc(var(--fs-body) * var(--post-desk-leading,
|
|
5256
|
+
line-height: calc(var(--fs-body) * var(--post-desk-leading, var(--lh-ruled)));
|
|
5257
|
+
--_line: calc(var(--fs-body) * var(--post-desk-leading, var(--lh-ruled)));
|
|
4992
5258
|
/* the ruled lines: one under every line of writing */
|
|
4993
5259
|
background-image: linear-gradient(to bottom, transparent calc(100% - var(--border-w)), var(--_rule) 0);
|
|
4994
5260
|
background-size: 100% var(--_line);
|
|
@@ -5005,10 +5271,10 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5005
5271
|
/* the stamp box: empty it is a place of dots to press; pressed, the chosen stamp lies in it */
|
|
5006
5272
|
.post-desk__slot { border: 0; background: transparent; color: inherit; }
|
|
5007
5273
|
@media (prefers-reduced-motion: no-preference) {
|
|
5008
|
-
.post-desk__slot > .stamp {
|
|
5009
|
-
.post-desk .postmark { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) ease-in both; } /* struck as a StampOn is (`strike`: tokens.css), in the same time */
|
|
5274
|
+
.post-desk__slot > .stamp { --stamp-arrive: post-desk-stick var(--motion-quick) var(--ease-out) both; }
|
|
5275
|
+
.post-desk .postmark { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) var(--ease-in) both; } /* struck as a StampOn is (`strike`: tokens.css), in the same time */
|
|
5010
5276
|
}
|
|
5011
|
-
@keyframes post-desk-stick { from { translate: 0 calc(-1 * var(--sp-8)); rotate:
|
|
5277
|
+
@keyframes post-desk-stick { from { translate: 0 calc(-1 * var(--sp-8)); rotate: calc(var(--tilt-base) * 14); opacity: 0; } }
|
|
5012
5278
|
/* the other half's showing edge brings it across: a tap-wide strip out to the screen's edge */
|
|
5013
5279
|
.post-desk__reach { position: absolute; inset-block: 0; width: var(--tap); border: 0; background: transparent; }
|
|
5014
5280
|
.post-desk__reach--address { inset-inline-end: calc(-1 * var(--screen-pad)); }
|
|
@@ -5038,7 +5304,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5038
5304
|
border-block-end: var(--rule-w) solid transparent;
|
|
5039
5305
|
background: transparent;
|
|
5040
5306
|
}
|
|
5041
|
-
.post-desk__pick > .stamp {
|
|
5307
|
+
.post-desk__pick > .stamp { --stamp-turn: var(--post-corner-stamp-tilt, calc(var(--tilt-base) * 2)); } /* in the tray a stamp keeps its words */
|
|
5042
5308
|
.post-desk__pick[aria-pressed="true"] { border-block-end-color: var(--on-surface); }
|
|
5043
5309
|
.post-desk__note { margin: 0; font: var(--type-caption); color: var(--post-desk-note-ink, var(--on-surface-muted)); min-height: 1.35em; }
|
|
5044
5310
|
/* what stands where the card was, once it has left */
|
|
@@ -5063,7 +5329,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5063
5329
|
margin-block-end: calc(var(--_e) - var(--_h));
|
|
5064
5330
|
display: grid;
|
|
5065
5331
|
grid-template-rows: var(--post-box-edge, var(--_edge)) minmax(0, 1fr);
|
|
5066
|
-
transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
|
|
5332
|
+
transition: translate var(--motion-sheet) var(--ease), rotate var(--motion-sheet) var(--ease);
|
|
5067
5333
|
}
|
|
5068
5334
|
.post-box {
|
|
5069
5335
|
--_edge: calc(var(--sp-base) * 20);
|
|
@@ -5078,8 +5344,8 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5078
5344
|
}
|
|
5079
5345
|
.post-box:has(> .post-box__card.is-open:not(:last-child)) { padding-block-end: var(--_part); }
|
|
5080
5346
|
.post-box__card:last-child { margin-block-end: 0; }
|
|
5081
|
-
.post-box__card:nth-child(3n + 1) { rotate: var(--post-box-lean, 0.
|
|
5082
|
-
.post-box__card:nth-child(3n + 2) { rotate: calc(var(--post-box-lean, 0.
|
|
5347
|
+
.post-box__card:nth-child(3n + 1) { rotate: var(--post-box-lean, calc(var(--tilt-base) * 0.6)); }
|
|
5348
|
+
.post-box__card:nth-child(3n + 2) { rotate: calc(var(--post-box-lean, calc(var(--tilt-base) * 0.6)) * -0.8); }
|
|
5083
5349
|
.post-box__card.is-open { rotate: none; }
|
|
5084
5350
|
.post-box__card.is-open ~ .post-box__card { translate: 0 var(--_part); }
|
|
5085
5351
|
.post-box__edge {
|
|
@@ -5107,56 +5373,25 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5107
5373
|
.post-box__card:not(.is-open) .postmark { box-sizing: border-box; place-content: start center; padding-block-start: calc(var(--_size) * 0.18); }
|
|
5108
5374
|
@media (prefers-reduced-motion: reduce) { .post-box__card { transition: none; } }
|
|
5109
5375
|
|
|
5376
|
+
/* ---- the desk's card leaves as a card with `leave` does (card.css LEAVE): the same two knobs, the same time ---- */
|
|
5377
|
+
.post-desk__card { transition: translate var(--motion-sheet) var(--ease), rotate var(--motion-sheet) var(--ease); }
|
|
5378
|
+
.post-desk--sent .post-desk__card { --_y: calc(var(--leave-rise, 160%) * -1); rotate: var(--leave-turn, calc(var(--tilt-base) * -5)); }
|
|
5379
|
+
@media (prefers-reduced-motion: reduce) { .post-desk__card { transition: none; } }
|
|
5380
|
+
|
|
5110
5381
|
/* ---- css/media.css — cover card, quote, tile grid, fold, mosaic, wave ---- */
|
|
5111
5382
|
/* ============================================================
|
|
5112
5383
|
cardds/media.css — media & display inside cards:
|
|
5113
|
-
|
|
5384
|
+
quote · tile grid · waveform (the cover card and the fold are card variants: card.css)
|
|
5114
5385
|
============================================================ */
|
|
5115
5386
|
|
|
5116
|
-
/* ---- cover: the image IS the card; text over a scrim ----
|
|
5117
|
-
<article class="card card--cover"><div class="ph" role="img"></div>
|
|
5118
|
-
<div class="card__head"><button class="icon-btn icon-btn--sm">♥</button></div>
|
|
5119
|
-
<div><h2 class="t-h1">Art Institute</h2><p class="t-body">111 South Michigan Ave</p></div>
|
|
5120
|
-
<div class="row">…meta…</div></article> */
|
|
5121
|
-
.card--cover {
|
|
5122
|
-
--card-bg: var(--card-3-bg);
|
|
5123
|
-
--dark: var(--card-3-dark);
|
|
5124
|
-
--accent: var(--card-ink);
|
|
5125
|
-
--accent-dark: calc(1 - var(--dark));
|
|
5126
|
-
min-height: var(--_cover-min-h);
|
|
5127
|
-
align-content: space-between;
|
|
5128
|
-
overflow: hidden;
|
|
5129
|
-
isolation: isolate;
|
|
5130
|
-
}
|
|
5131
|
-
.card--cover > img,
|
|
5132
|
-
.card--cover > .ph {
|
|
5133
|
-
position: absolute;
|
|
5134
|
-
inset: 0;
|
|
5135
|
-
width: 100%;
|
|
5136
|
-
height: 100%;
|
|
5137
|
-
object-fit: cover;
|
|
5138
|
-
z-index: -2;
|
|
5139
|
-
border: 0;
|
|
5140
|
-
border-radius: 0;
|
|
5141
|
-
min-height: 0;
|
|
5142
|
-
}
|
|
5143
|
-
/* scrim: dark at both ends so the meta stays readable */
|
|
5144
|
-
.card--cover::before {
|
|
5145
|
-
content: "";
|
|
5146
|
-
position: absolute;
|
|
5147
|
-
inset: 0;
|
|
5148
|
-
z-index: -1;
|
|
5149
|
-
background: linear-gradient(to bottom, var(--scrim), transparent 40% 60%, var(--scrim));
|
|
5150
|
-
}
|
|
5151
|
-
|
|
5152
5387
|
/* ---- quote: giant mark, big text, attribution ----
|
|
5153
5388
|
<blockquote class="quote"><p class="quote__text">It's overcast…</p><footer class="quote__by">Wednesday 15</footer></blockquote> */
|
|
5154
5389
|
.quote { margin: 0; display: grid; gap: var(--quote-gap, var(--sp-3)); }
|
|
5155
5390
|
.quote::before {
|
|
5156
5391
|
content: "\201C";
|
|
5157
|
-
font:
|
|
5392
|
+
font: var(--fw-black) calc(var(--fs-h1) * 3) / var(--lh-mark) var(--font-display);
|
|
5158
5393
|
height: var(--sp-10);
|
|
5159
|
-
opacity:
|
|
5394
|
+
opacity: var(--fade-most);
|
|
5160
5395
|
}
|
|
5161
5396
|
.quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
|
|
5162
5397
|
.quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
|
|
@@ -5170,7 +5405,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5170
5405
|
grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), 100%), 1fr));
|
|
5171
5406
|
gap: var(--tile-grid-gap, var(--sp-3));
|
|
5172
5407
|
}
|
|
5173
|
-
.tile-grid--3 { --tile-min:
|
|
5408
|
+
.tile-grid--3 { --tile-min: calc(var(--tap-base) * 2); } /* three across on a phone (attachments, upload kinds) */
|
|
5174
5409
|
.tile {
|
|
5175
5410
|
display: grid;
|
|
5176
5411
|
align-content: space-between;
|
|
@@ -5217,42 +5452,18 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5217
5452
|
.tile--5 { background: var(--tile-5-bg, var(--card-5-bg)); --dark: var(--card-5-dark); color: var(--tile-5-ink, var(--card-ink)); border-color: transparent; }
|
|
5218
5453
|
.tile--3 { background: var(--tile-3-bg, var(--card-3-bg)); --dark: var(--card-3-dark); color: var(--tile-3-ink, var(--card-ink)); border-color: transparent; }
|
|
5219
5454
|
|
|
5220
|
-
/* ---- fold: a sticky-note corner on any card (decorative) ----
|
|
5221
|
-
No border. The card's outline follows its border box, so it ran straight
|
|
5222
|
-
across the squared bottom-right corner and cut through the fold's diagonal —
|
|
5223
|
-
the corner read as a notch with a line through it rather than a lifted flap.
|
|
5224
|
-
The COLOUR goes transparent rather than the border going to 0, so the box
|
|
5225
|
-
keeps its geometry and a fold card still measures the same as the cards
|
|
5226
|
-
beside it. */
|
|
5227
|
-
.card--fold {
|
|
5228
|
-
border-color: transparent;
|
|
5229
|
-
border-bottom-right-radius: 0;
|
|
5230
|
-
background-image: linear-gradient(to top left,
|
|
5231
|
-
var(--card-bg) 0 50%, /* the card's own background, not --surface (the page's) — a mismatch here drew a seam at the flap's edge */
|
|
5232
|
-
color-mix(in srgb, currentColor 18%, var(--card-bg)) 50% 100%);
|
|
5233
|
-
background-repeat: no-repeat;
|
|
5234
|
-
background-position: bottom right;
|
|
5235
|
-
background-size: var(--sp-8) var(--sp-8);
|
|
5236
|
-
background-origin: border-box; /* the flap sits on the true corner, not inside the (transparent) border */
|
|
5237
|
-
/* the corner is CUT: the triangle under the flap shows what is behind the card. Painted in the card's own colour it
|
|
5238
|
-
read as the square corner of a second card underneath (Lh 2026-09-23). The polygon reaches 3rem past the other
|
|
5239
|
-
edges so a coloured palette's shadow is not clipped there. */
|
|
5240
|
-
clip-path: polygon(-3rem -3rem, calc(100% + 3rem) -3rem, calc(100% + 3rem) calc(100% - var(--sp-8) - 3rem),
|
|
5241
|
-
calc(100% - var(--sp-8) - 3rem) calc(100% + 3rem), -3rem calc(100% + 3rem));
|
|
5242
|
-
}
|
|
5243
|
-
|
|
5244
5455
|
/* ---- wave: audio waveform stand-in for voice rows ----
|
|
5245
5456
|
<span class="wave"></span> — sits in a .row__body, fills the width */
|
|
5246
5457
|
.wave {
|
|
5247
5458
|
/* only WHOLE bars show (Lh 2026-09-23 — a clipped last bar read thinner): the columns are as many whole bar-widths as fit
|
|
5248
5459
|
(auto-fill), spread edge to edge; bars that do not fit go to rows of no height, clipped away */
|
|
5249
5460
|
display: grid;
|
|
5250
|
-
grid-template-columns: repeat(auto-fit, var(--wave-bar-width,
|
|
5461
|
+
grid-template-columns: repeat(auto-fit, var(--wave-bar-width, var(--hairline-w))); /* auto-FIT: columns no bar fills collapse, so a wide row still spreads its bars edge to edge */
|
|
5251
5462
|
grid-template-rows: 100%;
|
|
5252
5463
|
grid-auto-rows: 0;
|
|
5253
5464
|
justify-content: space-between;
|
|
5254
5465
|
align-items: center;
|
|
5255
|
-
column-gap: var(--wave-gap,
|
|
5466
|
+
column-gap: var(--wave-gap, var(--hairline-w));
|
|
5256
5467
|
height: var(--wave-h, var(--icon));
|
|
5257
5468
|
width: 100%;
|
|
5258
5469
|
overflow: hidden;
|
|
@@ -5278,7 +5489,6 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5278
5489
|
}
|
|
5279
5490
|
.mosaic > :first-child { grid-row: span 2; }
|
|
5280
5491
|
.mosaic > * { min-height: 0; border-radius: 0; } /* opacity/border-width here were no-ops: .ph restates its own below, and nothing else in a mosaic carries either by default */
|
|
5281
|
-
.mosaic > .ph { opacity: var(--off); border-width: var(--border-w); }
|
|
5282
5492
|
.mosaic > img { width: 100%; height: 100%; object-fit: cover; }
|
|
5283
5493
|
|
|
5284
5494
|
/* ---- postcard: a member's introduction in the shape of a cover card — the photo fills it,
|
|
@@ -5336,8 +5546,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5336
5546
|
.postcard__turn:focus-visible { outline-offset: calc(-1 * var(--sp-1)); } /* the ring inside the card's edge */
|
|
5337
5547
|
/* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
|
|
5338
5548
|
.postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); --dark: var(--card-3-dark); color: var(--postcard-front-ink, var(--card-ink)); }
|
|
5339
|
-
.postcard__front .postcard__turn > img
|
|
5340
|
-
.postcard__front .postcard__turn > .ph {
|
|
5549
|
+
.postcard__front .postcard__turn > img {
|
|
5341
5550
|
position: absolute;
|
|
5342
5551
|
inset: 0;
|
|
5343
5552
|
width: 100%;
|
|
@@ -5351,7 +5560,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5351
5560
|
content: "";
|
|
5352
5561
|
position: absolute;
|
|
5353
5562
|
inset: 0;
|
|
5354
|
-
z-index:
|
|
5563
|
+
z-index: var(--z-over);
|
|
5355
5564
|
pointer-events: none;
|
|
5356
5565
|
background: linear-gradient(to top, var(--scrim), transparent 45%);
|
|
5357
5566
|
}
|
|
@@ -5367,7 +5576,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5367
5576
|
Taps between its words fall through to the turn; its buttons take their own. */
|
|
5368
5577
|
.postcard__band {
|
|
5369
5578
|
position: relative;
|
|
5370
|
-
z-index: 2;
|
|
5579
|
+
z-index: var(--z-over-2);
|
|
5371
5580
|
display: flex;
|
|
5372
5581
|
align-items: end;
|
|
5373
5582
|
gap: var(--postcard-band-gap, var(--sp-3));
|
|
@@ -5377,12 +5586,13 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5377
5586
|
.postcard__band > * { pointer-events: auto; }
|
|
5378
5587
|
.postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
|
|
5379
5588
|
.postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-ink)); --accent-dark: calc(1 - var(--dark)); }
|
|
5380
|
-
.postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
|
|
5381
5589
|
/* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
|
|
5382
5590
|
under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
|
|
5383
|
-
.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; }
|
|
5591
|
+
.postcard__voice { position: absolute; inset: 0; z-index: var(--z-over-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; }
|
|
5384
5592
|
.postcard__voice > * { pointer-events: auto; }
|
|
5385
|
-
|
|
5593
|
+
|
|
5594
|
+
/* ==== the tile a choice draws, picked (choice.css drives it with its input) (#62: a block is written in its own file) ==== */
|
|
5595
|
+
.tile--pick:has(:checked) { background: var(--accent); --dark: var(--accent-dark); color: var(--card-ink); border-color: transparent; }
|
|
5386
5596
|
|
|
5387
5597
|
/* ---- css/layover.css — ask drawer, popover menu, banner, float bar, callout ---- */
|
|
5388
5598
|
/* ============================================================
|
|
@@ -5418,14 +5628,14 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5418
5628
|
sets open={false}).
|
|
5419
5629
|
Reduced motion: the finished state, no beats; closing fires at once. */
|
|
5420
5630
|
.modal {
|
|
5421
|
-
--drawer-h:
|
|
5631
|
+
--drawer-h: calc(var(--tap-base) * 14 / 3);
|
|
5422
5632
|
position: absolute; /* at the stage's centre, and kept there as it grows: its middle is pinned at 50% and */
|
|
5423
5633
|
top: 50%; /* translate -50% of its OWN height re-centres it on every height. NOT inset 0 + margin auto + */
|
|
5424
5634
|
inset-inline: 0; /* align-self: center — Safari 18 ignores align-self on an absolute box in a block container, */
|
|
5425
5635
|
margin-inline: auto; /* so the card stretched to the whole stage (iOS 18.6, Lh 2026-09-24); nor height: fit-content */
|
|
5426
5636
|
translate: 0 -50%;
|
|
5427
5637
|
width: calc(100% - 2 * var(--screen-pad));
|
|
5428
|
-
z-index: 2; /* over the dim stage's scrim (z 1) */
|
|
5638
|
+
z-index: var(--z-over-2); /* over the dim stage's scrim (z 1) */
|
|
5429
5639
|
max-height: calc(100cqh - 2 * var(--sp-5)); /* cqh: the stage (a size container) */
|
|
5430
5640
|
padding: 0; /* the parts pad themselves */
|
|
5431
5641
|
border: 0; /* no frame on the modal (Lh 2026-09-16) — its edge is the shadow */
|
|
@@ -5470,18 +5680,17 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5470
5680
|
}
|
|
5471
5681
|
/* the CONFIRM: a Modal with no ModalBody: the ModalAsk IS the card, as tall as its words (no fixed band, no second beat) */
|
|
5472
5682
|
.modal:not(:has(> .modal__lift)) > .modal__drawer { height: auto; padding-block: var(--modal-confirm-pad-y, var(--sp-6)); animation: none; }
|
|
5473
|
-
.modal__drawer .t-caption {
|
|
5683
|
+
.modal__drawer .t-caption { --t-caption-ink: var(--modal-drawer-caption-ink, var(--card-muted)); }
|
|
5474
5684
|
.modal__drawer > h2:focus { outline: none; } /* the question takes the cursor when the modal arrives (for a screen reader, the keyboard's way in) — it is not a control: no ring */
|
|
5475
5685
|
/* the answers as WORD buttons (a BtnRow of Btns; Lh 2026-10-11): the row fills the ask with room over it and each button keeps its words on one
|
|
5476
5686
|
line — left to its words' width the row squeezed two buttons into round blobs, their words on two lines. A quiet button is an outline only,
|
|
5477
5687
|
so on the ask its edge is the ask's ink at full strength (light on the dark end, dark on --light). The BtnTrio of round buttons is not touched. */
|
|
5478
|
-
.modal__drawer
|
|
5479
|
-
.modal__drawer .btn
|
|
5480
|
-
.modal__drawer .icon-btn {
|
|
5481
|
-
.modal__drawer .icon-btn--invert { background: var(--accent); color: var(--on-accent); border-color: transparent; width: var(--tap-lg); height: var(--tap-lg); }
|
|
5688
|
+
.modal__drawer .btn--quiet { --btn-quiet-border: var(--modal-drawer-quiet-border, currentColor); }
|
|
5689
|
+
.modal__drawer .icon-btn { --icon-btn-bg: transparent; --icon-btn-ink: currentColor; --icon-btn-border: var(--ink-rail); }
|
|
5690
|
+
.modal__drawer .icon-btn--invert { --icon-btn-size: var(--tap-lg); }
|
|
5482
5691
|
@media (prefers-reduced-motion: no-preference) {
|
|
5483
|
-
.modal { animation: modal-drop calc(2 * var(--motion-sheet)) ease both; }
|
|
5484
|
-
.modal__drawer { animation: modal-open calc(2 * var(--motion-sheet)) ease both; }
|
|
5692
|
+
.modal { animation: modal-drop calc(2 * var(--motion-sheet)) var(--ease) both; }
|
|
5693
|
+
.modal__drawer { animation: modal-open calc(2 * var(--motion-sheet)) var(--ease) both; }
|
|
5485
5694
|
@keyframes modal-drop {
|
|
5486
5695
|
0% { translate: 0 calc(-50% - 100cqh); } /* above the stage */
|
|
5487
5696
|
50%, 100% { translate: 0 -50%; } /* 1. at the centre — and it stays: beat 2 is the drawer's */
|
|
@@ -5496,8 +5705,8 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5496
5705
|
.btn-trio__dots {
|
|
5497
5706
|
width: var(--btn-trio-dots-w, var(--sp-5));
|
|
5498
5707
|
height: var(--btn-trio-dots-h, var(--sp-1));
|
|
5499
|
-
background: radial-gradient(circle, currentColor var(--sp-0), transparent calc(var(--sp-0) +
|
|
5500
|
-
opacity:
|
|
5708
|
+
background: radial-gradient(circle, currentColor var(--sp-0), transparent calc(var(--sp-0) + var(--px) / 2)) 0 0 / calc(var(--sp-5) / 2) 100% repeat-x;
|
|
5709
|
+
opacity: var(--fade-soft);
|
|
5501
5710
|
}
|
|
5502
5711
|
|
|
5503
5712
|
/* ---- popover menu: a card anchored to its button, rows of icon + label ----
|
|
@@ -5520,7 +5729,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5520
5729
|
border-radius: var(--menu-radius, var(--r-card));
|
|
5521
5730
|
--menu-pad: var(--sp-2); /* published: a row's radius is concentric with the panel's */
|
|
5522
5731
|
padding: var(--menu-pad);
|
|
5523
|
-
min-width:
|
|
5732
|
+
min-width: calc(var(--tap-base) * 4);
|
|
5524
5733
|
align-content: start; /* rows keep their own height: WebKit stretches a popover grid's rows to the whole room under its anchor (iOS 26.5, 2026-10-08) */
|
|
5525
5734
|
--_menu-max: min(60dvh, calc(100dvh - 2 * var(--sp-5)));
|
|
5526
5735
|
max-height: var(--menu-max-h, var(--_menu-max)); /* a long list (twenty neighbours) scrolls inside the panel — it ran off the screen's foot, the last names out of reach (timebank, 2026-10-08) */
|
|
@@ -5554,7 +5763,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5554
5763
|
.menu[popover] {
|
|
5555
5764
|
position-area: block-end span-inline-end;
|
|
5556
5765
|
margin-top: var(--sp-2);
|
|
5557
|
-
min-width: max(
|
|
5766
|
+
min-width: max(calc(var(--tap-base) * 4), anchor-size(width)); /* at least as wide as its trigger */
|
|
5558
5767
|
position-try-fallbacks: flip-block, flip-inline;
|
|
5559
5768
|
}
|
|
5560
5769
|
.menu--end[popover] { position-area: block-end span-inline-start; }
|
|
@@ -5587,11 +5796,11 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5587
5796
|
.banner__summary {
|
|
5588
5797
|
list-style: none;
|
|
5589
5798
|
display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
|
|
5590
|
-
padding-block: var(--banner-summary-pad-y, calc(var(--sp-base) * 1.5)); font: var(--banner-summary-font, var(--type-caption)); font-weight:
|
|
5799
|
+
padding-block: var(--banner-summary-pad-y, calc(var(--sp-base) * 1.5)); font: var(--banner-summary-font, var(--type-caption)); font-weight: var(--fw-regular);
|
|
5591
5800
|
}
|
|
5592
5801
|
.banner__summary::-webkit-details-marker { display: none; }
|
|
5593
|
-
.banner__summary .icon {
|
|
5594
|
-
.banner[open] .banner__summary .icon {
|
|
5802
|
+
.banner__summary .icon { --icon-motion: rotate var(--motion-quick) var(--ease); }
|
|
5803
|
+
.banner[open] .banner__summary .icon { --icon-turn: 180deg; }
|
|
5595
5804
|
/* the band's words never run past it: one column that cannot be wider than the band (a grid track is as wide as its longest
|
|
5596
5805
|
word otherwise), and a word with no break in it — a link — breaks where it must (mailpool, Lh 2026-10-01) */
|
|
5597
5806
|
.banner__body { display: grid; grid-template-columns: minmax(0, 1fr); overflow-wrap: anywhere; gap: var(--banner-body-gap, var(--sp-1)); padding-bottom: var(--sp-4); }
|
|
@@ -5599,7 +5808,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5599
5808
|
@supports (interpolate-size: allow-keywords) {
|
|
5600
5809
|
@media (prefers-reduced-motion: no-preference) {
|
|
5601
5810
|
.banner { interpolate-size: allow-keywords; }
|
|
5602
|
-
.banner::details-content { block-size: 0; overflow: clip; transition: block-size var(--motion-quick) ease, content-visibility var(--motion-quick) allow-discrete; }
|
|
5811
|
+
.banner::details-content { block-size: 0; overflow: clip; transition: block-size var(--motion-quick) var(--ease), content-visibility var(--motion-quick) allow-discrete; }
|
|
5603
5812
|
.banner[open]::details-content { block-size: auto; }
|
|
5604
5813
|
}
|
|
5605
5814
|
}
|
|
@@ -5658,7 +5867,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5658
5867
|
/* aria-current="false" is the correct way to mark the OTHER tabs, so the
|
|
5659
5868
|
bare attribute selector lights every one of them up */
|
|
5660
5869
|
.float-bar__item[aria-current]:not([aria-current="false"]) { background: var(--accent); color: var(--on-accent); }
|
|
5661
|
-
.float-bar > .icon-btn {
|
|
5870
|
+
.float-bar > .icon-btn { --icon-btn-size: var(--bar-h); --icon-btn-shadow: var(--shadow-float); }
|
|
5662
5871
|
|
|
5663
5872
|
/* ---- callout: a small card with a tail — over a map, a media, a mascot ----
|
|
5664
5873
|
<div class="callout" style="top: 30%; left: 40%"><span class="callout__title">Telok Ayer Park</span><span class="callout__sub">1.2 km</span></div> */
|
|
@@ -5667,7 +5876,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5667
5876
|
display: grid;
|
|
5668
5877
|
gap: var(--callout-gap, var(--sp-0));
|
|
5669
5878
|
width: max-content;
|
|
5670
|
-
max-width:
|
|
5879
|
+
max-width: calc(var(--tap-base) * 5);
|
|
5671
5880
|
padding: var(--callout-pad-y, var(--sp-3)) var(--callout-pad-x, var(--sp-4));
|
|
5672
5881
|
border: var(--border-w) solid var(--callout-border, var(--action-border));
|
|
5673
5882
|
border-radius: var(--callout-radius, var(--r-card));
|
|
@@ -5700,7 +5909,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5700
5909
|
<p class="splash__text">…</p></div></section>
|
|
5701
5910
|
The group's MIDDLE is on --screen-centre (40% down), the same line a centred card uses. The mark is the app's own content —
|
|
5702
5911
|
a lottie box often carries empty room around its drawing — the app sets the knobs below, cardds does not guess it. */
|
|
5703
|
-
.splash { position: relative; background: var(--splash-bg, var(--surface)); transition: opacity var(--motion-sheet) ease; }
|
|
5912
|
+
.splash { position: relative; background: var(--splash-bg, var(--surface)); transition: opacity var(--motion-sheet) var(--ease); }
|
|
5704
5913
|
.splash--leaving { opacity: 0; }
|
|
5705
5914
|
.splash__group {
|
|
5706
5915
|
position: absolute; inset-inline: var(--screen-pad); top: var(--splash-at, var(--screen-centre)); translate: 0 -50%;
|
|
@@ -5722,13 +5931,13 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5722
5931
|
position: fixed; inset-inline: 0; top: calc(var(--sp-3) + var(--safe-top));
|
|
5723
5932
|
max-width: var(--screen-w); margin-inline: auto; padding-inline: var(--screen-pad, var(--sp-4));
|
|
5724
5933
|
display: grid; justify-items: center;
|
|
5725
|
-
z-index: calc(var(--z-top) +
|
|
5934
|
+
z-index: calc(var(--z-top) + var(--z-over));
|
|
5726
5935
|
pointer-events: none; visibility: hidden;
|
|
5727
5936
|
translate: 0 calc(var(--_out) * (100% + var(--sp-8) + var(--safe-top)));
|
|
5728
|
-
transition: translate var(--motion-sheet) ease, visibility 0s var(--motion-sheet);
|
|
5937
|
+
transition: translate var(--motion-sheet) var(--ease), visibility 0s var(--motion-sheet);
|
|
5729
5938
|
}
|
|
5730
5939
|
.screen--fill > .snackbar, .sheet-stage > .snackbar { position: absolute; }
|
|
5731
|
-
.snackbar--bottom { --_out: 1; top: auto; bottom: calc(var(--sp-3) + var(--bottom-h,
|
|
5940
|
+
.snackbar--bottom { --_out: 1; top: auto; bottom: calc(var(--sp-3) + var(--bottom-h, 0rem) + max(var(--safe-bottom), var(--kb, 0rem))); }
|
|
5732
5941
|
.snackbar--open { translate: 0; visibility: visible; transition-delay: 0s; }
|
|
5733
5942
|
.snackbar__body {
|
|
5734
5943
|
display: flex; align-items: center; gap: var(--snackbar-gap, var(--sp-3));
|
|
@@ -5743,12 +5952,15 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5743
5952
|
font: inherit; text-align: start;
|
|
5744
5953
|
pointer-events: auto;
|
|
5745
5954
|
}
|
|
5746
|
-
.snackbar__body > .icon {
|
|
5955
|
+
.snackbar__body > .icon { --icon-ink: var(--snackbar-icon-ink, var(--signal-ok)); }
|
|
5747
5956
|
.snackbar__words { display: grid; min-width: 0; overflow-wrap: anywhere; }
|
|
5748
5957
|
.snackbar__text { font: var(--snackbar-text-font, var(--type-body)); }
|
|
5749
5958
|
.snackbar__caption { font: var(--snackbar-caption-font, var(--type-caption)); color: var(--snackbar-caption-ink, inherit); } /* on the signal's soft ground the full ink: a muted shade there fell under AA */
|
|
5750
5959
|
@media (prefers-reduced-motion: reduce) { .snackbar { transition: none; } }
|
|
5751
5960
|
|
|
5961
|
+
/* ---- where it sits in what actions.css draws (docs/reference.md rule 15) ---- */
|
|
5962
|
+
.dropdown--end .menu[popover] { position-area: block-end span-inline-start; } /* right-aligned trigger: panel grows leftwards (= .menu--end, layover.css) */
|
|
5963
|
+
|
|
5752
5964
|
/* ---- css/finish.css — light, shadow, edges — only under data-finish="lit" ---- */
|
|
5753
5965
|
/* ============================================================
|
|
5754
5966
|
cardds/finish.css — FINISH: the finish of a card's paper. A style of its own (Lh 2026-10-02: the plain
|
|
@@ -5785,28 +5997,28 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5785
5997
|
============================================================ */
|
|
5786
5998
|
[data-finish="lit"] {
|
|
5787
5999
|
--_shade: var(--on-surface);
|
|
5788
|
-
--finish-edge: inset 0
|
|
5789
|
-
--finish-ring: 0 0 0
|
|
6000
|
+
--finish-edge: inset 0 0.0625rem 0 rgb(255 255 255 / 0.55); /* the lit top edge */
|
|
6001
|
+
--finish-ring: 0 0 0 0.0625rem color-mix(in srgb, var(--_shade) 7%, transparent); /* the hairline round a raised thing */
|
|
5790
6002
|
|
|
5791
6003
|
/* the three shadows every component already reads — layered: one close and crisp, one far and soft */
|
|
5792
6004
|
--shadow-card: var(--finish-edge), var(--finish-ring),
|
|
5793
|
-
0
|
|
6005
|
+
0 0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 8%, transparent),
|
|
5794
6006
|
0 0.375rem 0.75rem -0.125rem color-mix(in srgb, var(--_shade) 8%, transparent),
|
|
5795
6007
|
0 1.25rem 2.5rem -0.75rem color-mix(in srgb, var(--_shade) 14%, transparent);
|
|
5796
6008
|
--shadow-float: var(--finish-edge), var(--finish-ring),
|
|
5797
|
-
0
|
|
6009
|
+
0 0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 12%, transparent),
|
|
5798
6010
|
0 0.5rem 1.25rem -0.25rem color-mix(in srgb, var(--_shade) 22%, transparent);
|
|
5799
6011
|
--shadow-sheet: var(--finish-edge), var(--finish-ring),
|
|
5800
|
-
0 -
|
|
6012
|
+
0 -0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 6%, transparent),
|
|
5801
6013
|
0 -0.75rem 2.5rem color-mix(in srgb, var(--_shade) 18%, transparent);
|
|
5802
6014
|
|
|
5803
6015
|
/* a control: its own small lift, and the sheen that runs down its face */
|
|
5804
6016
|
--finish-control: var(--finish-edge), var(--finish-ring),
|
|
5805
|
-
0
|
|
6017
|
+
0 0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 12%, transparent),
|
|
5806
6018
|
0 0.25rem 0.5rem -0.125rem color-mix(in srgb, var(--_shade) 12%, transparent);
|
|
5807
|
-
--finish-control-down: var(--finish-ring), inset 0
|
|
6019
|
+
--finish-control-down: var(--finish-ring), inset 0 0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 18%, transparent);
|
|
5808
6020
|
--finish-sheen: linear-gradient(to bottom, rgb(255 255 255 / 0.16), rgb(255 255 255 / 0) 60%);
|
|
5809
|
-
--finish-sunk: inset 0
|
|
6021
|
+
--finish-sunk: inset 0 0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 14%, transparent), 0 0.0625rem 0 rgb(255 255 255 / 0.5);
|
|
5810
6022
|
|
|
5811
6023
|
--outline: color-mix(in srgb, currentColor 14%, transparent);
|
|
5812
6024
|
}
|
|
@@ -5828,9 +6040,9 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5828
6040
|
}
|
|
5829
6041
|
/* the main button throws its own colour */
|
|
5830
6042
|
[data-finish="lit"] .btn--primary,
|
|
5831
|
-
[data-finish="lit"] .icon-btn--invert:not(.icon-btn-group > *) {
|
|
6043
|
+
[data-finish="lit"] :is(.icon-btn--invert, .icon-btn--primary):not(.icon-btn-group > *) {
|
|
5832
6044
|
box-shadow: var(--finish-edge),
|
|
5833
|
-
0
|
|
6045
|
+
0 0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 20%, transparent),
|
|
5834
6046
|
0 0.5rem 1rem -0.25rem color-mix(in srgb, var(--accent) 45%, transparent);
|
|
5835
6047
|
}
|
|
5836
6048
|
/* a peeked stack tab is the icon alone: no sheen and no shadow until its sheet is open (the fill fades with the sheet) */
|
|
@@ -5844,7 +6056,7 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5844
6056
|
}
|
|
5845
6057
|
[data-finish="lit"] .icon-btn-group { box-shadow: var(--finish-control); }
|
|
5846
6058
|
[data-finish="lit"] .toggle { box-shadow: var(--finish-sunk); }
|
|
5847
|
-
[data-finish="lit"] .toggle::after { box-shadow: 0
|
|
6059
|
+
[data-finish="lit"] .toggle::after { box-shadow: 0 0.0625rem 0.125rem color-mix(in srgb, var(--_shade) 35%, transparent); }
|
|
5848
6060
|
|
|
5849
6061
|
/* ---- sunk: what the member types into ---- */
|
|
5850
6062
|
[data-finish="lit"] :is(.field, .pin__cell) { box-shadow: var(--finish-sunk); }
|
|
@@ -5862,12 +6074,12 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5862
6074
|
}
|
|
5863
6075
|
[data-finish="print"] .card {
|
|
5864
6076
|
border: var(--border-w) solid var(--_ink);
|
|
5865
|
-
box-shadow: var(--print-off,
|
|
6077
|
+
box-shadow: var(--print-off, 0.1875rem) var(--print-off, 0.1875rem) 0 0 color-mix(in srgb, var(--card-ink) 28%, transparent);
|
|
5866
6078
|
isolation: isolate; /* the grain's blend stays inside the card */
|
|
5867
6079
|
}
|
|
5868
6080
|
/* the grain: a ::after, not ::before — .card--cover's scrim is its ::before. Over everything in the card (z-index 1, the foot's own level), touches pass through it */
|
|
5869
6081
|
[data-finish="print"] .card::after {
|
|
5870
|
-
content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index:
|
|
6082
|
+
content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: var(--z-over);
|
|
5871
6083
|
opacity: var(--print-grain, 0.32); mix-blend-mode: multiply;
|
|
5872
6084
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.3 0 0 0 0 0.25 0 0 0 0 0.2 0 0 0 0.9 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
5873
6085
|
}
|
|
@@ -5879,10 +6091,11 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5879
6091
|
}
|
|
5880
6092
|
[data-finish="print"] :is(.btn, .chip) {
|
|
5881
6093
|
border-color: var(--_ink);
|
|
5882
|
-
box-shadow:
|
|
6094
|
+
box-shadow: 0.125rem 0.125rem 0 0 color-mix(in srgb, currentColor 35%, transparent);
|
|
5883
6095
|
}
|
|
5884
|
-
[data-finish="print"] .
|
|
5885
|
-
[data-finish="print"]
|
|
6096
|
+
[data-finish="print"] .chip { border-color: var(--chip-border, var(--_ink)); } /* a holder that clears a chip's edge (a note's chips) clears it in print too */
|
|
6097
|
+
[data-finish="print"] .btn--primary { box-shadow: 0.125rem 0.125rem 0 0 color-mix(in srgb, var(--card-ink) 60%, transparent); }
|
|
6098
|
+
[data-finish="print"] :is(.btn, .chip):active { box-shadow: none; translate: 0.125rem 0.125rem; }
|
|
5886
6099
|
[data-finish="print"] .card__thumb { border-style: dashed; }
|
|
5887
6100
|
|
|
5888
6101
|
/* ============================================================
|
|
@@ -5894,29 +6107,29 @@ button.calendar__label:focus-visible { outline: var(--focus); outline-offset: va
|
|
|
5894
6107
|
[data-finish="sticker"] { --outline: color-mix(in srgb, currentColor 18%, transparent); }
|
|
5895
6108
|
[data-finish="sticker"] .card {
|
|
5896
6109
|
box-shadow:
|
|
5897
|
-
0 0 0 var(--sticker-cut,
|
|
5898
|
-
0 calc(var(--sticker-cut,
|
|
5899
|
-
0 calc(var(--sticker-cut,
|
|
6110
|
+
0 0 0 var(--sticker-cut, 0.3125rem) #fff,
|
|
6111
|
+
0 calc(var(--sticker-cut, 0.3125rem) + 0.25rem) 0 0 color-mix(in srgb, var(--on-surface) 16%, transparent),
|
|
6112
|
+
0 calc(var(--sticker-cut, 0.3125rem) + 0.75rem) 1.25rem -0.5rem color-mix(in srgb, var(--on-surface) 22%, transparent);
|
|
5900
6113
|
}
|
|
5901
6114
|
[data-finish="sticker"] .card:not(.sheet, .modal, .card--away, .sheet-stack *, .card-list > *, .card-feed > *) { rotate: var(--sticker-tilt, 0.8deg); }
|
|
5902
6115
|
[data-finish="sticker"] .card:not(.sheet, .modal, .card--away, .sheet-stack *, .card-list > *, .card-feed > *):nth-of-type(even) { rotate: calc(-1 * var(--sticker-tilt, 0.8deg)); }
|
|
5903
6116
|
[data-finish="sticker"] .card-list { gap: var(--sp-5); }
|
|
5904
6117
|
[data-finish="sticker"] .btn {
|
|
5905
|
-
box-shadow: 0
|
|
6118
|
+
box-shadow: 0 0.1875rem 0 0 color-mix(in srgb, currentColor 30%, transparent);
|
|
5906
6119
|
border-color: color-mix(in srgb, currentColor 25%, transparent);
|
|
5907
6120
|
}
|
|
5908
|
-
[data-finish="sticker"] .btn--primary { box-shadow: 0
|
|
5909
|
-
[data-finish="sticker"] .btn:active { box-shadow: none; translate: 0
|
|
5910
|
-
[data-finish="sticker"] .chip { box-shadow: 0 0 0
|
|
6121
|
+
[data-finish="sticker"] .btn--primary { box-shadow: 0 0.1875rem 0 0 color-mix(in srgb, var(--btn-primary-bg, var(--accent)) 55%, var(--on-surface)); }
|
|
6122
|
+
[data-finish="sticker"] .btn:active { box-shadow: none; translate: 0 0.1875rem; }
|
|
6123
|
+
[data-finish="sticker"] .chip { box-shadow: 0 0 0 0.1875rem #fff; }
|
|
5911
6124
|
|
|
5912
6125
|
/* ============================================================
|
|
5913
6126
|
ink — a poster / comic print: --ink-w of the surface's ink round every boundary and control, and a hard
|
|
5914
6127
|
offset of --ink-off in the same ink. The two wireframe borders come back as that line.
|
|
5915
6128
|
============================================================ */
|
|
5916
6129
|
[data-finish="ink"] { --card-border: var(--on-surface); --action-border: var(--on-surface); --outline: currentColor; }
|
|
5917
|
-
[data-finish="ink"] .card { border: var(--ink-w,
|
|
5918
|
-
[data-finish="ink"] :is(.btn, .chip, .icon-btn:not(.icon-btn-group > *)) { border: var(--ink-w,
|
|
5919
|
-
[data-finish="ink"] .btn--primary { border-color: var(--on-surface); box-shadow:
|
|
5920
|
-
[data-finish="ink"] .toggle { box-shadow:
|
|
5921
|
-
[data-finish="ink"] .card__thumb { border-width: var(--ink-w,
|
|
6130
|
+
[data-finish="ink"] .card { border: var(--ink-w, 0.15625rem) solid var(--on-surface); box-shadow: var(--ink-off, 0.3125rem) var(--ink-off, 0.3125rem) 0 0 var(--on-surface); }
|
|
6131
|
+
[data-finish="ink"] :is(.btn, .chip, .icon-btn:not(.icon-btn-group > *)) { border: var(--ink-w, 0.15625rem) solid currentColor; box-shadow: 0.1875rem 0.1875rem 0 0 currentColor; }
|
|
6132
|
+
[data-finish="ink"] .btn--primary { border-color: var(--on-surface); box-shadow: 0.1875rem 0.1875rem 0 0 var(--on-surface); }
|
|
6133
|
+
[data-finish="ink"] .toggle { box-shadow: 0.125rem 0.125rem 0 0 var(--on-surface); }
|
|
6134
|
+
[data-finish="ink"] .card__thumb { border-width: var(--ink-w, 0.15625rem); }
|
|
5922
6135
|
|