@usableapp/cardds 0.7.106 → 0.7.107
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 +97 -0
- package/bin/cardds.mjs +5 -3
- package/cardds.css +1 -0
- package/css/actions.css +9 -9
- package/css/base.css +6 -9
- package/css/card.css +16 -186
- package/css/finish.css +4 -4
- package/css/journey.css +1 -1
- package/css/layover.css +4 -14
- package/css/lists.css +3 -18
- package/css/media.css +1 -7
- package/css/parts.css +380 -0
- package/css/sheet.css +12 -139
- package/css/stack.css +15 -26
- package/css/stamp.css +1 -1
- package/css/step.css +3 -3
- package/css/theme.template.css +1 -1
- package/css/tokens.css +4 -4
- package/dist/actions/BarNote.d.ts +2 -2
- package/dist/actions/BarNote.js +2 -2
- package/dist/actions/Btn.d.ts +2 -2
- package/dist/actions/Btn.js +1 -1
- package/dist/actions/BtnRow.d.ts +1 -1
- package/dist/actions/BtnRow.js +1 -1
- package/dist/actions/IconBtn.d.ts +1 -1
- package/dist/actions/IconBtn.js +1 -1
- package/dist/cardds.css +461 -422
- package/dist/cards/Card.d.ts +6 -22
- package/dist/cards/Card.js +11 -22
- package/dist/cards/CardFeed.d.ts +1 -1
- package/dist/cards/CardFeed.js +5 -5
- package/dist/cards/CardParts.d.ts +0 -12
- package/dist/cards/CardParts.js +0 -12
- package/dist/cards/cardGo.js +1 -1
- package/dist/cards/cardGrounds.js +2 -2
- package/dist/catalog.json +1 -1
- package/dist/forms/Field.d.ts +1 -1
- package/dist/forms/Field.js +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -5
- package/dist/lists/Row.d.ts +2 -28
- package/dist/lists/Row.js +2 -17
- package/dist/media/MapArea.d.ts +2 -2
- package/dist/media/MapArea.js +1 -1
- package/dist/media/Tile.d.ts +3 -7
- package/dist/media/Tile.js +5 -5
- package/dist/parts/Body.d.ts +7 -0
- package/dist/parts/Body.js +11 -0
- package/dist/parts/End.d.ts +10 -0
- package/dist/parts/End.js +7 -0
- package/dist/parts/Foot.d.ts +17 -0
- package/dist/parts/Foot.js +11 -0
- package/dist/parts/Head.d.ts +15 -0
- package/dist/parts/Head.js +34 -0
- package/dist/parts/Lead.d.ts +10 -0
- package/dist/parts/Lead.js +9 -0
- package/dist/parts/Words.d.ts +13 -0
- package/dist/parts/Words.js +20 -0
- package/dist/parts/headCtx.d.ts +7 -0
- package/dist/parts/headCtx.js +9 -0
- package/dist/scaffold/EmptyState.d.ts +1 -1
- package/dist/scaffold/EmptyState.js +1 -1
- package/dist/scaffold/Screen.js +1 -1
- package/dist/sheets/Modal.d.ts +3 -7
- package/dist/sheets/Modal.js +3 -7
- package/dist/sheets/Sheet.d.ts +2 -2
- package/dist/sheets/Sheet.js +8 -9
- package/dist/sheets/SheetStack.d.ts +3 -3
- package/dist/sheets/SheetStack.js +4 -4
- package/dist/sheets/SheetSteps.d.ts +1 -1
- package/dist/sheets/SheetSteps.js +2 -2
- package/dist/sheets/StackBack.d.ts +1 -1
- package/dist/sheets/StackBack.js +1 -1
- package/dist/sheets/StackSheet.js +7 -7
- package/dist/sheets/sheetMeasure.js +3 -3
- package/dist/sheets/sheetStepsPile.js +2 -2
- package/dist/sheets/stackCard.js +6 -6
- package/dist/templates/CallSheet.js +6 -5
- package/dist/templates/Walkthrough.js +3 -2
- package/docs/reference.md +75 -36
- package/lib/catalog.mjs +1 -1
- package/lib/mcp.mjs +1 -1
- package/lint/rules.mjs +14 -7
- package/package.json +2 -1
- package/scripts/codemod-parts.mjs +324 -0
- package/dist/cards/CardFoot.d.ts +0 -15
- package/dist/cards/CardFoot.js +0 -9
- package/dist/cards/CardHead.d.ts +0 -21
- package/dist/cards/CardHead.js +0 -39
- package/dist/cards/Stat.d.ts +0 -19
- package/dist/cards/Stat.js +0 -14
- package/dist/sheets/SheetBody.d.ts +0 -5
- package/dist/sheets/SheetBody.js +0 -9
- package/dist/sheets/SheetHead.d.ts +0 -15
- package/dist/sheets/SheetHead.js +0 -6
package/css/finish.css
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
0 0.5rem 1rem -0.25rem color-mix(in srgb, var(--accent) 45%, transparent);
|
|
83
83
|
}
|
|
84
84
|
/* a peeked stack tab is the icon alone: no sheen and no shadow until its sheet is open (the fill fades with the sheet) */
|
|
85
|
-
[data-finish="lit"] .sheet-stack--tap > .card:not(.is-open) > .
|
|
85
|
+
[data-finish="lit"] .sheet-stack--tap > .card:not(.is-open) > .head .stack-sheet__tab {
|
|
86
86
|
background-image: none;
|
|
87
87
|
box-shadow: none;
|
|
88
88
|
}
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
}
|
|
122
122
|
/* a dark card (the brand tone, a cover): multiply would only blacken it, so the grain lightens, fainter */
|
|
123
123
|
[data-finish="print"] :is(.card--3, .card--cover)::after { mix-blend-mode: screen; opacity: calc(var(--print-grain, 0.32) * 0.56); }
|
|
124
|
-
[data-finish="print"] .card:not(.card--row, .card--cover, .feed-card--small, .sheet-stack > *, .sheet-stack > :not(.sheet-stack__base) > *) > .
|
|
124
|
+
[data-finish="print"] .card:not(.card--row, .card--cover, .feed-card--small, .sheet-stack > *, .sheet-stack > :not(.sheet-stack__base) > *) > .head {
|
|
125
125
|
padding-bottom: var(--sp-2);
|
|
126
126
|
border-bottom: var(--rule-w) dotted color-mix(in srgb, currentColor 45%, transparent);
|
|
127
127
|
}
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
[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 */
|
|
133
133
|
[data-finish="print"] .btn--primary { box-shadow: 0.125rem 0.125rem 0 0 color-mix(in srgb, var(--card-ink) 60%, transparent); }
|
|
134
134
|
[data-finish="print"] :is(.btn, .chip):active { box-shadow: none; translate: 0.125rem 0.125rem; }
|
|
135
|
-
[data-finish="print"] .
|
|
135
|
+
[data-finish="print"] .card--row > .lead { border-style: dashed; }
|
|
136
136
|
|
|
137
137
|
/* ============================================================
|
|
138
138
|
sticker — a die-cut sticker on a sheet: --sticker-cut of white paper left round the print, a short hard
|
|
@@ -167,4 +167,4 @@
|
|
|
167
167
|
[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; }
|
|
168
168
|
[data-finish="ink"] .btn--primary { border-color: var(--on-surface); box-shadow: 0.1875rem 0.1875rem 0 0 var(--on-surface); }
|
|
169
169
|
[data-finish="ink"] .toggle { box-shadow: 0.125rem 0.125rem 0 0 var(--on-surface); }
|
|
170
|
-
[data-finish="ink"] .
|
|
170
|
+
[data-finish="ink"] .card--row > .lead { border-width: var(--ink-w, 0.15625rem); }
|
package/css/journey.css
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/* where it sits in a head that leads with the date (card.css): the first cell, as tall as the chips beside it however many rows, so no cap */
|
|
77
|
-
.
|
|
77
|
+
.head:has(> .tile-badge:first-child + .chip-row) > .tile-badge--stretch:first-child { grid-column: 1; grid-row: 1; max-block-size: none; }
|
|
78
78
|
|
|
79
79
|
/* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
|
|
80
80
|
.tile-badge--corner { position: absolute; right: var(--sp-3); bottom: var(--sp-3); }
|
package/css/layover.css
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
/* ---- the Modal — the case drops in, its drawer opens below (Lh 2026-09-16: ONE card, two parts)
|
|
9
9
|
<div class="sheet-stage sheet-stage--dim"> (dim = the page under a scrim; a Modal on a clean ground is the stage without it — Lh 2026-09-22, `--ask` retired: the stage does the ground, the modal centres itself)
|
|
10
10
|
<article class="card modal">
|
|
11
|
-
<div class="
|
|
11
|
+
<div class="body">…the card's content (a head, rows, a timeline)…</div>
|
|
12
12
|
<div class="modal__drawer"><h2 class="t-h2">Take the case?</h2><p class="t-caption">This cannot be undone.</p>
|
|
13
13
|
<div class="btn-trio">…</div>
|
|
14
14
|
</div>
|
|
15
15
|
</article>
|
|
16
16
|
</div>
|
|
17
17
|
.modal is the card — no border (the shadow is its edge on the scrim);
|
|
18
|
-
.
|
|
18
|
+
.body the light part that holds the thing being asked about (it
|
|
19
19
|
scrolls when tall); .modal__drawer the dark end that asks. The ground is
|
|
20
20
|
whatever the stage shows (dim it or not). Two beats (2 × --motion-sheet) when
|
|
21
21
|
the stage appears, and the same two backwards when it closes:
|
|
@@ -47,16 +47,6 @@
|
|
|
47
47
|
overflow: clip;
|
|
48
48
|
box-shadow: var(--modal-shadow, var(--shadow-sheet));
|
|
49
49
|
}
|
|
50
|
-
.modal__lift {
|
|
51
|
-
padding: var(--card-pad);
|
|
52
|
-
display: grid;
|
|
53
|
-
gap: var(--card-gap);
|
|
54
|
-
align-content: start;
|
|
55
|
-
flex: 0 1 auto;
|
|
56
|
-
min-height: 0;
|
|
57
|
-
overflow-y: auto; /* tall content scrolls inside; the drawer stays */
|
|
58
|
-
overscroll-behavior: contain;
|
|
59
|
-
}
|
|
60
50
|
.modal__drawer {
|
|
61
51
|
flex-shrink: 0;
|
|
62
52
|
height: var(--drawer-h);
|
|
@@ -81,8 +71,8 @@
|
|
|
81
71
|
--accent: var(--card-ink);
|
|
82
72
|
--accent-dark: calc(1 - var(--dark));
|
|
83
73
|
}
|
|
84
|
-
/* the CONFIRM: a Modal with no
|
|
85
|
-
.modal:not(:has(> .
|
|
74
|
+
/* the CONFIRM: a Modal with no Body: the ModalAsk IS the card, as tall as its words (no fixed band, no second beat) */
|
|
75
|
+
.modal:not(:has(> .body)) > .modal__drawer { height: auto; padding-block: var(--modal-confirm-pad-y, var(--sp-6)); animation: none; }
|
|
86
76
|
.modal__drawer .t-caption { --t-caption-ink: var(--modal-drawer-caption-ink, var(--card-muted)); }
|
|
87
77
|
.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 */
|
|
88
78
|
/* 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
|
package/css/lists.css
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/* ============================================================
|
|
2
2
|
cardds/lists.css — frameless rows and pairs INSIDE a card:
|
|
3
|
-
row (lead ·
|
|
4
|
-
timeline · legend · link
|
|
3
|
+
row (its parts, lead · words · end, are parts.css) · key-value
|
|
4
|
+
rows · kv grid · timeline · legend · link
|
|
5
5
|
A row inside a card has no boundary of its own; .rows draws
|
|
6
6
|
hairlines between rows. A row that IS a card = .card--row.
|
|
7
7
|
============================================================ */
|
|
8
8
|
|
|
9
9
|
/* ---- row: leading visual · title + meta · trailing control ----
|
|
10
|
-
<div class="row"><span class="avatar">A</span><
|
|
10
|
+
<div class="row"><span class="avatar">A</span><span class="words"><span class="words__title">Alice Kau</span><span class="words__meta">Owner</span></span><button class="icon-btn icon-btn--sm">›</button></div> */
|
|
11
11
|
.row {
|
|
12
12
|
display: flex;
|
|
13
13
|
align-items: center;
|
|
@@ -20,21 +20,7 @@
|
|
|
20
20
|
/* no padding on a row (Lh 2026-09-16): padding is for room, not for spreading.
|
|
21
21
|
Space BETWEEN rows is .rows' gap; a lone row as card content takes the card's
|
|
22
22
|
gap. (.row--flush, which zeroed the old padding, is gone — every row is flush.) */
|
|
23
|
-
.row__lead {
|
|
24
|
-
width: var(--row-lead-size, var(--tap));
|
|
25
|
-
height: var(--row-lead-size, var(--tap));
|
|
26
|
-
flex: none;
|
|
27
|
-
border-radius: var(--row-lead-radius, var(--r-control));
|
|
28
|
-
border: var(--border-w) solid var(--row-lead-border, var(--outline));
|
|
29
|
-
display: grid;
|
|
30
|
-
place-items: center;
|
|
31
|
-
}
|
|
32
|
-
.row__body { flex: 1; min-width: 0; display: grid; gap: var(--row-body-gap, var(--sp-0)); }
|
|
33
|
-
.row__title { font: var(--row-title-font, var(--type-title)); overflow-wrap: anywhere; } /* wraps freely — no clip, so no padding trick for Thai marks (Lh 2026-09-16, same call as the card heading) */
|
|
34
|
-
.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)); }
|
|
35
|
-
.row__end { margin-left: auto; text-align: right; display: grid; gap: var(--row-end-gap, var(--sp-0)); justify-items: end; flex: none; }
|
|
36
23
|
.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 */
|
|
37
|
-
.row--lg .row__title { font: var(--fw-bold) var(--fs-lg) / var(--lh-h1) var(--font-display); }
|
|
38
24
|
/* a row that EXPANDS (Lh 2026-09-22): the row is a <details>' summary, what it shows comes out under it —
|
|
39
25
|
who liked a postcard, the rest of a list. An end chevron turns when open. The app owns the list.
|
|
40
26
|
<details class="row-expand"><summary class="row">…lead · body · end…</summary><div class="row-expand__more">…</div></details> */
|
|
@@ -49,7 +35,6 @@
|
|
|
49
35
|
pointer-events off here, aria-disabled from the markup. The tick is the .check's checked look
|
|
50
36
|
at --tap-sm, but not an input: nothing to press. */
|
|
51
37
|
.row--done { pointer-events: none; cursor: default; }
|
|
52
|
-
.row--done .row__title { color: var(--card-muted); }
|
|
53
38
|
.row__done {
|
|
54
39
|
width: var(--row-done-size, var(--tap-sm));
|
|
55
40
|
height: var(--row-done-size, var(--tap-sm));
|
package/css/media.css
CHANGED
|
@@ -43,17 +43,11 @@
|
|
|
43
43
|
position: relative;
|
|
44
44
|
}
|
|
45
45
|
.tile__label { font: var(--tile-label-font, var(--type-title)); }
|
|
46
|
-
.tile__value { font: var(--tile-value-font, var(--type-h2)); letter-spacing: var(--track-h2); font-variant-numeric: tabular-nums; }
|
|
47
46
|
.tile__sub { font: var(--tile-sub-font, var(--type-caption)); color: var(--tile-sub-ink, var(--card-muted)); } /* the muted words of the tile's ground; --dim faded the full ink to 2.4–3.4:1 */
|
|
48
47
|
.tile__text { display: grid; gap: var(--tile-text-gap, var(--sp-0)); }
|
|
49
48
|
.tile--on { background: var(--tile-on-bg, var(--accent)); --dark: var(--accent-dark); color: var(--tile-on-ink, var(--card-ink)); border-color: transparent; }
|
|
50
49
|
/* .tile--pick's hidden input and :has(:checked) fill are declared once in choice.css, alongside .chip--pick and .mood__opt */
|
|
51
50
|
.tile--wide { grid-column: 1 / -1; }
|
|
52
|
-
/* stat: READ, not pressed — no frame of its own inside a card (a frame says "tap me"): icon · value · words on the card's ground */
|
|
53
|
-
/* a stat tile has no fill of its own, so it has no ink of its own: it takes the ink of what it stands on (currentColor on `color`
|
|
54
|
-
= the inherited colour), as the card's heading does. It kept the plain tile's --action-ink, dark on a dark card (timebank, tone 3,
|
|
55
|
-
Lh 2026-10-18). --tile-ink still overrides; a toned stat tile (.tile--N, below) takes its fill and its ink back */
|
|
56
|
-
.tile--stat { --dark: inherit; color: var(--tile-ink, currentColor); border-color: transparent; background: none; padding-inline: 0; min-height: var(--tile-stat-min-h, var(--avatar-xl)); gap: var(--tile-stat-gap, var(--sp-3)); grid-template-columns: auto 1fr; align-items: center; }
|
|
57
51
|
/* upload: dashed, centred plus + label */
|
|
58
52
|
.tile--upload {
|
|
59
53
|
border-style: dashed;
|
|
@@ -72,7 +66,7 @@
|
|
|
72
66
|
.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; }
|
|
73
67
|
|
|
74
68
|
/* ---- wave: audio waveform stand-in for voice rows ----
|
|
75
|
-
<span class="wave"></span> — sits in a .
|
|
69
|
+
<span class="wave"></span> — sits in a .words, fills the width */
|
|
76
70
|
.wave {
|
|
77
71
|
/* only WHOLE bars show (Lh 2026-09-23 — a clipped last bar read thinner): the columns are as many whole bar-widths as fit
|
|
78
72
|
(auto-fill), spread edge to edge; bars that do not fit go to rows of no height, clipped away */
|
package/css/parts.css
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
cardds/parts.css — the parts of a LINE: lead · words · end.
|
|
3
|
+
A Row (lists.css) and a row card (.card--row, card.css) are
|
|
4
|
+
both made of them, as their direct children in that order.
|
|
5
|
+
An outer governs a part only through the part's knobs or a
|
|
6
|
+
selector naming both, written here (Lh 2026-10-21).
|
|
7
|
+
<div class="row"><span class="lead"><svg class="icon"/></span><span class="words"><span class="words__title">Alice Kau</span><span class="words__meta">Owner</span></span><span class="end"><span class="t-title">+1.0 h</span><span class="t-caption t-muted">Tue</span></span></div>
|
|
8
|
+
============================================================ */
|
|
9
|
+
|
|
10
|
+
/* ---- lead: the outlined box at the start of a line ---- */
|
|
11
|
+
.lead {
|
|
12
|
+
width: var(--lead-size, var(--tap));
|
|
13
|
+
height: var(--lead-size, var(--tap));
|
|
14
|
+
flex: none;
|
|
15
|
+
border-radius: var(--lead-radius, var(--r-control));
|
|
16
|
+
border: var(--border-w) solid var(--lead-border, var(--outline));
|
|
17
|
+
display: grid;
|
|
18
|
+
place-items: center;
|
|
19
|
+
}
|
|
20
|
+
/* in a row card or a feed card's head: the small box in the card's own ink, a number or an initial in label type */
|
|
21
|
+
:is(.card--row, .feed-card > .head) > .lead {
|
|
22
|
+
width: var(--lead-size, var(--tap-sm));
|
|
23
|
+
height: var(--lead-size, var(--tap-sm));
|
|
24
|
+
border-color: var(--lead-border, currentColor);
|
|
25
|
+
font: var(--lead-font, var(--type-label));
|
|
26
|
+
}
|
|
27
|
+
.feed-card > .head:has(> .t-caption) > .lead { grid-row: span 2; }
|
|
28
|
+
|
|
29
|
+
/* ---- words: the title over its meta, taking the room between the lead and the end ---- */
|
|
30
|
+
.words { flex: 1; display: grid; gap: var(--words-gap, var(--sp-0)); }
|
|
31
|
+
.row > .words { min-width: 0; }
|
|
32
|
+
.words__title { font: var(--words-title-font, var(--type-title)); overflow-wrap: anywhere; } /* wraps freely — no clip, so no padding trick for Thai marks (Lh 2026-09-16, same call as the card heading) */
|
|
33
|
+
.words__meta { font: var(--words-meta-font, var(--type-caption)); color: var(--words-meta-ink, var(--card-muted)); display: flex; align-items: center; gap: var(--words-meta-gap, var(--sp-1)); }
|
|
34
|
+
.row--lg > .words > .words__title { font: var(--fw-bold) var(--fs-lg) / var(--lh-h1) var(--font-display); }
|
|
35
|
+
.row--done > .words > .words__title { color: var(--card-muted); }
|
|
36
|
+
|
|
37
|
+
/* ---- end: the value over its caption, right-aligned at the end of a line ---- */
|
|
38
|
+
.end { margin-left: auto; text-align: right; display: grid; gap: var(--end-gap, var(--sp-0)); justify-items: end; }
|
|
39
|
+
.row > .end { flex: none; }
|
|
40
|
+
|
|
41
|
+
/* ---- head: the first row of a card, a sheet and a stack sheet. A lead, the Words, chips, an end, as its direct children.
|
|
42
|
+
<div class="head"><h2 class="words__title t-h2">…</h2><span class="words__meta t-caption t-muted">…</span><button class="icon-btn icon-btn--sm">…</button></div>
|
|
43
|
+
The Words of a head are its title and its meta, flat in the head (no .words box round them): the title and the end share the first
|
|
44
|
+
row, the meta takes a row of its own under both (base.css `order: 1`). An outer governs a head through the head's knobs or a selector
|
|
45
|
+
naming both, written below under the outer's name (Lh 2026-10-21). ---- */
|
|
46
|
+
/* header row: title left, floating action right — centered, since the
|
|
47
|
+
trailing control (chip/badge/icon-btn/link) is almost always taller than
|
|
48
|
+
a single line of title text; top-aligning them left the control visually
|
|
49
|
+
low against the title's optical center. A head with a genuinely tall,
|
|
50
|
+
multi-line title can opt back out with .head--top. */
|
|
51
|
+
.head {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-wrap: var(--head-wrap, wrap); /* the caption takes a line of its own — no wrapper div */
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
column-gap: var(--head-col-gap, var(--sp-3));
|
|
57
|
+
row-gap: var(--head-row-gap, var(--sp-0));
|
|
58
|
+
min-height: var(--head-min-h, auto);
|
|
59
|
+
}
|
|
60
|
+
.head--top { align-items: flex-start; }
|
|
61
|
+
|
|
62
|
+
/* ---- the card skeleton: head · content · foot ----
|
|
63
|
+
Every card is a head (the heading left, hints / actions / a more-menu at
|
|
64
|
+
the far right), its content, and an optional foot of action buttons.
|
|
65
|
+
The head's leading child — the heading, or the div that wraps heading and
|
|
66
|
+
caption — takes the room, so a heading with nothing at the far right runs
|
|
67
|
+
all the way across; whatever trails it hugs its own width. Only the
|
|
68
|
+
heading (or the div that wraps it) is that leading child: a control that
|
|
69
|
+
leads a custom head — a chip, an avatar, a tile badge — keeps its own
|
|
70
|
+
width, else the rule stretched a chip into a bar and an avatar into an
|
|
71
|
+
ellipse (two agents and the wallet demo caught it, 2026-09-10). */
|
|
72
|
+
.head > :is(h1, h2, h3, div, p):first-child { flex: 1; min-width: 0; }
|
|
73
|
+
|
|
74
|
+
/* Skeleton head — the heading in a card's OWN head, and only there.
|
|
75
|
+
The heading wraps, no squeeze, up to three lines and cut there with an
|
|
76
|
+
ellipsis (Lh 2026-10-21: "มันควรจะ max สามบรรทัด" — a request's title ran to
|
|
77
|
+
four lines on a phone; reverses 2026-09-16's "no clamp"). The count is the knob
|
|
78
|
+
--head-title-lines. The group that holds heading ·
|
|
79
|
+
caption is the head's own wrapping row (above) — no wrapper element, no
|
|
80
|
+
height, only the row gap. */
|
|
81
|
+
.head :is(h1, h2, h3) {
|
|
82
|
+
overflow-wrap: anywhere;
|
|
83
|
+
display: -webkit-box;
|
|
84
|
+
-webkit-box-orient: vertical;
|
|
85
|
+
-webkit-line-clamp: var(--head-title-lines, 3);
|
|
86
|
+
line-clamp: var(--head-title-lines, 3);
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
padding-block-start: var(--_mark-room); /* a stacked Thai mark rises over the first line's box: room inside the clip, given back outside */
|
|
89
|
+
margin-block-start: calc(-1 * var(--_mark-room));
|
|
90
|
+
--_mark-room: 0.2em;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.head > .words__title { font: var(--words-title-font, var(--_type, var(--type-h2))); } /* --_type: the level's own face, set by .t-h1 and .t-display (base.css) */
|
|
94
|
+
.page > .head > h1.words__title { font-size: var(--fs-h2); } /* the page's ONE heading on the base (a screen with no TopBar) reads at the card heading's size — h1 is the semantic level, not a bigger face; --fs-h1 is the bar title's size (Lh 2026-09-22) */
|
|
95
|
+
.head > .words__meta { display: block; align-items: initial; gap: initial; } /* a head's caption is text; the flex line with a Dot is a row's */
|
|
96
|
+
|
|
97
|
+
/* a stage sheet's head (`Head centre`): a round button · the centred title over its meta · a round button. The title and the meta
|
|
98
|
+
stand in the middle column, the buttons span both rows; the 1fr rows keep the pair centred when a button is the taller */
|
|
99
|
+
.head--centre {
|
|
100
|
+
display: grid;
|
|
101
|
+
grid-template-columns: var(--tap-sm) 1fr var(--tap-sm);
|
|
102
|
+
grid-template-rows: 1fr auto auto 1fr;
|
|
103
|
+
align-items: center;
|
|
104
|
+
column-gap: var(--sp-3);
|
|
105
|
+
row-gap: 0;
|
|
106
|
+
min-height: var(--tap);
|
|
107
|
+
text-align: center;
|
|
108
|
+
}
|
|
109
|
+
.head--centre > .words__title { grid-area: 2 / 2; }
|
|
110
|
+
.head--centre > .words__meta { grid-area: 3 / 2; line-height: calc(var(--lh-body) * var(--fs-base)); } /* the meta stands on the body's line, as the sub did, an inline span in a line of the head's own type */
|
|
111
|
+
.head--centre > :first-child:not(.words__title) { grid-area: 1 / 1 / -1 / 2; }
|
|
112
|
+
.head--centre > :last-child:not(.words__title, .words__meta) { grid-area: 1 / 3 / -1 / 4; }
|
|
113
|
+
|
|
114
|
+
/* a Stamp in a card's head (a `Stamp` as the head's end): the words stand at the left, the stamp at the right BESIDE BOTH LINES — the heading and the
|
|
115
|
+
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,
|
|
116
|
+
stamp.css); the head only places it.
|
|
117
|
+
<div class="head"><h2 class="words__title t-h2">…</h2><span class="words__meta t-caption t-muted">…</span><span class="stamp">…</span></div> */
|
|
118
|
+
.head:has(> .stamp) { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
|
|
119
|
+
.head:has(> .stamp) > :is(h1, h2, h3) { grid-column: 1; grid-row: 1; align-self: end; }
|
|
120
|
+
|
|
121
|
+
/* a head that LEADS with a date (Lh 2026-10-21, timebank's request card): a `Head` whose first child is a JourneyBadge and whose second is a
|
|
122
|
+
ChipRow. The date stands at the left and the chips beside it, the two as tall as each other; the heading and its caption take the whole
|
|
123
|
+
width under them, so the heading sits right over the words of the middle. The date is always as tall as the chips. The gap under the date row is --head-lead-gap; the date and the chips place themselves (journey.css, actions.css).
|
|
124
|
+
<div class="head"><span class="tile-badge tile-badge--stretch">…</span><div class="chip-row">…</div><div><h2 class="words__title t-h1">…</h2>…</div></div> */
|
|
125
|
+
.head:has(> .tile-badge:first-child + .chip-row) {
|
|
126
|
+
display: grid;
|
|
127
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
128
|
+
align-items: center;
|
|
129
|
+
row-gap: var(--head-lead-gap, var(--sp-3));
|
|
130
|
+
}
|
|
131
|
+
.head:has(> .tile-badge:first-child + .chip-row) > :nth-child(n + 3) { grid-column: 1 / -1; }
|
|
132
|
+
|
|
133
|
+
/* in a card with paper bands: the head and the foot are paper bands edge to edge, one look */
|
|
134
|
+
:is(.card--bands, .card--bands > .card__face) > :is(.head, .foot) {
|
|
135
|
+
--card-bg: var(--card-1-bg);
|
|
136
|
+
--dark: var(--card-1-dark);
|
|
137
|
+
background: var(--card-bg);
|
|
138
|
+
color: var(--card-ink);
|
|
139
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
140
|
+
padding-inline: var(--card-pad);
|
|
141
|
+
}
|
|
142
|
+
:is(.card--bands, .card--bands > .card__face) > .head { margin-top: calc(-1 * var(--card-pad)); padding-block: var(--card-pad); }
|
|
143
|
+
:is(.card--bands, .card--bands > .card__face) > .head { padding-block-end: var(--card-band-seam, var(--sp-2)); }
|
|
144
|
+
/* the seam: the heading and the words under it read as ONE paragraph that changes colour at the band's edge (Lh 2026-10-21), so the room on
|
|
145
|
+
each side of the head band's lower edge is --card-band-seam, not the card's padding and gap */
|
|
146
|
+
:is(.card--bands, .card--bands > .card__face) > .head + * { margin-block-start: calc(var(--card-band-seam, var(--sp-2)) - var(--card-gap)); }
|
|
147
|
+
|
|
148
|
+
/* in a sheet stack */
|
|
149
|
+
/* stack headers: uniform — [back?] title left, icons right, one height,
|
|
150
|
+
same gap to content on every card */
|
|
151
|
+
.sheet-stack > .card > .head {
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: flex-start;
|
|
154
|
+
min-height: var(--tap);
|
|
155
|
+
}
|
|
156
|
+
.sheet-stack--display > .card > .head { align-items: start; min-height: 0; }
|
|
157
|
+
.sheet-stack--tap > .card > .head { touch-action: none; }
|
|
158
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .head {
|
|
159
|
+
position: relative;
|
|
160
|
+
padding-left: 0;
|
|
161
|
+
transition: padding-left var(--motion-sheet) var(--ease);
|
|
162
|
+
}
|
|
163
|
+
.stack-sheet > .head { flex-wrap: nowrap; }
|
|
164
|
+
.sheet-stack--tap > .card.is-open > .head:has(> .sheet-stack__back:not(.sheet-stack__back--end)) { padding-left: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
165
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) var(--ease); }
|
|
166
|
+
.sheet-stack--tap > .card.is-open > .head:has(> .sheet-stack__back--end) { padding-right: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
167
|
+
/* a stacked sheet is a control until it is open: its head is not text to select */
|
|
168
|
+
.sheet-stack > .card > .head { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
169
|
+
|
|
170
|
+
/* in a sheet */
|
|
171
|
+
/* the head — a title row at least --tap tall, either kind, so the peek strip
|
|
172
|
+
(--sheet-peek) is the same for every sheet:
|
|
173
|
+
- in a step it is a plain card head (card.css): the heading left, the
|
|
174
|
+
contextual thing at the far right
|
|
175
|
+
- `Head centre` is the stage sheet's three-column head: a round button,
|
|
176
|
+
the centred title and meta, a round button
|
|
177
|
+
- a label .row with its .check / .toggle may be the head when the sheet's
|
|
178
|
+
whole ask is that switch: peek shows it (a row is --tap tall plus its own
|
|
179
|
+
padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
|
|
180
|
+
.sheet > .head { --head-min-h: var(--sheet-head-h); }
|
|
181
|
+
.sheet > .head:not(:has(> :is(.t-caption, .t-body))) { --head-wrap: nowrap; }
|
|
182
|
+
|
|
183
|
+
/* a sheet's title is ONE line, always (Lh 2026-09-28, timebank #153: "ข้อกำหนดและเงื่อนไขการใช้งาน" wrapped to two lines at 390px) — the
|
|
184
|
+
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
|
|
185
|
+
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
|
|
186
|
+
measures --_over, the width it overflows by, and sets .is-long; one way — it waits, runs to its end, waits, and starts again
|
|
187
|
+
from the first letter. The move is text-indent: the heading is the app's own element, there is no inner box to slide.
|
|
188
|
+
Before the Sheet has measured, and where motion is off, it is cut with …. */
|
|
189
|
+
.sheet > .head > :is(.words__title, :is(h1, h2, h3):first-child) { 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 */
|
|
190
|
+
.sheet > .head > :is(.words__title, :is(h1, h2, h3):first-child).is-long {
|
|
191
|
+
text-overflow: clip;
|
|
192
|
+
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 */
|
|
193
|
+
}
|
|
194
|
+
@keyframes sheet-title-run { 0%, 20% { text-indent: 0; } 85%, 100% { text-indent: calc(-1 * var(--_over)); } }
|
|
195
|
+
@media (prefers-reduced-motion: reduce) {
|
|
196
|
+
.sheet > .head > :is(.words__title, :is(h1, h2, h3):first-child).is-long { animation: none; text-overflow: ellipsis; }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* in a stack sheet: the head is ONE line, always (Lh 2026-09-24) — the tabs keep their size, the TITLE gives. It never wraps, it runs sideways
|
|
200
|
+
(StackSheet measures --_over, the width it overflows by, and sets .is-long; the run itself is stack.css's), one way: it waits, runs to the end,
|
|
201
|
+
waits, and starts again from the first letter — like a song title. The head's line clamp (above) already cuts it, so no overflow is set here */
|
|
202
|
+
.stack-sheet > .head > .words__title {
|
|
203
|
+
flex: 1; min-width: 0; white-space: nowrap; overflow-wrap: normal;
|
|
204
|
+
translate: 0 var(--stack-sheet-title-shift, -0.1em); /* FC Pride's line box carries more room above its letters than below: the
|
|
205
|
+
letters sat ~0.1em under the middle of the back disc and the tabs — lifted onto it (Lh 2026-09-24) */
|
|
206
|
+
}
|
|
207
|
+
@media (prefers-reduced-motion: reduce) {
|
|
208
|
+
.stack-sheet > .head > .words__title.is-long { text-overflow: ellipsis; }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* in a feed card */
|
|
212
|
+
.feed-card > .head { display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; min-height: var(--feed-card-head-h, var(--tap)); }
|
|
213
|
+
.feed-card > .head:has(> :is(.lead, .feed-card__lead)) { grid-template-columns: auto minmax(0, 1fr); } /* a Lead's own row of the head: parts.css */
|
|
214
|
+
|
|
215
|
+
/* ---- body: the content row, under the head and over the foot. What the member reads or fills in; in a sheet, a stack sheet
|
|
216
|
+
and a modal the one part that scrolls. A grid column from the top, the card's rhythm inside.
|
|
217
|
+
<div class="body">…controls, rows, small content cards…</div>
|
|
218
|
+
Knobs --body-gap (the card's gap) and --body-pad-top (none; a paper sheet's inset). An outer governs a body through those
|
|
219
|
+
knobs or a selector naming both, written below under the outer's name (Lh 2026-10-21). ---- */
|
|
220
|
+
.body {
|
|
221
|
+
display: grid;
|
|
222
|
+
gap: var(--body-gap, var(--card-gap));
|
|
223
|
+
align-content: start;
|
|
224
|
+
min-height: 0;
|
|
225
|
+
padding-top: var(--body-pad-top, 0rem);
|
|
226
|
+
overflow-y: auto;
|
|
227
|
+
overscroll-behavior: contain;
|
|
228
|
+
}
|
|
229
|
+
/* what a body holds alone: a map fills it, a SheetSteps runs its pile down it, an EmptyState stands at --empty-state-at of its room as in a Step (step.css) */
|
|
230
|
+
.body:has(> .map--inline:only-child) { display: flex; flex-direction: column; }
|
|
231
|
+
.body:has(> .sheet-steps) { display: flex; flex-direction: column; }
|
|
232
|
+
.body:has(> .empty-state:only-child) { display: flex; flex-direction: column; gap: 0; }
|
|
233
|
+
.body:has(> .empty-state:only-child)::before { content: ""; flex: calc(var(--empty-state-at, 0) * 5) 1 0; }
|
|
234
|
+
.body:has(> .empty-state:only-child)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0) * 5) 1 0; }
|
|
235
|
+
|
|
236
|
+
/* in a sheet and a stack sheet */
|
|
237
|
+
/* everything under the head. Its scrollport runs to the card's side and bottom edges (reading --card-pad, the way .bleed does) so
|
|
238
|
+
content scrolls clear of the edge rather than stopping inside the padding, and a .bleed strip inside it still reaches the edges.
|
|
239
|
+
The outers are in a :where() so the body keeps the weight of one class, as the state rules below expect. */
|
|
240
|
+
:where(.sheet, .sheet-stack .card) > .body {
|
|
241
|
+
flex: 1;
|
|
242
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
243
|
+
padding-inline: var(--card-pad);
|
|
244
|
+
/* the part of the sheet that slid under the box's bottom edge cannot be
|
|
245
|
+
seen, and the scroller has to know that: scroll-padding is exactly "the
|
|
246
|
+
obscured part of my scrollport", so a focused field, scrollIntoView and
|
|
247
|
+
anchor navigation all land a control in the visible part, never under
|
|
248
|
+
the cut. A length from the box's container units (--_box), since the
|
|
249
|
+
cut is a share of the SHEET's height, not of the body's. */
|
|
250
|
+
scroll-padding-bottom: var(--_cover);
|
|
251
|
+
/* …and its content has to be able to LEAVE that part: the same length as bottom padding, so the last row
|
|
252
|
+
scrolls up to the visible edge at half and 3q as it does at full — everything between the head and the
|
|
253
|
+
foot can be reached at any state (Lh, 2026-09-21). Padding, not a shorter box: nothing that shows moves
|
|
254
|
+
when the state changes, only how far the body scrolls; a child that fills the body (a composer) fills
|
|
255
|
+
the part that shows. */
|
|
256
|
+
padding-bottom: var(--_cover);
|
|
257
|
+
}
|
|
258
|
+
.sheet:has(> .foot) > .body { margin-bottom: calc(var(--_foot) - var(--card-pad)); } /* the body stops above the foot's strip (the card's own padding is the rest) */
|
|
259
|
+
/* that padding can be taller than the body's own box (a short box: a small phone with the keyboard up) and a
|
|
260
|
+
box is never smaller than its padding: the body runs past the sheet's clipped edge instead, where nothing
|
|
261
|
+
shows anyway (the sheet's other parts keep their size, sheet.css). At peek and away no part of the body shows. */
|
|
262
|
+
:is(.sheet--peek, .sheet--away) > .body { padding-bottom: 0; }
|
|
263
|
+
:where(.sheet, .sheet-stack .card) > .body > * { min-width: 0; } /* as .card > *: children shrink to the track, never widen it */
|
|
264
|
+
:where(.sheet, .sheet-stack .card) > .body:last-child { margin-bottom: calc(-1 * var(--card-pad)); padding-bottom: calc(var(--card-pad) + var(--_cover) + var(--_safe-b-open)); }
|
|
265
|
+
/* 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).
|
|
266
|
+
The body and the foot read as paper; the handle and the head stay on the card.
|
|
267
|
+
The flex gap under the head is outside the body, so it stays the sheet's tint.
|
|
268
|
+
The first field would sit on that colour seam. The paper's inset is --sp-6 (24px at
|
|
269
|
+
the default spacing), the body's own top padding, and only when the sheet is paper.
|
|
270
|
+
A plain sheet keeps padding-top 0. A sheet in a stack wears the same class and the same
|
|
271
|
+
inset: the stack's own gap under the head is the card's colour, so the row still needs this paper.
|
|
272
|
+
The paper takes the side of the controls' paper (--action-dark): on a dark sheet its words still turn to onlight. */
|
|
273
|
+
.sheet--paper > .body {
|
|
274
|
+
--card-bg: var(--action-bg);
|
|
275
|
+
--dark: var(--action-dark);
|
|
276
|
+
background: var(--card-bg);
|
|
277
|
+
color: var(--card-ink);
|
|
278
|
+
padding-top: var(--body-pad-top, var(--sp-6));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* in a sheet stack */
|
|
282
|
+
/* nothing slid under here: the stack's sheet is always whole. A pull in the content, already at the top: the words move down, the card stays.
|
|
283
|
+
--_refresh is the finger, private. A paper body adds its own --sp-6 on top of this (the rule below): the stack's gap under the head is the
|
|
284
|
+
card's colour, so without the paper's inset the first row sits on that seam. */
|
|
285
|
+
.sheet-stack > .card > .body {
|
|
286
|
+
--_cover: 0rem;
|
|
287
|
+
padding-top: calc(var(--body-pad-top, 0rem) + var(--_refresh, 0rem));
|
|
288
|
+
transition: padding-top var(--motion-sheet) var(--ease);
|
|
289
|
+
}
|
|
290
|
+
/* 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
|
|
291
|
+
--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
|
|
292
|
+
stack's step and the body's own rows read it. */
|
|
293
|
+
.sheet-stack > .card.sheet--paper > .body {
|
|
294
|
+
margin-top: calc(var(--sp-3) - var(--card-gap));
|
|
295
|
+
padding-top: calc(var(--body-pad-top, var(--sp-6)) + var(--_refresh, 0rem));
|
|
296
|
+
transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
|
|
297
|
+
}
|
|
298
|
+
/* 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 */
|
|
299
|
+
.sheet-stack > .card.sheet--paper > .body.is-refreshing { transition: none; }
|
|
300
|
+
/* a closed paper sheet's body stays clear (Lh 2026-10-06). The next sheet's round top does not
|
|
301
|
+
cover the corners, and a paper body showed there beside the head. Clear, the card's own colour
|
|
302
|
+
shows in that pocket. Open, the body is paper, and the first row sits --sp-6 down, the same paper
|
|
303
|
+
inset a single paper sheet keeps: the flex gap under the head is the card's colour, so the row
|
|
304
|
+
would otherwise sit on that seam. A pull adds --_refresh on top of the inset. On the way back
|
|
305
|
+
into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
|
|
306
|
+
sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
|
|
307
|
+
at the moment the sheet stops. Rising, the body is paper at once. */
|
|
308
|
+
.sheet-stack > .card.sheet--paper:not(.is-open) > .body {
|
|
309
|
+
background-color: transparent;
|
|
310
|
+
transition:
|
|
311
|
+
padding-top var(--motion-sheet) var(--ease),
|
|
312
|
+
background-color var(--motion-quick) var(--ease) calc(var(--stagger, 0) * var(--motion-stagger) + var(--motion-sheet) - var(--motion-quick));
|
|
313
|
+
}
|
|
314
|
+
@media (prefers-reduced-motion: reduce) { .sheet-stack > .card.sheet--paper:not(.is-open) > .body {
|
|
315
|
+
transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
|
|
316
|
+
} }
|
|
317
|
+
/* a body pulled to refresh holds still */
|
|
318
|
+
.sheet-stack > .card > .body.is-refreshing { transition: none; }
|
|
319
|
+
|
|
320
|
+
/* in a modal: the light part that holds the thing being asked about, padded on every side, shrinking (and scrolling) under the
|
|
321
|
+
modal's max-height while the ask keeps its height (layover.css) */
|
|
322
|
+
.modal > .body {
|
|
323
|
+
padding: var(--body-pad-top, var(--card-pad)) var(--card-pad) var(--card-pad);
|
|
324
|
+
flex: 0 1 auto;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* ---- foot: the last row of a card, a sheet and a stack sheet. What the member can do next: the action buttons on one line.
|
|
328
|
+
One button takes the width, up to four share it in even parts — so the gaps read even too — and from three on they drop their
|
|
329
|
+
side padding so the labels keep the room. <div class="foot"><button class="btn btn--primary btn--block">…</button></div>
|
|
330
|
+
Knobs --foot-gap, --foot-ink, --foot-link-gap. An outer governs a foot through those knobs or a selector naming both, written
|
|
331
|
+
below under the outer's name (Lh 2026-10-21). ---- */
|
|
332
|
+
.foot { display: flex; gap: var(--foot-gap, var(--sp-2)); align-items: center; color: var(--foot-ink, inherit); }
|
|
333
|
+
.foot:has(> :nth-child(3)) > .btn { --btn-pad-x: var(--sp-2); }
|
|
334
|
+
/* a small link under the buttons (Foot link — Lh 2026-09-23, as the ActionBar's ctaLink): the row wraps, the link takes a
|
|
335
|
+
whole short row of its own, centred — it never shares the buttons' line */
|
|
336
|
+
.foot--link { flex-wrap: wrap; align-content: end; row-gap: var(--foot-link-gap, var(--sp-1)); }
|
|
337
|
+
.foot__link { flex: 1 0 100%; display: flex; justify-content: center; }
|
|
338
|
+
/* round (Foot round): the foot holds a few round IconBtns, each its own size (they never share the line), as ONE group in the middle with
|
|
339
|
+
a gap that does not follow the card's width. The gap is wider than the Btns' (a miss between two round targets): --foot-gap sets it */
|
|
340
|
+
.foot--round { justify-content: center; gap: var(--foot-gap, var(--sp-6)); }
|
|
341
|
+
|
|
342
|
+
/* in a card with paper bands: the foot band (the head's look, above) is slim */
|
|
343
|
+
:is(.card--bands, .card--bands > .card__face) > .foot { margin-bottom: calc(-1 * var(--card-pad)); padding-block: var(--sp-3); }
|
|
344
|
+
|
|
345
|
+
/* in a sheet stack */
|
|
346
|
+
/* a paper stack's foot is paper to the card's edges, and it covers the tinted flex gap above it so the
|
|
347
|
+
button does not sit on a colour stripe. The head's gap stays: it is outside this foot. The button stays
|
|
348
|
+
where it was — the padding gives back what the bleed takes. Its ink reads the action's side. */
|
|
349
|
+
.sheet-stack > .card.sheet--paper > .foot {
|
|
350
|
+
--dark: var(--action-dark);
|
|
351
|
+
background: var(--action-bg);
|
|
352
|
+
color: var(--card-ink);
|
|
353
|
+
margin-top: calc(-1 * var(--card-gap));
|
|
354
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
355
|
+
margin-bottom: calc(-1 * var(--card-pad));
|
|
356
|
+
padding-top: var(--card-gap);
|
|
357
|
+
padding-inline: var(--card-pad);
|
|
358
|
+
padding-bottom: var(--card-pad);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* in a sheet: the button that FINISHES the sheet stands on the box's bottom edge at half, 3q and full alike (sheet.css says why).
|
|
362
|
+
The layer is as tall as the sheet, its buttons at its bottom, slid UP by the share the sheet slid down; it takes no taps, its buttons do */
|
|
363
|
+
.sheet > .foot {
|
|
364
|
+
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 */
|
|
365
|
+
align-items: end;
|
|
366
|
+
align-content: end;
|
|
367
|
+
padding: 0 var(--card-pad) calc(var(--card-pad) + var(--_safe-b-open));
|
|
368
|
+
background: linear-gradient(var(--card-bg), var(--card-bg)) bottom / 100% var(--_foot) no-repeat;
|
|
369
|
+
pointer-events: none;
|
|
370
|
+
translate: 0 calc(-1 * var(--_y) - var(--_drag, 0rem));
|
|
371
|
+
transition: translate var(--motion-sheet) var(--ease), visibility 0s;
|
|
372
|
+
}
|
|
373
|
+
.sheet > .foot > * { pointer-events: auto; }
|
|
374
|
+
.sheet.is-dragging > .foot { transition: none; }
|
|
375
|
+
/* 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
|
|
376
|
+
LEAVES AND ARRIVES WITH ITS SHEET — solid the whole way, hidden only once the slide is over. It used to stay in place and
|
|
377
|
+
fade: the body slid through the strip behind a half-transparent button (timebank, Lh 2026-10-01: "ตัวหนังสือลอดใต้ปุ่ม"). */
|
|
378
|
+
:is(.sheet--peek, .sheet--away) > .foot { translate: 0 0; visibility: hidden; transition: translate var(--motion-sheet) var(--ease), visibility 0s var(--ease-linear) var(--motion-sheet); }
|
|
379
|
+
/* a paper sheet's foot is paper, as its body is: the words read the controls' side */
|
|
380
|
+
.sheet--paper > .foot { --card-bg: var(--action-bg); --dark: var(--action-dark); color: var(--foot-ink, var(--card-ink)); }
|