@justai/cuts 0.130.0 → 0.131.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.130.0",
4
- "description": "A persona's named parts \u2014 the page shell that holds them, and the cuts themselves.",
3
+ "version": "0.131.0",
4
+ "description": "A persona's named parts — the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
7
7
  "type": "git",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@justai/core": "^0.34.0",
45
- "@justai/ui": "^0.21.1",
45
+ "@justai/ui": "^0.22.0",
46
46
  "@justai/atrium": "^0.1.9"
47
47
  }
48
- }
48
+ }
@@ -129,7 +129,7 @@ const href = `https://${apex}`;
129
129
  Dark is the un-queried default because the fleet's tokens are dark-first. */
130
130
  .home:hover .azmark .ctr, .home:focus-visible .azmark .ctr { fill: #F5F1E8; }
131
131
  @media (prefers-color-scheme: light) {
132
- .home:hover .azmark .ctr, .home:focus-visible .azmark .ctr { fill: #2B2724; }
132
+ :global(html:not([data-theme="dark"])) .home:hover .azmark .ctr, :global(html:not([data-theme="dark"])) .home:focus-visible .azmark .ctr { fill: #2B2724; }
133
133
  }
134
134
  </style>
135
135
 
package/src/Posts.astro CHANGED
@@ -172,7 +172,7 @@ const vars = [
172
172
  /* The light default is white because that is what --card-bg is there; the dark default is a faint white
173
173
  overlay because on dark --card-bg equals --surface, which is the column, so copying it would vanish. */
174
174
  @media (prefers-color-scheme: light) {
175
- .post:hover { background: color-mix(in srgb, var(--card-bg, #fff) 55%, transparent); }
175
+ :global(html:not([data-theme="dark"])) .post:hover { background: color-mix(in srgb, var(--card-bg, #fff) 55%, transparent); }
176
176
  }
177
177
  .post + .post { border-block-start: 1px solid var(--border); }
178
178
 
@@ -225,7 +225,7 @@ const vars = [
225
225
  .pa .mk, .rav .mk { width: 100%; height: 100%; object-fit: cover; display: block; }
226
226
  /* a dark tile must invert in light mode, not stay dark — personas whose avatar ground is a gradient
227
227
  pass avatarBgLight; the rest fall back to their single background. */
228
- @media (prefers-color-scheme: light) { .pa, .rav { background: var(--feed-av-bg-light, var(--feed-av-bg, #fff)); } }
228
+ @media (prefers-color-scheme: light) { :global(html:not([data-theme="dark"])) .pa, :global(html:not([data-theme="dark"])) .rav { background: var(--feed-av-bg-light, var(--feed-av-bg, #fff)); } }
229
229
 
230
230
  /* A bare mark stands on the feed's own ground, so the tile and its hairline have to go — and they have to
231
231
  go from the CONTAINER, which is why this reads upward from the image with :has(). Scoping it to the image
@@ -256,6 +256,9 @@ const vars = [
256
256
  .pa .mk.av-light, .rav .mk.av-light { display: none; }
257
257
  .pa .mk.av-dark, .rav .mk.av-dark { display: block; }
258
258
  }
259
+ /* The dark fork's data-theme twin: a light-system page forced dark must swap the marks too. */
260
+ :global(html[data-theme="dark"]) .pa .mk.av-light, :global(html[data-theme="dark"]) .rav .mk.av-light { display: none; }
261
+ :global(html[data-theme="dark"]) .pa .mk.av-dark, :global(html[data-theme="dark"]) .rav .mk.av-dark { display: block; }
259
262
 
260
263
  .ph { display: flex; align-items: center; gap: 5px; font-size: var(--fs-xs); flex-wrap: wrap; }
261
264
  .ph b { font-weight: 600; }
package/src/Preroll.astro CHANGED
@@ -364,34 +364,22 @@ const safeId = "game-" + (id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/
364
364
  * container, and layout containment makes it the containing block for FIXED descendants too — a
365
365
  * fixed overlay written inside it would be clipped to the frame it is meant to surround. */
366
366
  html[data-stage="playing"] { overflow: hidden; }
367
- /* The kernel that holds a preroll rides above the room PERMANENTLY, not only while playing: the
368
- * raise used to follow data-stage, so pausing dropped the kernel in one frame while the dim took
369
- * half a second to fade - and every pause put a brief wave of darkness across the game itself.
370
- * Static costs nothing (the room is transparent outside playing) and cannot mistime. */
371
- .cut.kernel:has(#pr) { position: relative; z-index: 35; }
372
- /* ── THE ROOM IS DARK DOT PAPER, NOT A BLACK FILM ───────────────────────────────────────────
373
- * queries: theater dim flat colour · the dots disappear in the dark · blender dark theme ·
374
- * dot paper grid in theater mode
367
+ /* queries: theater mode · where did the dim layer go · dark theme while playing
375
368
  *
376
- * The founder, looking at the first dim: *"dot paper grid'de dot kendini belli etmeyince bu
377
- * sefer duz bir renk gibi algilaniyor"* — and pointing at the fix: 3dviewer's player, whose
378
- * stage is Blender's own Theme Space grey, read out of a running Blender. A flat translucent
379
- * black erased the ground's identity; a dark room should still be THE SAME PAPER with the
380
- * lights down, the way Blender's viewport keeps its grid in the dark.
369
+ * ── THE THEATER IS THE PERSONA'S OWN DARK THEME, NOT A LAYER ────────────────────────────────
381
370
  *
382
- * So the layer carries its own lattice — the door's 24px dot grid, light-on-dark — over a
383
- * neutral Blender-family dark. The opacity is high on purpose: the dots belong to THIS layer,
384
- * and a translucent lattice over a busy page is noise, while a nearly-opaque one is a surface.
385
- * The chrome beneath (the toolbar steps under at z 20) stays legible as shapes in a dark room. */
386
- .pr-room {
387
- position: fixed; inset: 0; z-index: 30;
388
- background:
389
- radial-gradient(rgba(255, 255, 255, 0.07) 1px, transparent 1px) 0 0 / 24px 24px,
390
- #262626;
391
- opacity: 0; pointer-events: none;
392
- transition: opacity 0.5s var(--ease-out, ease-out);
393
- }
394
- html[data-stage="playing"] .pr-room { opacity: 0.86; }
371
+ * Three custom dims were built and struck here in one day — a black film, a z-order dance, dark
372
+ * dot paper — before the founder pointed at what was already true: *"justai ecosisteminde zaten
373
+ * tum personalarin light/dark temalari vardi ve kusursuz calisiyorlardi. senin yapman gereken tek
374
+ * sey vardi: theme switch, simple."* Every persona already ships a complete dark theme - the
375
+ * ground, the dots, the toolbar, the cards, all of it designed and measured - and a second
376
+ * darkness laid over the first is two systems doing one job.
377
+ *
378
+ * So the stage sets data-theme="dark" on <html> while playing and removes it on pause, and the
379
+ * guard the fleet's light forks now carry (time's wall wrote the idiom first: data-theme wins
380
+ * over the media query) does everything the dim layer ever tried to. A dark-system visitor sees
381
+ * no change at all; a light-system visitor gets the persona's own night, and their auto theme
382
+ * back the moment they pause. */
395
383
  </style>
396
384
 
397
385
  <script>
@@ -502,16 +490,25 @@ const safeId = "game-" + (id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/
502
490
  let declareProtocol: (v: number) => void = () => {};
503
491
  const declared: Promise<number> = new Promise((r) => { declareProtocol = r; });
504
492
  if (bus) {
505
- // The room's lights, hung on <body> — see the global style block for why not in here.
506
- const room = document.createElement("div");
507
- room.className = "pr-room";
508
- room.setAttribute("aria-hidden", "true");
509
- document.body.appendChild(room);
493
+ // Whatever theme the page carried before the stage took it — restored on pause, so a manual
494
+ // data-theme set by anything else survives a round trip through a game.
495
+ let priorTheme: string | undefined;
510
496
 
511
497
  const setStage = (mode: string) => {
512
498
  const playing = mode === "playing";
513
- if (playing) document.documentElement.dataset.stage = "playing";
514
- else delete document.documentElement.dataset.stage;
499
+ if (playing) {
500
+ document.documentElement.dataset.stage = "playing";
501
+ // The theater: the persona's own dark theme, switched on. See the style block above.
502
+ if (document.documentElement.dataset.theme !== "dark") {
503
+ priorTheme = document.documentElement.dataset.theme;
504
+ document.documentElement.dataset.theme = "dark";
505
+ }
506
+ } else {
507
+ delete document.documentElement.dataset.stage;
508
+ if (priorTheme === undefined) delete document.documentElement.dataset.theme;
509
+ else document.documentElement.dataset.theme = priorTheme;
510
+ priorTheme = undefined;
511
+ }
515
512
  // The other cuts go INERT rather than display:none — the page must not reflow, and the
516
513
  // player's scroll position has to survive a pause. inert takes them out of tab order and
517
514
  // the accessibility tree both; a screen reader must not keep walking a page the player
package/src/Profile.astro CHANGED
@@ -129,7 +129,7 @@ const vars = [
129
129
  .avatar :global(svg) { width: 100%; height: 100%; display: block; }
130
130
  .avatar :global(img) { width: 100%; height: 100%; display: block; object-fit: cover; }
131
131
  @media (prefers-color-scheme: light) {
132
- .avatar { border-color: #fff; background: var(--avatar-bg-light, var(--avatar-bg, #fff)); }
132
+ :global(html:not([data-theme="dark"])) .avatar { border-color: #fff; background: var(--avatar-bg-light, var(--avatar-bg, #fff)); }
133
133
  }
134
134
  .pname { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -0.02em; margin-block-start: 12px; }
135
135
  .handle { color: var(--text-dim); font-size: var(--fs-xs); margin-block-start: 2px; }
package/src/Toolbar.astro CHANGED
@@ -344,7 +344,8 @@ import AuthMount from "./AuthMount.astro";
344
344
  FALLBACK differs by scheme, and a band carrying only the dark default is a black strip on a light
345
345
  page — which is exactly what the founder photographed a minute after the first attempt. */
346
346
  @media (prefers-color-scheme: light) {
347
- .tb-slot::before { background: var(--toolbar-bg, #fbfcfe); }
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); }
348
349
  }
349
350
  .toolbar {
350
351
  /* position, top and z-index live on .tb-slot above — this element is static inside it now. */
@@ -389,7 +390,7 @@ import AuthMount from "./AuthMount.astro";
389
390
  border-block-end: none;
390
391
  }
391
392
  @media (prefers-color-scheme: light) {
392
- .toolbar {
393
+ :global(html:not([data-theme="dark"])) .toolbar {
393
394
  background: var(--toolbar-bg, #fbfcfe);
394
395
  box-shadow: var(--toolbar-shadow, 0 1px 2px rgba(16, 24, 40, 0.06), 0 8px 20px -8px rgba(16, 24, 40, 0.16));
395
396
  }
@@ -457,21 +458,15 @@ import AuthMount from "./AuthMount.astro";
457
458
  * Opacity and pointer-events here; the INERT that takes them out of the tab order is set in the
458
459
  * script, because CSS cannot. The transition matters more than it looks: chrome that snaps away
459
460
  * reads as breaking, chrome that withdraws reads as dimming the house lights. */
460
- /* ── IN THE DARK, NOT DISABLED — how this row goes quiet while a game plays ─────────────────
461
- * queries: toolbar looks disabled · faded but clickable · toolbar during a game · theater
461
+ /* ── HOW THIS ROW GOES QUIET WHILE A GAME PLAYS: IT DOES NOT ─────────────────────────────────
462
+ * queries: toolbar during a game · theater · toolbar looks disabled
462
463
  *
463
- * Three versions of quieting, each struck by the founder. Fading the elements was the last one:
464
- * *"neden disable mis gibi gozukuyor ama tiklanabiliyor? tiklanabiliyorsa disable gibi
465
- * gozukmemeli."* Right - reduced opacity IS the visual language of disabled, and nothing
466
- * clickable may speak it.
467
- *
468
- * The answer was in the theater metaphor the whole time: in a dark cinema the seats are not
469
- * disabled, they are in the dark. So the elements keep their full strength and the ROW STEPS
470
- * UNDER THE ROOM'S DIM - below the .pr-room layer the stage protocol hangs over the page - and
471
- * darkens with everything else in one coherent light change. The dim takes no pointer events,
472
- * so every control underneath works exactly as it looks like it should: like chrome in a dark
473
- * room, waiting. */
474
- :global(html[data-stage="playing"]) .tb-slot { z-index: 20; }
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. */
475
470
  /* icon-only section nav — current cut highlighted */
476
471
  .tb-nav {
477
472
  display: grid; place-items: center; width: 38px; height: 36px;
package/src/ground.css CHANGED
@@ -79,12 +79,41 @@
79
79
  second storefront is the first one. */
80
80
  --card-bg: rgba(28, 28, 34, 0.55);
81
81
  --card-blur: blur(30px) saturate(1.6);
82
- --card-shadow: 0 1px 2px rgba(0, 0, 0, 0.32);
83
82
  --card-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.4), 0 12px 28px -14px rgba(0, 0, 0, 0.55);
83
+
84
+ /* ── ELEVATION — mirrors @justai/ui tokens.css, and the tokens spec holds the two identical ──
85
+ *
86
+ * queries: box shadow token · which shadow · the panel looks detached · drop shadow standard ·
87
+ * golge standardi · card shadow vs sheet shadow
88
+ *
89
+ * THE VALUES WERE ALREADY HERE, under one name and one meaning: `--card-shadow`, described as
90
+ * "a storefront tile's own surface". Measured across the live fleet on 2026-08-20 they turned out
91
+ * to be the shadow of EVERY resting surface — app.zone's catalogue cards, qr's type buttons,
92
+ * calculator's panel, all byte-identical — so the name was narrower than the fact, and a persona
93
+ * building its own kernel panel had nothing to reach for and invented one. The one that produced
94
+ * the ruling had invented a MODAL's: twice the opacity, 3.7× the blur, and in dark deeper than
95
+ * the fleet's actual modal.
96
+ *
97
+ * So the scale is named by ELEVATION — how far off the page a thing is — and the catalogue's own
98
+ * token now reads from it rather than restating it. `--card-shadow` stays because live personas
99
+ * consume it; it is an alias now, not a second definition.
100
+ *
101
+ * The law: **nothing that is not a sheet may exceed `--shadow-card`, and no shadow is coloured.**
102
+ * A tinted shadow is a glow, a glow is a light source, and this ground has one sun and no lamps. */
103
+ --shadow-card: 0 1px 2px 0 rgba(0, 0, 0, 0.32);
104
+ --shadow-bar: 0 2px 12px -2px rgba(0, 0, 0, 0.55);
105
+ --shadow-sheet: 0 18px 40px -20px rgba(0, 0, 0, 0.45);
106
+ --card-shadow: var(--shadow-card);
84
107
  }
85
108
 
109
+ /* queries: force dark while playing · theme switch · data-theme dark · stage theme
110
+ * THE GUARD IS THE FLEET'S OWN IDIOM — time's wall wrote it first: *"data-theme wins over the media
111
+ * query in both directions."* A light-system visitor whose page carries data-theme="dark" gets the
112
+ * dark theme this file already ships; removing the attribute returns them to auto. The stage
113
+ * protocol is the first caster (a game playing = dark), and any future manual theme switch gets
114
+ * this seam for free. */
86
115
  @media (prefers-color-scheme: light) {
87
- :where(:root) {
116
+ :where(:root:not([data-theme="dark"])) {
88
117
  --bg: #f6f7f9;
89
118
  --text: #0a0c10;
90
119
  --text-dim: #5b6472;
@@ -97,8 +126,14 @@
97
126
  panel over a pale page reads as a smudge, so app.zone drops it and pays the shadow instead. */
98
127
  --card-bg: #fff;
99
128
  --card-blur: none;
100
- --card-shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 4px 12px -4px rgba(16, 24, 40, 0.1);
101
129
  --card-shadow-hover: 0 2px 4px rgba(16, 24, 40, 0.06), 0 12px 26px -8px rgba(16, 24, 40, 0.15);
130
+
131
+ /* Light needs TWO layers where dark needs one: on a near-white ground a single ambient blur
132
+ reads as dirt, so the pair is a 1px contact line that seats the object plus a wide, faint
133
+ ambient. `--card-shadow` inherits its alias from the block above and needs no line here. */
134
+ --shadow-card: 0 1px 2px 0 rgba(16, 24, 40, 0.05), 0 4px 12px -4px rgba(16, 24, 40, 0.1);
135
+ --shadow-bar: 0 1px 2px rgba(16, 24, 40, 0.06), 0 8px 20px -8px rgba(16, 24, 40, 0.16);
136
+ --shadow-sheet: 0 18px 40px -20px rgba(0, 0, 0, 0.45);
102
137
  }
103
138
  }
104
139