@usableapp/cardds 0.7.105 → 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 +134 -0
- package/bin/cardds.mjs +6 -2
- package/cardds.css +1 -0
- package/css/actions.css +16 -9
- package/css/base.css +6 -9
- package/css/card.css +21 -173
- package/css/finish.css +4 -4
- package/css/journey.css +4 -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 +21 -140
- 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 +5 -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/Chip.d.ts +7 -3
- package/dist/actions/Chip.js +5 -5
- package/dist/actions/IconBtn.d.ts +1 -1
- package/dist/actions/IconBtn.js +1 -1
- package/dist/actions/IconBtnGroup.d.ts +1 -1
- package/dist/cardds.css +486 -410
- 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 +7 -22
- package/dist/lists/Row.js +6 -4
- package/dist/media/MapArea.d.ts +7 -3
- package/dist/media/MapArea.js +4 -3
- package/dist/media/Tile.d.ts +3 -7
- package/dist/media/Tile.js +5 -5
- package/dist/numbers/Spinner.d.ts +1 -1
- package/dist/numbers/Spinner.js +1 -1
- 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 +2 -0
- package/lib/mcp.mjs +1 -1
- package/lint/rules.mjs +22 -8
- 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/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)); }
|
package/css/sheet.css
CHANGED
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
the listed states, and the state it lands on becomes the one a tap
|
|
35
35
|
returns to.
|
|
36
36
|
<button class="sheet__handle" type="button" aria-label="…"></button>
|
|
37
|
-
<div class="
|
|
38
|
-
<div class="
|
|
37
|
+
<div class="head"><h2 class="words__title t-h2">…</h2>…</div>
|
|
38
|
+
<div class="body">…controls, or small content cards…</div>
|
|
39
39
|
</article>
|
|
40
40
|
Written top-down: the handle, the title row (what peek shows), then
|
|
41
41
|
the body. The handle and the head stay where they are at every
|
|
@@ -107,38 +107,9 @@
|
|
|
107
107
|
.sheet--away { --_y: 100%; --_cover: var(--_box); visibility: hidden; transition: translate var(--motion-sheet) var(--ease), visibility 0s var(--ease-linear) var(--motion-sheet); }
|
|
108
108
|
.sheet.is-dragging { transition: none; } /* follows the finger; the snap gets the ease back */
|
|
109
109
|
|
|
110
|
-
/* the body — everything under the head, the one thing in a sheet that
|
|
111
|
-
scrolls. Its scrollport runs to the card's side and bottom edges (reading
|
|
112
|
-
--card-pad, the way .bleed does) so content scrolls clear of the edge
|
|
113
|
-
rather than stopping inside the padding, and a .bleed strip inside it
|
|
114
|
-
still reaches the edges. The card's own grid rhythm continues inside. */
|
|
115
|
-
.sheet__body {
|
|
116
|
-
flex: 1;
|
|
117
|
-
min-height: 0;
|
|
118
|
-
display: grid;
|
|
119
|
-
gap: var(--card-gap);
|
|
120
|
-
align-content: start;
|
|
121
|
-
margin-inline: calc(-1 * var(--card-pad));
|
|
122
|
-
padding-inline: var(--card-pad);
|
|
123
|
-
overflow-y: auto;
|
|
124
|
-
overscroll-behavior: contain;
|
|
125
|
-
/* the part of the sheet that slid under the box's bottom edge cannot be
|
|
126
|
-
seen, and the scroller has to know that: scroll-padding is exactly "the
|
|
127
|
-
obscured part of my scrollport", so a focused field, scrollIntoView and
|
|
128
|
-
anchor navigation all land a control in the visible part, never under
|
|
129
|
-
the cut. A length from the box's container units (--_box), since the
|
|
130
|
-
cut is a share of the SHEET's height, not of the body's. */
|
|
131
|
-
scroll-padding-bottom: var(--_cover);
|
|
132
|
-
/* …and its content has to be able to LEAVE that part: the same length as bottom padding, so the last row
|
|
133
|
-
scrolls up to the visible edge at half and 3q as it does at full — everything between the head and the
|
|
134
|
-
foot can be reached at any state (Lh, 2026-09-21). Padding, not a shorter box: nothing that shows moves
|
|
135
|
-
when the state changes, only how far the body scrolls; a child that fills the body (a composer) fills
|
|
136
|
-
the part that shows. */
|
|
137
|
-
padding-bottom: var(--_cover);
|
|
138
|
-
}
|
|
139
110
|
/* ---- the sheet's foot: the button that FINISHES the sheet, on the sheet (Lh, 2026-09-20) ----
|
|
140
111
|
A screen with a sheet has no bar in front of it: the button that completes what the sheet asks — the
|
|
141
|
-
page's conclusion — is a .
|
|
112
|
+
page's conclusion — is a .foot, the sheet's last child, on the sheet's own material. The sheet is
|
|
142
113
|
full height at every state, so its real bottom edge is under the cut unless it stands at full. So the
|
|
143
114
|
foot is a layer as tall as the sheet, its buttons at its bottom, slid UP by the very share the sheet
|
|
144
115
|
slid down (--_y, + the drag): the two cancel, and the buttons stand on the box's bottom edge at half,
|
|
@@ -146,18 +117,11 @@
|
|
|
146
117
|
length — so a box that resizes (the keyboard) moves them as one, with nothing to catch up. The layer
|
|
147
118
|
takes no taps, its buttons do; the strip behind them is the sheet's ground, --_foot tall, and the body
|
|
148
119
|
stops that far above the sheet's edge. At peek there is only the handle and the title: the foot is gone.
|
|
149
|
-
One row, ≤ 2 buttons. <div class="
|
|
120
|
+
One row, ≤ 2 buttons (parts.css). <div class="foot"><button class="btn btn--primary btn--block">…</button></div> */
|
|
150
121
|
/* …and the link row under the button, when there is one (its line + the gap over it) */
|
|
151
|
-
.sheet:has(> .
|
|
152
|
-
|
|
153
|
-
.sheet:
|
|
154
|
-
/* that padding can be taller than the body's own box (a short box: a small phone with the keyboard up) and a
|
|
155
|
-
box is never smaller than its padding — so the pinned parts must not give way to it: the body runs past
|
|
156
|
-
the sheet's clipped edge instead, where nothing shows anyway. At peek and away no part of the body shows. */
|
|
157
|
-
.sheet > :not(.sheet__body) { flex-shrink: 0; }
|
|
158
|
-
:is(.sheet--peek, .sheet--away) > .sheet__body { padding-bottom: 0; }
|
|
159
|
-
.sheet__body > * { min-width: 0; } /* as .card > *: children shrink to the track, never widen it */
|
|
160
|
-
.sheet__body:last-child { margin-bottom: calc(-1 * var(--card-pad)); padding-bottom: calc(var(--card-pad) + var(--_cover) + var(--_safe-b-open)); }
|
|
122
|
+
.sheet:has(> .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) */
|
|
123
|
+
/* the body's padding can be taller than its own box, so the pinned parts must not give way to it (parts.css) */
|
|
124
|
+
.sheet > :not(.body) { flex-shrink: 0; }
|
|
161
125
|
|
|
162
126
|
/* grab handle — first child of the sheet, but it does not take a row: it sits in the
|
|
163
127
|
card's top padding, the same air as a stack sheet's four dots (stack.css --_grip-y),
|
|
@@ -183,22 +147,6 @@
|
|
|
183
147
|
touch-action: none;
|
|
184
148
|
cursor: grab;
|
|
185
149
|
}
|
|
186
|
-
/* 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).
|
|
187
|
-
The body and the foot read as paper; the handle and the head stay on the card.
|
|
188
|
-
The flex gap under the head is outside the body, so it stays the sheet's tint.
|
|
189
|
-
The first field would sit on that colour seam. The paper's inset is --sp-6 (24px at
|
|
190
|
-
the default spacing), the body's own top padding, and only when the sheet is paper.
|
|
191
|
-
A plain sheet keeps padding-top 0. A sheet in a stack wears the same class and the same
|
|
192
|
-
inset: the stack's own gap under the head is the card's colour, so the row still needs this paper.
|
|
193
|
-
The paper takes the side of the controls' paper (--action-dark): on a dark sheet its words still turn to onlight. */
|
|
194
|
-
.sheet--paper > .sheet__body {
|
|
195
|
-
--card-bg: var(--action-bg);
|
|
196
|
-
--dark: var(--action-dark);
|
|
197
|
-
background: var(--card-bg);
|
|
198
|
-
color: var(--card-ink);
|
|
199
|
-
padding-top: var(--sp-6);
|
|
200
|
-
}
|
|
201
|
-
.sheet--paper > .card__foot { --card-bg: var(--action-bg); --dark: var(--action-dark); --card-foot-ink: var(--card-ink); }
|
|
202
150
|
.sheet__handle--dots {
|
|
203
151
|
--_dot: var(--sp-1);
|
|
204
152
|
--_step: calc(var(--_dot) * 2.5);
|
|
@@ -238,42 +186,6 @@
|
|
|
238
186
|
.sheet__handle:focus-visible { outline: var(--focus); outline-offset: var(--sp-2); }
|
|
239
187
|
.sheet.is-dragging .sheet__handle { cursor: grabbing; }
|
|
240
188
|
|
|
241
|
-
/* the head — a title row at least --tap tall, either kind, so the peek strip
|
|
242
|
-
(--sheet-peek) is the same for every sheet:
|
|
243
|
-
- in a step it is a plain card head (card.css): the heading left, the
|
|
244
|
-
contextual thing at the far right
|
|
245
|
-
- .sheet__head is the stage sheet's three-column head: a round button,
|
|
246
|
-
the centred title and sub, a round button
|
|
247
|
-
- a label .row with its .check / .toggle may be the head when the sheet's
|
|
248
|
-
whole ask is that switch: peek shows it (a row is --tap tall plus its own
|
|
249
|
-
padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
|
|
250
|
-
.sheet > .card__head { --card-head-min-h: var(--sheet-head-h); }
|
|
251
|
-
/* a sheet's title is ONE line, always (Lh 2026-09-28, timebank #153: "ข้อกำหนดและเงื่อนไขการใช้งาน" wrapped to two lines at 390px) — the
|
|
252
|
-
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
|
|
253
|
-
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
|
|
254
|
-
measures --_over, the width it overflows by, and sets .is-long; one way — it waits, runs to its end, waits, and starts again
|
|
255
|
-
from the first letter. The move is text-indent: the heading is the app's own element, there is no inner box to slide.
|
|
256
|
-
Before the Sheet has measured, and where motion is off, it is cut with …. */
|
|
257
|
-
.sheet > .card__head:not(:has(> :is(.t-caption, .t-body))) { --card-head-wrap: nowrap; }
|
|
258
|
-
.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 */
|
|
259
|
-
.sheet > .card__head > :is(h1, h2, h3):first-child.is-long, .sheet__title.is-long {
|
|
260
|
-
text-overflow: clip;
|
|
261
|
-
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 */
|
|
262
|
-
}
|
|
263
|
-
@keyframes sheet-title-run { 0%, 20% { text-indent: 0; } 85%, 100% { text-indent: calc(-1 * var(--_over)); } }
|
|
264
|
-
@media (prefers-reduced-motion: reduce) {
|
|
265
|
-
.sheet > .card__head > :is(h1, h2, h3):first-child.is-long, .sheet__title.is-long { animation: none; text-overflow: ellipsis; }
|
|
266
|
-
}
|
|
267
|
-
.sheet__head {
|
|
268
|
-
display: grid;
|
|
269
|
-
grid-template-columns: var(--tap-sm) 1fr var(--tap-sm);
|
|
270
|
-
align-items: center;
|
|
271
|
-
gap: var(--sp-3);
|
|
272
|
-
min-height: var(--tap);
|
|
273
|
-
text-align: center;
|
|
274
|
-
}
|
|
275
|
-
.sheet__title { font: var(--sheet-title-font, var(--type-h2)); letter-spacing: var(--track-h2); margin: 0; }
|
|
276
|
-
.sheet__sub { font: var(--sheet-sub-font, var(--type-caption)); color: var(--sheet-sub-ink, var(--card-muted)); }
|
|
277
189
|
|
|
278
190
|
/* ============================================================
|
|
279
191
|
.sheet-stage — the overlay stage: a page-height box the sheet
|
|
@@ -420,11 +332,11 @@
|
|
|
420
332
|
}
|
|
421
333
|
|
|
422
334
|
/* ---- the map IN FLOW (Lh 2026-10-17): `.map--inline` — a box in a card, or the whole of a sheet's body ----
|
|
423
|
-
<div class="
|
|
335
|
+
<div class="body"><div class="map map--inline"><div>…the engine…</div><label class="map-pick" …>…</label>…
|
|
424
336
|
<span class="map__ring" …></span></div></div>
|
|
425
337
|
Choosing where to meet without leaving the sheet: the open sheet becomes the map and its head stays on top (a stage
|
|
426
338
|
behind a half sheet covers the head and changes the look). No stage, so no band and no bar: the ring is at the centre
|
|
427
|
-
of the map's OWN box and the hint at its own top. In a card it is a rounded media box; as a
|
|
339
|
+
of the map's OWN box and the hint at its own top. In a card it is a rounded media box; as a sheet's Body's only child it
|
|
428
340
|
fills the body (the part above the foot), however far the sheet slid. */
|
|
429
341
|
.map--inline {
|
|
430
342
|
--_top: 0rem;
|
|
@@ -434,8 +346,9 @@
|
|
|
434
346
|
border-radius: var(--map-radius, var(--r-media));
|
|
435
347
|
overflow: hidden;
|
|
436
348
|
}
|
|
437
|
-
.
|
|
438
|
-
|
|
349
|
+
.map--bleed { margin-inline: calc(-1 * var(--card-pad, 0rem)); border-radius: 0; } /* edge to edge is a band, and a band has no corner; --map-radius is for the box in the inset */
|
|
350
|
+
/* a map that is a body's only child fills it; the body turns to a column for it (parts.css) */
|
|
351
|
+
.body > .map--inline:only-child { flex: 1 0 auto; }
|
|
439
352
|
|
|
440
353
|
/* ---- map pick: a place on the map the member taps to choose it (Lh 2026-10-17) ----
|
|
441
354
|
<label class="map-pick" style="top: 30%; left: 40%"><input type="radio" name="meet" value="library">
|
|
@@ -524,7 +437,6 @@
|
|
|
524
437
|
the form's own spacing, from the top (never spread or centred: the keyboard needs the room under it); the next steps wait in a pile at the
|
|
525
438
|
bottom of the body, each its title line. Every step has a thin rule on top. A step opens and folds as a 1fr ↔ 0fr grid row, so the pile rises and
|
|
526
439
|
falls — no height is written. Nothing fades: done and next read at full ink. */
|
|
527
|
-
.sheet__body:has(> .sheet-steps) { display: flex; flex-direction: column; }
|
|
528
440
|
.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; }
|
|
529
441
|
.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 */
|
|
530
442
|
.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; }
|
|
@@ -558,8 +470,15 @@
|
|
|
558
470
|
}
|
|
559
471
|
.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; }
|
|
560
472
|
.sheet-step__check { margin-inline-start: auto; }
|
|
561
|
-
|
|
562
|
-
|
|
473
|
+
/* a step's child that bleeds (ChipRow bleed, MapArea bleed, a Banner) reaches the SHEET's edges (Lh 2026-10-19, the ask sheet's
|
|
474
|
+
จุดนัด step: the map edge to edge under the step's buttons). The fold's clip is the step body's box, which sat inside the sheet's
|
|
475
|
+
inset, so the body takes that inset back and pads by it again, the way a sheet's .body does; the inner keeps its own box and its own
|
|
476
|
+
inset (--sheet-step-inner-pad-x: the inset BEYOND the sheet's) and publishes the whole, the sheet's plus its own, as --card-pad,
|
|
477
|
+
so a .bleed cancels it exactly. The sum is computed on the body, before the inner's --card-pad takes over; outside a card there is
|
|
478
|
+
no --card-pad and the step keeps its own inset (0px, as .bleed reads it). The inner reads the knob itself, not the sum: a theme's
|
|
479
|
+
unitless 0 (SheetSteps · Ruled) is a length there but not in a calc, so the step stays right and only the bleed is lost. */
|
|
480
|
+
.sheet-step__body { --_step-pad: var(--sheet-step-inner-pad-x, var(--sheet-step-bar-pad-x, var(--sp-3))); --_pad: calc(var(--card-pad, 0rem) + var(--_step-pad)); margin-inline: calc(-1 * var(--card-pad, 0rem)); padding-inline: var(--card-pad, 0rem); min-height: 0; overflow: clip; overflow-clip-margin: var(--sp-1); }
|
|
481
|
+
.sheet-step__inner { --card-pad: var(--_pad); display: grid; gap: var(--card-gap); padding-block: var(--card-gap); padding-inline: var(--_step-pad); } /* 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) */
|
|
563
482
|
.sheet-step__inner > * { min-width: 0; }
|
|
564
483
|
.sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s var(--ease-linear) var(--motion-sheet); }
|
|
565
484
|
.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; }
|
|
@@ -570,45 +489,7 @@
|
|
|
570
489
|
/* ==== the step bar answers a press, as PRESS in actions.css answers every other control (#62: a block is written in its own file) ==== */
|
|
571
490
|
.sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
|
|
572
491
|
|
|
573
|
-
/* ==== 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) ==== */
|
|
574
|
-
.sheet__body:has(> .empty-state:only-child) { display: flex; flex-direction: column; gap: 0; }
|
|
575
|
-
.sheet__body:has(> .empty-state:only-child)::before { content: ""; flex: calc(var(--empty-state-at, 0) * 5) 1 0; }
|
|
576
|
-
.sheet__body:has(> .empty-state:only-child)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0) * 5) 1 0; }
|
|
577
492
|
|
|
578
|
-
/* ---- where it sits in what stack.css draws (docs/reference.md rule 15) ---- */
|
|
579
|
-
/* a pull in the content, already at the top: the words move down, the card stays. --_refresh is the finger, private.
|
|
580
|
-
A paper body adds its own --sp-6 on top of this (the rule below): the stack's gap under the head is the
|
|
581
|
-
card's colour, so without the paper's inset the first row sits on that seam. */
|
|
582
|
-
.sheet-stack > .card > .sheet__body { padding-top: var(--_refresh, 0rem); transition: padding-top var(--motion-sheet) var(--ease); }
|
|
583
|
-
/* 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
|
|
584
|
-
--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
|
|
585
|
-
stack's step and the body's own rows read it. */
|
|
586
|
-
.sheet-stack > .card.sheet--paper > .sheet__body {
|
|
587
|
-
margin-top: calc(var(--sp-3) - var(--card-gap));
|
|
588
|
-
padding-top: calc(var(--sp-6) + var(--_refresh, 0rem));
|
|
589
|
-
transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
|
|
590
|
-
}
|
|
591
|
-
/* 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 */
|
|
592
|
-
.sheet-stack > .card.sheet--paper > .sheet__body.is-refreshing { transition: none; }
|
|
593
|
-
/* a closed paper sheet's body stays clear (Lh 2026-10-06). The next sheet's round top does not
|
|
594
|
-
cover the corners, and a paper body showed there beside the head. Clear, the card's own colour
|
|
595
|
-
shows in that pocket. Open, the body is paper, and the first row sits --sp-6 down, the same paper
|
|
596
|
-
inset a single paper sheet keeps: the flex gap under the head is the card's colour, so the row
|
|
597
|
-
would otherwise sit on that seam. A pull adds --_refresh on top of the inset. On the way back
|
|
598
|
-
into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
|
|
599
|
-
sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
|
|
600
|
-
at the moment the sheet stops. Rising, the body is paper at once. */
|
|
601
|
-
.sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
602
|
-
background-color: transparent;
|
|
603
|
-
transition:
|
|
604
|
-
padding-top var(--motion-sheet) var(--ease),
|
|
605
|
-
background-color var(--motion-quick) var(--ease) calc(var(--stagger, 0) * var(--motion-stagger) + var(--motion-sheet) - var(--motion-quick));
|
|
606
|
-
}
|
|
607
|
-
@media (prefers-reduced-motion: reduce) { .sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
608
|
-
transition: padding-top var(--motion-sheet) var(--ease), background-color 0s var(--ease-linear);
|
|
609
|
-
} }
|
|
610
493
|
/* ---- where it sits in what step.css draws (docs/reference.md rule 15) ---- */
|
|
611
494
|
.screen--fill > .appbar + .sheet-stage { margin-top: 0; }
|
|
612
495
|
|
|
613
|
-
/* ---- where it sits in what stack.css draws: a body pulled to refresh holds still (docs/reference.md rule 15) ---- */
|
|
614
|
-
.sheet-stack > .card > .sheet__body.is-refreshing { transition: none; }
|