@usableapp/cardds 0.7.102 → 0.7.104
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 +36 -0
- package/css/actions.css +102 -36
- package/css/base.css +139 -15
- package/css/card.css +308 -79
- package/css/choice.css +25 -33
- package/css/finish.css +28 -27
- package/css/forms.css +6 -14
- package/css/journey.css +4 -4
- 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 +1 -1
- package/css/people.css +3 -8
- package/css/post.css +23 -19
- package/css/sheet.css +84 -56
- package/css/stack.css +39 -215
- 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 +100 -26
- package/dist/actions/Chip.d.ts +1 -1
- package/dist/actions/Chip.js +3 -2
- package/dist/cardds.css +1053 -868
- package/dist/cards/Card.d.ts +3 -3
- package/dist/cards/Card.js +1 -1
- package/dist/cards/CardFeed.js +7 -9
- package/dist/catalog.json +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 +71 -9
- package/lib/catalog.mjs +23 -7
- package/lint/rules.mjs +11 -6
- package/lint/values.mjs +144 -0
- package/package.json +6 -4
package/css/card.css
CHANGED
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
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
|
|
30
30
|
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:
|
|
31
31
|
the knob walk switches motion off */
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
.card--leaver { transition: translate var(--motion-sheet) var(--ease), rotate var(--motion-sheet) var(--ease); }
|
|
33
|
+
.card--away { --_y: calc(var(--leave-rise, 160%) * -1); rotate: var(--leave-turn, calc(var(--tilt-base) * -5)); }
|
|
34
34
|
.card--away { translate: 0 var(--_y); }
|
|
35
|
-
@media (prefers-reduced-motion: reduce) {
|
|
35
|
+
@media (prefers-reduced-motion: reduce) { .card--leaver { transition: none; } }
|
|
36
36
|
|
|
37
37
|
/* each tone is a fill and a side; its words and glyphs follow from the side (tokens.css) */
|
|
38
38
|
.card--2 { --card-bg: var(--card-2-bg); --dark: var(--card-2-dark); }
|
|
@@ -49,18 +49,13 @@
|
|
|
49
49
|
multi-line title can opt back out with .card__head--top. */
|
|
50
50
|
.card__head {
|
|
51
51
|
display: flex;
|
|
52
|
-
flex-wrap: wrap;
|
|
52
|
+
flex-wrap: var(--card-head-wrap, wrap); /* the caption takes a line of its own — no wrapper div */
|
|
53
53
|
align-items: center;
|
|
54
54
|
justify-content: space-between;
|
|
55
55
|
column-gap: var(--card-head-col-gap, var(--sp-3));
|
|
56
56
|
row-gap: var(--card-head-row-gap, var(--sp-0));
|
|
57
|
+
min-height: var(--card-head-min-h, auto);
|
|
57
58
|
}
|
|
58
|
-
/* flat head markup (Lh 2026-09-16 — no <p>, no wrapper, only padding and gap tokens):
|
|
59
|
-
<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>
|
|
60
|
-
The heading and the far-right thing share the first row; a caption sits on
|
|
61
|
-
a row below (order 1), whatever the DOM order — so an author writes the
|
|
62
|
-
words in reading order. */
|
|
63
|
-
.card__head > :is(.t-caption, .t-body) { flex: 1 1 100%; order: 1; margin: 0; }
|
|
64
59
|
.card__head--top { align-items: flex-start; }
|
|
65
60
|
|
|
66
61
|
.card__media { border-radius: var(--card-media-radius, var(--r-media)); overflow: hidden; position: relative; } /* relative: corner badges */
|
|
@@ -93,15 +88,13 @@
|
|
|
93
88
|
<div class="card__head"><h2 class="t-h2">…</h2><span class="t-caption t-muted">…</span><span class="stamp">…</span></div> */
|
|
94
89
|
.card__head:has(> .stamp) { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
|
|
95
90
|
.card__head:has(> .stamp) > :is(h1, h2, h3) { grid-column: 1; grid-row: 1; align-self: end; }
|
|
96
|
-
.card__head:has(> .stamp) > :is(.t-caption, .t-body) { grid-column: 1; grid-row: 2; align-self: start; }
|
|
97
|
-
.card__head > .stamp { grid-column: 2; grid-row: 1 / span 2; margin-inline: 0; }
|
|
98
91
|
|
|
99
92
|
/* 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
|
|
100
93
|
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,
|
|
101
94
|
so a theme that sets that knob still wins. */
|
|
102
95
|
.card > .card__head:first-child > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); }
|
|
103
96
|
|
|
104
|
-
/* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are
|
|
97
|
+
/* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are paper bands edge to
|
|
105
98
|
edge over the card's padding, no hairline; the middle between them keeps the card's tone, lightened (--card-middle-bg). The card
|
|
106
99
|
clips, so the bands keep its corners. A two-faced card (below) carries the look on each face. */
|
|
107
100
|
:is(.card--bands, .card--bands > .card__face) {
|
|
@@ -138,13 +131,10 @@
|
|
|
138
131
|
position: relative;
|
|
139
132
|
}
|
|
140
133
|
.card__face > * { min-width: 0; }
|
|
141
|
-
.card__middle { display: grid; align-content: start; gap: var(--card-gap); height: var(--card-middle-h,
|
|
142
|
-
.card__turn { position: absolute; inset: 0; width:
|
|
134
|
+
.card__middle { display: grid; align-content: start; gap: var(--card-gap); height: var(--card-middle-h, calc(var(--tap-base) * 25 / 4)); overflow: hidden; }
|
|
135
|
+
.card__turn { position: absolute; inset: 0; width: var(--px); height: var(--px); padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); }
|
|
143
136
|
.card__face:has(> .card__turn:focus-visible) { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
|
|
144
|
-
|
|
145
|
-
.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; }
|
|
146
|
-
.card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; }
|
|
147
|
-
.card__head > .chip-row > .chip > .icon { display: inline-block; vertical-align: -0.15em; margin-inline-end: var(--chip-sm-gap, var(--sp-1)); }
|
|
137
|
+
.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; }
|
|
148
138
|
|
|
149
139
|
/* ---- bleed: a scrolling track that runs to the container's edges ----
|
|
150
140
|
For a horizontal strip inside a padded container — .avatar-pick, .chip-row,
|
|
@@ -165,15 +155,15 @@
|
|
|
165
155
|
nothing jumps. Chromium/Safari paint it through the ::-webkit-scrollbar
|
|
166
156
|
parts; Firefox gets its thin bar in the same ink. */
|
|
167
157
|
.bleed {
|
|
168
|
-
margin-inline: calc(-1 * var(--card-pad,
|
|
169
|
-
padding-inline: var(--card-pad,
|
|
158
|
+
margin-inline: calc(-1 * var(--card-pad, 0rem));
|
|
159
|
+
padding-inline: var(--card-pad, 0rem);
|
|
170
160
|
padding-block-end: var(--sp-2);
|
|
171
|
-
scroll-padding-inline: var(--card-pad,
|
|
161
|
+
scroll-padding-inline: var(--card-pad, 0rem);
|
|
172
162
|
flex-wrap: nowrap;
|
|
173
163
|
overflow-x: auto;
|
|
174
164
|
}
|
|
175
165
|
.bleed::-webkit-scrollbar { height: var(--scroll-w); }
|
|
176
|
-
.bleed::-webkit-scrollbar-track { background: transparent; margin-inline: var(--card-pad,
|
|
166
|
+
.bleed::-webkit-scrollbar-track { background: transparent; margin-inline: var(--card-pad, 0rem); }
|
|
177
167
|
.bleed::-webkit-scrollbar-thumb { background: transparent; }
|
|
178
168
|
.bleed:is(:hover, .is-scrolling)::-webkit-scrollbar-thumb { background: currentColor; }
|
|
179
169
|
@supports not selector(::-webkit-scrollbar) {
|
|
@@ -187,12 +177,8 @@
|
|
|
187
177
|
/* foot: the card's action buttons on one line. One button takes the width,
|
|
188
178
|
up to four share it in even parts — so the gaps read even too — and from
|
|
189
179
|
three on they drop their side padding so the labels keep the room. */
|
|
190
|
-
.card__foot { display: flex; gap: var(--card-foot-gap, var(--sp-2)); align-items: center; }
|
|
191
|
-
.card__foot > .btn {
|
|
192
|
-
/* …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
|
|
193
|
-
a card's foot read too big — which button a card wears is the project's choice, so the foot must not undo it) */
|
|
194
|
-
.card__foot > .btn--form { flex: 0 1 auto; }
|
|
195
|
-
.card__foot:has(> :nth-child(3)) > .btn { padding-inline: var(--sp-2); }
|
|
180
|
+
.card__foot { display: flex; gap: var(--card-foot-gap, var(--sp-2)); align-items: center; color: var(--card-foot-ink, inherit); }
|
|
181
|
+
.card__foot:has(> :nth-child(3)) > .btn { --btn-pad-x: var(--sp-2); }
|
|
196
182
|
/* a small link under the buttons (CardFoot link — Lh 2026-09-23, as the ActionBar's ctaLink): the row wraps, the link takes a
|
|
197
183
|
whole short row of its own, centred — it never shares the buttons' line */
|
|
198
184
|
.card__foot--link { flex-wrap: wrap; align-content: end; row-gap: var(--card-foot-link-gap, var(--sp-1)); }
|
|
@@ -201,15 +187,15 @@
|
|
|
201
187
|
/* media placeholder for wireframes — currentColor so it survives any palette */
|
|
202
188
|
.ph {
|
|
203
189
|
background:
|
|
204
|
-
linear-gradient(to top right, transparent calc(50% -
|
|
205
|
-
linear-gradient(to top left, transparent calc(50% -
|
|
190
|
+
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))),
|
|
191
|
+
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)));
|
|
206
192
|
border: var(--border-w) solid currentColor;
|
|
207
193
|
border-radius: var(--r-media);
|
|
208
194
|
opacity: var(--off);
|
|
209
|
-
min-height:
|
|
195
|
+
min-height: calc(var(--tap-base) * 10 / 3);
|
|
210
196
|
}
|
|
211
|
-
.ph--sm { min-height:
|
|
212
|
-
.ph--md { min-height:
|
|
197
|
+
.ph--sm { min-height: calc(var(--tap-base) * 5 / 2); }
|
|
198
|
+
.ph--md { min-height: calc(var(--tap-base) * 3); }
|
|
213
199
|
/* ---- centred card: one thing to read, at the human centre of a fill screen ----
|
|
214
200
|
(.screen__centre below is the same placement for a small GROUP of things)
|
|
215
201
|
Where the centre of a phone IS, to the person holding it: the bottom fifth is
|
|
@@ -222,8 +208,7 @@
|
|
|
222
208
|
The card hugs its words and keeps the screen's inset. It is capped so that it
|
|
223
209
|
never reaches the title bar above it nor the bar's room below it — past that it
|
|
224
210
|
scrolls inside — from the same tokens those bars reserve their room with. */
|
|
225
|
-
.card--centre
|
|
226
|
-
.screen--fill > .screen__centre {
|
|
211
|
+
.card--centre {
|
|
227
212
|
position: absolute;
|
|
228
213
|
top: var(--screen-centre);
|
|
229
214
|
translate: 0 -50%;
|
|
@@ -240,38 +225,6 @@
|
|
|
240
225
|
:is(.topbar, .appbar) ~ :is(.card--centre, .screen__centre) { --_lead: var(--header-h); }
|
|
241
226
|
.card--centre { inset-inline: var(--screen-pad); }
|
|
242
227
|
|
|
243
|
-
/* ---- centred block: the same place, for one thing to read that is not a
|
|
244
|
-
lone card — an empty state, a card with its quiet second button and the
|
|
245
|
-
pager under it. The block runs edge to
|
|
246
|
-
edge and keeps the screen's inset as padding, and
|
|
247
|
-
its children stack at the screen's own rhythm (Lh, 2026-09-11):
|
|
248
|
-
<div class="screen screen--fill"><header class="topbar">…</header>
|
|
249
|
-
<div class="screen__centre"><article class="card">…</article><button class="btn btn--quiet btn--block">…</button></div>
|
|
250
|
-
</div> */
|
|
251
|
-
.screen__centre {
|
|
252
|
-
inset-inline: 0;
|
|
253
|
-
padding-inline: var(--screen-pad);
|
|
254
|
-
display: grid;
|
|
255
|
-
gap: var(--screen-gap);
|
|
256
|
-
align-content: start;
|
|
257
|
-
}
|
|
258
|
-
.screen__centre > * { min-width: 0; }
|
|
259
|
-
|
|
260
|
-
/* ---- the same group on a plain .screen: centred when short, as long as it
|
|
261
|
-
likes when not (Lh, 2026-09-12) ----
|
|
262
|
-
The member sees one content area — whatever is between the top bar and the
|
|
263
|
-
bar — and reads it as a whole page: a short card sits at its middle, a long
|
|
264
|
-
one simply makes the page longer, runs under the bar (a sticky last row,
|
|
265
|
-
actions.css) and scrolls up like any page — nothing is capped, nothing
|
|
266
|
-
shrinks, no scroll box inside a box.
|
|
267
|
-
So on a .screen that is NOT --fill the group is in flow: the column is at
|
|
268
|
-
least one phone tall (step.css), the group takes the room after the top bar and
|
|
269
|
-
centres its children in it. The screen's own inset stays the inset.
|
|
270
|
-
<div class="screen"><header class="topbar">…</header>
|
|
271
|
-
<div class="screen__centre"><article class="card">…</article><div class="pager__at">…</div></div>
|
|
272
|
-
</div><div class="action-bar">…</div> */
|
|
273
|
-
.screen:not(.screen--fill) > .screen__centre { flex: 1; padding-inline: 0; align-content: center; }
|
|
274
|
-
|
|
275
228
|
/* ---- the card that changes slides: out one side, the next in from the other ----
|
|
276
229
|
A .screen__centre group that walks pages (data-slide="next" | "back" on the
|
|
277
230
|
root while a step changes) turns its card like a page: the old card slides
|
|
@@ -300,7 +253,7 @@
|
|
|
300
253
|
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) */
|
|
301
254
|
touch-action: pan-y; /* leaves vertical scroll to the browser; horizontal is the app's own drag, never a native pan */
|
|
302
255
|
}
|
|
303
|
-
.pages >
|
|
256
|
+
.pages > .card { grid-area: 1 / 1; align-content: start; } /* stretched to the cell, the words stay at the top */
|
|
304
257
|
.pages > [inert] { visibility: hidden; }
|
|
305
258
|
/* pages of WORDS (Lh, 2026-09-20): the same walk with .page pages, on the base — in a .step the
|
|
306
259
|
.pages is the reading block (step.css), the round pager in the bar turns it, the same slideTo().
|
|
@@ -312,7 +265,7 @@
|
|
|
312
265
|
::view-transition-old(centre-card-out), ::view-transition-new(centre-card-in),
|
|
313
266
|
::view-transition-old(screen-out), ::view-transition-new(screen-in) { /* screen-*: slideScreen — one whole screen to the next (Lh 2026-09-25) */
|
|
314
267
|
animation-duration: var(--motion-slide);
|
|
315
|
-
animation-timing-function: ease-out;
|
|
268
|
+
animation-timing-function: var(--ease-out);
|
|
316
269
|
mix-blend-mode: normal;
|
|
317
270
|
}
|
|
318
271
|
: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; }
|
|
@@ -327,9 +280,6 @@
|
|
|
327
280
|
::view-transition-old(centre-card-out), ::view-transition-new(centre-card-in),
|
|
328
281
|
::view-transition-old(screen-out), ::view-transition-new(screen-in) { animation: none; }
|
|
329
282
|
}
|
|
330
|
-
/* the page dots under the card sit centred under it — the indicator belongs
|
|
331
|
-
with the page that changes, never in the bar (Lh, 2026-09-12) */
|
|
332
|
-
.screen__centre > .pager__at { justify-self: center; }
|
|
333
283
|
|
|
334
284
|
/* compact card inside a card (nested note, promo) */
|
|
335
285
|
.card--sm { padding: var(--card-sm-pad, var(--sp-4)); --card-gap: var(--sp-2); border-radius: var(--card-sm-radius, var(--r-card)); }
|
|
@@ -338,7 +288,7 @@
|
|
|
338
288
|
stack.css; a stacked sheet is a plain .card, the material */
|
|
339
289
|
|
|
340
290
|
/* trailing icon cluster in a card header */
|
|
341
|
-
.icon-row { display: flex; align-items: center; gap: var(--icon-row-gap, var(--sp-4)); opacity:
|
|
291
|
+
.icon-row { display: flex; align-items: center; gap: var(--icon-row-gap, var(--sp-4)); opacity: var(--fade-near); }
|
|
342
292
|
|
|
343
293
|
/* ---- card list: rows, each row its own boundary ---- */
|
|
344
294
|
.card-list { display: grid; gap: var(--card-list-gap, var(--sp-3)); }
|
|
@@ -374,7 +324,7 @@
|
|
|
374
324
|
.card__remove {
|
|
375
325
|
--_size: var(--card-remove-size, calc(var(--tap-base) / 2));
|
|
376
326
|
position: relative;
|
|
377
|
-
z-index:
|
|
327
|
+
z-index: var(--z-over);
|
|
378
328
|
flex: none;
|
|
379
329
|
align-self: flex-start;
|
|
380
330
|
display: grid;
|
|
@@ -392,7 +342,7 @@
|
|
|
392
342
|
cursor: pointer;
|
|
393
343
|
}
|
|
394
344
|
.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%; }
|
|
395
|
-
.card__remove > .icon {
|
|
345
|
+
.card__remove > .icon { --icon-size: 62%; }
|
|
396
346
|
.card__remove:focus-visible { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
397
347
|
|
|
398
348
|
/* leading thumb/icon in a row */
|
|
@@ -435,7 +385,7 @@
|
|
|
435
385
|
.feed-card__rest > :first-child { margin-top: var(--card-gap); }
|
|
436
386
|
.feed-card--small .feed-card__rest { overflow: clip; visibility: hidden; }
|
|
437
387
|
@media (prefers-reduced-motion: no-preference) {
|
|
438
|
-
.feed-card__more { transition: grid-template-rows var(--motion-sheet) ease; }
|
|
388
|
+
.feed-card__more { transition: grid-template-rows var(--motion-sheet) var(--ease); }
|
|
439
389
|
.feed-card--small .feed-card__rest { transition: visibility 0s var(--motion-sheet); }
|
|
440
390
|
}
|
|
441
391
|
|
|
@@ -446,7 +396,7 @@
|
|
|
446
396
|
|
|
447
397
|
.delta {
|
|
448
398
|
font: var(--delta-font, var(--type-caption));
|
|
449
|
-
font-weight:
|
|
399
|
+
font-weight: var(--fw-bold);
|
|
450
400
|
padding: var(--delta-pad-y, var(--sp-1)) var(--delta-pad-x, var(--sp-2));
|
|
451
401
|
border-radius: var(--delta-radius, var(--r-chip));
|
|
452
402
|
background: var(--delta-bg, var(--card-3-bg));
|
|
@@ -473,4 +423,283 @@
|
|
|
473
423
|
scroll-snap-align: start; scroll-snap-stop: always;
|
|
474
424
|
display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--card-gap); align-content: start;
|
|
475
425
|
}
|
|
476
|
-
|
|
426
|
+
|
|
427
|
+
/* ==== the card's variants another family draws: Cover and the fold (media), Band (numbers) (#62: a block is written in its own file) ==== */
|
|
428
|
+
|
|
429
|
+
/* ---- cover: the image IS the card; text over a scrim ----
|
|
430
|
+
<article class="card card--cover"><div class="ph" role="img"></div>
|
|
431
|
+
<div class="card__head"><button class="icon-btn icon-btn--sm">♥</button></div>
|
|
432
|
+
<div><h2 class="t-h1">Art Institute</h2><p class="t-body">111 South Michigan Ave</p></div>
|
|
433
|
+
<div class="row">…meta…</div></article> */
|
|
434
|
+
.card--cover {
|
|
435
|
+
--card-bg: var(--card-3-bg);
|
|
436
|
+
--dark: var(--card-3-dark);
|
|
437
|
+
--accent: var(--card-ink);
|
|
438
|
+
--accent-dark: calc(1 - var(--dark));
|
|
439
|
+
min-height: var(--_cover-min-h);
|
|
440
|
+
align-content: space-between;
|
|
441
|
+
overflow: hidden;
|
|
442
|
+
isolation: isolate;
|
|
443
|
+
}
|
|
444
|
+
.card--cover > img,
|
|
445
|
+
.card--cover > .ph {
|
|
446
|
+
position: absolute;
|
|
447
|
+
inset: 0;
|
|
448
|
+
width: 100%;
|
|
449
|
+
height: 100%;
|
|
450
|
+
object-fit: cover;
|
|
451
|
+
z-index: var(--z-under-2);
|
|
452
|
+
border: 0;
|
|
453
|
+
border-radius: 0;
|
|
454
|
+
min-height: 0;
|
|
455
|
+
}
|
|
456
|
+
/* scrim: dark at both ends so the meta stays readable */
|
|
457
|
+
.card--cover::before {
|
|
458
|
+
content: "";
|
|
459
|
+
position: absolute;
|
|
460
|
+
inset: 0;
|
|
461
|
+
z-index: var(--z-under);
|
|
462
|
+
background: linear-gradient(to bottom, var(--scrim), transparent 40% 60%, var(--scrim));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/* ---- fold: a sticky-note corner on any card (decorative) ----
|
|
466
|
+
No border. The card's outline follows its border box, so it ran straight
|
|
467
|
+
across the squared bottom-right corner and cut through the fold's diagonal —
|
|
468
|
+
the corner read as a notch with a line through it rather than a lifted flap.
|
|
469
|
+
The COLOUR goes transparent rather than the border going to 0, so the box
|
|
470
|
+
keeps its geometry and a fold card still measures the same as the cards
|
|
471
|
+
beside it. */
|
|
472
|
+
.card--fold {
|
|
473
|
+
border-color: transparent;
|
|
474
|
+
border-bottom-right-radius: 0;
|
|
475
|
+
background-image: linear-gradient(to top left,
|
|
476
|
+
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 */
|
|
477
|
+
color-mix(in srgb, currentColor 18%, var(--card-bg)) 50% 100%);
|
|
478
|
+
background-repeat: no-repeat;
|
|
479
|
+
background-position: bottom right;
|
|
480
|
+
background-size: var(--sp-8) var(--sp-8);
|
|
481
|
+
background-origin: border-box; /* the flap sits on the true corner, not inside the (transparent) border */
|
|
482
|
+
/* the corner is CUT: the triangle under the flap shows what is behind the card. Painted in the card's own colour it
|
|
483
|
+
read as the square corner of a second card underneath (Lh 2026-09-23). The polygon reaches --sp-12 past the other
|
|
484
|
+
edges so a coloured palette's shadow is not clipped there. */
|
|
485
|
+
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)),
|
|
486
|
+
calc(100% - var(--sp-8) - var(--sp-12)) calc(100% + var(--sp-12)), calc(-1 * var(--sp-12)) calc(100% + var(--sp-12)));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* ---- band: data card; numbers.css draws its parts and the band stack ---- */
|
|
490
|
+
.card--band {
|
|
491
|
+
grid-template-columns: 1fr auto;
|
|
492
|
+
grid-template-areas: "label num" "foot num";
|
|
493
|
+
align-items: end;
|
|
494
|
+
gap: var(--sp-2) var(--sp-4);
|
|
495
|
+
min-height: var(--card-band-min-h, var(--tile-h));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/* ---- where it sits in what base.css draws (docs/reference.md rule 15) ---- */
|
|
499
|
+
.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 */
|
|
500
|
+
.page > .ph { width: 100%; height: var(--page-media-h, calc(var(--tap-base) * 14 / 3)); }
|
|
501
|
+
/* ---- where it sits in what stack.css draws (docs/reference.md rule 15) ---- */
|
|
502
|
+
.sheet-stack > .card {
|
|
503
|
+
grid-area: 1 / 1; /* every card shares the stack's cell; later ones offset down */
|
|
504
|
+
border-bottom-left-radius: 0;
|
|
505
|
+
border-bottom-right-radius: 0;
|
|
506
|
+
border-bottom: 0;
|
|
507
|
+
align-content: start;
|
|
508
|
+
align-self: start; /* own natural height (padding + head) by default */
|
|
509
|
+
z-index: var(--level, var(--z-over)); /* explicit paint order, 1 = back of the stack */
|
|
510
|
+
}
|
|
511
|
+
/* THE FRONT SHEET is the last .card, not the last child: a --closed stack
|
|
512
|
+
may carry a .sheet-stack__base after its sheets (below). Written once here
|
|
513
|
+
as "a card no card follows" so every rule about the front sheet agrees. */
|
|
514
|
+
.sheet-stack > .card:not(:has(~ .card)) {
|
|
515
|
+
align-self: stretch; /* the front card fills the remaining stack height */
|
|
516
|
+
}
|
|
517
|
+
.sheet-stack > .card:nth-child(2) { margin-top: var(--stack-step); }
|
|
518
|
+
.sheet-stack > .card:nth-child(3) { margin-top: calc(2 * var(--stack-step)); }
|
|
519
|
+
.sheet-stack > .card:nth-child(4) { margin-top: calc(3 * var(--stack-step)); }
|
|
520
|
+
.sheet-stack > .card:nth-child(5) { margin-top: calc(4 * var(--stack-step)); }
|
|
521
|
+
/* stack headers: uniform — [back?] title left, icons right, one height,
|
|
522
|
+
same gap to content on every card */
|
|
523
|
+
.sheet-stack > .card > .card__head {
|
|
524
|
+
align-items: center;
|
|
525
|
+
justify-content: flex-start;
|
|
526
|
+
min-height: var(--tap);
|
|
527
|
+
}
|
|
528
|
+
.sheet-stack--display > .card > .card__head { align-items: start; min-height: 0; }
|
|
529
|
+
/* ============================================================
|
|
530
|
+
.sheet-stack--tap — tap a peeked sheet open; a back button returns it.
|
|
531
|
+
|
|
532
|
+
A sheet (a .card — the material, sheet.css's single sheet is the same
|
|
533
|
+
stuff) is rigid: it can move in, move out, or be covered — it can
|
|
534
|
+
never change shape. So every sheet here is the SAME full height,
|
|
535
|
+
always, set once and never touched again by anything. A "peek" is
|
|
536
|
+
not a short sheet; it's a full sheet the next one happens to cover,
|
|
537
|
+
showing only the strip above it, exactly like a sheet of paper part
|
|
538
|
+
way out of a real stack.
|
|
539
|
+
|
|
540
|
+
Because of that, the whole animation is one property: translate.
|
|
541
|
+
Nothing resizes, nothing re-stacks — z-index is the plain resting
|
|
542
|
+
--level order and is never touched, since the order already says
|
|
543
|
+
what has to happen. Cards after the tapped one sit IN FRONT of it,
|
|
544
|
+
so they're what's covering it, so they're what must leave. Cards
|
|
545
|
+
before it are already BEHIND it, so they need do nothing at all
|
|
546
|
+
except let themselves be covered as it rises over them.
|
|
547
|
+
|
|
548
|
+
.is-open — the tapped card, moved up to the top of the stack. Its
|
|
549
|
+
own resting offset is margin-top (below); opening just
|
|
550
|
+
translates that offset back off, landing it flush at
|
|
551
|
+
the stack's top with its full height exactly filling
|
|
552
|
+
to the bottom.
|
|
553
|
+
.is-before — an earlier card, nudged one step down so it slips
|
|
554
|
+
under the next card's resting strip and is covered
|
|
555
|
+
there. Each carries its own --stagger (set in JS: 0
|
|
556
|
+
for the one nearest the tapped card, counting up
|
|
557
|
+
further back) so they tuck away, and later un-tuck,
|
|
558
|
+
one at a time.
|
|
559
|
+
.is-after — a later card, moved out below, together, no stagger.
|
|
560
|
+
============================================================ */
|
|
561
|
+
.sheet-stack--tap > .card {
|
|
562
|
+
height: 100%; /* every card identical and full length, always — the one
|
|
563
|
+
size in this whole component, never toggled, never transitioned. At a
|
|
564
|
+
resting margin-top of n steps it simply overhangs the stack's bottom
|
|
565
|
+
by n steps (clipped by .screen--fill), so translating it up by exactly
|
|
566
|
+
that much lands it filling the stack precisely. */
|
|
567
|
+
overflow: clip;
|
|
568
|
+
transition: translate var(--motion-sheet) var(--ease); /* the only animated property here */
|
|
569
|
+
transition-delay: calc(var(--stagger, 0) * var(--motion-stagger)); /* 0 for everything
|
|
570
|
+
except a staggered is-before card: the card renders --stagger as it tucks or un-tucks */
|
|
571
|
+
}
|
|
572
|
+
.sheet-stack--tap > .card.is-open {
|
|
573
|
+
/* up by its own resting offset — pure movement, nothing resizes */
|
|
574
|
+
translate: 0 calc(-1 * var(--slot, 0) * var(--stack-step) + var(--_pull, 0rem));
|
|
575
|
+
cursor: default;
|
|
576
|
+
overflow-y: auto;
|
|
577
|
+
overflow-x: clip;
|
|
578
|
+
overscroll-behavior: contain;
|
|
579
|
+
}
|
|
580
|
+
.sheet-stack--tap > .card.is-open.is-dragging { transition: none; }
|
|
581
|
+
.sheet-stack--tap > .card > .card__head { touch-action: none; }
|
|
582
|
+
.sheet-stack--tap > .card.is-open:not(:has(> .sheet__body)) {
|
|
583
|
+
padding-top: calc(var(--card-pad) + var(--_refresh, 0rem));
|
|
584
|
+
transition: translate var(--motion-sheet) var(--ease), padding-top var(--motion-sheet) var(--ease);
|
|
585
|
+
}
|
|
586
|
+
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
587
|
+
tappable), so its resting offset would leave its last n steps below the
|
|
588
|
+
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
|
589
|
+
alone is cut to the room it actually has, and it scrolls like an opened
|
|
590
|
+
sheet does. Every other sheet keeps the one full height. */
|
|
591
|
+
.sheet-stack--tap:not(.sheet-stack--closed) > .card:not(:has(~ .card)) {
|
|
592
|
+
height: calc(100% - var(--slot, 0) * var(--stack-step));
|
|
593
|
+
overflow-y: auto;
|
|
594
|
+
overflow-x: clip;
|
|
595
|
+
overscroll-behavior: contain;
|
|
596
|
+
}
|
|
597
|
+
/* a scrolling sheet shows no scrollbar (a desktop with a mouse would paint one
|
|
598
|
+
down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself
|
|
599
|
+
(the hiding rule is base.css's, shared with every other scrolling track) */
|
|
600
|
+
.sheet-stack--tap > .card.is-before { translate: 0 var(--stack-step); }
|
|
601
|
+
.sheet-stack--tap > .card.is-after { translate: 0 100vh; }
|
|
602
|
+
.sheet-stack--closed > .card { translate: 0 calc(var(--_headroom) - var(--_lift, 0rem)); }
|
|
603
|
+
.sheet-stack--closed > .card.is-dragging { transition: none; }
|
|
604
|
+
.sheet-stack--tap:not(.sheet-stack--closed) > .card.is-dragging:not(.is-open) {
|
|
605
|
+
translate: 0 calc(-1 * var(--_lift, 0rem));
|
|
606
|
+
transition: none;
|
|
607
|
+
}
|
|
608
|
+
.sheet-stack--closed > .card.is-before { translate: 0 calc(var(--_headroom) + var(--stack-step)); }
|
|
609
|
+
.sheet-stack--closed > .card.is-after { translate: 0 100vh; }
|
|
610
|
+
/* 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
|
|
611
|
+
line under the bar and along the first sheet. Its ground and its radius are what say "card" here. */
|
|
612
|
+
:is([data-palette], [data-finish]) .sheet-stack__base .card { box-shadow: none; }
|
|
613
|
+
/* 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
|
|
614
|
+
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
|
|
615
|
+
only, the sheet's size is untouched. A pull on the head keeps them square until the finger lets go. */
|
|
616
|
+
.sheet-stack--flood > .card { transition: translate var(--motion-sheet) var(--ease), border-radius var(--motion-sheet) var(--ease); }
|
|
617
|
+
.sheet-stack--flood > .card.is-open { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
618
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head {
|
|
619
|
+
position: relative;
|
|
620
|
+
padding-left: 0;
|
|
621
|
+
transition: padding-left var(--motion-sheet) var(--ease);
|
|
622
|
+
}
|
|
623
|
+
/* ---- a stacked sheet with a SheetBody has the ONE sheet shape (Lh 2026-09-23, the home layout): sheet head
|
|
624
|
+
pinned at the top · sheet body scrolls, as long as it needs · sheet foot (a CardFoot, the CTA) at the bottom.
|
|
625
|
+
The card becomes a flex column and stops scrolling itself — only the body does. A sheet with no SheetBody
|
|
626
|
+
keeps the old way: the whole open card scrolls. ---- */
|
|
627
|
+
.sheet-stack--tap > .card:has(> .sheet__body) { display: flex; flex-direction: column; gap: var(--card-gap); }
|
|
628
|
+
.sheet-stack--tap > .card:has(> .sheet__body).is-open { overflow: clip; }
|
|
629
|
+
/* a paper stack's foot is paper to the card's edges, and it covers the tinted flex gap above it so the
|
|
630
|
+
button does not sit on a colour stripe. The head's gap stays: it is outside this foot. The button stays
|
|
631
|
+
where it was — the padding gives back what the bleed takes. */
|
|
632
|
+
.sheet-stack > .card.sheet--paper > .card__foot {
|
|
633
|
+
background: var(--action-bg);
|
|
634
|
+
color: var(--card-ink);
|
|
635
|
+
margin-top: calc(-1 * var(--card-gap));
|
|
636
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
637
|
+
margin-bottom: calc(-1 * var(--card-pad));
|
|
638
|
+
padding-top: var(--card-gap);
|
|
639
|
+
padding-inline: var(--card-pad);
|
|
640
|
+
padding-bottom: var(--card-pad);
|
|
641
|
+
}
|
|
642
|
+
.stack-sheet > .card__head { flex-wrap: nowrap; }
|
|
643
|
+
/* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
|
|
644
|
+
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
|
|
645
|
+
(was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
|
|
646
|
+
.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))); }
|
|
647
|
+
: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); }
|
|
648
|
+
.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))); }
|
|
649
|
+
/* 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),
|
|
650
|
+
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) */
|
|
651
|
+
.sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
652
|
+
/* ---- where it sits in what sheet.css draws (docs/reference.md rule 15) ---- */
|
|
653
|
+
.sheet > .card__foot {
|
|
654
|
+
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 */
|
|
655
|
+
align-items: end;
|
|
656
|
+
align-content: end;
|
|
657
|
+
padding: 0 var(--card-pad) calc(var(--card-pad) + var(--_safe-b-open));
|
|
658
|
+
background: linear-gradient(var(--card-bg), var(--card-bg)) bottom / 100% var(--_foot) no-repeat;
|
|
659
|
+
pointer-events: none;
|
|
660
|
+
translate: 0 calc(-1 * var(--_y) - var(--_drag, 0rem));
|
|
661
|
+
transition: translate var(--motion-sheet) var(--ease), visibility 0s;
|
|
662
|
+
}
|
|
663
|
+
.sheet.is-dragging > .card__foot { transition: none; }
|
|
664
|
+
/* 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
|
|
665
|
+
LEAVES AND ARRIVES WITH ITS SHEET — solid the whole way, hidden only once the slide is over. It used to stay in place and
|
|
666
|
+
fade: the body slid through the strip behind a half-transparent button (timebank, Lh 2026-10-01: "ตัวหนังสือลอดใต้ปุ่ม"). */
|
|
667
|
+
: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); }
|
|
668
|
+
/* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
|
|
669
|
+
/* 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
|
|
670
|
+
(the picture is never pinned). Each page starts at the screen's lead and ends with the same room under it — the lead as
|
|
671
|
+
padding above AND below — so the words begin near the middle and, when a big text size runs them long, scroll up clear.
|
|
672
|
+
The walk shows no scrollbar. */
|
|
673
|
+
.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) */
|
|
674
|
+
.step > .pages > .page > :is(img, .ph):first-child {
|
|
675
|
+
width: calc(100% + 2 * var(--screen-pad)); /* the content's width: the page's inset given back */
|
|
676
|
+
max-width: none;
|
|
677
|
+
margin-inline: calc(-1 * var(--screen-pad));
|
|
678
|
+
height: calc(var(--_lead, 0rem) + var(--page-media-h, calc(var(--tap-base) * 14 / 3)));
|
|
679
|
+
max-height: none;
|
|
680
|
+
border-radius: 0;
|
|
681
|
+
border-inline: 0;
|
|
682
|
+
border-top: 0;
|
|
683
|
+
object-fit: cover;
|
|
684
|
+
}
|
|
685
|
+
/* ---- where it sits in what numbers.css draws (docs/reference.md rule 15) ---- */
|
|
686
|
+
.band-stack > .card--band { border: 0; border-radius: 0; box-shadow: none; }
|
|
687
|
+
.band-stack > .card--band + .card--band { border-top: var(--border-w) solid var(--hairline); }
|
|
688
|
+
/* ---- where it sits in what sticky.css draws (docs/reference.md rule 15) ---- */
|
|
689
|
+
.sticky__head > .ph { flex: 1 1 0; min-width: 0; } /* a picture in the head takes the note's width */
|
|
690
|
+
.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 */
|
|
691
|
+
|
|
692
|
+
/* ---- where it sits in what media.css draws: a postcard's picture (docs/reference.md rule 15) ---- */
|
|
693
|
+
.postcard__front .postcard__turn > .ph { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border: 0; border-radius: 0; min-height: 0; }
|
|
694
|
+
|
|
695
|
+
/* ---- where it sits in what stack.css draws: a sheet pulled to refresh holds still (docs/reference.md rule 15) ---- */
|
|
696
|
+
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
697
|
+
|
|
698
|
+
/* ---- where it sits in what step.css draws: the reading block, a walk of pages (docs/reference.md rule 15) ---- */
|
|
699
|
+
.step:has(> .sheet--peek) > .pages { padding-bottom: var(--sheet-peek); }
|
|
700
|
+
.step > .pages {
|
|
701
|
+
flex: 0 1 auto;
|
|
702
|
+
min-height: 0;
|
|
703
|
+
overflow-y: auto;
|
|
704
|
+
overscroll-behavior: contain;
|
|
705
|
+
}
|