@usableapp/cardds 0.7.103 → 0.7.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/css/actions.css +112 -37
- package/css/base.css +139 -15
- package/css/card.css +331 -86
- package/css/choice.css +25 -33
- package/css/finish.css +29 -28
- package/css/forms.css +6 -14
- package/css/journey.css +8 -6
- package/css/layover.css +26 -24
- package/css/lists.css +8 -6
- package/css/media.css +13 -74
- package/css/numbers.css +37 -32
- package/css/palette.css +2 -2
- package/css/people.css +3 -8
- package/css/post.css +23 -19
- package/css/sheet.css +84 -56
- package/css/stack.css +40 -216
- package/css/stamp.css +41 -29
- package/css/step.css +1 -107
- package/css/sticky.css +43 -41
- package/css/theme.template.css +12 -0
- package/css/tokens.css +101 -26
- package/dist/actions/Chip.d.ts +1 -1
- package/dist/actions/Chip.js +3 -2
- package/dist/actions/IconBtn.d.ts +12 -3
- package/dist/actions/IconBtn.js +9 -3
- package/dist/cardds.css +1094 -881
- package/dist/cards/Card.d.ts +8 -6
- package/dist/cards/Card.js +4 -4
- package/dist/cards/CardFeed.js +7 -9
- package/dist/cards/CardFoot.d.ts +7 -2
- package/dist/cards/CardFoot.js +4 -3
- package/dist/catalog.json +1 -1
- package/dist/journey/JourneyBadge.d.ts +1 -1
- package/dist/journey/JourneyBadge.js +1 -1
- package/dist/layover/Snackbar.js +2 -2
- package/dist/lists/Bubble.d.ts +1 -1
- package/dist/lists/Bubble.js +1 -1
- package/dist/lists/Row.js +5 -7
- package/dist/numbers/Picker.js +11 -18
- package/dist/people/Avatar.d.ts +1 -1
- package/dist/people/Avatar.js +3 -2
- package/dist/post/PostDesk.js +6 -9
- package/dist/post/PostLetter.js +4 -4
- package/dist/pressable.d.ts +9 -0
- package/dist/pressable.js +6 -0
- package/dist/room.d.ts +5 -0
- package/dist/room.js +9 -0
- package/dist/scaffold/Panes.js +8 -14
- package/dist/sheets/Sheet.d.ts +1 -1
- package/dist/sheets/Sheet.js +2 -3
- package/dist/sheets/SheetSteps.js +3 -3
- package/dist/sheets/StackSheet.d.ts +1 -1
- package/dist/sheets/sheetStepsPile.js +2 -3
- package/dist/sheets/stackCard.js +67 -115
- package/dist/state.d.ts +21 -0
- package/dist/state.js +28 -1
- package/dist/sticky/Sticky.js +3 -7
- package/dist/templates/Home.d.ts +1 -1
- package/dist/templates/Splash.js +2 -2
- package/dist/templates/Walkthrough.js +4 -5
- package/docs/reference.md +76 -12
- package/lib/catalog.mjs +23 -7
- package/lint/rules.mjs +7 -5
- package/lint/values.mjs +144 -0
- package/package.json +6 -4
package/css/stack.css
CHANGED
|
@@ -53,22 +53,9 @@
|
|
|
53
53
|
Lh, 2026-09-11). --_n: how many sheets the stack holds, read from the DOM
|
|
54
54
|
by :has below so the closed geometry needs no script and no per-stack
|
|
55
55
|
declaration. */
|
|
56
|
-
.sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step:
|
|
56
|
+
.sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step: calc(var(--tap-base) * 11 / 6); margin-inline: calc(-1 * var(--screen-pad)); --_n: 1; }
|
|
57
57
|
.sheet-stack > .card {
|
|
58
|
-
grid-area: 1 / 1; /* every card shares the stack's cell; later ones offset down */
|
|
59
|
-
border-bottom-left-radius: 0;
|
|
60
|
-
border-bottom-right-radius: 0;
|
|
61
|
-
border-bottom: 0;
|
|
62
|
-
align-content: start;
|
|
63
|
-
align-self: start; /* own natural height (padding + head) by default */
|
|
64
58
|
--card-gap: var(--sp-5); /* head→content gap = the strip's 20px rhythm */
|
|
65
|
-
z-index: var(--level, 1); /* explicit paint order, 1 = back of the stack */
|
|
66
|
-
}
|
|
67
|
-
/* THE FRONT SHEET is the last .card, not the last child: a --closed stack
|
|
68
|
-
may carry a .sheet-stack__base after its sheets (below). Written once here
|
|
69
|
-
as "a card no card follows" so every rule about the front sheet agrees. */
|
|
70
|
-
.sheet-stack > .card:not(:has(~ .card)) {
|
|
71
|
-
align-self: stretch; /* the front card fills the remaining stack height */
|
|
72
59
|
}
|
|
73
60
|
.sheet-stack:has(> .card:nth-child(2)) { --_n: 2; }
|
|
74
61
|
.sheet-stack:has(> .card:nth-child(3)) { --_n: 3; }
|
|
@@ -79,24 +66,17 @@
|
|
|
79
66
|
script at all. --slot is that place in steps; the tap animation reads it
|
|
80
67
|
to know how far up to travel. Five is the practical peek depth. */
|
|
81
68
|
.sheet-stack > .card:nth-child(1) { --slot: 0; --level: 1; }
|
|
82
|
-
.sheet-stack > .card:nth-child(2) { --slot: 1; --level: 2;
|
|
83
|
-
.sheet-stack > .card:nth-child(3) { --slot: 2; --level: 3;
|
|
84
|
-
.sheet-stack > .card:nth-child(4) { --slot: 3; --level: 4;
|
|
85
|
-
.sheet-stack > .card:nth-child(5) { --slot: 4; --level: 5;
|
|
69
|
+
.sheet-stack > .card:nth-child(2) { --slot: 1; --level: 2; }
|
|
70
|
+
.sheet-stack > .card:nth-child(3) { --slot: 2; --level: 3; }
|
|
71
|
+
.sheet-stack > .card:nth-child(4) { --slot: 3; --level: 4; }
|
|
72
|
+
.sheet-stack > .card:nth-child(5) { --slot: 4; --level: 5; }
|
|
86
73
|
|
|
87
|
-
/* stack headers: uniform — [back?] title left, icons right, one height,
|
|
88
|
-
same gap to content on every card */
|
|
89
|
-
.sheet-stack > .card > .card__head {
|
|
90
|
-
align-items: center;
|
|
91
|
-
justify-content: flex-start;
|
|
92
|
-
min-height: var(--tap);
|
|
93
|
-
}
|
|
94
74
|
/* NOT a skeleton head: a peek is one line at its own size, and the display
|
|
95
75
|
stack deliberately runs a second line under the next card. */
|
|
96
76
|
.sheet-stack > .card > .card__head :is(h1, h2, h3) {
|
|
97
77
|
font-size: var(--fs-h1);
|
|
98
|
-
line-height:
|
|
99
|
-
letter-spacing: -
|
|
78
|
+
line-height: var(--lh-solid);
|
|
79
|
+
letter-spacing: var(--track-h2);
|
|
100
80
|
margin-right: auto; /* pins the icon cluster right */
|
|
101
81
|
display: block;
|
|
102
82
|
overflow-wrap: normal;
|
|
@@ -104,17 +84,16 @@
|
|
|
104
84
|
|
|
105
85
|
/* display stack: the giant title IS the peek — line one shows, line two is
|
|
106
86
|
clipped by the next card (recents / library lists) */
|
|
107
|
-
.sheet-stack--display { --stack-step: 6
|
|
108
|
-
.sheet-stack--display > .card > .card__head { align-items: start; min-height: 0; }
|
|
87
|
+
.sheet-stack--display { --stack-step: calc(var(--tap-base) * 13 / 6); }
|
|
109
88
|
.sheet-stack--display > .card > .card__head :is(h1, h2, h3) {
|
|
110
89
|
font: var(--type-display);
|
|
111
90
|
letter-spacing: var(--track-display);
|
|
112
91
|
/* the FIRST line sits on the centre of the peek strip (--stack-step tall,
|
|
113
92
|
measured from the card's top edge); line two runs under the next card */
|
|
114
|
-
margin-top: calc((var(--stack-step) - 1lh) / 2 - var(--sp-5));
|
|
93
|
+
margin-top: calc((var(--stack-step) - 1lh) / 2 - var(--sp-5) - var(--_mark-room)); /* --_mark-room: card.css, the room for the marks over line one */
|
|
115
94
|
}
|
|
116
95
|
/* fanned stack: a toggle opens the stack wider so every peek shows more */
|
|
117
|
-
.sheet-stack--fanned { --stack-step:
|
|
96
|
+
.sheet-stack--fanned { --stack-step: calc(var(--tap-base) * 19 / 6); }
|
|
118
97
|
|
|
119
98
|
/* a peeked card in a plain stack hides its own body. A --tap stack does
|
|
120
99
|
NOT: there, every card is full height and full content always, and a
|
|
@@ -122,76 +101,18 @@
|
|
|
122
101
|
card in a real deck is never a shorter card, just a mostly hidden one. */
|
|
123
102
|
.sheet-stack:not(.sheet-stack--tap) > .card:has(~ .card):not(.is-open) > :not(.card__head) { display: none; }
|
|
124
103
|
|
|
125
|
-
/*
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
A sheet (a .card — the material, sheet.css's single sheet is the same
|
|
129
|
-
stuff) is rigid: it can move in, move out, or be covered — it can
|
|
130
|
-
never change shape. So every sheet here is the SAME full height,
|
|
131
|
-
always, set once and never touched again by anything. A "peek" is
|
|
132
|
-
not a short sheet; it's a full sheet the next one happens to cover,
|
|
133
|
-
showing only the strip above it, exactly like a sheet of paper part
|
|
134
|
-
way out of a real stack.
|
|
135
|
-
|
|
136
|
-
Because of that, the whole animation is one property: translate.
|
|
137
|
-
Nothing resizes, nothing re-stacks — z-index is the plain resting
|
|
138
|
-
--level order and is never touched, since the order already says
|
|
139
|
-
what has to happen. Cards after the tapped one sit IN FRONT of it,
|
|
140
|
-
so they're what's covering it, so they're what must leave. Cards
|
|
141
|
-
before it are already BEHIND it, so they need do nothing at all
|
|
142
|
-
except let themselves be covered as it rises over them.
|
|
143
|
-
|
|
144
|
-
.is-open — the tapped card, moved up to the top of the stack. Its
|
|
145
|
-
own resting offset is margin-top (below); opening just
|
|
146
|
-
translates that offset back off, landing it flush at
|
|
147
|
-
the stack's top with its full height exactly filling
|
|
148
|
-
to the bottom.
|
|
149
|
-
.is-before — an earlier card, nudged one step down so it slips
|
|
150
|
-
under the next card's resting strip and is covered
|
|
151
|
-
there. Each carries its own --stagger (set in JS: 0
|
|
152
|
-
for the one nearest the tapped card, counting up
|
|
153
|
-
further back) so they tuck away, and later un-tuck,
|
|
154
|
-
one at a time.
|
|
155
|
-
.is-after — a later card, moved out below, together, no stagger.
|
|
156
|
-
============================================================ */
|
|
104
|
+
/* ---- .sheet-stack--tap — tap a peeked sheet open; a back button returns it. How its sheets move (one property: translate) is
|
|
105
|
+
told over their rules, where they sit, in card.css. ---- */
|
|
157
106
|
.sheet-stack--tap > .card {
|
|
158
|
-
height: 100%; /* every card identical and full length, always — the one
|
|
159
|
-
size in this whole component, never toggled, never transitioned. At a
|
|
160
|
-
resting margin-top of n steps it simply overhangs the stack's bottom
|
|
161
|
-
by n steps (clipped by .screen--fill), so translating it up by exactly
|
|
162
|
-
that much lands it filling the stack precisely. */
|
|
163
|
-
overflow: clip;
|
|
164
|
-
transition: translate var(--motion-sheet) ease; /* the only animated property here */
|
|
165
|
-
transition-delay: calc(var(--stagger, 0) * 70ms); /* 0 for everything
|
|
166
|
-
except a staggered is-before card: the card renders --stagger as it tucks or un-tucks */
|
|
167
107
|
--_back-size: var(--stack-back-size, calc(var(--tap-base) * 2 / 3)); /* read once, shared by the back button and the head's padding below */
|
|
168
108
|
}
|
|
169
|
-
|
|
170
|
-
/* up by its own resting offset — pure movement, nothing resizes */
|
|
171
|
-
translate: 0 calc(-1 * var(--slot, 0) * var(--stack-step) + var(--_pull, 0px));
|
|
172
|
-
cursor: default;
|
|
173
|
-
overflow-y: auto;
|
|
174
|
-
overflow-x: clip;
|
|
175
|
-
overscroll-behavior: contain;
|
|
176
|
-
}
|
|
177
|
-
.sheet-stack--tap > .card.is-open.is-dragging { transition: none; }
|
|
178
|
-
.sheet-stack--tap > .card > .card__head { touch-action: none; }
|
|
179
|
-
/* a pull in the content, already at the top: the words move down, the card stays. --_refresh is the finger, private.
|
|
180
|
-
A paper body adds its own --sp-6 on top of this (the rule below): the stack's gap under the head is the
|
|
181
|
-
card's colour, so without the white inset the first row sits on that seam. */
|
|
182
|
-
.sheet-stack > .card > .sheet__body { padding-top: var(--_refresh, 0px); transition: padding-top var(--motion-sheet) ease; }
|
|
183
|
-
.sheet-stack--tap > .card.is-open:not(:has(> .sheet__body)) {
|
|
184
|
-
padding-top: calc(var(--card-pad) + var(--_refresh, 0px));
|
|
185
|
-
transition: translate var(--motion-sheet) ease, padding-top var(--motion-sheet) ease;
|
|
186
|
-
}
|
|
187
|
-
.sheet-stack > .card > .sheet__body.is-refreshing,
|
|
188
|
-
.sheet-stack--tap > .card.is-open.is-refreshing:not(:has(> .sheet__body)) { transition: none; }
|
|
109
|
+
|
|
189
110
|
/* the GRIP (Lh 2026-10-05): four small dots at the middle of every sheet's top edge — a sign that this card
|
|
190
111
|
slides up and down. A mark, not a control: nothing to press, nothing added to the sheet (the card's own background, in the
|
|
191
112
|
room above its head). The card renders `.stack-card` when it is a sheet of a tap stack, so the dots are its own.
|
|
192
113
|
At rest they are the card's own ink, faint. Only while THAT sheet reloads (`.is-loading`, from its `refreshing` prop,
|
|
193
|
-
and only while it is open) they dissolve
|
|
194
|
-
Nothing travels. --_grip is the mix, 0
|
|
114
|
+
and only while it is open) they dissolve from the ondark ink to the onlight ink, so a light card and a dark one each get a moment the dots stand out.
|
|
115
|
+
Nothing travels. --_grip is the mix, 0 the ondark ink → 1 the onlight ink, private. */
|
|
195
116
|
@property --_grip { syntax: "<number>"; inherits: false; initial-value: 0; }
|
|
196
117
|
.stack-card {
|
|
197
118
|
--_grip-dot: var(--sp-1);
|
|
@@ -207,8 +128,8 @@
|
|
|
207
128
|
calc(50% + 0.5 * var(--_grip-step)) var(--_grip-y), calc(50% + 1.5 * var(--_grip-step)) var(--_grip-y);
|
|
208
129
|
}
|
|
209
130
|
.stack-card.is-open.is-loading {
|
|
210
|
-
--_grip-ink: color-mix(in srgb,
|
|
211
|
-
animation: stack-grip-dissolve
|
|
131
|
+
--_grip-ink: color-mix(in srgb, var(--ink-onlight) calc(var(--_grip) * 100%), var(--ink-ondark));
|
|
132
|
+
animation: stack-grip-dissolve var(--motion-pulse) var(--ease-in-out) infinite alternate;
|
|
212
133
|
}
|
|
213
134
|
@keyframes stack-grip-dissolve { from { --_grip: 0; } to { --_grip: 1; } }
|
|
214
135
|
/* the grip as ONE LINE (`mark="line"`, Lh 2026-10-06): the single Sheet's line, at the same place and the same length. It reads the
|
|
@@ -228,22 +149,6 @@
|
|
|
228
149
|
@media (prefers-reduced-motion: reduce) {
|
|
229
150
|
.stack-card.is-open.is-loading { animation: none; --_grip-ink: currentColor; }
|
|
230
151
|
}
|
|
231
|
-
/* the front sheet — the one open on arrival — never travels up (it is not
|
|
232
|
-
tappable), so its resting offset would leave its last n steps below the
|
|
233
|
-
screen forever, unreachable by any scroll (timebank home, 2026-09-13). It
|
|
234
|
-
alone is cut to the room it actually has, and it scrolls like an opened
|
|
235
|
-
sheet does. Every other sheet keeps the one full height. */
|
|
236
|
-
.sheet-stack--tap:not(.sheet-stack--closed) > .card:not(:has(~ .card)) {
|
|
237
|
-
height: calc(100% - var(--slot, 0) * var(--stack-step));
|
|
238
|
-
overflow-y: auto;
|
|
239
|
-
overflow-x: clip;
|
|
240
|
-
overscroll-behavior: contain;
|
|
241
|
-
}
|
|
242
|
-
/* a scrolling sheet shows no scrollbar (a desktop with a mouse would paint one
|
|
243
|
-
down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself
|
|
244
|
-
(the hiding rule is base.css's, shared with every other scrolling track) */
|
|
245
|
-
.sheet-stack--tap > .card.is-before { translate: 0 var(--stack-step); }
|
|
246
|
-
.sheet-stack--tap > .card.is-after { translate: 0 100vh; }
|
|
247
152
|
|
|
248
153
|
/* ============================================================
|
|
249
154
|
.sheet-stack--closed — nothing open on arrival. Every sheet, the front
|
|
@@ -260,14 +165,6 @@
|
|
|
260
165
|
lands flush at the top. is-before tucks one step below its resting place.
|
|
261
166
|
============================================================ */
|
|
262
167
|
.sheet-stack--closed { --_headroom: calc(100% - var(--_n) * var(--stack-step)); }
|
|
263
|
-
.sheet-stack--closed > .card { translate: 0 calc(var(--_headroom) - var(--_lift, 0px)); }
|
|
264
|
-
.sheet-stack--closed > .card.is-dragging { transition: none; }
|
|
265
|
-
.sheet-stack--tap:not(.sheet-stack--closed) > .card.is-dragging:not(.is-open) {
|
|
266
|
-
translate: 0 calc(-1 * var(--_lift, 0px));
|
|
267
|
-
transition: none;
|
|
268
|
-
}
|
|
269
|
-
.sheet-stack--closed > .card.is-before { translate: 0 calc(var(--_headroom) + var(--stack-step)); }
|
|
270
|
-
.sheet-stack--closed > .card.is-after { translate: 0 100vh; }
|
|
271
168
|
|
|
272
169
|
/* .sheet-stack__base — the base's content, in the headroom of a closed stack:
|
|
273
170
|
behind every sheet (level 0), the stack's inset put back (the stack bleeds),
|
|
@@ -277,9 +174,9 @@
|
|
|
277
174
|
.sheet-stack > .sheet-stack__base {
|
|
278
175
|
grid-area: 1 / 1;
|
|
279
176
|
align-self: start;
|
|
280
|
-
z-index:
|
|
281
|
-
--_under:
|
|
282
|
-
height: calc(var(--_headroom,
|
|
177
|
+
z-index: var(--z-flat);
|
|
178
|
+
--_under: 0rem;
|
|
179
|
+
height: calc(var(--_headroom, 0rem) + var(--_under));
|
|
283
180
|
padding-inline: var(--screen-pad);
|
|
284
181
|
--_gap: var(--card-gap, var(--sp-5));
|
|
285
182
|
display: grid;
|
|
@@ -296,7 +193,6 @@
|
|
|
296
193
|
/* a base that is ONE Centre is one thing handed over (a note, an empty state): it stands in the MIDDLE of the room above the peeks
|
|
297
194
|
(Lh 2026-10-06). The base already keeps the screen's inset, so the centre block gives its own back. */
|
|
298
195
|
.sheet-stack__base:has(> .screen__centre:only-child) { align-content: center; }
|
|
299
|
-
.sheet-stack__base > .screen__centre { padding-inline: 0; }
|
|
300
196
|
/* loose things rest against the peeks, so the room under the last one is the room between two of them (Lh 2026-10-09: a card's
|
|
301
197
|
bottom edge lay on the first sheet's top edge). A lone Page reads from the top and a lone Centre stands in the middle: neither rests there. */
|
|
302
198
|
.sheet-stack__base:not(:has(> .page:only-child, > .screen__centre:only-child)) {
|
|
@@ -319,9 +215,6 @@
|
|
|
319
215
|
overscroll-behavior: contain;
|
|
320
216
|
touch-action: pan-y;
|
|
321
217
|
}
|
|
322
|
-
/* a card standing in this room is FLAT (Lh 2026-10-09): the room clips at its own edges, and a card's shadow cut there drew a straight
|
|
323
|
-
line under the bar and along the first sheet. Its ground and its radius are what say "card" here. */
|
|
324
|
-
:is([data-palette], [data-finish]) .sheet-stack__base .card { box-shadow: none; }
|
|
325
218
|
|
|
326
219
|
/* ============================================================
|
|
327
220
|
.sheet-stack--flood — a closed stack whose open sheet FLOODS the screen: it rises over the top bar and fills the whole screen
|
|
@@ -343,38 +236,17 @@
|
|
|
343
236
|
============================================================ */
|
|
344
237
|
.screen--fill:has(> .sheet-stack--flood) { --_bar-gap: var(--sp-2); }
|
|
345
238
|
.screen--fill:has(> .sheet-stack--flood):has(> .topbar:first-child) { --_bar-gap: var(--screen-gap); }
|
|
346
|
-
.screen--fill:has(> .sheet-stack--flood) > :is(.appbar, .topbar):first-child {
|
|
347
|
-
position: absolute;
|
|
348
|
-
inset: var(--safe-top) 0 auto;
|
|
349
|
-
max-width: none;
|
|
350
|
-
margin: 0;
|
|
351
|
-
z-index: 0; /* under every sheet (--level 1-5); the base clears it by margin */
|
|
352
|
-
}
|
|
353
239
|
.sheet-stack--flood { --_bar: calc(var(--top-h, var(--header-h)) + var(--_bar-gap)); }
|
|
354
240
|
.sheet-stack--flood > .sheet-stack__base {
|
|
355
241
|
margin-top: var(--_bar);
|
|
356
|
-
height: calc(var(--_headroom,
|
|
242
|
+
height: calc(var(--_headroom, 0rem) - var(--_bar) + var(--_under));
|
|
357
243
|
}
|
|
358
|
-
.screen--fill:has(> .sheet-stack--flood > .stack-card.is-open, > .sheet-stack--flood > :not(.stack-card) > .stack-card.is-open) > :is(.appbar, .topbar):first-child {
|
|
359
|
-
visibility: hidden;
|
|
360
|
-
transition: visibility 0s linear var(--motion-sheet); /* gone once the sheet has covered it; back at once on a fold (the rule above has no delay) */
|
|
361
|
-
}
|
|
362
|
-
/* at the screen's top the open sheet's top corners are SQUARE (Lh 2026-10-08): round, they left two small pockets of the
|
|
363
|
-
bar's ground at the glass. They square as the sheet rises and round again as it folds, in the slide's own time. Paint
|
|
364
|
-
only, the sheet's size is untouched. A pull on the head keeps them square until the finger lets go. */
|
|
365
|
-
.sheet-stack--flood > .card { transition: translate var(--motion-sheet) ease, border-radius var(--motion-sheet) ease; }
|
|
366
|
-
.sheet-stack--flood > .card.is-open { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
367
244
|
|
|
368
245
|
/* back button: lives in a slot the header reserves once open, and DISSOLVES
|
|
369
246
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
370
247
|
slide read as a second thing moving while the sheet itself moved; Lh,
|
|
371
248
|
2026-09-11). Only the sheet travels; the button appears where it is. */
|
|
372
249
|
/* a sheet's OWN head and back: the card is the stack's child or, with one host wrapper, the wrapper's. A card standing in the sheet's body wears its own head */
|
|
373
|
-
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head {
|
|
374
|
-
position: relative;
|
|
375
|
-
padding-left: 0;
|
|
376
|
-
transition: padding-left var(--motion-sheet) ease;
|
|
377
|
-
}
|
|
378
250
|
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head > .sheet-stack__back {
|
|
379
251
|
position: absolute;
|
|
380
252
|
left: 0;
|
|
@@ -382,7 +254,7 @@
|
|
|
382
254
|
translate: 0 -50%;
|
|
383
255
|
opacity: 0;
|
|
384
256
|
visibility: hidden;
|
|
385
|
-
transition: opacity var(--motion-sheet) ease, visibility var(--motion-sheet);
|
|
257
|
+
transition: opacity var(--motion-sheet) var(--ease), visibility var(--motion-sheet);
|
|
386
258
|
/* SMALL (see "the back button, SMALL" below) */
|
|
387
259
|
width: var(--_back-size);
|
|
388
260
|
height: var(--_back-size);
|
|
@@ -395,63 +267,16 @@
|
|
|
395
267
|
visibility: visible;
|
|
396
268
|
}
|
|
397
269
|
|
|
398
|
-
/* ---- a stacked sheet with a SheetBody has the ONE sheet shape (Lh 2026-09-23, the home layout): sheet head
|
|
399
|
-
pinned at the top · sheet body scrolls, as long as it needs · sheet foot (a CardFoot, the CTA) at the bottom.
|
|
400
|
-
The card becomes a flex column and stops scrolling itself — only the body does. A sheet with no SheetBody
|
|
401
|
-
keeps the old way: the whole open card scrolls. ---- */
|
|
402
|
-
.sheet-stack--tap > .card:has(> .sheet__body) { display: flex; flex-direction: column; gap: var(--card-gap); }
|
|
403
|
-
.sheet-stack--tap > .card:has(> .sheet__body).is-open { overflow: clip; }
|
|
404
270
|
.sheet-stack > .card:has(> .sheet__body) > :not(.sheet__body) { flex-shrink: 0; }
|
|
405
271
|
.sheet-stack > .card > .sheet__body { --_cover: 0rem; } /* nothing slid under here: the stack's sheet is always whole */
|
|
406
|
-
/* a
|
|
407
|
-
|
|
408
|
-
shows in that pocket. Open, the body is white, and the first row sits --sp-6 down, the same white
|
|
409
|
-
inset a single paper sheet keeps: the flex gap under the head is the card's colour, so the row
|
|
410
|
-
would otherwise sit on that seam. A pull adds --_refresh on top of the inset. On the way back
|
|
411
|
-
into the stack the card colour fades in quickly (--motion-quick) and that fade ends with the
|
|
412
|
-
sheet's own slide (--motion-sheet, plus the same stagger), so the corner is the head colour
|
|
413
|
-
at the moment the sheet stops. Rising, the body is white at once. */
|
|
414
|
-
/* the head's band is as slim as it can be (Lh 2026-10-06: 20 over · 48 head · 20 under read as a heavy bar): the white starts
|
|
415
|
-
--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
|
|
416
|
-
stack's step and the body's own rows read it. */
|
|
417
|
-
.sheet-stack > .card.sheet--paper > .sheet__body {
|
|
418
|
-
margin-top: calc(var(--sp-3) - var(--card-gap));
|
|
419
|
-
padding-top: calc(var(--sp-6) + var(--_refresh, 0px));
|
|
420
|
-
transition: padding-top var(--motion-sheet) ease, background-color 0s linear;
|
|
421
|
-
}
|
|
422
|
-
/* 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 */
|
|
423
|
-
.sheet-stack > .card.sheet--paper > .sheet__body.is-refreshing { transition: none; }
|
|
424
|
-
.sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
425
|
-
background-color: transparent;
|
|
426
|
-
transition:
|
|
427
|
-
padding-top var(--motion-sheet) ease,
|
|
428
|
-
background-color var(--motion-quick) ease calc(var(--stagger, 0) * 70ms + var(--motion-sheet) - var(--motion-quick));
|
|
429
|
-
}
|
|
430
|
-
@media (prefers-reduced-motion: reduce) {
|
|
431
|
-
.sheet-stack > .card.sheet--paper:not(.is-open) > .sheet__body {
|
|
432
|
-
transition: padding-top var(--motion-sheet) ease, background-color 0s linear;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
/* a paper stack's foot is white to the card's edges, and it covers the tinted flex gap above it so the
|
|
436
|
-
button does not sit on a colour stripe. The head's gap stays: it is outside this foot. The button stays
|
|
437
|
-
where it was — the padding gives back what the bleed takes. */
|
|
438
|
-
.sheet-stack > .card.sheet--paper > .card__foot {
|
|
439
|
-
background: var(--color-paper, #fff);
|
|
440
|
-
color: var(--color-ink, #141414);
|
|
441
|
-
margin-top: calc(-1 * var(--card-gap));
|
|
442
|
-
margin-inline: calc(-1 * var(--card-pad));
|
|
443
|
-
margin-bottom: calc(-1 * var(--card-pad));
|
|
444
|
-
padding-top: var(--card-gap);
|
|
445
|
-
padding-inline: var(--card-pad);
|
|
446
|
-
padding-bottom: var(--card-pad);
|
|
447
|
-
}
|
|
272
|
+
/* a paper stack's foot is paper (card.css paints it): its ink reads the action's side */
|
|
273
|
+
.sheet-stack > .card.sheet--paper > .card__foot { --dark: var(--action-dark); }
|
|
448
274
|
|
|
449
275
|
/* ---- StackSheet's tabs (Lh 2026-09-23): the head's end is a row of small icon buttons, the sheet's pages — the chosen one
|
|
450
276
|
filled (.icon-btn--invert) only while the sheet is open; a count is a small circle over the icon's top right corner ---- */
|
|
451
277
|
.stack-sheet__tabs { display: flex; gap: var(--stack-sheet-tabs-gap, var(--sp-2)); flex: none; } /* room between the tabs: with none, the chosen tab's disc touched its neighbour's (Lh 2026-09-24) */
|
|
452
278
|
/* the head is ONE line, always (Lh 2026-09-24): the tabs keep their size, the TITLE gives — it never wraps, it runs sideways
|
|
453
279
|
(StackSheet measures --_over, the width it overflows by, and sets .is-long), one way: it waits, runs to the end, waits, and starts again from the first letter — like a song title */
|
|
454
|
-
.stack-sheet > .card__head { flex-wrap: nowrap; }
|
|
455
280
|
.stack-sheet__title {
|
|
456
281
|
flex: 1; min-width: 0; overflow-x: clip; overflow-y: visible; white-space: nowrap; overflow-wrap: normal; /* clip SIDEWAYS only:
|
|
457
282
|
hidden clipped the Thai tone marks above a line-height-1 line (เพื่อน lost its ไม้เอก — Lh 2026-09-24) */
|
|
@@ -460,7 +285,7 @@
|
|
|
460
285
|
}
|
|
461
286
|
.stack-sheet__run { display: inline-block; }
|
|
462
287
|
.stack-sheet__title.is-long .stack-sheet__run {
|
|
463
|
-
animation: stack-sheet-run var(--stack-sheet-run-time, var(--_run,
|
|
288
|
+
animation: stack-sheet-run var(--stack-sheet-run-time, var(--_run, var(--motion-run))) var(--ease-in-out) infinite; /* ONE way: start → end, then back to the start at once (Lh 2026-09-24) */ /* --_run: from the overflow, 30px a second + the pauses */
|
|
464
289
|
}
|
|
465
290
|
@keyframes stack-sheet-run { 0%, 20% { translate: 0; } 85%, 100% { translate: calc(-1 * var(--_over)); } }
|
|
466
291
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -473,11 +298,11 @@
|
|
|
473
298
|
/* the disc fades with the sheet: in as it rises, out as it folds, and across when another tab is pressed.
|
|
474
299
|
scale stays on the press (actions.css) so the give under a finger stays quick */
|
|
475
300
|
transition:
|
|
476
|
-
scale var(--motion-press) ease-out,
|
|
477
|
-
background-color var(--motion-sheet) ease,
|
|
478
|
-
color var(--motion-sheet) ease,
|
|
479
|
-
border-color var(--motion-sheet) ease,
|
|
480
|
-
box-shadow var(--motion-sheet) ease;
|
|
301
|
+
scale var(--motion-press) var(--ease-out),
|
|
302
|
+
background-color var(--motion-sheet) var(--ease),
|
|
303
|
+
color var(--motion-sheet) var(--ease),
|
|
304
|
+
border-color var(--motion-sheet) var(--ease),
|
|
305
|
+
box-shadow var(--motion-sheet) var(--ease);
|
|
481
306
|
}
|
|
482
307
|
.stack-sheet__tab > .stack-sheet__count { /* outranks .badge (numbers.css loads after this file) */
|
|
483
308
|
position: absolute;
|
|
@@ -490,7 +315,7 @@
|
|
|
490
315
|
padding: 0 var(--stack-sheet-count-pad-x, 0.25em) var(--stack-sheet-count-lift, 0.1em); /* "99+" keeps its room at every text size;
|
|
491
316
|
the room UNDER the number lifts it onto the circle's middle: a digit has no descender, so a centred line box sets it low (Lh 2026-10-01) */
|
|
492
317
|
font: var(--type-caption);
|
|
493
|
-
line-height:
|
|
318
|
+
line-height: var(--lh-solid);
|
|
494
319
|
pointer-events: none;
|
|
495
320
|
}
|
|
496
321
|
/* a tab not chosen is the icon alone — no ring, no ground (Lh 2026-09-23: "the circle around is not pretty"); the chosen one turns
|
|
@@ -506,22 +331,21 @@
|
|
|
506
331
|
margin around it reaches into the card's padding — and an open head (back · title · three tabs) keeps one line (Lh 2026-09-23) */
|
|
507
332
|
.card__head > .stack-sheet__tabs { margin-right: calc(-1 * var(--sp-2)); }
|
|
508
333
|
|
|
509
|
-
/* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
|
|
510
|
-
tappable"): a SMALL DISC — its ring kept, 32px instead of 44, the chevron in it at 20 — so the head gives it 32px + a gap
|
|
511
|
-
(was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
|
|
512
|
-
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back:not(.sheet-stack__back--end)) { padding-left: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
513
334
|
/* AT THE END (Lh 2026-09-29, the home): after the tabs, chevron down — the open sheet folds down. The head's end gives it its room
|
|
514
335
|
while open; the last tab then sits on the inset (no reach into the padding) */
|
|
515
336
|
.sheet-stack--tap .sheet-stack__back--end { left: auto; right: 0; }
|
|
516
|
-
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) ease; }
|
|
517
|
-
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) { padding-right: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
518
337
|
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) > .stack-sheet__tabs { margin-right: 0; }
|
|
519
|
-
.sheet-stack--tap .sheet-stack__back > .icon {
|
|
338
|
+
.sheet-stack--tap .sheet-stack__back > .icon { --icon-size: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
|
|
520
339
|
.sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--_back-size) - var(--tap-sm)) / 2); }
|
|
521
340
|
|
|
522
341
|
/* the open sheet's title folds it (SheetStack.tsx): it says so to a pointer */
|
|
523
342
|
.sheet-stack--tap > .card.is-open > .card__head > :is(h1, h2, h3) { cursor: pointer; }
|
|
524
|
-
/* a stacked sheet is a control until it is open — tapped, never read into: no text selection (a drag, a double tap, a long press),
|
|
525
|
-
no grey flash, no iOS callout; its head stays one when open. Only the OPEN sheet's body is text to select (Lh 2026-09-23) */
|
|
526
|
-
.sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
527
343
|
.sheet-stack--tap > .card.is-open > :not(.card__head) { -webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); } /* the open sheet reads as the rest of the screen: not selectable (Lh 2026-09-25; it was text) */
|
|
344
|
+
|
|
345
|
+
/* ---- where it sits in what base.css draws (docs/reference.md rule 15) ---- */
|
|
346
|
+
/* min-height: 0 — a flex item's floor is its content, and the stack's content
|
|
347
|
+
is its own full-height sheets, so without it the stack grew to the whole
|
|
348
|
+
screen and its open sheet ran past the bottom edge by the bars' height,
|
|
349
|
+
unreachable by scrolling (timebank home, 2026-09-13). The stack IS the room
|
|
350
|
+
left under the bars; every sheet fills exactly that. */
|
|
351
|
+
.screen--fill > .sheet-stack { flex: 1; min-height: 0; }
|
package/css/stamp.css
CHANGED
|
@@ -33,42 +33,44 @@
|
|
|
33
33
|
position: relative;
|
|
34
34
|
display: block;
|
|
35
35
|
container-type: inline-size;
|
|
36
|
-
color: var(--stamp-ink,
|
|
37
|
-
filter: drop-shadow(var(--stamp-drop,
|
|
36
|
+
color: var(--stamp-ink, var(--ink-onlight));
|
|
37
|
+
filter: drop-shadow(var(--stamp-drop, var(--shadow-paper))); /* drop-shadow follows the teeth; a box-shadow would draw the rectangle */
|
|
38
|
+
rotate: var(--stamp-turn, none);
|
|
39
|
+
animation: var(--stamp-arrive, none);
|
|
38
40
|
}
|
|
39
41
|
.stamp__paper {
|
|
40
42
|
--_pitch: calc(100cqw / 12); /* one tooth: 12 across, 15 down — the holes land whole on every edge. Fewer and larger than a real stamp's: a small stamp's teeth must still read (Lh 2026-10-07; it was 20 × 25) */
|
|
41
43
|
--_hole: 2.6cqw;
|
|
42
44
|
position: absolute;
|
|
43
45
|
inset: 0;
|
|
44
|
-
background-color: var(--stamp-paper-bg,
|
|
46
|
+
background-color: var(--stamp-paper-bg, var(--stamp-paper));
|
|
45
47
|
-webkit-mask: linear-gradient(#000 0 0) center / calc(100% - var(--_pitch)) calc(100% - var(--_pitch)) no-repeat,
|
|
46
48
|
radial-gradient(circle, transparent var(--_hole), #000 calc(var(--_hole) + 0.4cqw)) calc(var(--_pitch) / -2) calc(var(--_pitch) / -2) / var(--_pitch) var(--_pitch);
|
|
47
49
|
mask: linear-gradient(#000 0 0) center / calc(100% - var(--_pitch)) calc(100% - var(--_pitch)) no-repeat,
|
|
48
50
|
radial-gradient(circle, transparent var(--_hole), #000 calc(var(--_hole) + 0.4cqw)) calc(var(--_pitch) / -2) calc(var(--_pitch) / -2) / var(--_pitch) var(--_pitch);
|
|
49
51
|
}
|
|
50
|
-
.stamp__pic { position: absolute; inset: var(--stamp-frame, 8cqw); overflow: hidden; } /* the
|
|
52
|
+
.stamp__pic { position: absolute; inset: var(--stamp-frame, 8cqw); overflow: hidden; } /* the paper's frame round the picture; the ground is the tone class's */
|
|
51
53
|
|
|
52
54
|
/* the pictures, placed by COUNT: where, how wide, how far turned — the layout is the stamp's look, never the app's.
|
|
53
55
|
A share of the picture area; the foot stays clear for the words. */
|
|
54
56
|
.stamp__art { position: absolute; left: var(--_x); top: var(--_y); width: var(--_w); aspect-ratio: 1; translate: -50% -50%; rotate: var(--_r); }
|
|
55
57
|
.stamp__art > * { display: block; width: 100%; height: 100%; }
|
|
56
58
|
.stamp--1 .stamp__art { --_x: 50%; --_y: 40%; --_w: 72%; --_r: 0deg; }
|
|
57
|
-
.stamp--2 .stamp__art:nth-child(1) { --_x: 37%; --_y: 27%; --_w: 58%; --_r: -
|
|
58
|
-
.stamp--2 .stamp__art:nth-child(2) { --_x: 63%; --_y: 56%; --_w: 54%; --_r:
|
|
59
|
-
.stamp--3 .stamp__art:nth-child(1) { --_x: 30%; --_y: 21%; --_w: 50%; --_r: -
|
|
60
|
-
.stamp--3 .stamp__art:nth-child(2) { --_x: 71%; --_y: 31%; --_w: 46%; --_r:
|
|
61
|
-
.stamp--3 .stamp__art:nth-child(3) { --_x: 47%; --_y: 58%; --_w: 54%; --_r: -
|
|
62
|
-
.stamp--4 .stamp__art:nth-child(1) { --_x: 29%; --_y: 19%; --_w: 44%; --_r: -
|
|
63
|
-
.stamp--4 .stamp__art:nth-child(2) { --_x: 71%; --_y: 24%; --_w: 42%; --_r:
|
|
64
|
-
.stamp--4 .stamp__art:nth-child(3) { --_x: 31%; --_y: 57%; --_w: 42%; --_r:
|
|
65
|
-
.stamp--4 .stamp__art:nth-child(4) { --_x: 70%; --_y: 60%; --_w: 44%; --_r: -
|
|
59
|
+
.stamp--2 .stamp__art:nth-child(1) { --_x: 37%; --_y: 27%; --_w: 58%; --_r: calc(var(--tilt-base) * -6); }
|
|
60
|
+
.stamp--2 .stamp__art:nth-child(2) { --_x: 63%; --_y: 56%; --_w: 54%; --_r: calc(var(--tilt-base) * 8); }
|
|
61
|
+
.stamp--3 .stamp__art:nth-child(1) { --_x: 30%; --_y: 21%; --_w: 50%; --_r: calc(var(--tilt-base) * -8); }
|
|
62
|
+
.stamp--3 .stamp__art:nth-child(2) { --_x: 71%; --_y: 31%; --_w: 46%; --_r: calc(var(--tilt-base) * 7); }
|
|
63
|
+
.stamp--3 .stamp__art:nth-child(3) { --_x: 47%; --_y: 58%; --_w: 54%; --_r: calc(var(--tilt-base) * -2); }
|
|
64
|
+
.stamp--4 .stamp__art:nth-child(1) { --_x: 29%; --_y: 19%; --_w: 44%; --_r: calc(var(--tilt-base) * -8); }
|
|
65
|
+
.stamp--4 .stamp__art:nth-child(2) { --_x: 71%; --_y: 24%; --_w: 42%; --_r: calc(var(--tilt-base) * 7); }
|
|
66
|
+
.stamp--4 .stamp__art:nth-child(3) { --_x: 31%; --_y: 57%; --_w: 42%; --_r: calc(var(--tilt-base) * 5); }
|
|
67
|
+
.stamp--4 .stamp__art:nth-child(4) { --_x: 70%; --_y: 60%; --_w: 44%; --_r: calc(var(--tilt-base) * -6); }
|
|
66
68
|
|
|
67
69
|
/* the words on the picture's foot: a small line, then the big name — one line each (clipped sideways only: Thai tone marks stand above the line box) */
|
|
68
70
|
.stamp__cap { position: absolute; inset-inline: 5cqw; bottom: 3.5cqw; display: grid; gap: 0.4cqw; }
|
|
69
71
|
.stamp__words, .stamp__name { display: block; min-width: 0; white-space: nowrap; overflow-x: clip; text-overflow: ellipsis; }
|
|
70
|
-
.stamp__words { --_words: 4.8cqw; font:
|
|
71
|
-
.stamp__name { --_name: 9.5cqw; font:
|
|
72
|
+
.stamp__words { --_words: 4.8cqw; font: var(--fw-medium) var(--_words) / var(--lh-title) var(--font-ui); font-size: var(--stamp-words-font-size, var(--_words)); }
|
|
73
|
+
.stamp__name { --_name: 9.5cqw; font: var(--fw-black) var(--_name) / var(--lh-display) var(--font-display); font-size: var(--stamp-name-font-size, var(--_name)); }
|
|
72
74
|
/* at the stamp's own size and under (and stuck on a note): the small line goes, the name grows */
|
|
73
75
|
/* --bare: the pictures alone, no words — a stamp stuck on something (a note, a postcard). The words belong to the album.
|
|
74
76
|
The pictures take the foot's room: each sits a little lower, so the group stands in the middle of the frame. */
|
|
@@ -82,14 +84,13 @@
|
|
|
82
84
|
|
|
83
85
|
/* --tilt: turned a little on its middle — the one stamp a start screen hands over */
|
|
84
86
|
.stamp--tilt-left, .stamp--tilt-right { margin-block: var(--sp-4); } /* the turned corners and the shadow need room: a centre scrolls and would cut them */
|
|
85
|
-
.stamp--tilt-left { rotate: calc(-1 * var(--stamp-tilt,
|
|
86
|
-
.stamp--tilt-right { rotate: var(--stamp-tilt,
|
|
87
|
+
.stamp--tilt-left { rotate: calc(-1 * var(--stamp-tilt, calc(var(--tilt-base) * 5))); }
|
|
88
|
+
.stamp--tilt-right { rotate: var(--stamp-tilt, calc(var(--tilt-base) * 5)); }
|
|
87
89
|
|
|
88
90
|
/* --tap: the whole stamp is the button (`onClick`); the press and the ring are the PRESS list's (actions.css) */
|
|
89
91
|
.stamp--tap { appearance: none; border: 0; padding: 0; background: none; font: inherit; text-align: start; -webkit-tap-highlight-color: transparent; }
|
|
90
92
|
|
|
91
93
|
/* a stamp — or the empty place for one — handed over in a Centre: its greeting centred under it. The stamp is its own size here as everywhere */
|
|
92
|
-
.screen__centre:has(> :is(.stamp, .stamp-place)) { justify-items: center; text-align: center; }
|
|
93
94
|
|
|
94
95
|
/* ---- stamp place: an empty place for a stamp — an album's gap, "make yours" ----
|
|
95
96
|
<span class="stamp-place"><svg class="stamp-place__edge" aria-hidden="true"><rect/></svg>สร้าง</span> · <button class="stamp-place">…</button> · <span class="stamp-place stamp-place--faint">…</span>
|
|
@@ -183,7 +184,7 @@
|
|
|
183
184
|
background-color: var(--stamp-cell-on-bg, var(--accent));
|
|
184
185
|
color: var(--stamp-cell-on-ink, var(--on-accent));
|
|
185
186
|
}
|
|
186
|
-
.stamp-cell__on > .icon {
|
|
187
|
+
.stamp-cell__on > .icon { --icon-size: 70%; }
|
|
187
188
|
/* --bare: no ground, no padding, no round — the stamps lie on what holds the book, three across as on the ground. The name is the Stamp's own; free places still draw.
|
|
188
189
|
<div class="stamp-book stamp-book--bare"> */
|
|
189
190
|
.stamp-book--bare { padding: 0; border-radius: 0; background: none; }
|
|
@@ -227,24 +228,24 @@
|
|
|
227
228
|
After the rule above, at the same weight: `main` is the one place a stuck stamp reads --stamp-size. */
|
|
228
229
|
.stamp-on--main > .stamp { width: var(--stamp-size, calc(var(--tap) * 2)); }
|
|
229
230
|
.stamp-on--move { will-change: transform; } /* its own layer: Safari left the stamp's shadow behind as a trail along the drag when it was painted with the note (seen on the simulator, 2026-10-07) */
|
|
230
|
-
.stamp-on--move { cursor: grab; touch-action: none; -webkit-user-select: none; user-select: none; transition: scale var(--motion-press) ease-out; }
|
|
231
|
+
.stamp-on--move { cursor: grab; touch-action: none; -webkit-user-select: none; user-select: none; transition: scale var(--motion-press) var(--ease-out); }
|
|
231
232
|
.stamp-on--move:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
232
|
-
.stamp-on.is-held { cursor: grabbing; z-index:
|
|
233
|
+
.stamp-on.is-held { cursor: grabbing; z-index: var(--z-over); scale: var(--scale-lift); } /* lifted off the paper while the finger holds it */
|
|
233
234
|
@media (prefers-reduced-motion: reduce) { .stamp-on.is-held { scale: 1; } }
|
|
234
235
|
|
|
235
236
|
/* ---- the cross that takes a stamp off (`StampOn onRemove`): a small dark disc with an x on the stamp's top right corner ----
|
|
236
237
|
<div class="stamp-on stamp-on--move">…<button class="stamp-on__remove" aria-label="Remove"><span class="stamp-on__remove-disc"><svg class="icon">x</svg></span></button></div>
|
|
237
238
|
The button is the touch target — at least --tap-floor (44) square, invisible, centred on the corner — and the disc inside it is what is seen
|
|
238
239
|
(24). It is NOT turned with the stamp: a hand turns a stamp, a button stays square to the screen, so the cross counter-turns by the stamp's own turn. Like the
|
|
239
|
-
stamp it
|
|
240
|
+
stamp it takes the theme's two inks, the onlight ink as its disc and the ondark ink as its x and ring: seen on any paper and over any picture. */
|
|
240
241
|
.stamp-on__remove-disc {
|
|
241
242
|
display: grid;
|
|
242
243
|
place-items: center;
|
|
243
244
|
width: var(--stamp-on-remove-size, var(--sp-6));
|
|
244
245
|
aspect-ratio: 1;
|
|
245
246
|
border-radius: 50%;
|
|
246
|
-
background-color: var(--stamp-on-remove-bg,
|
|
247
|
-
box-shadow: 0 0 0 var(--sp-0) currentColor,
|
|
247
|
+
background-color: var(--stamp-on-remove-bg, var(--ink-onlight));
|
|
248
|
+
box-shadow: 0 0 0 var(--sp-0) currentColor, var(--shadow-lift);
|
|
248
249
|
}
|
|
249
250
|
.stamp-on__remove {
|
|
250
251
|
--_hit: max(var(--tap-floor), var(--stamp-on-remove-size, var(--sp-6)));
|
|
@@ -261,16 +262,27 @@
|
|
|
261
262
|
border: 0;
|
|
262
263
|
border-radius: 50%;
|
|
263
264
|
background: none;
|
|
264
|
-
color: var(--stamp-on-remove-ink,
|
|
265
|
+
color: var(--stamp-on-remove-ink, var(--ink-ondark));
|
|
265
266
|
font: inherit;
|
|
266
267
|
-webkit-tap-highlight-color: transparent;
|
|
267
268
|
}
|
|
268
|
-
.stamp-on__remove-disc > .icon {
|
|
269
|
-
@media (prefers-reduced-motion: no-preference) { .stamp-on__remove { animation: stamp-on-remove-in var(--motion-press) ease-out; } }
|
|
270
|
-
@keyframes stamp-on-remove-in { from { opacity: 0; scale:
|
|
269
|
+
.stamp-on__remove-disc > .icon { --icon-size: 60%; }
|
|
270
|
+
@media (prefers-reduced-motion: no-preference) { .stamp-on__remove { animation: stamp-on-remove-in var(--motion-press) var(--ease-out); } }
|
|
271
|
+
@keyframes stamp-on-remove-in { from { opacity: 0; scale: var(--scale-arrive); } }
|
|
271
272
|
|
|
272
273
|
/* --arrive: the stamp sticks ITSELF (StampOn `arrive`; a Stamp's `arrive` too, where it already stands) — struck onto its place (`strike`: tokens.css).
|
|
273
274
|
The component reads the animation's name to tell the app it has landed. `scale` and `opacity` leave a --tilt's `rotate` alone. Knob: --stamp-on-arrive-time */
|
|
274
275
|
@media (prefers-reduced-motion: no-preference) {
|
|
275
|
-
.stamp-on--arrive, .stamp--arrive { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) ease-in both; }
|
|
276
|
+
.stamp-on--arrive, .stamp--arrive { animation: strike var(--stamp-on-arrive-time, var(--motion-sheet)) var(--ease-in) both; }
|
|
276
277
|
}
|
|
278
|
+
|
|
279
|
+
/* ---- where it sits in what card.css draws (docs/reference.md rule 15) ---- */
|
|
280
|
+
.card__head > .stamp { grid-column: 2; grid-row: 1 / span 2; margin-inline: 0; }
|
|
281
|
+
/* ---- where it sits in what people.css draws (docs/reference.md rule 15) ---- */
|
|
282
|
+
/* a stamp in the stack is the stamp's small size (--stamp-sm-size: one rule in stamp.css, 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
|
|
283
|
+
lead with a title, a meta line and an end button: at 375 the meta line breaks in two, accepted by Lh (a lap of 16 would hold it; the lap stays 8)
|
|
284
|
+
<span class="avatar-stack"><span class="stamp stamp--bare">…</span><span class="stamp stamp--bare">…</span><span class="avatar-stack__more">+9</span></span> */
|
|
285
|
+
.avatar-stack > .stamp { flex: none; margin-inline: 0; }
|
|
286
|
+
.avatar-stack > .stamp + .stamp { margin-left: calc(-1 * var(--sp-2)); }
|
|
287
|
+
/* ---- where it sits in what post.css draws (docs/reference.md rule 15) ---- */
|
|
288
|
+
.post-corner__stamp > :is(.stamp, .stamp-place) { grid-area: 1 / 1; width: 100%; max-width: none; } /* the corner is the postcard's own size (72 × 90), outside the stamp's two sizes, by the owner's decision (2026-10-11); its box sizes the stamp, which fills it */
|