@justai/cuts 0.138.0 → 0.140.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/package.json +3 -3
- package/src/ActionList.astro +27 -31
- package/src/ActionRow.astro +64 -68
- package/src/BackToZone.astro +35 -39
- package/src/Catalogue.astro +88 -88
- package/src/CatalogueCard.astro +68 -72
- package/src/ChildPersona.astro +51 -55
- package/src/ConfirmSheet.astro +65 -69
- package/src/FilePicker.astro +13 -17
- package/src/LangPicker.astro +41 -45
- package/src/Posts.astro +169 -173
- package/src/Preroll.astro +193 -197
- package/src/Profile.astro +35 -39
- package/src/Segmented.astro +54 -58
- package/src/Sheet.astro +212 -237
- package/src/Toolbar.astro +280 -284
- package/src/behaviour/catalogue.ts +16 -8
- package/src/ground.css +120 -124
package/src/Preroll.astro
CHANGED
|
@@ -135,216 +135,212 @@ const safeId = "game-" + (id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/
|
|
|
135
135
|
</div>
|
|
136
136
|
|
|
137
137
|
<style>
|
|
138
|
-
/*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
.pr-stage { flex: 1; min-height: 0; }
|
|
164
|
-
.pr-stage :global(iframe) { display: block; width: 100%; height: 100%; border: 0; }
|
|
138
|
+
/* ── THE COMPOSITION RESPONDS TO ITS OWN BOX, NOT TO THE WINDOW ──────────────────────────────
|
|
139
|
+
*
|
|
140
|
+
* queries: preroll responsive · the preroll is cramped · container query · cqw · dynamic preroll ·
|
|
141
|
+
* the game kernel size changes with the device
|
|
142
|
+
*
|
|
143
|
+
* The founder, and it is the constraint the whole layout is built on: *"preroll dynamic bir design.
|
|
144
|
+
* kullanıcının kullandığı device'a göre game kernel layout'un size'ı değişebilir."*
|
|
145
|
+
*
|
|
146
|
+
* So every measurement below is in CONTAINER units. A media query would ask the window how wide it
|
|
147
|
+
* is — and the window is not what this cut lives in: the same viewport gives a 448px frame on a
|
|
148
|
+
* phone and a 638px one inside the desktop column, and a landscape phone gives a frame that is
|
|
149
|
+
* wide and 300px tall. Asking the window produces a composition that is right on one of those.
|
|
150
|
+
*
|
|
151
|
+
* `cqw`/`cqh` with a rem floor and ceiling, never a bare px: `clamp(1.75rem, 12cqw, 4rem)` scales
|
|
152
|
+
* with the box AND keeps iOS Dynamic Type moving the bounds, which the fleet's typography law
|
|
153
|
+
* requires. A pure `cqw` size ignores the reader's own setting entirely. */
|
|
154
|
+
.pr {
|
|
155
|
+
position: relative; flex: 1; min-height: 240px; display: flex; flex-direction: column;
|
|
156
|
+
border-radius: var(--preroll-radius, var(--radius-lg)); overflow: hidden;
|
|
157
|
+
container-type: size;
|
|
158
|
+
container-name: pr;
|
|
159
|
+
}
|
|
160
|
+
.pr-stage { flex: 1; min-height: 0; }
|
|
161
|
+
.pr-stage :global(iframe) { display: block; width: 100%; height: 100%; border: 0; }
|
|
165
162
|
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
/* ── THE GROUND IS THE CARD'S ────────────────────────────────────────────────────────────────
|
|
164
|
+
* queries: why is the preroll dark · preroll background · the glow · the dot lattice
|
|
165
|
+
*
|
|
166
|
+
* The same three layers `builder/card` paints, in the same order: a near-black field, a wide soft
|
|
167
|
+
* bloom of the persona's accent off the top-left with a cooler counter-glow top-right so the field
|
|
168
|
+
* never reads flat, and a barely-there dot lattice. It is dark on every persona, including the ones
|
|
169
|
+
* whose page is light — a game is a room you go into, and the card the ecosystem already prints for
|
|
170
|
+
* this persona is dark. The accent is the persona's, so the fleet stays various without being loud.
|
|
171
|
+
*
|
|
172
|
+
* OPAQUE, and not `--surface`: this covers a game that is drawing itself underneath. */
|
|
173
|
+
/* ── A GRID, NOT A COLUMN OF FLEX ────────────────────────────────────────────────────────────
|
|
174
|
+
* queries: the play button is in the wrong place · landscape preroll · the composition breaks on
|
|
175
|
+
* a wide box · grid-template-areas
|
|
176
|
+
*
|
|
177
|
+
* The four parts have to REGROUP, not just re-order: in a tall box they stack, and in a wide one
|
|
178
|
+
* the copy and the button become one column with the art beside them. Written as a flex row that
|
|
179
|
+
* was measured on a landscape phone, the button became a third column and floated off to the
|
|
180
|
+
* top-right on its own — flex can reorder siblings but it cannot make two of them share a column.
|
|
181
|
+
* Named areas can, and the two layouts are then two grids rather than a pile of overrides. */
|
|
182
|
+
.pr-door {
|
|
183
|
+
position: absolute; inset: 0; z-index: 1;
|
|
184
|
+
display: grid;
|
|
185
|
+
grid-template-columns: 1fr;
|
|
186
|
+
/* ── THE ART TAKES THE TOP HALF; THE COPY BEGINS ON THE CENTRE LINE ────────────────────────
|
|
187
|
+
* queries: preroll composition · where does the copy start · the play button is too low ·
|
|
188
|
+
* the button is under the browser bar · centre line · x-ray grid
|
|
168
189
|
*
|
|
169
|
-
* The
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* whose page is light — a game is a room you go into, and the card the ecosystem already prints for
|
|
173
|
-
* this persona is dark. The accent is the persona's, so the fleet stays various without being loud.
|
|
190
|
+
* The founder, describing it through the x-ray's own grid — which is what that instrument was
|
|
191
|
+
* for: *"logo 1. satırın 2. cell'inde ortada olsa daha iyi olur… içerik de 2. satırın top'undan
|
|
192
|
+
* başlasa daha iyi olur."*
|
|
174
193
|
*
|
|
175
|
-
*
|
|
176
|
-
|
|
177
|
-
*
|
|
178
|
-
* a wide box · grid-template-areas
|
|
194
|
+
* `50%` on the first row is exact rather than approximate: the door's padding is equal top and
|
|
195
|
+
* bottom, so half of its content box IS the frame's centre — the same line the x-ray draws. The
|
|
196
|
+
* art centres in the half above it and the copy starts on it.
|
|
179
197
|
*
|
|
180
|
-
*
|
|
181
|
-
* the copy
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
* bottom, so half of its content box IS the frame's centre — the same line the x-ray draws. The
|
|
199
|
-
* art centres in the half above it and the copy starts on it.
|
|
200
|
-
*
|
|
201
|
-
* AND IT IS WHY THE BUTTON IS SAFE ON A REAL PHONE. The last row takes the slack, so the button
|
|
202
|
-
* sits directly under the copy instead of at the bottom of the frame — which on a real device is
|
|
203
|
-
* where the browser puts its own bar. He sent a screenshot from one: the URL bar sitting over the
|
|
204
|
-
* play button, and a strip of the next section showing beneath it. A composition that ends at the
|
|
205
|
-
* frame's bottom edge is a composition whose call to action a phone can cover. */
|
|
206
|
-
grid-template-rows: 50% auto auto minmax(0, 1fr);
|
|
207
|
-
grid-template-areas: "art" "copy" "play" "imprint";
|
|
208
|
-
padding: clamp(18px, 7cqw, 64px);
|
|
209
|
-
/* And the strip the OS keeps for its home indicator, on top of the inset — never instead of it. */
|
|
210
|
-
padding-block-end: calc(clamp(18px, 7cqw, 64px) + env(safe-area-inset-bottom, 0px));
|
|
211
|
-
background:
|
|
212
|
-
radial-gradient(90cqw 52cqh at 8% -12%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 62%),
|
|
213
|
-
radial-gradient(76cqw 46cqh at 96% 4%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%),
|
|
214
|
-
radial-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px) 0 0 / 24px 24px,
|
|
215
|
-
#080b12;
|
|
216
|
-
color: #f5f7fa;
|
|
217
|
-
}
|
|
218
|
-
.pr-door[hidden] { display: none; }
|
|
198
|
+
* AND IT IS WHY THE BUTTON IS SAFE ON A REAL PHONE. The last row takes the slack, so the button
|
|
199
|
+
* sits directly under the copy instead of at the bottom of the frame — which on a real device is
|
|
200
|
+
* where the browser puts its own bar. He sent a screenshot from one: the URL bar sitting over the
|
|
201
|
+
* play button, and a strip of the next section showing beneath it. A composition that ends at the
|
|
202
|
+
* frame's bottom edge is a composition whose call to action a phone can cover. */
|
|
203
|
+
grid-template-rows: 50% auto auto minmax(0, 1fr);
|
|
204
|
+
grid-template-areas: "art" "copy" "play" "imprint";
|
|
205
|
+
padding: clamp(18px, 7cqw, 64px);
|
|
206
|
+
/* And the strip the OS keeps for its home indicator, on top of the inset — never instead of it. */
|
|
207
|
+
padding-block-end: calc(clamp(18px, 7cqw, 64px) + env(safe-area-inset-bottom, 0px));
|
|
208
|
+
background:
|
|
209
|
+
radial-gradient(90cqw 52cqh at 8% -12%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 62%),
|
|
210
|
+
radial-gradient(76cqw 46cqh at 96% 4%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%),
|
|
211
|
+
radial-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px) 0 0 / 24px 24px,
|
|
212
|
+
#080b12;
|
|
213
|
+
color: #f5f7fa;
|
|
214
|
+
}
|
|
215
|
+
.pr-door[hidden] { display: none; }
|
|
219
216
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
217
|
+
/* THE ART TAKES THE TOP AND SITS TO THE RIGHT, which is the card's own diagonal stood up: art one
|
|
218
|
+
side, type the other. Everything centred is the safe composition and it is the one that reads as
|
|
219
|
+
a placeholder — the card is left-aligned and that is this family's voice. */
|
|
220
|
+
/* CENTRED, not pushed to the edge. The first version put the mark hard right to echo the card's
|
|
221
|
+
diagonal — and the card is a landscape rectangle where that diagonal has room to be one. Stood up
|
|
222
|
+
in a tall frame it stops reading as a composition and starts reading as a thing that drifted. */
|
|
223
|
+
.pr-art {
|
|
224
|
+
grid-area: art; min-height: 0;
|
|
225
|
+
display: flex; align-items: center; justify-content: center;
|
|
226
|
+
}
|
|
227
|
+
.pr-art :global(svg), .pr-art :global(img) {
|
|
228
|
+
display: block; width: auto; height: auto;
|
|
229
|
+
max-width: clamp(96px, 40cqw, 300px);
|
|
230
|
+
max-height: clamp(96px, 34cqh, 300px);
|
|
231
|
+
}
|
|
235
232
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
233
|
+
.pr-copy { grid-area: copy; min-width: 0; }
|
|
234
|
+
.pr-kicker {
|
|
235
|
+
display: flex; align-items: center; gap: 0.55em; margin: 0 0 clamp(10px, 2.4cqh, 26px);
|
|
236
|
+
font-size: clamp(0.75rem, 3.1cqw, 1.05rem); font-weight: 600; color: #8b949e; letter-spacing: 0.01em;
|
|
237
|
+
}
|
|
238
|
+
.pr-kicker i { width: 0.62em; height: 0.62em; border-radius: 50%; background: var(--accent); flex: none; }
|
|
239
|
+
.pr-kicker span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
243
240
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
241
|
+
.pr-name {
|
|
242
|
+
margin: 0 0 clamp(6px, 1.6cqh, 18px);
|
|
243
|
+
font-size: clamp(1.75rem, 13cqw, 4.25rem); font-weight: 700;
|
|
244
|
+
letter-spacing: -0.033em; line-height: 0.98;
|
|
245
|
+
}
|
|
246
|
+
.pr-tag {
|
|
247
|
+
margin: 0 0 clamp(6px, 1.4cqh, 16px);
|
|
248
|
+
font-size: clamp(1rem, 5.2cqw, 1.9rem); font-weight: 700; color: var(--accent);
|
|
249
|
+
letter-spacing: -0.012em; line-height: 1.18;
|
|
250
|
+
}
|
|
251
|
+
/* The card writes its second phrase in ink rather than accent, inside the copy itself. Globalised
|
|
252
|
+
because it arrives through `set:html` and carries no scope attribute — the lesson the home mark
|
|
253
|
+
taught this package the hard way. */
|
|
254
|
+
.pr-tag :global(span) { color: #f5f7fa; }
|
|
255
|
+
.pr-sub {
|
|
256
|
+
margin: 0; font-size: clamp(0.8125rem, 3.4cqw, 1.15rem); font-weight: 500; color: #8b949e;
|
|
257
|
+
letter-spacing: -0.004em;
|
|
258
|
+
}
|
|
262
259
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
260
|
+
/* ── THE BUTTON, AND IT IS ALSO THE PROGRESS ─────────────────────────────────────────────────
|
|
261
|
+
* queries: preroll progress · the loading bar · where does progress show · the button fills
|
|
262
|
+
*
|
|
263
|
+
* One element does both jobs, in one place. `.pr-fill` is a layer inside the button that grows
|
|
264
|
+
* from the left; the label sits above it and changes word. A player who presses a button and
|
|
265
|
+
* watches that same button work is never asked where their press went. */
|
|
266
|
+
.pr-play {
|
|
267
|
+
grid-area: play; justify-self: stretch;
|
|
268
|
+
position: relative; overflow: hidden; isolation: isolate;
|
|
269
|
+
margin-top: clamp(16px, 3.6cqh, 40px);
|
|
270
|
+
font: inherit; font-weight: 650; font-size: clamp(0.9375rem, 4cqw, 1.35rem);
|
|
271
|
+
padding: clamp(12px, 2.4cqh, 22px) clamp(20px, 6cqw, 40px);
|
|
272
|
+
border: 0; border-radius: var(--radius-pill);
|
|
273
|
+
background: var(--accent); color: var(--accent-on); cursor: pointer;
|
|
274
|
+
transition: transform 0.18s var(--ease-spring), opacity 0.2s;
|
|
275
|
+
}
|
|
276
|
+
.pr-play:active { transform: scale(0.97); }
|
|
277
|
+
.pr-play[data-busy] { cursor: default; }
|
|
278
|
+
.pr-play[data-busy]:active { transform: none; }
|
|
279
|
+
.pr-play span { position: relative; z-index: 1; }
|
|
280
|
+
.pr-fill {
|
|
281
|
+
position: absolute; inset: 0 auto 0 0; z-index: 0; width: 0;
|
|
282
|
+
background: color-mix(in srgb, #ffffff 26%, transparent);
|
|
283
|
+
transition: width 0.3s var(--ease-out);
|
|
284
|
+
}
|
|
285
|
+
/* A game that reports nothing gets a sweep rather than a number — honest about not knowing. A bar
|
|
286
|
+
that moves without measuring anything teaches a player that the number means nothing. */
|
|
287
|
+
.pr-play[data-busy][data-indeterminate] .pr-fill {
|
|
288
|
+
width: 42%; animation: pr-sweep 1.15s var(--ease-out) infinite;
|
|
289
|
+
}
|
|
290
|
+
@keyframes pr-sweep { from { transform: translateX(-110%); } to { transform: translateX(250%); } }
|
|
294
291
|
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
.pr-imprint:empty { display: none; }
|
|
293
|
+
.pr-imprint { grid-area: imprint; margin-top: clamp(10px, 2cqh, 22px); }
|
|
297
294
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
.pr-art { justify-content: center; }
|
|
313
|
-
.pr-play { justify-self: start; }
|
|
314
|
-
.pr-name { font-size: clamp(1.75rem, 7cqw, 4.25rem); }
|
|
315
|
-
.pr-tag { font-size: clamp(1rem, 2.9cqw, 1.9rem); }
|
|
316
|
-
.pr-sub { font-size: clamp(0.8125rem, 1.9cqw, 1.15rem); }
|
|
317
|
-
.pr-kicker { font-size: clamp(0.75rem, 1.7cqw, 1.05rem); }
|
|
318
|
-
.pr-play { font-size: clamp(0.9375rem, 2.2cqw, 1.35rem); padding: clamp(12px, 2.2cqh, 22px) clamp(24px, 3.4cqw, 44px); }
|
|
319
|
-
}
|
|
320
|
-
/* A genuinely short box — a landscape phone gives about 312px — drops the third line rather than
|
|
321
|
-
crushing the type, and shrinks the art rather than removing it. Hiding the mark was the first
|
|
322
|
-
attempt and it left the persona anonymous in exactly the orientation somebody plays a game in. */
|
|
323
|
-
@container pr (max-height: 380px) {
|
|
324
|
-
.pr-sub { display: none; }
|
|
325
|
-
.pr-art :global(svg), .pr-art :global(img) { max-width: 22cqw; max-height: 46cqh; }
|
|
326
|
-
.pr-play { margin-top: clamp(10px, 2cqh, 18px); }
|
|
295
|
+
/* ── WIDE AND SHORT: THE CARD'S OWN LAYOUT ───────────────────────────────────────────────────
|
|
296
|
+
* A landscape phone, or a desktop column turned wide, gives a box the vertical stack cannot fill
|
|
297
|
+
* without floating everything in the middle. At that shape the composition becomes the card it
|
|
298
|
+
* came from: type on the left, art on the right, the button under the type. */
|
|
299
|
+
@container pr (min-aspect-ratio: 1/1) and (min-width: 460px) {
|
|
300
|
+
/* The card's own shape: copy and button stacked on the left, art beside them, the whole block
|
|
301
|
+
centred in the box rather than pinned to its top. */
|
|
302
|
+
.pr-door {
|
|
303
|
+
grid-template-columns: 1fr auto;
|
|
304
|
+
grid-template-rows: auto auto auto;
|
|
305
|
+
grid-template-areas: "copy art" "play art" "imprint art";
|
|
306
|
+
align-content: center;
|
|
307
|
+
column-gap: clamp(24px, 5cqw, 72px);
|
|
327
308
|
}
|
|
309
|
+
.pr-art { justify-content: center; }
|
|
310
|
+
.pr-play { justify-self: start; }
|
|
311
|
+
.pr-name { font-size: clamp(1.75rem, 7cqw, 4.25rem); }
|
|
312
|
+
.pr-tag { font-size: clamp(1rem, 2.9cqw, 1.9rem); }
|
|
313
|
+
.pr-sub { font-size: clamp(0.8125rem, 1.9cqw, 1.15rem); }
|
|
314
|
+
.pr-kicker { font-size: clamp(0.75rem, 1.7cqw, 1.05rem); }
|
|
315
|
+
.pr-play { font-size: clamp(0.9375rem, 2.2cqw, 1.35rem); padding: clamp(12px, 2.2cqh, 22px) clamp(24px, 3.4cqw, 44px); }
|
|
316
|
+
}
|
|
317
|
+
/* A genuinely short box — a landscape phone gives about 312px — drops the third line rather than
|
|
318
|
+
crushing the type, and shrinks the art rather than removing it. Hiding the mark was the first
|
|
319
|
+
attempt and it left the persona anonymous in exactly the orientation somebody plays a game in. */
|
|
320
|
+
@container pr (max-height: 380px) {
|
|
321
|
+
.pr-sub { display: none; }
|
|
322
|
+
.pr-art :global(svg), .pr-art :global(img) { max-width: 22cqw; max-height: 46cqh; }
|
|
323
|
+
.pr-play { margin-top: clamp(10px, 2cqh, 18px); }
|
|
324
|
+
}
|
|
328
325
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
.pr-kicker { animation-delay: 0.04s; }
|
|
339
|
-
.pr-name { animation-delay: 0.09s; }
|
|
340
|
-
.pr-tag { animation-delay: 0.14s; }
|
|
341
|
-
.pr-sub { animation-delay: 0.19s; }
|
|
342
|
-
.pr-play { animation-delay: 0.26s; }
|
|
343
|
-
}
|
|
344
|
-
@keyframes pr-rise {
|
|
345
|
-
from { opacity: 0; transform: translateY(10px); }
|
|
346
|
-
to { opacity: 1; transform: none; }
|
|
326
|
+
/* ── THE ENTRANCE ────────────────────────────────────────────────────────────────────────────
|
|
327
|
+
* Transform and opacity only, so nothing here changes a layout — the shell's own lesson about the
|
|
328
|
+
* toolbar's strip applies to anything that animates near a scroll spy. Four beats, ~520ms, and the
|
|
329
|
+
* button is tappable from the first frame regardless: an invitation you have to wait for is not
|
|
330
|
+
* one. */
|
|
331
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
332
|
+
.pr-art, .pr-kicker, .pr-name, .pr-tag, .pr-sub, .pr-play {
|
|
333
|
+
animation: pr-rise 0.52s var(--ease-out) both;
|
|
347
334
|
}
|
|
335
|
+
.pr-kicker { animation-delay: 0.04s; }
|
|
336
|
+
.pr-name { animation-delay: 0.09s; }
|
|
337
|
+
.pr-tag { animation-delay: 0.14s; }
|
|
338
|
+
.pr-sub { animation-delay: 0.19s; }
|
|
339
|
+
.pr-play { animation-delay: 0.26s; }
|
|
340
|
+
}
|
|
341
|
+
@keyframes pr-rise {
|
|
342
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
343
|
+
to { opacity: 1; transform: none; }
|
|
348
344
|
}
|
|
349
345
|
</style>
|
|
350
346
|
|
package/src/Profile.astro
CHANGED
|
@@ -99,44 +99,40 @@ const vars = [
|
|
|
99
99
|
</section>
|
|
100
100
|
|
|
101
101
|
<style>
|
|
102
|
-
/*
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
margin-inline: calc(-1 * max(var(--edge, 16px), env(safe-area-inset-left))) calc(-1 * max(var(--edge, 16px), env(safe-area-inset-right)));
|
|
111
|
-
background: var(--cover, linear-gradient(135deg, var(--accent), transparent));
|
|
112
|
-
position: relative;
|
|
113
|
-
}
|
|
114
|
-
@media (min-width: 640px) { .cover { margin-inline: -28px; } }
|
|
115
|
-
.avatar {
|
|
116
|
-
width: 76px; height: 76px; border-radius: var(--radius-lg); margin-block-start: -40px;
|
|
117
|
-
display: grid; place-items: center; padding: var(--avatar-pad, 0);
|
|
118
|
-
background: var(--avatar-bg, #fff);
|
|
119
|
-
border: 3px solid var(--bg); box-shadow: 0 8px 22px -16px rgba(0, 0, 0, 0.22);
|
|
120
|
-
position: relative;
|
|
121
|
-
/* it is an <a> now — undo the link ground, or a slotted SVG drawn in currentColor turns accent */
|
|
122
|
-
color: inherit; text-decoration: none;
|
|
123
|
-
transition: transform 0.15s var(--ease-out, ease-out);
|
|
124
|
-
}
|
|
125
|
-
/* Pressed, not hovered. The tile is the persona's FACE: growing it under the cursor would animate the
|
|
126
|
-
one object on the page that should hold still. Depressing it on press is the iOS answer — it says
|
|
127
|
-
"this responds" at the moment of contact, on touch as well, where there is no hover to say it. */
|
|
128
|
-
.avatar:active { transform: scale(0.97); }
|
|
129
|
-
.avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
130
|
-
@media (prefers-reduced-motion: reduce) { .avatar { transition: none; } }
|
|
131
|
-
/* the avatar content is slotted (persona-owned) → style it globally */
|
|
132
|
-
.avatar :global(svg) { width: 100%; height: 100%; display: block; }
|
|
133
|
-
.avatar :global(img) { width: 100%; height: 100%; display: block; object-fit: cover; }
|
|
134
|
-
@media (prefers-color-scheme: light) {
|
|
135
|
-
:global(html:not([data-theme="dark"])) .avatar { border-color: #fff; background: var(--avatar-bg-light, var(--avatar-bg, #fff)); }
|
|
136
|
-
}
|
|
137
|
-
.pname { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -0.02em; margin-block-start: 12px; }
|
|
138
|
-
.handle { color: var(--text-dim); font-size: var(--fs-xs); margin-block-start: 2px; }
|
|
139
|
-
.bio { font-size: var(--fs-sm); line-height: 1.5; margin-block-start: 12px; }
|
|
140
|
-
.meta { color: var(--text-dim); font-size: var(--fs-2xs); margin-block-start: 10px; }
|
|
102
|
+
/* ---- PROFILE cut ---- */
|
|
103
|
+
.profile { padding-block-end: 8px; }
|
|
104
|
+
.cover {
|
|
105
|
+
height: var(--cover-h, 132px);
|
|
106
|
+
/* full-bleed to the persona's page edge (see --edge on Toolbar) */
|
|
107
|
+
margin-inline: calc(-1 * max(var(--edge, 16px), env(safe-area-inset-left))) calc(-1 * max(var(--edge, 16px), env(safe-area-inset-right)));
|
|
108
|
+
background: var(--cover, linear-gradient(135deg, var(--accent), transparent));
|
|
109
|
+
position: relative;
|
|
141
110
|
}
|
|
111
|
+
@media (min-width: 640px) { .cover { margin-inline: -28px; } }
|
|
112
|
+
.avatar {
|
|
113
|
+
width: 76px; height: 76px; border-radius: var(--radius-lg); margin-block-start: -40px;
|
|
114
|
+
display: grid; place-items: center; padding: var(--avatar-pad, 0);
|
|
115
|
+
background: var(--avatar-bg, #fff);
|
|
116
|
+
border: 3px solid var(--bg); box-shadow: 0 8px 22px -16px rgba(0, 0, 0, 0.22);
|
|
117
|
+
position: relative;
|
|
118
|
+
/* it is an <a> now — undo the link ground, or a slotted SVG drawn in currentColor turns accent */
|
|
119
|
+
color: inherit; text-decoration: none;
|
|
120
|
+
transition: transform 0.15s var(--ease-out, ease-out);
|
|
121
|
+
}
|
|
122
|
+
/* Pressed, not hovered. The tile is the persona's FACE: growing it under the cursor would animate the
|
|
123
|
+
one object on the page that should hold still. Depressing it on press is the iOS answer — it says
|
|
124
|
+
"this responds" at the moment of contact, on touch as well, where there is no hover to say it. */
|
|
125
|
+
.avatar:active { transform: scale(0.97); }
|
|
126
|
+
.avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
127
|
+
@media (prefers-reduced-motion: reduce) { .avatar { transition: none; } }
|
|
128
|
+
/* the avatar content is slotted (persona-owned) → style it globally */
|
|
129
|
+
.avatar :global(svg) { width: 100%; height: 100%; display: block; }
|
|
130
|
+
.avatar :global(img) { width: 100%; height: 100%; display: block; object-fit: cover; }
|
|
131
|
+
@media (prefers-color-scheme: light) {
|
|
132
|
+
:global(html:not([data-theme="dark"])) .avatar { border-color: #fff; background: var(--avatar-bg-light, var(--avatar-bg, #fff)); }
|
|
133
|
+
}
|
|
134
|
+
.pname { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -0.02em; margin-block-start: 12px; }
|
|
135
|
+
.handle { color: var(--text-dim); font-size: var(--fs-xs); margin-block-start: 2px; }
|
|
136
|
+
.bio { font-size: var(--fs-sm); line-height: 1.5; margin-block-start: 12px; }
|
|
137
|
+
.meta { color: var(--text-dim); font-size: var(--fs-2xs); margin-block-start: 10px; }
|
|
142
138
|
</style>
|