@justai/cuts 0.133.0 → 0.138.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 +6 -3
- package/src/ActionList.astro +31 -27
- package/src/ActionRow.astro +68 -64
- package/src/BackToZone.astro +39 -35
- package/src/Base.astro +21 -3
- package/src/Catalogue.astro +95 -319
- package/src/CatalogueCard.astro +72 -68
- package/src/ChildPersona.astro +55 -51
- package/src/ConfirmSheet.astro +69 -65
- package/src/FilePicker.astro +21 -378
- package/src/LangPicker.astro +49 -104
- package/src/Posts.astro +177 -396
- package/src/Preroll.astro +204 -487
- package/src/Profile.astro +39 -35
- package/src/Segmented.astro +62 -111
- package/src/Sheet.astro +247 -507
- package/src/Toolbar.astro +288 -362
- package/src/behaviour/catalogue.ts +239 -0
- package/src/behaviour/filePicker.ts +372 -0
- package/src/behaviour/langPicker.ts +70 -0
- package/src/behaviour/posts.ts +234 -0
- package/src/behaviour/preroll.ts +298 -0
- package/src/behaviour/segmented.ts +64 -0
- package/src/behaviour/sheet.ts +300 -0
- package/src/behaviour/toolbar.ts +89 -0
- package/src/ground.css +124 -120
package/src/Toolbar.astro
CHANGED
|
@@ -286,382 +286,308 @@ import AuthMount from "./AuthMount.astro";
|
|
|
286
286
|
</div>
|
|
287
287
|
|
|
288
288
|
<style>
|
|
289
|
-
/*
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
* `position: sticky` NEVER LEAVES FLOW. It reserves its full border box and paints inside it — so
|
|
298
|
-
* the bar's LIVE height positioned everything under it (52 with the title strip shut, 78 open)
|
|
299
|
-
* while `--shell-top` was deliberately frozen at the open height so that nothing would sample the
|
|
300
|
-
* 0.35s animation. The HEIGHT stopped sampling it. THE POSITION NEVER DID. Two numbers that must
|
|
301
|
-
* be equal, written a hundred lines apart in two packages, and their difference is a band of the
|
|
302
|
-
* next section above the fold: measured on the live persona with the scroll untouched, the kernel
|
|
303
|
-
* sat 52px below `#below` instead of 78 and 25.81px of the feed was on screen.
|
|
304
|
-
*
|
|
305
|
-
* The wrapper separates them. The SLOT is sticky and its box is `--shell-top`; the BAR is static
|
|
306
|
-
* inside it and may be any height it likes. Nothing above the kernel changes size any more, so
|
|
307
|
-
* there is nothing for an engine to compensate for — which is also why only a device ever showed
|
|
308
|
-
* it: Blink scroll-anchors the step away and WebKit has no scroll anchoring. */
|
|
309
|
-
.tb-slot { position: sticky; top: 0; z-index: 40; display: flow-root; }
|
|
310
|
-
/* queries: the title strip moves the fold · the page drifts after it lands · a band of posts under
|
|
311
|
-
* the game · tb-slot min-height · the slot follows the bar · the strip pushes the page down
|
|
312
|
-
*
|
|
313
|
-
* ── THE BOX IS THE SLOT'S OWN, SO IT TRAVELS WITH THE SLOT ─────────────────────────────────────
|
|
314
|
-
*
|
|
315
|
-
* `position: sticky` never leaves flow: it reserves its full border box, so whatever the bar's LIVE
|
|
316
|
-
* height is at any instant, that is where everything under it sits. The strip opens and closes over
|
|
317
|
-
* 0.35s inside that box — so without a reserved height the shell breathes 26px and the whole
|
|
318
|
-
* document below it breathes with it. That is the drift the founder lost days to.
|
|
319
|
-
*
|
|
320
|
-
* IT LIVED IN `@justai/ui`'s base.css UNTIL 2026-08-06, and measured on pin.games that was the same
|
|
321
|
-
* as not existing: a persona that never imports base.css got the slot's structure from here and its
|
|
322
|
-
* BOX from a file it does not load, so the slot collapsed to the bar and posts moved 26px between
|
|
323
|
-
* the kernel and the profile. The first law again — a component brings its own ground — and the
|
|
324
|
-
* slot is this component's element, so its box is this component's business. The rule that keeps
|
|
325
|
-
* the whole strip safe cannot be the one rule that lives somewhere the strip does not.
|
|
326
|
-
*
|
|
327
|
-
* AND THE RESERVED BAND WEARS THE BAR'S OWN MATERIAL, not the room's. A slot taller than its bar is
|
|
328
|
-
* 26px of SOMETHING under the shelf whenever the strip is shut — the state at the profile, on
|
|
329
|
-
* screen, at rest. Painted in the persona's ground it was a hard black band between a light profile
|
|
330
|
-
* and a light kernel: the founder photographed it within a minute. It is the BAR'S box, so it takes
|
|
331
|
-
* the bar's fill, bleeding by the same `--edge` expression so the two cannot leave slivers. */
|
|
332
|
-
.tb-slot { min-height: var(--shell-top, 78px); }
|
|
333
|
-
.tb-slot::before {
|
|
334
|
-
content: "";
|
|
335
|
-
position: absolute;
|
|
336
|
-
z-index: -1;
|
|
337
|
-
inset-block: 0;
|
|
338
|
-
inset-inline:
|
|
339
|
-
calc(-1 * max(var(--edge, 16px), env(safe-area-inset-left)))
|
|
340
|
-
calc(-1 * max(var(--edge, 16px), env(safe-area-inset-right)));
|
|
341
|
-
background: var(--toolbar-bg, #1e202a);
|
|
342
|
-
}
|
|
343
|
-
/* The shelf's fill has two branches and so must its box: `--toolbar-bg` is one token but its
|
|
344
|
-
FALLBACK differs by scheme, and a band carrying only the dark default is a black strip on a light
|
|
345
|
-
page — which is exactly what the founder photographed a minute after the first attempt. */
|
|
346
|
-
@media (prefers-color-scheme: light) {
|
|
347
|
-
/* Guarded like every light fork in the fleet: data-theme="dark" wins over the media query. */
|
|
348
|
-
:global(html:not([data-theme="dark"])) .tb-slot::before { background: var(--toolbar-bg, #fbfcfe); }
|
|
349
|
-
}
|
|
350
|
-
.toolbar {
|
|
351
|
-
/* position, top and z-index live on .tb-slot above — this element is static inside it now. */
|
|
352
|
-
display: flex; flex-direction: column;
|
|
353
|
-
/* full-bleed: the bar (frosted bg + hairline) touches the contained-view edges; the icons stay
|
|
354
|
-
aligned with the body via matching inline padding. --edge is the persona's page inline padding
|
|
355
|
-
(16px default; a persona that pads its body differently — e.g. time at 20px — sets --edge so the
|
|
356
|
-
bar bleeds to the SAME edge as its content instead of a hardcoded 16px). */
|
|
357
|
-
margin-inline: calc(-1 * max(var(--edge, 16px), env(safe-area-inset-left))) calc(-1 * max(var(--edge, 16px), env(safe-area-inset-right)));
|
|
358
|
-
padding-block: max(8px, env(safe-area-inset-top)) 8px;
|
|
359
|
-
padding-inline: max(var(--edge, 16px), env(safe-area-inset-left)) max(var(--edge, 16px), env(safe-area-inset-right));
|
|
360
|
-
|
|
361
|
-
/* ── THE BAR IS A SHELF, NOT A TINT ──────────────────────────────────────────────────────
|
|
289
|
+
/* Scoped by Astro's hash AND placed in the cascade: a persona overriding this never has to
|
|
290
|
+
out-specify it. See @justai/ui/tokens.css for the order, and for what deliberately stays out. */
|
|
291
|
+
@layer justai.components {
|
|
292
|
+
/* ---- app toolbar — sticky to the top of the active region (kernel + posts); it never
|
|
293
|
+
enters the Profile (it lives inside .below). Flat / knife-cut: a left group and a
|
|
294
|
+
right group, no rounded pills. ---- */
|
|
295
|
+
/* queries: tb-slot · why is the toolbar wrapped · the bar changes height · the kernel is 26px
|
|
296
|
+
* short · a band of the feed at the bottom · the toolbar reserves a box · sticky flow
|
|
362
297
|
*
|
|
363
|
-
*
|
|
364
|
-
* the bar looks flat against the column · toolbar material · --toolbar-bg
|
|
298
|
+
* ── THE SLOT IS WHAT FLOW SEES; THE BAR IS WHAT THE EYE SEES ──────────────────────────────
|
|
365
299
|
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
300
|
+
* `position: sticky` NEVER LEAVES FLOW. It reserves its full border box and paints inside it — so
|
|
301
|
+
* the bar's LIVE height positioned everything under it (52 with the title strip shut, 78 open)
|
|
302
|
+
* while `--shell-top` was deliberately frozen at the open height so that nothing would sample the
|
|
303
|
+
* 0.35s animation. The HEIGHT stopped sampling it. THE POSITION NEVER DID. Two numbers that must
|
|
304
|
+
* be equal, written a hundred lines apart in two packages, and their difference is a band of the
|
|
305
|
+
* next section above the fold: measured on the live persona with the scroll untouched, the kernel
|
|
306
|
+
* sat 52px below `#below` instead of 78 and 25.81px of the feed was on screen.
|
|
371
307
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
308
|
+
* The wrapper separates them. The SLOT is sticky and its box is `--shell-top`; the BAR is static
|
|
309
|
+
* inside it and may be any height it likes. Nothing above the kernel changes size any more, so
|
|
310
|
+
* there is nothing for an engine to compensate for — which is also why only a device ever showed
|
|
311
|
+
* it: Blink scroll-anchors the step away and WebKit has no scroll anchoring. */
|
|
312
|
+
.tb-slot { position: sticky; top: 0; z-index: 40; display: flow-root; }
|
|
313
|
+
/* queries: the title strip moves the fold · the page drifts after it lands · a band of posts under
|
|
314
|
+
* the game · tb-slot min-height · the slot follows the bar · the strip pushes the page down
|
|
376
315
|
*
|
|
377
|
-
*
|
|
378
|
-
* two shadows — and BOTH left a comment saying it belongs in cuts. Two consumers is the
|
|
379
|
-
* promotion trigger, and the note the second one wrote is the specification:
|
|
316
|
+
* ── THE BOX IS THE SLOT'S OWN, SO IT TRAVELS WITH THE SLOT ─────────────────────────────────────
|
|
380
317
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
318
|
+
* `position: sticky` never leaves flow: it reserves its full border box, so whatever the bar's LIVE
|
|
319
|
+
* height is at any instant, that is where everything under it sits. The strip opens and closes over
|
|
320
|
+
* 0.35s inside that box — so without a reserved height the shell breathes 26px and the whole
|
|
321
|
+
* document below it breathes with it. That is the drift the founder lost days to.
|
|
383
322
|
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
323
|
+
* IT LIVED IN `@justai/ui`'s base.css UNTIL 2026-08-06, and measured on pin.games that was the same
|
|
324
|
+
* as not existing: a persona that never imports base.css got the slot's structure from here and its
|
|
325
|
+
* BOX from a file it does not load, so the slot collapsed to the bar and posts moved 26px between
|
|
326
|
+
* the kernel and the profile. The first law again — a component brings its own ground — and the
|
|
327
|
+
* slot is this component's element, so its box is this component's business. The rule that keeps
|
|
328
|
+
* the whole strip safe cannot be the one rule that lives somewhere the strip does not.
|
|
329
|
+
*
|
|
330
|
+
* AND THE RESERVED BAND WEARS THE BAR'S OWN MATERIAL, not the room's. A slot taller than its bar is
|
|
331
|
+
* 26px of SOMETHING under the shelf whenever the strip is shut — the state at the profile, on
|
|
332
|
+
* screen, at rest. Painted in the persona's ground it was a hard black band between a light profile
|
|
333
|
+
* and a light kernel: the founder photographed it within a minute. It is the BAR'S box, so it takes
|
|
334
|
+
* the bar's fill, bleeding by the same `--edge` expression so the two cannot leave slivers. */
|
|
335
|
+
.tb-slot { min-height: var(--shell-top, 78px); }
|
|
336
|
+
.tb-slot::before {
|
|
337
|
+
content: "";
|
|
338
|
+
position: absolute;
|
|
339
|
+
z-index: -1;
|
|
340
|
+
inset-block: 0;
|
|
341
|
+
inset-inline:
|
|
342
|
+
calc(-1 * max(var(--edge, 16px), env(safe-area-inset-left)))
|
|
343
|
+
calc(-1 * max(var(--edge, 16px), env(safe-area-inset-right)));
|
|
344
|
+
background: var(--toolbar-bg, #1e202a);
|
|
396
345
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
*
|
|
415
|
-
* So the number is a custom property rather than a literal: this rule uses it, and @justai/core's
|
|
416
|
-
* `personaScroll` reads it to compute the row's EXPANDED height without waiting for or racing the
|
|
417
|
-
* transition — `toolbar - strip` is the row, and the row plus this is what the bar will be. One
|
|
418
|
-
* declaration, two readers, and nothing samples a moving value. */
|
|
419
|
-
.toolbar { --tb-strip: 26px; }
|
|
420
|
-
/* queries: the bar grows after load · why does the strip animate on arrival · data-motion ·
|
|
421
|
-
* first paint animation · can this transition wait for the first interaction
|
|
422
|
-
*
|
|
423
|
-
* ── THE TRANSITION IS ALSO A DAMPER, WHICH IS WHY IT CANNOT SIMPLY WAIT ─────────────────────
|
|
424
|
-
*
|
|
425
|
-
* The founder asked the obvious and right question: *"şu geçiş süresi güzel — ama neden bu özellik
|
|
426
|
-
* kullanıcının ilk interaction'ından sonra aktif edilmiyor?"* A persona lands on its kernel, so the
|
|
427
|
-
* strip's correct state on the first frame is open; animating into it grows the bar 26px just after
|
|
428
|
-
* the page appears, which is a glitch nobody asked for.
|
|
429
|
-
*
|
|
430
|
-
* IT WAS TRIED, AND IT BREAKS SOMETHING ELSE. Gated behind a `data-motion` flag set on the first
|
|
431
|
-
* pointer/key/wheel, the strip snaps open during the landing — and that 26px arrives in a SINGLE
|
|
432
|
-
* frame, inside `#below`, while the scroll spy is deciding which cut is active. That is the
|
|
433
|
-
* bistable oscillator `personaScroll` documents at length: the spy measures a page its own output
|
|
434
|
-
* moved. Measured: with the gate in place, `#navProfile` no longer navigates — the page re-asserts
|
|
435
|
-
* the kernel and stays. The 0.35s is not only easing; it is what keeps that step out of one frame.
|
|
436
|
-
*
|
|
437
|
-
* SO THE HEIGHT IS NEVER READ INSTEAD. `--shell-top` is computed as `row + --tb-strip` — the row
|
|
438
|
-
* being the bar minus whatever the strip currently is, which is the same number at every frame of
|
|
439
|
-
* the transition. Nothing samples the animation, so nothing depends on when it was sampled.
|
|
440
|
-
*
|
|
441
|
-
* The real fix for the founder's question is a toolbar whose height does not change at all: reserve
|
|
442
|
-
* the strip's space always and fade only its content. That removes the feedback at the source and
|
|
443
|
-
* makes the gate free — and it is a fleet-wide design decision about how the bar looks at the
|
|
444
|
-
* profile, so it is his to take rather than one to slip in. */
|
|
445
|
-
.tb-title { display: flex; align-items: center; height: 0; overflow: hidden; opacity: 0; transition: height 0.35s var(--ease-out), opacity 0.25s var(--ease-out); }
|
|
446
|
-
/* The fallback is not decoration: the token is declared on `.toolbar` and consumed here, which is
|
|
447
|
-
a different selector — so the framework's own token spec reads it as ungrounded and is right to.
|
|
448
|
-
A rule that silently collapses to `height: 0` would close the strip and say nothing. */
|
|
449
|
-
.tb-title.show { height: 26px; height: var(--tb-strip, 26px); opacity: 1; }
|
|
450
|
-
.tb-title-text { font-size: var(--fs-sm); font-weight: 600; letter-spacing: -0.01em; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: opacity 0.16s var(--ease-out); }
|
|
451
|
-
.tb-title-text.fade { opacity: 0; }
|
|
452
|
-
/* nav lives on the left, beside the back affordance — the middle is intentionally empty */
|
|
453
|
-
.tb-left { display: flex; align-items: center; gap: 2px; min-width: 0; }
|
|
454
|
-
.tb-nav-group { display: flex; align-items: center; gap: 2px; }
|
|
455
|
-
.tb-right { display: flex; align-items: center; min-width: 0; }
|
|
456
|
-
|
|
457
|
-
/* ── the withdraw: what this row does while a game is playing ───────────────────────────────
|
|
458
|
-
* Opacity and pointer-events here; the INERT that takes them out of the tab order is set in the
|
|
459
|
-
* script, because CSS cannot. The transition matters more than it looks: chrome that snaps away
|
|
460
|
-
* reads as breaking, chrome that withdraws reads as dimming the house lights. */
|
|
461
|
-
/* ── HOW THIS ROW GOES QUIET WHILE A GAME PLAYS: IT DOES NOT ─────────────────────────────────
|
|
462
|
-
* queries: toolbar during a game · theater · toolbar looks disabled
|
|
463
|
-
*
|
|
464
|
-
* Four versions in one day, each struck by the founder, and the last one is no CSS at all. A
|
|
465
|
-
* pause handle in the row; then inert; then faded ("disable gibi gozukmemeli - tiklanabiliyorsa");
|
|
466
|
-
* then stepped under a custom dim layer. The standing answer: the stage switches the page to the
|
|
467
|
-
* persona's own DARK THEME (data-theme on html, see Preroll), and this row simply wears it - the
|
|
468
|
-
* same bar every dark-system visitor already sees, fully alive, auto-pausing the game when
|
|
469
|
-
* touched. Nothing here needs to know a game exists. */
|
|
470
|
-
/* icon-only section nav — current cut highlighted */
|
|
471
|
-
.tb-nav {
|
|
472
|
-
display: grid; place-items: center; width: 38px; height: 36px;
|
|
473
|
-
color: var(--text-dim);
|
|
474
|
-
transition: color 0.2s, opacity 0.2s, transform 0.2s var(--ease-spring);
|
|
475
|
-
}
|
|
476
|
-
.tb-nav:hover { color: var(--text); }
|
|
477
|
-
.tb-nav:active { transform: scale(0.9); }
|
|
478
|
-
/* The active glyph is a graphic, so it wants 3:1 rather than 4.5 — which a light accent still
|
|
479
|
-
misses. Same ink hook as everywhere the accent stops being paint. */
|
|
480
|
-
.tb-nav.active { color: var(--accent-text, var(--accent)); }
|
|
481
|
-
|
|
482
|
-
/* queries: should the persona mark be coloured · the mark is always accent · two icons look active
|
|
483
|
-
THE MARK IS A CUT ICON LIKE THE OTHER TWO — the founder's rule, 2026-08-03: the ACTIVE cut wears
|
|
484
|
-
the accent and no passive one does, the persona's own mark included.
|
|
485
|
-
|
|
486
|
-
This comment used to argue the opposite: *"the three stroke icons are generic glyphs whose colour
|
|
487
|
-
IS a UI state; a mark is an identity… a faded logo reads as disabled."* Kept, because it is a true
|
|
488
|
-
premise ageing into a wrong conclusion. It was true while the mark arrived as an `<img>`, which
|
|
489
|
-
cannot take `currentColor` — dimming was then the only way to say "not here", and a dimmed logo
|
|
490
|
-
does read as disabled. The premise expired when `toolbarMark` began deriving an inline SVG: an
|
|
491
|
-
inline mark can be INK, and ink is not dimming. `toolbarMark` neutralises its paint, so the two
|
|
492
|
-
rules below reach it exactly as they reach the bolt and the bubble.
|
|
346
|
+
/* The shelf's fill has two branches and so must its box: `--toolbar-bg` is one token but its
|
|
347
|
+
FALLBACK differs by scheme, and a band carrying only the dark default is a black strip on a light
|
|
348
|
+
page — which is exactly what the founder photographed a minute after the first attempt. */
|
|
349
|
+
@media (prefers-color-scheme: light) {
|
|
350
|
+
/* Guarded like every light fork in the fleet: data-theme="dark" wins over the media query. */
|
|
351
|
+
:global(html:not([data-theme="dark"])) .tb-slot::before { background: var(--toolbar-bg, #fbfcfe); }
|
|
352
|
+
}
|
|
353
|
+
.toolbar {
|
|
354
|
+
/* position, top and z-index live on .tb-slot above — this element is static inside it now. */
|
|
355
|
+
display: flex; flex-direction: column;
|
|
356
|
+
/* full-bleed: the bar (frosted bg + hairline) touches the contained-view edges; the icons stay
|
|
357
|
+
aligned with the body via matching inline padding. --edge is the persona's page inline padding
|
|
358
|
+
(16px default; a persona that pads its body differently — e.g. time at 20px — sets --edge so the
|
|
359
|
+
bar bleeds to the SAME edge as its content instead of a hardcoded 16px). */
|
|
360
|
+
margin-inline: calc(-1 * max(var(--edge, 16px), env(safe-area-inset-left))) calc(-1 * max(var(--edge, 16px), env(safe-area-inset-right)));
|
|
361
|
+
padding-block: max(8px, env(safe-area-inset-top)) 8px;
|
|
362
|
+
padding-inline: max(var(--edge, 16px), env(safe-area-inset-left)) max(var(--edge, 16px), env(safe-area-inset-right));
|
|
493
363
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
364
|
+
/* ── THE BAR IS A SHELF, NOT A TINT ──────────────────────────────────────────────────────
|
|
365
|
+
*
|
|
366
|
+
* queries: the toolbar reads as a black strip · why is the bar darker in dark than in light ·
|
|
367
|
+
* the bar looks flat against the column · toolbar material · --toolbar-bg
|
|
368
|
+
*
|
|
369
|
+
* It used to derive its fill from `--bg` — `color-mix(--bg 70%, transparent)` under a blur.
|
|
370
|
+
* One declaration, but it meant two different things: `--bg` is a near-white in light and #000
|
|
371
|
+
* in dark, so the same rule read "lighten a near-white a little" on one ground and "pull a
|
|
372
|
+
* lifted surface to pure black" on the other. Measured against the column it sits on: **-6 in
|
|
373
|
+
* light, -17 in dark.** That asymmetry was a by-product of the token, never a decision.
|
|
374
|
+
*
|
|
375
|
+
* So it takes the CARD's material instead, and the light comes from above like everything else:
|
|
376
|
+
* an opaque fill with a shadow cast downward, and the page sliding under it. The fill is opaque
|
|
377
|
+
* ON PURPOSE — the blur was inert in light anyway, because the column is 92% opaque and there is
|
|
378
|
+
* nothing behind it to refract.
|
|
379
|
+
*
|
|
380
|
+
* WHY IT IS HERE NOW. app.zone settled this, qr copied it verbatim — same two hex values, same
|
|
381
|
+
* two shadows — and BOTH left a comment saying it belongs in cuts. Two consumers is the
|
|
382
|
+
* promotion trigger, and the note the second one wrote is the specification:
|
|
383
|
+
*
|
|
384
|
+
* "the right end state is cuts reading `var(--toolbar-bg, <its current expression>)`,
|
|
385
|
+
* after which these four declarations delete themselves and the tokens alone do the work."
|
|
386
|
+
*
|
|
387
|
+
* The token remains the seam: a persona whose ground is genuinely different sets `--toolbar-bg`
|
|
388
|
+
* and `--toolbar-shadow` and this rule steps aside. */
|
|
389
|
+
background: var(--toolbar-bg, #1e202a);
|
|
390
|
+
box-shadow: var(--toolbar-shadow, 0 2px 12px -2px rgba(0, 0, 0, 0.55));
|
|
391
|
+
/* No hairline: it was the only thing separating the bar from the column, and the shadow does
|
|
392
|
+
that now. A line under an OPAQUE shelf reads as a seam rather than as a separation. */
|
|
393
|
+
border-block-end: none;
|
|
394
|
+
}
|
|
395
|
+
@media (prefers-color-scheme: light) {
|
|
396
|
+
:global(html:not([data-theme="dark"])) .toolbar {
|
|
397
|
+
background: var(--toolbar-bg, #fbfcfe);
|
|
398
|
+
box-shadow: var(--toolbar-shadow, 0 1px 2px rgba(16, 24, 40, 0.06), 0 8px 20px -8px rgba(16, 24, 40, 0.16));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/* `.stuck` is still set by the script below and still means "content is under the bar" — it simply
|
|
402
|
+
has nothing to paint now that the shelf is opaque and always shadowed. Kept because the class is
|
|
403
|
+
part of this cut's contract and a consumer may hang its own behaviour on it. */
|
|
404
|
+
@media (min-width: 640px) { .toolbar { margin-inline: -28px; padding-inline: 28px; } }
|
|
405
|
+
/* icons row + the scroll-reactive title strip below it */
|
|
406
|
+
.tb-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
407
|
+
/* queries: --tb-strip · the title strip's height · the toolbar changes height · 26px ·
|
|
408
|
+
* the kernel is 26px short · the game frame does not reach the fold
|
|
409
|
+
*
|
|
410
|
+
* ── THE ONE NUMBER THAT MAKES THIS ROW'S HEIGHT A MOVING TARGET, DECLARED SO IT CAN BE READ ──
|
|
411
|
+
*
|
|
412
|
+
* The strip opens and closes over 0.35s, so `.toolbar`'s height is anywhere in a 26px range at any
|
|
413
|
+
* given moment — and anything that measures the row and feeds a LAYOUT with it is sampling an
|
|
414
|
+
* animation. `--shell-top` did exactly that and the kernel's height came out 26px short whenever
|
|
415
|
+
* the page happened to be measured with the strip open, which is to say whenever somebody opened
|
|
416
|
+
* the persona at #kernel. It reads as a strip of the next section showing above the fold.
|
|
417
|
+
*
|
|
418
|
+
* So the number is a custom property rather than a literal: this rule uses it, and @justai/core's
|
|
419
|
+
* `personaScroll` reads it to compute the row's EXPANDED height without waiting for or racing the
|
|
420
|
+
* transition — `toolbar - strip` is the row, and the row plus this is what the bar will be. One
|
|
421
|
+
* declaration, two readers, and nothing samples a moving value. */
|
|
422
|
+
.toolbar { --tb-strip: 26px; }
|
|
423
|
+
/* queries: the bar grows after load · why does the strip animate on arrival · data-motion ·
|
|
424
|
+
* first paint animation · can this transition wait for the first interaction
|
|
425
|
+
*
|
|
426
|
+
* ── THE TRANSITION IS ALSO A DAMPER, WHICH IS WHY IT CANNOT SIMPLY WAIT ─────────────────────
|
|
427
|
+
*
|
|
428
|
+
* The founder asked the obvious and right question: *"şu geçiş süresi güzel — ama neden bu özellik
|
|
429
|
+
* kullanıcının ilk interaction'ından sonra aktif edilmiyor?"* A persona lands on its kernel, so the
|
|
430
|
+
* strip's correct state on the first frame is open; animating into it grows the bar 26px just after
|
|
431
|
+
* the page appears, which is a glitch nobody asked for.
|
|
432
|
+
*
|
|
433
|
+
* IT WAS TRIED, AND IT BREAKS SOMETHING ELSE. Gated behind a `data-motion` flag set on the first
|
|
434
|
+
* pointer/key/wheel, the strip snaps open during the landing — and that 26px arrives in a SINGLE
|
|
435
|
+
* frame, inside `#below`, while the scroll spy is deciding which cut is active. That is the
|
|
436
|
+
* bistable oscillator `personaScroll` documents at length: the spy measures a page its own output
|
|
437
|
+
* moved. Measured: with the gate in place, `#navProfile` no longer navigates — the page re-asserts
|
|
438
|
+
* the kernel and stays. The 0.35s is not only easing; it is what keeps that step out of one frame.
|
|
439
|
+
*
|
|
440
|
+
* SO THE HEIGHT IS NEVER READ INSTEAD. `--shell-top` is computed as `row + --tb-strip` — the row
|
|
441
|
+
* being the bar minus whatever the strip currently is, which is the same number at every frame of
|
|
442
|
+
* the transition. Nothing samples the animation, so nothing depends on when it was sampled.
|
|
443
|
+
*
|
|
444
|
+
* The real fix for the founder's question is a toolbar whose height does not change at all: reserve
|
|
445
|
+
* the strip's space always and fade only its content. That removes the feedback at the source and
|
|
446
|
+
* makes the gate free — and it is a fleet-wide design decision about how the bar looks at the
|
|
447
|
+
* profile, so it is his to take rather than one to slip in. */
|
|
448
|
+
.tb-title { display: flex; align-items: center; height: 0; overflow: hidden; opacity: 0; transition: height 0.35s var(--ease-out), opacity 0.25s var(--ease-out); }
|
|
449
|
+
/* The fallback is not decoration: the token is declared on `.toolbar` and consumed here, which is
|
|
450
|
+
a different selector — so the framework's own token spec reads it as ungrounded and is right to.
|
|
451
|
+
A rule that silently collapses to `height: 0` would close the strip and say nothing. */
|
|
452
|
+
.tb-title.show { height: 26px; height: var(--tb-strip, 26px); opacity: 1; }
|
|
453
|
+
.tb-title-text { font-size: var(--fs-sm); font-weight: 600; letter-spacing: -0.01em; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: opacity 0.16s var(--ease-out); }
|
|
454
|
+
.tb-title-text.fade { opacity: 0; }
|
|
455
|
+
/* nav lives on the left, beside the back affordance — the middle is intentionally empty */
|
|
456
|
+
.tb-left { display: flex; align-items: center; gap: 2px; min-width: 0; }
|
|
457
|
+
.tb-nav-group { display: flex; align-items: center; gap: 2px; }
|
|
458
|
+
.tb-right { display: flex; align-items: center; min-width: 0; }
|
|
504
459
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
* drawing uses — so a redraw changes it, and the comment beside each one says so.
|
|
530
|
-
*
|
|
531
|
-
* (This replaced a 19-with-a-20-in-front rule that lasted an afternoon. The distinction was real —
|
|
532
|
-
* the first slot is not one of the sections — but it is said by POSITION and the -7px pull above,
|
|
533
|
-
* not by a pixel of size nobody can see and everybody can drift.) */
|
|
534
|
-
.tb-nav.tb-mark :global(img) { width: 20px; height: 20px; }
|
|
460
|
+
/* ── the withdraw: what this row does while a game is playing ───────────────────────────────
|
|
461
|
+
* Opacity and pointer-events here; the INERT that takes them out of the tab order is set in the
|
|
462
|
+
* script, because CSS cannot. The transition matters more than it looks: chrome that snaps away
|
|
463
|
+
* reads as breaking, chrome that withdraws reads as dimming the house lights. */
|
|
464
|
+
/* ── HOW THIS ROW GOES QUIET WHILE A GAME PLAYS: IT DOES NOT ─────────────────────────────────
|
|
465
|
+
* queries: toolbar during a game · theater · toolbar looks disabled
|
|
466
|
+
*
|
|
467
|
+
* Four versions in one day, each struck by the founder, and the last one is no CSS at all. A
|
|
468
|
+
* pause handle in the row; then inert; then faded ("disable gibi gozukmemeli - tiklanabiliyorsa");
|
|
469
|
+
* then stepped under a custom dim layer. The standing answer: the stage switches the page to the
|
|
470
|
+
* persona's own DARK THEME (data-theme on html, see Preroll), and this row simply wears it - the
|
|
471
|
+
* same bar every dark-system visitor already sees, fully alive, auto-pausing the game when
|
|
472
|
+
* touched. Nothing here needs to know a game exists. */
|
|
473
|
+
/* icon-only section nav — current cut highlighted */
|
|
474
|
+
.tb-nav {
|
|
475
|
+
display: grid; place-items: center; width: 38px; height: 36px;
|
|
476
|
+
color: var(--text-dim);
|
|
477
|
+
transition: color 0.2s, opacity 0.2s, transform 0.2s var(--ease-spring);
|
|
478
|
+
}
|
|
479
|
+
.tb-nav:hover { color: var(--text); }
|
|
480
|
+
.tb-nav:active { transform: scale(0.9); }
|
|
481
|
+
/* The active glyph is a graphic, so it wants 3:1 rather than 4.5 — which a light accent still
|
|
482
|
+
misses. Same ink hook as everywhere the accent stops being paint. */
|
|
483
|
+
.tb-nav.active { color: var(--accent-text, var(--accent)); }
|
|
535
484
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
* the row starts in a different place
|
|
540
|
-
*
|
|
541
|
-
* `BackToZone` pulls itself 7px left so the row begins flush with the bar's edge rather than a tap
|
|
542
|
-
* target's worth inside it. Anything standing in the first position has the same job and needs the
|
|
543
|
-
* same pull — measured on a live child, the parent's face sat 26px from the bar's edge where a
|
|
544
|
-
* persona's way out sits at 17. Nine pixels, found by eye before any probe looked: a person moving
|
|
545
|
-
* between two surfaces sees a difference no single page can show.
|
|
546
|
-
*
|
|
547
|
-
* POSITION IS WHAT MAKES THE FIRST SLOT DIFFERENT — not size. Every icon reads at 20; what says
|
|
548
|
-
* "this one is the way out rather than a section" is where it sits, flush to the edge, before the
|
|
549
|
-
* group. Structural for that reason: BackToZone sizes and pulls itself, and when it is absent the
|
|
550
|
-
* nav group becomes the first child of `.tb-left` and its first item inherits the job. */
|
|
551
|
-
.tb-left > .tb-nav-group:first-child > .tb-nav:first-child { margin-inline-start: -7px; }
|
|
552
|
-
.tb-nav :global(svg) { width: 20px; height: 20px; }
|
|
485
|
+
/* queries: should the persona mark be coloured · the mark is always accent · two icons look active
|
|
486
|
+
THE MARK IS A CUT ICON LIKE THE OTHER TWO — the founder's rule, 2026-08-03: the ACTIVE cut wears
|
|
487
|
+
the accent and no passive one does, the persona's own mark included.
|
|
553
488
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
*
|
|
562
|
-
* "19px" means two different things in this row. A Lucide glyph is drawn in a 24 viewBox with its
|
|
563
|
-
* ink between 3 and 21, so a 19px box puts **14.25px** of ink on screen; a mark runs to the edge
|
|
564
|
-
* of its own viewBox and puts **18.65px**. The mark therefore reads about a third larger, which is
|
|
565
|
-
* exactly what it did — and while the mark was still coloured, colour carried part of that
|
|
566
|
-
* difference. Now that it is monochrome at rest, the size gap is the only thing left and it shows.
|
|
567
|
-
*
|
|
568
|
-
* 19 × 24/18 = 25.33px is the box at which a glyph's INK measures 19px, the same 19px the mark
|
|
569
|
-
* occupies. `:not(.tb-mark)` is not a special case — it is the same rule read the other way: a
|
|
570
|
-
* glyph carries padding inside its viewBox and a mark does not, so equal ink needs unequal boxes.
|
|
571
|
-
*
|
|
572
|
-
* app.zone settled it, qr arrived at the identical number, and qr's own comment says why it is
|
|
573
|
-
* here: *"otherwise every persona has to remember to carry it, which is precisely the thing the
|
|
574
|
-
* shared shell exists to prevent."* */
|
|
575
|
-
.tb-nav:not(.tb-mark) :global(svg) { width: 26.67px; height: 26.67px; }
|
|
489
|
+
This comment used to argue the opposite: *"the three stroke icons are generic glyphs whose colour
|
|
490
|
+
IS a UI state; a mark is an identity… a faded logo reads as disabled."* Kept, because it is a true
|
|
491
|
+
premise ageing into a wrong conclusion. It was true while the mark arrived as an `<img>`, which
|
|
492
|
+
cannot take `currentColor` — dimming was then the only way to say "not here", and a dimmed logo
|
|
493
|
+
does read as disabled. The premise expired when `toolbarMark` began deriving an inline SVG: an
|
|
494
|
+
inline mark can be INK, and ink is not dimming. `toolbarMark` neutralises its paint, so the two
|
|
495
|
+
rules below reach it exactly as they reach the bolt and the bubble.
|
|
576
496
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
497
|
+
A mark that keeps its colours while another cut is current is a second voice on the one channel
|
|
498
|
+
the accent has — measured, several passive marks were more coloured than the active icon. A
|
|
499
|
+
persona that wants its own gradient WHILE CURRENT re-points to it from its own CSS; the `<defs>`
|
|
500
|
+
survive the derivation for that. `.tb-mark` remains, because the sizing rule below still needs to
|
|
501
|
+
know a mark is here. */
|
|
502
|
+
/* The shell sizes the mark and says NOTHING about `display`, deliberately. Two files arrive for the two
|
|
503
|
+
themes — an SVG's internal `prefers-color-scheme` does not survive <img>, measured — and the persona
|
|
504
|
+
hides one with a media query. A `display: block` here would be two classes deep against the persona's
|
|
505
|
+
one and would WIN, showing both marks stacked. That exact mistake shipped once in this shell and the
|
|
506
|
+
founder counted the duplicate logos before anyone read the CSS. Width and height only.
|
|
585
507
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
508
|
+
19px, the same box the stroke icons get. 0.65.0 set this to 14.5px to make the mark's INK match the
|
|
509
|
+
neighbours' — a glyph carries padding and a mark does not, so at an equal box the mark reads about a
|
|
510
|
+
third larger. The measurement was right and the conclusion was the founder's to make, not mine: he
|
|
511
|
+
wants the mark at its own size. A logo is allowed to be more present than a glyph.
|
|
512
|
+
So the difference stays, and it is a difference in ink rather than in box: mark ~19px, glyphs ~14.3px
|
|
513
|
+
inside the same 19px. If the row is ever to read level, the way there is to grow the GLYPHS, not to
|
|
514
|
+
shrink the identity. */
|
|
515
|
+
/* ── EVERY ICON READS AT 20 ─────────────────────────────────────────────────────────────────
|
|
516
|
+
*
|
|
517
|
+
* queries: toolbar icon size · 20 · why is my icon smaller than the others · 26.67 · the mark
|
|
518
|
+
* looks small · icon ink
|
|
519
|
+
*
|
|
520
|
+
* The founder's rule, and it is about the DRAWING rather than the box: an icon's picture hits 20
|
|
521
|
+
* on its longer side, whichever side its design naturally uses. A bolt is tall and narrow and hits
|
|
522
|
+
* 20 on its height; a folder hits it on its width. The container is whatever that takes.
|
|
523
|
+
*
|
|
524
|
+
* One number, two boxes, because a viewBox is not a drawing:
|
|
525
|
+
*
|
|
526
|
+
* a MARK fills its own viewBox → a 20px box puts 20px on screen
|
|
527
|
+
* a GLYPH sits inside 18 of 24 (0.75) → 20 / 0.75 = 26.67px
|
|
528
|
+
*
|
|
529
|
+
* A persona whose mark does NOT fill its viewBox needs its own number, and that is where the
|
|
530
|
+
* confusion came from: those marks looked small in the same box as everything else because their
|
|
531
|
+
* drawing occupies less of it. The box is derived — 20 divided by how much of the viewBox the
|
|
532
|
+
* drawing uses — so a redraw changes it, and the comment beside each one says so.
|
|
533
|
+
*
|
|
534
|
+
* (This replaced a 19-with-a-20-in-front rule that lasted an afternoon. The distinction was real —
|
|
535
|
+
* the first slot is not one of the sections — but it is said by POSITION and the -7px pull above,
|
|
536
|
+
* not by a pixel of size nobody can see and everybody can drift.) */
|
|
537
|
+
.tb-nav.tb-mark :global(img) { width: 20px; height: 20px; }
|
|
601
538
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
539
|
+
/* ── THE FIRST POSITION STARTS WHERE THE ROW STARTS ─────────────────────────────────────────
|
|
540
|
+
*
|
|
541
|
+
* queries: the first icon is further in on a child persona · navParent inset · -7px ·
|
|
542
|
+
* the row starts in a different place
|
|
543
|
+
*
|
|
544
|
+
* `BackToZone` pulls itself 7px left so the row begins flush with the bar's edge rather than a tap
|
|
545
|
+
* target's worth inside it. Anything standing in the first position has the same job and needs the
|
|
546
|
+
* same pull — measured on a live child, the parent's face sat 26px from the bar's edge where a
|
|
547
|
+
* persona's way out sits at 17. Nine pixels, found by eye before any probe looked: a person moving
|
|
548
|
+
* between two surfaces sees a difference no single page can show.
|
|
549
|
+
*
|
|
550
|
+
* POSITION IS WHAT MAKES THE FIRST SLOT DIFFERENT — not size. Every icon reads at 20; what says
|
|
551
|
+
* "this one is the way out rather than a section" is where it sits, flush to the edge, before the
|
|
552
|
+
* group. Structural for that reason: BackToZone sizes and pulls itself, and when it is absent the
|
|
553
|
+
* nav group becomes the first child of `.tb-left` and its first item inherits the job. */
|
|
554
|
+
.tb-left > .tb-nav-group:first-child > .tb-nav:first-child { margin-inline-start: -7px; }
|
|
555
|
+
.tb-nav :global(svg) { width: 20px; height: 20px; }
|
|
614
556
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
557
|
+
/* ── EQUAL INK, TWO BOXES ────────────────────────────────────────────────────────────────────
|
|
558
|
+
*
|
|
559
|
+
* queries: the toolbar icons are not the same size · the mark looks bigger than the glyphs ·
|
|
560
|
+
* the row is not level · 25.33px
|
|
561
|
+
*
|
|
562
|
+
* The paragraph above ends by naming the way out — *"grow the GLYPHS, not the identity"* — and
|
|
563
|
+
* this is that, arriving from the two repos that measured it.
|
|
564
|
+
*
|
|
565
|
+
* "19px" means two different things in this row. A Lucide glyph is drawn in a 24 viewBox with its
|
|
566
|
+
* ink between 3 and 21, so a 19px box puts **14.25px** of ink on screen; a mark runs to the edge
|
|
567
|
+
* of its own viewBox and puts **18.65px**. The mark therefore reads about a third larger, which is
|
|
568
|
+
* exactly what it did — and while the mark was still coloured, colour carried part of that
|
|
569
|
+
* difference. Now that it is monochrome at rest, the size gap is the only thing left and it shows.
|
|
570
|
+
*
|
|
571
|
+
* 19 × 24/18 = 25.33px is the box at which a glyph's INK measures 19px, the same 19px the mark
|
|
572
|
+
* occupies. `:not(.tb-mark)` is not a special case — it is the same rule read the other way: a
|
|
573
|
+
* glyph carries padding inside its viewBox and a mark does not, so equal ink needs unequal boxes.
|
|
574
|
+
*
|
|
575
|
+
* app.zone settled it, qr arrived at the identical number, and qr's own comment says why it is
|
|
576
|
+
* here: *"otherwise every persona has to remember to carry it, which is precisely the thing the
|
|
577
|
+
* shared shell exists to prevent."* */
|
|
578
|
+
.tb-nav:not(.tb-mark) :global(svg) { width: 26.67px; height: 26.67px; }
|
|
631
579
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
// blocked: the action proceeds naturally while the pause lands beside it (a locked page still
|
|
642
|
-
// scrolls programmatically, so even the hash navigation works before the unlock).
|
|
643
|
-
//
|
|
644
|
-
// Declarative seam, as asked: an element that must NOT pause the game opts out by carrying
|
|
645
|
-
// `data-stage-silent` — on itself or any ancestor. Everything else pauses by default, because a
|
|
646
|
-
// rule with a default and an opt-out survives a fleet; a per-element opt-in is the third copy
|
|
647
|
-
// that forgets.
|
|
648
|
-
document.querySelector(".toolbar")?.addEventListener(
|
|
649
|
-
"click",
|
|
650
|
-
(e) => {
|
|
651
|
-
if (document.documentElement.dataset.stage !== "playing") return;
|
|
652
|
-
const hit = (e.target as Element | null)?.closest?.("a, button, select, [role=button]");
|
|
653
|
-
if (!hit || hit.closest("[data-stage-silent]")) return;
|
|
654
|
-
document.dispatchEvent(new CustomEvent("justai:pause-request"));
|
|
655
|
-
},
|
|
656
|
-
true,
|
|
657
|
-
);
|
|
580
|
+
/* THE BUBBLE HANGS. Its silhouette's centre measures 1.05px below the line the other three sit on
|
|
581
|
+
(39.05 against 38.0) because the tail is part of the shape and pulls the box down with it — the
|
|
582
|
+
glyph is centred while the thing you SEE is not. A pixel is inside the noise on most rows; on
|
|
583
|
+
four icons at one size sharing one baseline it is the only thing left that is not level.
|
|
584
|
+
Nudged rather than redrawn, and `transform` on the svg never collides with the press feedback:
|
|
585
|
+
`.tb-nav:active`'s scale lives on the anchor. Both repos carry this verbatim. */
|
|
586
|
+
#navPosts :global(svg) { transform: translateY(-1px); }
|
|
587
|
+
}
|
|
588
|
+
</style>
|
|
658
589
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
kt && pt
|
|
664
|
-
? { titleFor: (id: string) => (id === "kernel" ? kt : id === "posts" ? pt : "") }
|
|
665
|
-
: undefined,
|
|
666
|
-
);
|
|
667
|
-
</script>
|
|
590
|
+
<script>
|
|
591
|
+
// The behaviour lives in ./behaviour/toolbar.ts — see that file for why it is not in here.
|
|
592
|
+
import "./behaviour/toolbar";
|
|
593
|
+
</script>
|