@usableapp/cardds 0.1.0
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/README.md +562 -0
- package/cardds.css +18 -0
- package/cardds.js +473 -0
- package/css/actions.css +292 -0
- package/css/base.css +128 -0
- package/css/card.css +283 -0
- package/css/choice.css +297 -0
- package/css/fonts.css +37 -0
- package/css/forms.css +64 -0
- package/css/journey.css +61 -0
- package/css/layover.css +243 -0
- package/css/lists.css +142 -0
- package/css/media.css +232 -0
- package/css/numbers.css +258 -0
- package/css/palette.css +112 -0
- package/css/people.css +79 -0
- package/css/sheet.css +295 -0
- package/css/stack.css +262 -0
- package/css/step.css +71 -0
- package/css/tokens.css +177 -0
- package/dist/actions/Btn.d.ts +311 -0
- package/dist/actions/Btn.js +14 -0
- package/dist/actions/BtnRow.d.ts +5 -0
- package/dist/actions/BtnRow.js +6 -0
- package/dist/actions/Chip.d.ts +328 -0
- package/dist/actions/Chip.js +25 -0
- package/dist/actions/Dot.d.ts +7 -0
- package/dist/actions/Dot.js +6 -0
- package/dist/actions/Dropdown.d.ts +34 -0
- package/dist/actions/Dropdown.js +19 -0
- package/dist/actions/Fab.d.ts +9 -0
- package/dist/actions/Fab.js +7 -0
- package/dist/actions/IconBtn.d.ts +308 -0
- package/dist/actions/IconBtn.js +8 -0
- package/dist/actions/Link.d.ts +301 -0
- package/dist/actions/Link.js +8 -0
- package/dist/actions/Segment.d.ts +11 -0
- package/dist/actions/Segment.js +10 -0
- package/dist/cardds.css +3068 -0
- package/dist/cards/Card.d.ts +310 -0
- package/dist/cards/Card.js +14 -0
- package/dist/cards/CardFoot.d.ts +5 -0
- package/dist/cards/CardFoot.js +6 -0
- package/dist/cards/CardHead.d.ts +21 -0
- package/dist/cards/CardHead.js +11 -0
- package/dist/cards/CardParts.d.ts +25 -0
- package/dist/cards/CardParts.js +26 -0
- package/dist/cards/Placeholder.d.ts +9 -0
- package/dist/cards/Placeholder.js +6 -0
- package/dist/cards/Stat.d.ts +17 -0
- package/dist/cards/Stat.js +14 -0
- package/dist/choice/Calendar.d.ts +23 -0
- package/dist/choice/Calendar.js +7 -0
- package/dist/choice/Check.d.ts +14 -0
- package/dist/choice/Check.js +10 -0
- package/dist/choice/DayStrip.d.ts +16 -0
- package/dist/choice/DayStrip.js +10 -0
- package/dist/choice/Mood.d.ts +11 -0
- package/dist/choice/Mood.js +6 -0
- package/dist/choice/Slider.d.ts +11 -0
- package/dist/choice/Slider.js +14 -0
- package/dist/cx.d.ts +2 -0
- package/dist/cx.js +2 -0
- package/dist/forms/AddRow.d.ts +5 -0
- package/dist/forms/AddRow.js +7 -0
- package/dist/forms/Composer.d.ts +13 -0
- package/dist/forms/Composer.js +6 -0
- package/dist/forms/Field.d.ts +17 -0
- package/dist/forms/Field.js +10 -0
- package/dist/forms/FileBtn.d.ts +18 -0
- package/dist/forms/FileBtn.js +10 -0
- package/dist/forms/Pin.d.ts +19 -0
- package/dist/forms/Pin.js +27 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +75 -0
- package/dist/journey/NoteRow.d.ts +7 -0
- package/dist/journey/NoteRow.js +7 -0
- package/dist/journey/Route.d.ts +18 -0
- package/dist/journey/Route.js +8 -0
- package/dist/journey/TileBadge.d.ts +15 -0
- package/dist/journey/TileBadge.js +6 -0
- package/dist/layover/Banner.d.ts +9 -0
- package/dist/layover/Banner.js +6 -0
- package/dist/layover/Callout.d.ts +10 -0
- package/dist/layover/Callout.js +6 -0
- package/dist/layover/Deck.d.ts +9 -0
- package/dist/layover/Deck.js +10 -0
- package/dist/lists/Kv.d.ts +27 -0
- package/dist/lists/Kv.js +19 -0
- package/dist/lists/Legend.d.ts +11 -0
- package/dist/lists/Legend.js +11 -0
- package/dist/lists/Row.d.ts +319 -0
- package/dist/lists/Row.js +17 -0
- package/dist/lists/Timeline.d.ts +11 -0
- package/dist/lists/Timeline.js +10 -0
- package/dist/media/MapArea.d.ts +20 -0
- package/dist/media/MapArea.js +11 -0
- package/dist/media/Mosaic.d.ts +11 -0
- package/dist/media/Mosaic.js +10 -0
- package/dist/media/Postcard.d.ts +20 -0
- package/dist/media/Postcard.js +10 -0
- package/dist/media/Quote.d.ts +11 -0
- package/dist/media/Quote.js +6 -0
- package/dist/media/Tile.d.ts +320 -0
- package/dist/media/Tile.js +16 -0
- package/dist/numbers/Badge.d.ts +7 -0
- package/dist/numbers/Badge.js +6 -0
- package/dist/numbers/Band.d.ts +16 -0
- package/dist/numbers/Band.js +10 -0
- package/dist/numbers/Bars.d.ts +14 -0
- package/dist/numbers/Bars.js +10 -0
- package/dist/numbers/DotGrid.d.ts +12 -0
- package/dist/numbers/DotGrid.js +6 -0
- package/dist/numbers/Picker.d.ts +13 -0
- package/dist/numbers/Picker.js +18 -0
- package/dist/numbers/Ring.d.ts +15 -0
- package/dist/numbers/Ring.js +6 -0
- package/dist/numbers/Track.d.ts +12 -0
- package/dist/numbers/Track.js +10 -0
- package/dist/people/Avatar.d.ts +314 -0
- package/dist/people/Avatar.js +13 -0
- package/dist/people/AvatarPick.d.ts +18 -0
- package/dist/people/AvatarPick.js +10 -0
- package/dist/scaffold/ActionBar.d.ts +21 -0
- package/dist/scaffold/ActionBar.js +16 -0
- package/dist/scaffold/AppBar.d.ts +18 -0
- package/dist/scaffold/AppBar.js +10 -0
- package/dist/scaffold/Centre.d.ts +30 -0
- package/dist/scaffold/Centre.js +41 -0
- package/dist/scaffold/EmptyState.d.ts +14 -0
- package/dist/scaffold/EmptyState.js +8 -0
- package/dist/scaffold/FilterRow.d.ts +5 -0
- package/dist/scaffold/FilterRow.js +6 -0
- package/dist/scaffold/FloatBar.d.ts +310 -0
- package/dist/scaffold/FloatBar.js +15 -0
- package/dist/scaffold/PagerAt.d.ts +11 -0
- package/dist/scaffold/PagerAt.js +6 -0
- package/dist/scaffold/Screen.d.ts +299 -0
- package/dist/scaffold/Screen.js +11 -0
- package/dist/scaffold/Section.d.ts +12 -0
- package/dist/scaffold/Section.js +13 -0
- package/dist/scaffold/TopBar.d.ts +15 -0
- package/dist/scaffold/TopBar.js +9 -0
- package/dist/sheets/CardBack.d.ts +7 -0
- package/dist/sheets/CardBack.js +7 -0
- package/dist/sheets/Drawer.d.ts +17 -0
- package/dist/sheets/Drawer.js +16 -0
- package/dist/sheets/Sheet.d.ts +21 -0
- package/dist/sheets/Sheet.js +13 -0
- package/dist/sheets/SheetBody.d.ts +5 -0
- package/dist/sheets/SheetBody.js +6 -0
- package/dist/sheets/SheetHead.d.ts +13 -0
- package/dist/sheets/SheetHead.js +6 -0
- package/dist/sheets/SheetStack.d.ts +21 -0
- package/dist/sheets/SheetStack.js +14 -0
- package/dist/sheets/SheetStage.d.ts +20 -0
- package/dist/sheets/SheetStage.js +11 -0
- package/dist/sheets/Step.d.ts +15 -0
- package/dist/sheets/Step.js +15 -0
- package/dist/type/Icon.d.ts +16 -0
- package/dist/type/Icon.js +12 -0
- package/dist/type/Text.d.ts +302 -0
- package/dist/type/Text.js +16 -0
- package/dist/type/icons.d.ts +6 -0
- package/dist/type/icons.js +2 -0
- package/fonts/LICENSES.md +44 -0
- package/fonts/fc-pride-medium.otf +0 -0
- package/fonts/pk-nonthaburi-demo.ttf +0 -0
- package/icons.svg +61 -0
- package/package.json +62 -0
package/css/sheet.css
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
cardds/sheet.css — sheet single: one card as a bottom sheet.
|
|
3
|
+
Three states: .sheet--peek · .sheet--half · .sheet--full
|
|
4
|
+
|
|
5
|
+
A sheet is rigid material, exactly like a sheet in .sheet-stack--tap
|
|
6
|
+
(stack.css): it is ALWAYS the full height of the box that holds it —
|
|
7
|
+
set once by inset: 0, never written again, never transitioned — and
|
|
8
|
+
a state is nothing but how far that full card has slid up. Peek is
|
|
9
|
+
a full sheet that slid down until only its handle and title show;
|
|
10
|
+
half is one that slid down by half of itself; full is one that has
|
|
11
|
+
not slid at all. The box clips whatever slid below its bottom edge,
|
|
12
|
+
so a sheet at any height is still a card: rounded top, a straight
|
|
13
|
+
cut at the box's edge, its content scrolling inside.
|
|
14
|
+
|
|
15
|
+
So translate is the only animated property. Nothing here toggles
|
|
16
|
+
height, display or z-index for a state — every past bug in the
|
|
17
|
+
stack's animation came from exactly that, and the sheet inherits
|
|
18
|
+
the rule.
|
|
19
|
+
|
|
20
|
+
Two boxes hold sheets, and the state classes mean the same in both:
|
|
21
|
+
- .step (step.css) — an app screen where the member acts: the
|
|
22
|
+
reading card behind, the sheet in front, .action-bar outside.
|
|
23
|
+
- .sheet-stage (below) — a page-level overlay stage: a sheet over
|
|
24
|
+
a map, the dim page sheet, the ask drawer (layover.css).
|
|
25
|
+
|
|
26
|
+
This is the SINGLE sheet: one sheet the member acts in, whatever it
|
|
27
|
+
holds — a short entry in a step, a place on a map, a payment on a dim
|
|
28
|
+
page. Many sheets stacked as the app's menu are the other system,
|
|
29
|
+
.sheet-stack (stack.css): the same material, its own class and file.
|
|
30
|
+
|
|
31
|
+
<article class="card sheet sheet--half" data-sheet-states="peek half full">
|
|
32
|
+
<button class="sheet__handle" type="button" aria-label="…"></button>
|
|
33
|
+
<div class="card__head"><h2 class="t-h2">…</h2>…</div>
|
|
34
|
+
<div class="sheet__body">…controls, or small content cards…</div>
|
|
35
|
+
</article>
|
|
36
|
+
Written top-down: the handle, the title row (what peek shows), then
|
|
37
|
+
the body. The handle and the head stay where they are at every
|
|
38
|
+
state; the body is what scrolls — a sheet is a card whose head is
|
|
39
|
+
pinned, not a scrolling column with a title somewhere in it.
|
|
40
|
+
cardds.js adds the handle's click / drag / keyboard and the focus
|
|
41
|
+
raise; without it every state still renders.
|
|
42
|
+
============================================================ */
|
|
43
|
+
|
|
44
|
+
.sheet {
|
|
45
|
+
--_handle: var(--sp-1);
|
|
46
|
+
/* the strip a peeked sheet shows: the card's own top padding, the handle,
|
|
47
|
+
the gap, one title row (--tap — the head rule below makes every head at
|
|
48
|
+
least that tall) and the gap under it, so the cut lands exactly where
|
|
49
|
+
the first control starts and nothing of it leaks through. Published
|
|
50
|
+
(and registered in tokens.css) so a sheet whose head is taller can
|
|
51
|
+
retune it: .my-sheet { --sheet-peek: … } */
|
|
52
|
+
--sheet-peek: calc(var(--card-pad) + var(--_handle) + 2 * var(--card-gap) + var(--tap));
|
|
53
|
+
--sheet-half: 0.5; /* a share of the sheet's own height */
|
|
54
|
+
/* the sheet's own height, seen from inside it: the box it slides in is the
|
|
55
|
+
size container (.step, .sheet-stage), so 100cqh is that box, less what
|
|
56
|
+
the sheet gives up at its top (--_top) and bottom (--_lift). The sheet
|
|
57
|
+
cannot be its own container — cq units measure a content box, and the
|
|
58
|
+
sheet's is its border box less the card's padding. */
|
|
59
|
+
--_box: calc(100cqh - var(--_top, 0rem) - var(--_lift, 0rem));
|
|
60
|
+
/* the resting offset of the current state, said twice: as a share of the
|
|
61
|
+
sheet's own height for its translate (--_y), and as a length for the
|
|
62
|
+
body's scroll-padding (--_cover) — a percentage down there would be of
|
|
63
|
+
the body, not of the sheet. Half by default. */
|
|
64
|
+
--_y: calc(var(--sheet-half) * 100%);
|
|
65
|
+
--_cover: calc(var(--sheet-half) * var(--_box));
|
|
66
|
+
|
|
67
|
+
position: absolute;
|
|
68
|
+
inset: 0;
|
|
69
|
+
top: var(--_top, 0); /* a stage keeps its top bar's line free — see .sheet-stage */
|
|
70
|
+
translate: 0 calc(var(--_y) + var(--_drag, 0rem)); /* --_drag: cardds.js, only while the handle is held */
|
|
71
|
+
transition: translate var(--motion-sheet) ease;
|
|
72
|
+
|
|
73
|
+
/* handle · head · body: a column, the body takes what is left and scrolls
|
|
74
|
+
(below) — so the handle and the head never move, whatever the content */
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
overflow: clip;
|
|
78
|
+
border-radius: var(--r-card) var(--r-card) 0 0;
|
|
79
|
+
border-bottom: 0;
|
|
80
|
+
box-shadow: var(--shadow-sheet);
|
|
81
|
+
}
|
|
82
|
+
.sheet--full { --_y: 0%; --_cover: 0rem; }
|
|
83
|
+
.sheet--half { --_y: calc(var(--sheet-half) * 100%); --_cover: calc(var(--sheet-half) * var(--_box)); }
|
|
84
|
+
.sheet--peek { --_y: calc(100% - var(--sheet-peek)); --_cover: calc(var(--_box) - var(--sheet-peek)); }
|
|
85
|
+
.sheet.is-dragging { transition: none; } /* follows the finger; the snap gets the ease back */
|
|
86
|
+
|
|
87
|
+
/* the body — everything under the head, the one thing in a sheet that
|
|
88
|
+
scrolls. Its scrollport runs to the card's side and bottom edges (reading
|
|
89
|
+
--card-pad, the way .bleed does) so content scrolls clear of the edge
|
|
90
|
+
rather than stopping inside the padding, and a .bleed strip inside it
|
|
91
|
+
still reaches the edges. The card's own grid rhythm continues inside. */
|
|
92
|
+
.sheet__body {
|
|
93
|
+
flex: 1;
|
|
94
|
+
min-height: 0;
|
|
95
|
+
display: grid;
|
|
96
|
+
gap: var(--card-gap);
|
|
97
|
+
align-content: start;
|
|
98
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
99
|
+
padding-inline: var(--card-pad);
|
|
100
|
+
overflow-y: auto;
|
|
101
|
+
overscroll-behavior: contain;
|
|
102
|
+
/* the part of the sheet that slid under the box's bottom edge cannot be
|
|
103
|
+
seen, and the scroller has to know that: scroll-padding is exactly "the
|
|
104
|
+
obscured part of my scrollport", so a focused field, scrollIntoView and
|
|
105
|
+
anchor navigation all land a control in the visible part, never under
|
|
106
|
+
the cut. A length from the box's container units (--_box), since the
|
|
107
|
+
cut is a share of the SHEET's height, not of the body's. */
|
|
108
|
+
scroll-padding-bottom: var(--_cover);
|
|
109
|
+
}
|
|
110
|
+
.sheet__body > * { min-width: 0; } /* as .card > *: children shrink to the track, never widen it */
|
|
111
|
+
.sheet__body:last-child { margin-bottom: calc(-1 * var(--card-pad)); padding-bottom: var(--card-pad); }
|
|
112
|
+
|
|
113
|
+
/* grab handle — first child of the sheet. Drawn as a small bar, but it is
|
|
114
|
+
the thumb's grab area for the whole strip above the title: a pseudo widens
|
|
115
|
+
its hit box to --tap tall and wider than the bar without changing the
|
|
116
|
+
picture. touch-action: none hands a touch drag to cardds.js instead of to
|
|
117
|
+
the scroller behind it. */
|
|
118
|
+
.sheet__handle {
|
|
119
|
+
position: relative;
|
|
120
|
+
border: 0;
|
|
121
|
+
color: inherit; /* a <button> has its own UA colour; the bar is the card's ink */
|
|
122
|
+
background: currentColor;
|
|
123
|
+
opacity: 0.3;
|
|
124
|
+
width: var(--tap-sm);
|
|
125
|
+
height: var(--_handle);
|
|
126
|
+
border-radius: var(--r-chip);
|
|
127
|
+
margin: 0 auto;
|
|
128
|
+
padding: 0;
|
|
129
|
+
touch-action: none;
|
|
130
|
+
cursor: grab;
|
|
131
|
+
}
|
|
132
|
+
.sheet__handle::before {
|
|
133
|
+
content: "";
|
|
134
|
+
position: absolute;
|
|
135
|
+
inset: calc((var(--_handle) - var(--tap)) / 2) calc(-1 * var(--sp-8));
|
|
136
|
+
}
|
|
137
|
+
.sheet__handle:focus-visible { outline: var(--focus); outline-offset: var(--sp-2); }
|
|
138
|
+
.sheet.is-dragging .sheet__handle { cursor: grabbing; }
|
|
139
|
+
|
|
140
|
+
/* the head — a title row at least --tap tall, either kind, so the peek strip
|
|
141
|
+
(--sheet-peek) is the same for every sheet:
|
|
142
|
+
- in a step it is a plain card head (card.css): the heading left, the
|
|
143
|
+
contextual thing at the far right, the two-line budget and the squeeze
|
|
144
|
+
- .sheet__head is the stage sheet's three-column head: a round button,
|
|
145
|
+
the centred title and sub, a round button
|
|
146
|
+
- a label .row with its .check / .toggle may be the head when the sheet's
|
|
147
|
+
whole ask is that switch: peek shows it (a row is --tap tall plus its own
|
|
148
|
+
padding; the strip cuts in that padding) — card #67's "ไม่อยู่บ้าน" */
|
|
149
|
+
.sheet > .card__head { min-height: var(--tap); }
|
|
150
|
+
.sheet__head {
|
|
151
|
+
display: grid;
|
|
152
|
+
grid-template-columns: var(--tap-sm) 1fr var(--tap-sm);
|
|
153
|
+
align-items: center;
|
|
154
|
+
gap: var(--sp-3);
|
|
155
|
+
min-height: var(--tap);
|
|
156
|
+
text-align: center;
|
|
157
|
+
}
|
|
158
|
+
.sheet__title { font: var(--type-h2); letter-spacing: var(--track-h2); margin: 0; }
|
|
159
|
+
.sheet__sub { font: var(--type-caption); color: var(--card-muted); }
|
|
160
|
+
|
|
161
|
+
/* ============================================================
|
|
162
|
+
.sheet-stage — the overlay stage: a page-height box the sheet
|
|
163
|
+
slides in, over a map, a page, previous cards. It is its own
|
|
164
|
+
100dvh page, never a section of a scrolling screen — an app
|
|
165
|
+
screen where the member acts is a .step (step.css).
|
|
166
|
+
============================================================ */
|
|
167
|
+
.sheet-stage {
|
|
168
|
+
/* the centre of the band a half sheet leaves open — from the bar's line to the sheet's
|
|
169
|
+
top edge at half ((100% + --_top) / 2): where a hero or a map's ring sits */
|
|
170
|
+
--_band-centre: calc((100% + 3 * var(--_top, 0rem)) / 4);
|
|
171
|
+
--_top: var(--header-h); /* full stops UNDER the line a top bar takes rather than
|
|
172
|
+
covering it — the same reserve .screen makes for a fixed .appbar — and
|
|
173
|
+
keeps its corners; the sheet reads it as its top edge */
|
|
174
|
+
position: relative;
|
|
175
|
+
width: 100%; /* also as a grid/flex child: children are absolute, so it must size itself */
|
|
176
|
+
height: 100dvh;
|
|
177
|
+
max-width: var(--screen-w);
|
|
178
|
+
margin-inline: auto;
|
|
179
|
+
overflow: clip; /* not hidden: a clipped box is not a scroll container, so revealing a focused field never shifts the stage */
|
|
180
|
+
container-type: size; /* the sheet's body reads this box's height as 100cqh (--_box above) */
|
|
181
|
+
background: var(--surface);
|
|
182
|
+
}
|
|
183
|
+
/* on an app screen the stage is the page: it takes the screen's inset back (edge to
|
|
184
|
+
edge, like a step) and, after a fixed .appbar, the room the screen reserved for
|
|
185
|
+
that bar — the stage keeps the bar's line free itself (--_top), so the reserve
|
|
186
|
+
would be paid twice and the page would scroll by it */
|
|
187
|
+
.screen > .sheet-stage { margin-inline: calc(-1 * var(--screen-pad)); width: auto; } /* auto: 100% would keep the inset width */
|
|
188
|
+
.appbar + .sheet-stage { margin-top: calc(-1 * (var(--header-h) + var(--header-gap, 0rem))); }
|
|
189
|
+
.sheet-stage__bg { position: absolute; inset: 0; } /* map / previous cards behind */
|
|
190
|
+
/* the hero: ONE thing shown in the band a half sheet leaves open (a postcard being made,
|
|
191
|
+
a photo being chosen) — centred in that band like a map's ring, on the screen's inset;
|
|
192
|
+
the sheet slides over it at full */
|
|
193
|
+
.sheet-stage__hero {
|
|
194
|
+
position: absolute;
|
|
195
|
+
inset-inline: var(--screen-pad);
|
|
196
|
+
top: var(--_band-centre);
|
|
197
|
+
translate: 0 -50%;
|
|
198
|
+
/* no z-index: it follows the background in DOM order and the sheet, later, covers it at full */
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* ---- map: the box a map engine draws in, behind a stage sheet ----
|
|
202
|
+
<div class="sheet-stage__bg"><div class="map"><div>…the engine's canvas…</div>
|
|
203
|
+
<span class="map__ring" role="img" aria-label="ย่านของคุณโดยประมาณ"></span>
|
|
204
|
+
<span class="map__hint">ลากแผนที่ให้วงครอบย่านที่คุณอยู่</span>
|
|
205
|
+
<span class="map__credit">© OpenStreetMap</span></div></div>
|
|
206
|
+
The first child is the engine's mount (the app hands it to Leaflet or the
|
|
207
|
+
like); it fills the box. Before tiles arrive — or offline — the box shows the
|
|
208
|
+
wireframe's street pattern, so a map is never a hole. The ring marks the
|
|
209
|
+
rough area the member is choosing: fixed at the box's centre, so dragging
|
|
210
|
+
the map underneath is what moves it (the centre IS the choice), never a
|
|
211
|
+
pin to place. The hint rides at the top, under the bar's line. */
|
|
212
|
+
.map {
|
|
213
|
+
position: absolute;
|
|
214
|
+
inset: 0;
|
|
215
|
+
isolation: isolate; /* the ring and the hint stack inside the map only — a sheet over the map covers them */
|
|
216
|
+
color: var(--on-surface);
|
|
217
|
+
background:
|
|
218
|
+
repeating-linear-gradient(75deg, transparent 0 5.5rem, var(--hairline) 5.5rem 6.25rem),
|
|
219
|
+
repeating-linear-gradient(-18deg, transparent 0 8.75rem, var(--ink-tint) 8.75rem 9.25rem),
|
|
220
|
+
var(--card-2-bg);
|
|
221
|
+
}
|
|
222
|
+
.map > :first-child { position: absolute; inset: 0; z-index: 0; isolation: isolate; } /* its own stacking context: an engine's layered panes stay under the ring */
|
|
223
|
+
.sheet-stage__bg .map > :first-child { background: transparent; } /* an engine's own grey ground would hide the pattern while tiles load */
|
|
224
|
+
.map__ring {
|
|
225
|
+
position: absolute;
|
|
226
|
+
left: 50%;
|
|
227
|
+
/* centred in the band a half sheet leaves open (the stage's --_band-centre) — the
|
|
228
|
+
member sees the whole ring while the sheet asks; the engine reads the point under it */
|
|
229
|
+
top: var(--_band-centre);
|
|
230
|
+
width: var(--map-ring);
|
|
231
|
+
height: var(--map-ring);
|
|
232
|
+
translate: -50% -50%;
|
|
233
|
+
border-radius: 50%;
|
|
234
|
+
border: var(--border-w) solid var(--accent);
|
|
235
|
+
background: color-mix(in srgb, var(--accent) 22%, transparent);
|
|
236
|
+
box-shadow: var(--shadow-float);
|
|
237
|
+
pointer-events: none;
|
|
238
|
+
z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
|
|
239
|
+
}
|
|
240
|
+
.map__credit { /* the tile provider's credit — required by OSM; a caption, not the engine's own control */
|
|
241
|
+
position: absolute;
|
|
242
|
+
right: var(--sp-2);
|
|
243
|
+
bottom: var(--sp-2);
|
|
244
|
+
padding: var(--sp-0) var(--sp-2);
|
|
245
|
+
border-radius: var(--r-chip);
|
|
246
|
+
background: color-mix(in srgb, var(--surface) 80%, transparent);
|
|
247
|
+
color: var(--on-surface-muted);
|
|
248
|
+
font: var(--type-caption);
|
|
249
|
+
pointer-events: none;
|
|
250
|
+
z-index: 1;
|
|
251
|
+
}
|
|
252
|
+
.map__hint {
|
|
253
|
+
position: absolute;
|
|
254
|
+
top: calc(var(--_top, 0rem) + var(--sp-3));
|
|
255
|
+
left: 50%;
|
|
256
|
+
translate: -50% 0;
|
|
257
|
+
max-width: calc(100% - 2 * var(--sp-4));
|
|
258
|
+
padding: var(--sp-2) var(--sp-4);
|
|
259
|
+
border-radius: var(--r-chip);
|
|
260
|
+
background: var(--card-3-bg);
|
|
261
|
+
color: var(--card-3-ink);
|
|
262
|
+
font: var(--type-label);
|
|
263
|
+
text-align: center;
|
|
264
|
+
box-shadow: var(--shadow-float);
|
|
265
|
+
pointer-events: none;
|
|
266
|
+
z-index: 1;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* raised: the sheet floats above a footnote strip on the scrim. Its bottom
|
|
270
|
+
edge shows, so it gets its corners and border back; full still stops at
|
|
271
|
+
the top bar's line, because the top edge never moved. */
|
|
272
|
+
.sheet--raised { --_lift: var(--tap-lg); bottom: var(--_lift); border-radius: var(--r-card); border-bottom: var(--border-w) solid var(--card-border); }
|
|
273
|
+
.sheet-stage__foot {
|
|
274
|
+
position: absolute;
|
|
275
|
+
inset-inline: 0;
|
|
276
|
+
bottom: 0;
|
|
277
|
+
height: var(--tap-lg);
|
|
278
|
+
margin: 0;
|
|
279
|
+
display: grid;
|
|
280
|
+
place-items: center;
|
|
281
|
+
font: var(--type-caption);
|
|
282
|
+
color: var(--card-3-ink);
|
|
283
|
+
z-index: 2;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* dim stage: blur + darken whatever sits behind the sheet */
|
|
287
|
+
.sheet-stage--dim .sheet-stage__bg { filter: blur(var(--sp-2)); }
|
|
288
|
+
.sheet-stage--dim::after {
|
|
289
|
+
content: "";
|
|
290
|
+
position: absolute;
|
|
291
|
+
inset: 0;
|
|
292
|
+
background: var(--scrim);
|
|
293
|
+
z-index: 1;
|
|
294
|
+
}
|
|
295
|
+
.sheet-stage--dim .sheet { z-index: 2; }
|
package/css/stack.css
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
cardds/stack.css — .sheet-stack: the menu.
|
|
3
|
+
Sheets stacked in one column like paper, every one a main section
|
|
4
|
+
of the app, their heads peeking, the front one open. Tap a peeked
|
|
5
|
+
head and that sheet comes to the front; its back button returns it.
|
|
6
|
+
|
|
7
|
+
cardds has two sheet systems and one material:
|
|
8
|
+
- sheet single (sheet.css): ONE sheet the member acts in — a short
|
|
9
|
+
entry in front of a .step's card, or an overlay in a .sheet-stage.
|
|
10
|
+
Its state is how far it slid: peek / half / full.
|
|
11
|
+
- sheet stack (this file): MANY sheets, each a section, stacked at
|
|
12
|
+
the middle of the home screen. A peek is the strip the next sheet
|
|
13
|
+
does not cover; open is one sheet brought to the front.
|
|
14
|
+
A stacked sheet is written as a .card — the material — directly
|
|
15
|
+
inside .sheet-stack; .sheet stays the single sheet's class. This
|
|
16
|
+
component was first named "card stack": wrong, and retired — the
|
|
17
|
+
members are sheets and the stack is the menu, not a list of cards.
|
|
18
|
+
|
|
19
|
+
<div class="sheet-stack sheet-stack--tap"> (drop --tap for a static stack)
|
|
20
|
+
<article class="card card--2">
|
|
21
|
+
<div class="card__head">
|
|
22
|
+
<button class="icon-btn icon-btn--sm card__back" aria-label="Back">…</button>
|
|
23
|
+
<h2 class="t-h2 t-caps">Shopping</h2>
|
|
24
|
+
<span class="icon-row">…</span> (optional trailing icons)
|
|
25
|
+
</div>
|
|
26
|
+
…the sheet's own content…
|
|
27
|
+
</article>
|
|
28
|
+
<article class="card">…</article> (last = the front sheet)
|
|
29
|
+
<div class="sheet-stack__base">…</div> (--closed only, optional: what the base shows above the peeks)
|
|
30
|
+
</div>
|
|
31
|
+
--closed (with --tap): nothing open on arrival — every sheet peeks at the
|
|
32
|
+
bottom, the headroom above is the base's (block below).
|
|
33
|
+
Direct children only, five at most (slots are :nth-child below):
|
|
34
|
+
first = the back of the stack, last = the front. README "Sheet stack
|
|
35
|
+
markup". cardds.js opens and closes a --tap stack; without it the
|
|
36
|
+
stack still renders, it just won't open.
|
|
37
|
+
============================================================ */
|
|
38
|
+
|
|
39
|
+
/* ---- the stack: cascading peek sheets; heads peek, the set bleeds to
|
|
40
|
+
the screen's bottom edge, so bottom corners are square ---- */
|
|
41
|
+
/* step = card padding (20) + head (48) + 20 breathing below the title,
|
|
42
|
+
so every peeked strip shows the title with even space above and below */
|
|
43
|
+
/* one track, exactly the stack's own height: an auto track would size itself
|
|
44
|
+
to the tallest sheet's CONTENT (a sheet's `height: 100%` is cyclic while the
|
|
45
|
+
track is being sized), so a long sheet grew the track past the screen and
|
|
46
|
+
every sheet's 100% then resolved against that — the bottom of every open
|
|
47
|
+
sheet sat below the screen, unreachable (timebank home, 2026-09-13). */
|
|
48
|
+
.sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step: 5.5rem; }
|
|
49
|
+
|
|
50
|
+
/* the stack breaks out of the screen inset, edge to edge, always: the peek
|
|
51
|
+
strips and the opened sheet span the screen's full width whatever the
|
|
52
|
+
viewport (it used to bleed only under a 27em media query — a stack in a
|
|
53
|
+
phone frame on a desktop kept the inset, and read as a narrower card;
|
|
54
|
+
Lh, 2026-09-11) */
|
|
55
|
+
.sheet-stack { margin-inline: calc(-1 * var(--screen-pad)); }
|
|
56
|
+
.sheet-stack > .card {
|
|
57
|
+
grid-area: 1 / 1; /* every card shares the stack's cell; later ones offset down */
|
|
58
|
+
border-bottom-left-radius: 0;
|
|
59
|
+
border-bottom-right-radius: 0;
|
|
60
|
+
border-bottom: 0;
|
|
61
|
+
align-content: start;
|
|
62
|
+
align-self: start; /* own natural height (padding + head) by default */
|
|
63
|
+
--card-gap: var(--sp-5); /* head→content gap = the strip's 20px rhythm */
|
|
64
|
+
z-index: var(--level, 1); /* explicit paint order, 1 = back of the stack */
|
|
65
|
+
}
|
|
66
|
+
/* THE FRONT SHEET is the last .card, not the last child: a --closed stack
|
|
67
|
+
may carry a .sheet-stack__base after its sheets (below). Written once here
|
|
68
|
+
as "a card no card follows" so every rule about the front sheet agrees. */
|
|
69
|
+
.sheet-stack > .card:not(:has(~ .card)) {
|
|
70
|
+
align-self: stretch; /* the front card fills the remaining stack height */
|
|
71
|
+
}
|
|
72
|
+
/* --_n: how many sheets the stack holds, read from the DOM by :has so the
|
|
73
|
+
closed geometry needs no script and no per-stack declaration */
|
|
74
|
+
.sheet-stack { --_n: 1; }
|
|
75
|
+
.sheet-stack:has(> .card:nth-child(2)) { --_n: 2; }
|
|
76
|
+
.sheet-stack:has(> .card:nth-child(3)) { --_n: 3; }
|
|
77
|
+
.sheet-stack:has(> .card:nth-child(4)) { --_n: 4; }
|
|
78
|
+
.sheet-stack:has(> .card:nth-child(5)) { --_n: 5; }
|
|
79
|
+
/* a card's place in the deck, declared here rather than measured in JS, so
|
|
80
|
+
a stack renders correctly — right offsets, right paint order — with no
|
|
81
|
+
script at all. --slot is that place in steps; the tap animation reads it
|
|
82
|
+
to know how far up to travel. Five is the practical peek depth. */
|
|
83
|
+
.sheet-stack > .card:nth-child(1) { --slot: 0; --level: 1; }
|
|
84
|
+
.sheet-stack > .card:nth-child(2) { --slot: 1; --level: 2; margin-top: var(--stack-step); }
|
|
85
|
+
.sheet-stack > .card:nth-child(3) { --slot: 2; --level: 3; margin-top: calc(2 * var(--stack-step)); }
|
|
86
|
+
.sheet-stack > .card:nth-child(4) { --slot: 3; --level: 4; margin-top: calc(3 * var(--stack-step)); }
|
|
87
|
+
.sheet-stack > .card:nth-child(5) { --slot: 4; --level: 5; margin-top: calc(4 * var(--stack-step)); }
|
|
88
|
+
|
|
89
|
+
/* stack headers: uniform — [back?] title left, icons right, one height,
|
|
90
|
+
same gap to content on every card */
|
|
91
|
+
.sheet-stack > .card > .card__head {
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: flex-start;
|
|
94
|
+
min-height: var(--tap);
|
|
95
|
+
}
|
|
96
|
+
/* NOT a skeleton head: a peek is one line at its own size, and the display
|
|
97
|
+
stack deliberately runs a second line under the next card. So it opts out of
|
|
98
|
+
the whole skeleton-head treatment — budget, squeeze and clamp. cardds.js
|
|
99
|
+
skips the same cards. */
|
|
100
|
+
.sheet-stack > .card > .card__head :is(h1, h2, h3) {
|
|
101
|
+
font-size: var(--fs-h1);
|
|
102
|
+
line-height: 1;
|
|
103
|
+
letter-spacing: -0.01em;
|
|
104
|
+
margin-right: auto; /* pins the icon cluster right */
|
|
105
|
+
display: block;
|
|
106
|
+
-webkit-line-clamp: none;
|
|
107
|
+
overflow: visible;
|
|
108
|
+
overflow-wrap: normal;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* display stack: the giant title IS the peek — line one shows, line two is
|
|
112
|
+
clipped by the next card (recents / library lists) */
|
|
113
|
+
.sheet-stack--display { --stack-step: 6.5rem; }
|
|
114
|
+
.sheet-stack--display > .card > .card__head { align-items: start; min-height: 0; }
|
|
115
|
+
.sheet-stack--display > .card > .card__head :is(h1, h2, h3) {
|
|
116
|
+
font: var(--type-display);
|
|
117
|
+
letter-spacing: var(--track-display);
|
|
118
|
+
/* the FIRST line sits on the centre of the peek strip (--stack-step tall,
|
|
119
|
+
measured from the card's top edge); line two runs under the next card */
|
|
120
|
+
margin-top: calc((var(--stack-step) - 1lh) / 2 - var(--sp-5));
|
|
121
|
+
}
|
|
122
|
+
/* fanned stack: a toggle opens the stack wider so every peek shows more */
|
|
123
|
+
.sheet-stack--fanned { --stack-step: 9.5rem; }
|
|
124
|
+
|
|
125
|
+
.sheet-stack > .card:has(~ .card) { cursor: pointer; }
|
|
126
|
+
/* a peeked card in a plain stack hides its own body. A --tap stack does
|
|
127
|
+
NOT: there, every card is full height and full content always, and a
|
|
128
|
+
peek is simply the strip of it the next card doesn't cover — the way a
|
|
129
|
+
card in a real deck is never a shorter card, just a mostly hidden one. */
|
|
130
|
+
.sheet-stack:not(.sheet-stack--tap) > .card:has(~ .card):not(.is-open) > :not(.card__head) { display: none; }
|
|
131
|
+
|
|
132
|
+
/* ============================================================
|
|
133
|
+
.sheet-stack--tap — tap a peeked sheet open; a back button returns it.
|
|
134
|
+
|
|
135
|
+
A sheet (a .card — the material, sheet.css's single sheet is the same
|
|
136
|
+
stuff) is rigid: it can move in, move out, or be covered — it can
|
|
137
|
+
never change shape. So every sheet here is the SAME full height,
|
|
138
|
+
always, set once and never touched again by anything. A "peek" is
|
|
139
|
+
not a short sheet; it's a full sheet the next one happens to cover,
|
|
140
|
+
showing only the strip above it, exactly like a sheet of paper part
|
|
141
|
+
way out of a real stack.
|
|
142
|
+
|
|
143
|
+
Because of that, the whole animation is one property: translate.
|
|
144
|
+
Nothing resizes, nothing re-stacks — z-index is the plain resting
|
|
145
|
+
--level order and is never touched, since the order already says
|
|
146
|
+
what has to happen. Cards after the tapped one sit IN FRONT of it,
|
|
147
|
+
so they're what's covering it, so they're what must leave. Cards
|
|
148
|
+
before it are already BEHIND it, so they need do nothing at all
|
|
149
|
+
except let themselves be covered as it rises over them.
|
|
150
|
+
|
|
151
|
+
.is-open — the tapped card, moved up to the top of the stack. Its
|
|
152
|
+
own resting offset is margin-top (below); opening just
|
|
153
|
+
translates that offset back off, landing it flush at
|
|
154
|
+
the stack's top with its full height exactly filling
|
|
155
|
+
to the bottom.
|
|
156
|
+
.is-before — an earlier card, nudged one step down so it slips
|
|
157
|
+
under the next card's resting strip and is covered
|
|
158
|
+
there. Each carries its own --stagger (set in JS: 0
|
|
159
|
+
for the one nearest the tapped card, counting up
|
|
160
|
+
further back) so they tuck away, and later un-tuck,
|
|
161
|
+
one at a time.
|
|
162
|
+
.is-after — a later card, moved out below, together, no stagger.
|
|
163
|
+
============================================================ */
|
|
164
|
+
.sheet-stack--tap > .card {
|
|
165
|
+
height: 100%; /* every card identical and full length, always — the one
|
|
166
|
+
size in this whole component, never toggled, never transitioned. At a
|
|
167
|
+
resting margin-top of n steps it simply overhangs the stack's bottom
|
|
168
|
+
by n steps (clipped by .screen--fill), so translating it up by exactly
|
|
169
|
+
that much lands it filling the stack precisely. */
|
|
170
|
+
overflow: clip;
|
|
171
|
+
transition: translate 0.35s ease; /* the only animated property here */
|
|
172
|
+
transition-delay: calc(var(--stagger, 0) * 70ms); /* 0 for everything
|
|
173
|
+
except a staggered is-before card, set in JS as it tucks or un-tucks */
|
|
174
|
+
}
|
|
175
|
+
.sheet-stack--tap > .card.is-open {
|
|
176
|
+
/* up by its own resting offset — pure movement, nothing resizes */
|
|
177
|
+
translate: 0 calc(-1 * var(--slot, 0) * var(--stack-step));
|
|
178
|
+
cursor: default;
|
|
179
|
+
overflow-y: auto;
|
|
180
|
+
overflow-x: clip;
|
|
181
|
+
overscroll-behavior: contain;
|
|
182
|
+
}
|
|
183
|
+
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
184
|
+
tappable), so its resting offset would leave its last n steps below the
|
|
185
|
+
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
|
186
|
+
alone is cut to the room it actually has, and it scrolls like an opened
|
|
187
|
+
sheet does. Every other sheet keeps the one full height. */
|
|
188
|
+
.sheet-stack--tap:not(.sheet-stack--closed) > .card:not(:has(~ .card)) {
|
|
189
|
+
height: calc(100% - var(--slot, 0) * var(--stack-step));
|
|
190
|
+
overflow-y: auto;
|
|
191
|
+
overflow-x: clip;
|
|
192
|
+
overscroll-behavior: contain;
|
|
193
|
+
}
|
|
194
|
+
/* a scrolling sheet shows no scrollbar (a desktop with a mouse would paint one
|
|
195
|
+
down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself */
|
|
196
|
+
.sheet-stack--tap > .card { scrollbar-width: none; }
|
|
197
|
+
.sheet-stack--tap > .card::-webkit-scrollbar { display: none; }
|
|
198
|
+
.sheet-stack--tap > .card.is-before { translate: 0 var(--stack-step); }
|
|
199
|
+
.sheet-stack--tap > .card.is-after { translate: 0 100vh; }
|
|
200
|
+
|
|
201
|
+
/* ============================================================
|
|
202
|
+
.sheet-stack--closed — nothing open on arrival. Every sheet, the front
|
|
203
|
+
one too, is a peek at the BOTTOM of the stack; the room above the peeks
|
|
204
|
+
is the screen's base, free for whatever the screen wants there (a
|
|
205
|
+
picture, a greeting — timebank home, Lh 2026-09-15). Tap any head and
|
|
206
|
+
that sheet rises over the base to fill the stack; back drops it home.
|
|
207
|
+
|
|
208
|
+
Same rigid material, same one animated property. The sheets keep their
|
|
209
|
+
full height and their margin-top slots; what changes is where they REST:
|
|
210
|
+
translated down by the headroom, 100% − n × step — a card's 100% is the
|
|
211
|
+
stack's height, so the last peek strip ends exactly at the stack's
|
|
212
|
+
bottom edge. Open is translate to −slot × step, as before: the sheet
|
|
213
|
+
lands flush at the top. is-before tucks one step below its resting place.
|
|
214
|
+
============================================================ */
|
|
215
|
+
.sheet-stack--closed { --_headroom: calc(100% - var(--_n) * var(--stack-step)); }
|
|
216
|
+
.sheet-stack--closed > .card { translate: 0 var(--_headroom); cursor: pointer; }
|
|
217
|
+
.sheet-stack--closed > .card.is-before { translate: 0 calc(var(--_headroom) + var(--stack-step)); }
|
|
218
|
+
.sheet-stack--closed > .card.is-open { translate: 0 calc(-1 * var(--slot, 0) * var(--stack-step)); }
|
|
219
|
+
.sheet-stack--closed > .card.is-after { translate: 0 100vh; }
|
|
220
|
+
|
|
221
|
+
/* .sheet-stack__base — the base's content, in the headroom of a closed stack:
|
|
222
|
+
behind every sheet (level 0), the stack's inset put back (the stack bleeds),
|
|
223
|
+
exactly the headroom tall so it can lay its content out against the
|
|
224
|
+
peeks (align-content: end puts it right above them). Written LAST, after
|
|
225
|
+
the sheets, so the sheets' :nth-child slots stay what they are. */
|
|
226
|
+
.sheet-stack > .sheet-stack__base {
|
|
227
|
+
grid-area: 1 / 1;
|
|
228
|
+
align-self: start;
|
|
229
|
+
z-index: 0;
|
|
230
|
+
height: var(--_headroom, 0px);
|
|
231
|
+
padding-inline: var(--screen-pad);
|
|
232
|
+
display: grid;
|
|
233
|
+
align-content: end;
|
|
234
|
+
gap: var(--card-gap, var(--sp-5));
|
|
235
|
+
overflow: clip;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
239
|
+
into it — an opacity fade in place, never a slide in from the left (the
|
|
240
|
+
slide read as a second thing moving while the sheet itself moved; Lh,
|
|
241
|
+
2026-09-11). Only the sheet travels; the button appears where it is. */
|
|
242
|
+
.sheet-stack--tap .card__head {
|
|
243
|
+
position: relative;
|
|
244
|
+
padding-left: 0;
|
|
245
|
+
transition: padding-left 0.35s ease;
|
|
246
|
+
}
|
|
247
|
+
.sheet-stack--tap > .card.is-open > .card__head {
|
|
248
|
+
padding-left: calc(var(--tap-sm) + var(--sp-3));
|
|
249
|
+
}
|
|
250
|
+
.sheet-stack--tap .card__back {
|
|
251
|
+
position: absolute;
|
|
252
|
+
left: 0;
|
|
253
|
+
top: 50%;
|
|
254
|
+
translate: 0 -50%;
|
|
255
|
+
opacity: 0;
|
|
256
|
+
visibility: hidden;
|
|
257
|
+
transition: opacity 0.35s ease, visibility 0.35s;
|
|
258
|
+
}
|
|
259
|
+
.sheet-stack--tap > .card.is-open .card__back {
|
|
260
|
+
opacity: 1;
|
|
261
|
+
visibility: visible;
|
|
262
|
+
}
|
package/css/step.css
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
cardds/step.css — .step: one screen, one task.
|
|
3
|
+
What the member reads (a card), what the member does when the
|
|
4
|
+
step asks for it (a sheet, sheet.css), and the screen's one
|
|
5
|
+
move (.action-bar, actions.css) OUTSIDE the step, below it.
|
|
6
|
+
|
|
7
|
+
<section class="step">
|
|
8
|
+
<article class="card step__card">…the words…</article> ← first
|
|
9
|
+
<article class="card sheet sheet--half">…the controls…</article> ← last, optional
|
|
10
|
+
</section>
|
|
11
|
+
<div class="action-bar">…</div>
|
|
12
|
+
|
|
13
|
+
The step is the box the sheet slides in. It fills the screen
|
|
14
|
+
column between the top bar's reserve (.screen already pads for
|
|
15
|
+
.appbar) and the bar's, and clips at its own bottom edge — so the
|
|
16
|
+
sheet, at every state, stops above the bar and nothing ever sits
|
|
17
|
+
behind it. The card hugs its words and scrolls inside itself when
|
|
18
|
+
the column runs out. The sheet is full-width, like the bar: the
|
|
19
|
+
step bleeds to the screen's edges and the card takes the inset
|
|
20
|
+
back, so the card and the bar's buttons stand on the inset line
|
|
21
|
+
while the sheet runs edge to edge.
|
|
22
|
+
|
|
23
|
+
It needs a bounded column to fill: .screen--fill (base.css), or
|
|
24
|
+
an app section of a definite height. Reads → card only. Acts →
|
|
25
|
+
card + sheet. Never a hand-laid step, never a sheet height.
|
|
26
|
+
============================================================ */
|
|
27
|
+
.step {
|
|
28
|
+
position: relative;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
flex: 1;
|
|
32
|
+
min-height: 0;
|
|
33
|
+
overflow: clip;
|
|
34
|
+
margin-inline: calc(-1 * var(--screen-pad)); /* edge to edge, reading the inset the screen publishes (as .sheet-stack does) */
|
|
35
|
+
container-type: size; /* the sheet's body reads the step's height as 100cqh — sheet.css, --_box.
|
|
36
|
+
(size containment also makes the step the containing block for fixed
|
|
37
|
+
descendants: the bar is outside the step, and a popover is in the top layer) */
|
|
38
|
+
/* --kb: the on-screen keyboard's height, published on :root by cardds.js
|
|
39
|
+
from the visual viewport — 0 without one, and on a platform that shrinks
|
|
40
|
+
the layout viewport itself. It grows the room below the step the same
|
|
41
|
+
way it lifts the bar (actions.css), so the control being typed into and
|
|
42
|
+
the bar both stay above the keyboard. */
|
|
43
|
+
margin-bottom: var(--kb, 0);
|
|
44
|
+
}
|
|
45
|
+
/* the step touches the top bar it follows, so that the sheet at full reaches
|
|
46
|
+
the bar's bottom edge and nothing else: the room the screen would have
|
|
47
|
+
left between them moves INSIDE the step, above the card (--_lead), where
|
|
48
|
+
the sheet can rise over it. After an in-flow .topbar that room is the
|
|
49
|
+
screen's gap; after a fixed .appbar it is the reserve's breathing — both
|
|
50
|
+
read from the tokens the screen publishes, never recomputed. A step that
|
|
51
|
+
follows anything else keeps the screen's gap and the sheet stops under
|
|
52
|
+
that thing. */
|
|
53
|
+
.topbar + .step { margin-top: calc(-1 * var(--screen-gap)); --_lead: var(--screen-gap); }
|
|
54
|
+
.appbar + .step { margin-top: calc(-1 * var(--header-gap)); --_lead: var(--screen-gap); }
|
|
55
|
+
|
|
56
|
+
/* the bar's room — read from actions.css, never recomputed. Margin, not
|
|
57
|
+
padding, so the clip cuts at the box edge and nothing shows behind the
|
|
58
|
+
bar. Any parent that hosts the bar counts — <body>, an app shell, a demo
|
|
59
|
+
frame — the bar is fixed to the viewport wherever it sits. */
|
|
60
|
+
:has(> .action-bar) .step { margin-bottom: calc(var(--bar-reserve) + var(--kb, 0rem)); }
|
|
61
|
+
|
|
62
|
+
/* the reading card: a plain card with a flex rule — it hugs its content,
|
|
63
|
+
and when the column runs out (a short phone, large text) it shrinks and
|
|
64
|
+
scrolls inside itself instead of pushing the bar or the sheet */
|
|
65
|
+
.step__card {
|
|
66
|
+
flex: 0 1 auto;
|
|
67
|
+
min-height: 0;
|
|
68
|
+
margin: var(--_lead, 0) var(--screen-pad) 0; /* the screen's gap, moved in here from above the step; the inset, taken back from the bleed */
|
|
69
|
+
overflow-y: auto;
|
|
70
|
+
overscroll-behavior: contain;
|
|
71
|
+
}
|