/* ═══════════════════════════════════════════════════════════════════════════
   DKAI workshop · next — "field" design system (v3: no lines)
   A desk full of little instruments. Two axes you can switch independently:
     ground   <html data-scheme="light|dark">        the desk the devices sit on
     chassis  <html data-chassis="putty|black|clear"> the material the devices are made of
   Optional layers: data-lighting, data-tilt, data-motion ("on"|"off" — off: nothing sways, tilts, wobbles or magnifies), data-readout ("matrix"|"text")

   Material rules (the whole point of v3 — one look, no exceptions, and nothing is outlined):
     · a surface is told from what it sits on by TONE and by ONE soft shadow — never by a drawn line. The old
       1px `--edge` outlines are gone. Round 20: the 1px transparent borders that held their place went too —
       a key, a well, a sheet, a device has `border: 0` and a pixel more padding, so nothing moved and the light
       (an inset shadow) reaches the very edge: no row of the cap's own colour under a lip or over a highlight.
     · four finishes, each with one cue:
         MATTE   a device body — flat chassis colour, a contact shadow + an ambient one
         KEY     anything you press — a flat cap one step lighter than the chassis, on a 2px LIP in a mid tone
                 (`--key-lip`; the accent's own shade for a key that is on). The lip is drawn INSIDE the key's box
                 (an inset shadow along its bottom edge, the face padded up by as much), so a key is exactly as tall as
                 it looks and stands level with what sits beside it; a 1px contact shadow (`--contact`) grounds it.
                 Pressed: the lip goes (and the contact), the cap drops onto it. No outline, no gradient, no glow.
         WELL    anything you set — segmented, switch, fader — a recess: a translucent dark tone (`--well`)
         PAPER   anything you type into — a light sheet (`--field`); focus is a 2px accent ring
     · DISPLAYS (readout, batch, tips, toasts) are dark glass with ivory type — the third colour on the desk.
     · hairlines only INSIDE a device (`--line`, faint) to part a head, a body and a foot. Dashes only for a state
       (a missing print, a queued one).
     · the light comes from above (round 19): what stands up — a device, a cap, a card — catches it along its top
       edge (`--hi`, a 1px line); what is cut in — paper, a well, a display set into a panel — casts a shadow under
       its top edge and catches the light along its bottom one (`--cut`, `--cut-glass`).
     · the panels had four slotted screws in the bezel, and every device kept its content ≥ 12px
       from its edge to clear them. They are gone; the bezel went with them, which is why the
       panel headers are 4px shorter than the padding elsewhere in the sheet would suggest.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the properties that move every frame ──────────────────────────────── */
/* Registered as NOT inherited (round 20): a custom property that inherits is pushed down to every descendant whenever
   it changes, so a value written each frame on a device re-resolved the style of everything inside it — and, written
   on <html> or the stage, of every print on the desk. These are read only by the element they are set on. (The plane's
   lag is not a property at all any more: plane.js writes each device's `translate` — a change of one that inherits
   nothing, and re-resolves nothing but the device.) */
@property --gs { syntax: '<length>'; inherits: false; initial-value: 20px; }
@property --mag { syntax: '<number>'; inherits: false; initial-value: 1; }
@property --push { syntax: '<length>'; inherits: false; initial-value: 0px; }
/* the pointer's pools on a lit key (lighting.js writes them on the key): NOT inherited, so a write re-resolves the key alone
   and not its icon, label and ghosts — those read `--kedge`, which the key derives from `--kn` */
@property --klx { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
@property --kly { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
@property --kn { syntax: '<number>'; inherits: false; initial-value: 0; }
@property --jl-tilt-x { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
@property --jl-tilt-y { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
@property --po-x { syntax: '<length-percentage>'; inherits: false; initial-value: 50%; }
@property --po-y { syntax: '<length-percentage>'; inherits: false; initial-value: 50%; }
@property --press-y { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --gx { syntax: '<percentage>'; inherits: false; initial-value: 35%; }   /* a sticker's gloss: where it sits (under the pointer) */
@property --gy { syntax: '<percentage>'; inherits: false; initial-value: 28%; }

/* ── fonts (self-hosted, variable) ─────────────────────────────────────── */
@font-face { font-family: 'Space Grotesk'; font-style: normal; font-weight: 300 700; font-display: swap; src: url('../fonts/space-grotesk-latin-wght.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 100 800; font-display: swap; src: url('../fonts/jetbrains-mono-latin-wght.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 100 900; font-display: swap; src: url('../fonts/inter-latin-wght.woff2') format('woff2'); }

/* ═══════════════════════════════════════════════════════════════════════════
   tokens
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  --font-ui: 'Space Grotesk', 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* accent (user-pickable; --accent-rgb and --accent-ink are derived in JS) */
  --accent: #ff4d00; --accent-ink: #ffffff; --accent-rgb: 255 77 0;
  --ok: #2ec27e; --warn: #ffb020; --danger: #ff4a4a; --private: #a855f7;
  /* the same 72% shade `applyAccent` makes of the accent, for the one key that turns red */
  --danger-lip: #b83535;

  /* ground · light (the desk) — one colour, all day (the hour-driven warm/cool tint went in round 18) */
  --bg: #d5d3ca; --bg-2: #cbc9c0; --dot: rgb(0 0 0 / .17); --desk-ink: #1a1a1a;
  /* the plate a print cooks on: a shade of the desk on the light one (the darkest tone would punch a hole in it), black on the dark one */
  --plate: #bcbab1; --plate-edge: transparent; --plate-shadow: var(--hi), 0 1px 2px rgb(20 20 10 / .14), 0 8px 22px rgb(20 20 10 / .22);

  /* chassis · putty (the devices) — a hair translucent, so the desk shows through */
  --chassis: rgb(233 231 224 / .86); --chassis-solid: #e9e7e0; --chassis-2: #dfddd5; --chassis-3: #d3d1c8; --chassis-4: #c6c4bb;
  /* no outlines (v3): --edge keeps its name for the 1px borders that hold the geometry, and is see-through;
     --line is the faint hairline that parts a head from a body inside a device */
  --edge: transparent; --line: rgb(0 0 0 / .08);
  --ink: #141414; --ink-2: #55554f; --ink-3: #8a8983;
  /* keys: a cap one step lighter than the chassis on a 2px lip in a mid tone (the chassis in shade) */
  --key: #f7f6f1; --key-hover: #ffffff; --key-edge: transparent; --key-lip: #b6b4aa;
  /* the light from above */
  --hi: inset 0 1px 0 rgb(255 255 255 / .55);          /* the top edge of a raised thing: a device, a cap, a card */
  --key-glow: .5;   /* a plain key's pointer pools, as a fraction of an accent key's (LIT KEYS) — white on near-white barely shows anyway */
  --hi-accent: inset 0 1px 0 rgb(255 255 255 / .28);   /* …an accent key */
  --hi-glass: inset 0 1px 0 rgb(255 255 255 / .09);    /* …dark glass standing on the desk: the readout, a toast, a tip */
  --cut: inset 0 1px 2px rgb(0 0 0 / .1), inset 0 -1px 0 rgb(255 255 255 / .5);        /* a recess: paper, a well */
  --cut-glass: inset 0 1px 2px rgb(0 0 0 / .5), inset 0 -1px 0 rgb(255 255 255 / .05);  /* a display set into a panel */
  --contact: 0 1px 0 rgb(0 0 0 / .1);                   /* where a key meets the chassis: a 1px line of shadow under its lip */
  /* wells: what you set (a segmented, a switch, a fader) is a recess in the chassis */
  --well: rgb(0 0 0 / .09); --well-ink: #55554f;
  --scale: rgb(0 0 0 / .2);   /* a scale printed on the chassis (under a fader) */
  --panel-blur: 12px;
  --drop-panel: 0 1px 2px rgb(20 20 10 / .14), 0 10px 26px rgb(20 20 10 / .2);   /* the shadows a device casts (its light apart: see .panel) */
  --drop-pop: 0 2px 4px rgb(20 20 10 / .12), 0 14px 34px rgb(20 20 10 / .26);
  --shadow-panel: var(--hi), var(--drop-panel);
  --shadow-pop: var(--hi), var(--drop-pop);
  --shadow-glass: var(--hi-glass), 0 1px 2px rgb(20 20 10 / .14), 0 10px 26px rgb(20 20 10 / .2);       /* the readout */
  --shadow-glass-pop: var(--hi-glass), 0 2px 4px rgb(20 20 10 / .12), 0 14px 34px rgb(20 20 10 / .26);  /* a toast, a tip */
  --shadow-pic: 0 1px 3px rgb(20 20 10 / .2);   /* a picture (thumbnail, icon) sits on the chassis with a hair of shadow instead of a frame */
  --logo-filter: invert(1);
  --select-bg: rgb(var(--accent-rgb) / .3);

  /* displays: dark glass with ivory type (neutral — round 6) — a shade lighter on the light desk so they don't punch holes in it */
  --display: #3a3a37; --display-edge: transparent; --display-ink: #eeece4; --display-ink-2: #a9a79f; --display-ink-3: #6f6e68;
  /* paper (what you type into): a light sheet on the light desk, glass on the dark one */
  --field: #fbfaf6; --field-edge: transparent; --field-ink: #1b1b19; --field-ink-2: #62615a; --field-ink-3: #a19f97; --field-tick: rgb(0 0 0 / .14); --field-hover: rgb(0 0 0 / .05);

  /* shape */
  --r-panel: 10px; --r-key: 6px; --r-display: 4px; --r-item: 4px;
  --bw: 1px;

  /* type */
  --label-size: 10.5px;

  /* motion */
  /* the motion level (prefs.js writes --motion on <html>, 0..1; index.html the same before first paint): every
     transition's duration follows it — at 0 things still cross-fade, quickly, rather than pop; at 1 they take their time */
  --motion: 1; --mt: calc(.35 + .65 * var(--motion));
  --t-fast: calc(.12s * var(--mt)); --t-mid: calc(.22s * var(--mt)); --t-slow: calc(.38s * var(--mt));
  --ease: cubic-bezier(.3, .9, .3, 1); --spring: cubic-bezier(.3, 1.3, .5, 1);
  --press: 2px;

  /* lighting (set by lighting.js) */
  --lit: 0;   /* the sheen's opacity: 1 while the pointer moves, 0 once it has rested (lighting.js) */
  color-scheme: light;
}
:root[data-scheme="dark"] {
  --bg: #17171a; --bg-2: #1e1e22; --dot: rgb(255 255 255 / .12); --desk-ink: #f0efe9;
  /* the plate on the dark desk: a step up from it, not black (with no outline, a black plate on a black desk was a hole) */
  --plate: #1e1e21; --plate-shadow: var(--hi), 0 1px 2px rgb(0 0 0 / .45), 0 10px 30px rgb(0 0 0 / .45);
  --logo-filter: none;
  --display: #131313; --display-ink: #e6e4dd; --display-ink-2: #9a9891; --display-ink-3: #5c5b56;
  --field: #131313; --field-ink: #ebe9e2; --field-ink-2: #9c9a93; --field-ink-3: #5f5e58; --field-tick: rgb(255 255 255 / .12); --field-hover: rgb(255 255 255 / .06);
  --shadow-pic: 0 1px 3px rgb(0 0 0 / .5);
  color-scheme: dark;
}
:root[data-chassis="black"] {
  --chassis: rgb(43 43 43 / .88); --chassis-solid: #2b2b2b; --chassis-2: #232323; --chassis-3: #343434; --chassis-4: #3e3e3e;
  --line: rgb(255 255 255 / .08);
  --ink: #f0efe9; --ink-2: #a9a8a1; --ink-3: #6f6e68;
  /* the lip under a dark cap: the cap's own shade darker (#3f3f3f less 18%) — a step down, not black (round 19: black read too dark) */
  --key: #3f3f3f; --key-hover: #4b4b4b; --key-lip: #343434;
  --well: rgb(0 0 0 / .28); --well-ink: #a9a8a1;
  --scale: rgb(255 255 255 / .16);
  --hi: inset 0 1px 0 rgb(255 255 255 / .07); --hi-accent: inset 0 1px 0 rgb(255 255 255 / .22); --hi-glass: inset 0 1px 0 rgb(255 255 255 / .06);
  --key-glow: .35;   /* a plain key's pointer pools, as a fraction of an accent key's — the same ratio --hi keeps to --hi-accent here */
  --cut: inset 0 1px 2px rgb(0 0 0 / .45), inset 0 -1px 0 rgb(255 255 255 / .05);
  --cut-glass: inset 0 1px 2px rgb(0 0 0 / .55), inset 0 -1px 0 rgb(255 255 255 / .04);
  --contact: 0 1px 0 rgb(0 0 0 / .4);
  /* a dark body on a dark desk: the shadows, and a hair of light along the top edge, are what make it a surface */
  --drop-panel: 0 1px 2px rgb(0 0 0 / .5), 0 12px 30px rgb(0 0 0 / .45);
  --drop-pop: 0 2px 4px rgb(0 0 0 / .5), 0 14px 34px rgb(0 0 0 / .55);
  --shadow-panel: var(--hi), var(--drop-panel);
  --shadow-pop: var(--hi), var(--drop-pop);
  --shadow-glass: var(--hi-glass), 0 1px 2px rgb(0 0 0 / .5), 0 12px 30px rgb(0 0 0 / .45);
  --shadow-glass-pop: var(--hi-glass), 0 2px 4px rgb(0 0 0 / .5), 0 14px 34px rgb(0 0 0 / .55);
}
:root[data-chassis="clear"] {
  --chassis: rgb(232 230 224 / .42); --chassis-solid: #e8e6e0; --chassis-2: rgb(255 255 255 / .28); --chassis-3: rgb(255 255 255 / .42); --chassis-4: rgb(255 255 255 / .55);
  --line: rgb(20 20 20 / .1);
  --ink: #141414; --ink-2: #4c4b46; --ink-3: #7d7c76;
  --key: rgb(255 255 255 / .6); --key-hover: rgb(255 255 255 / .82); --key-lip: rgb(20 20 20 / .28);
  --well: rgb(0 0 0 / .1);
  --panel-blur: 16px;
  --shadow-panel: 0 1px 2px rgb(20 20 10 / .12), 0 14px 34px rgb(20 20 10 / .22), inset 0 0 0 1px rgb(255 255 255 / .35);
}
:root[data-chassis="clear"][data-scheme="dark"] {
  --chassis: rgb(60 60 66 / .42); --chassis-2: rgb(255 255 255 / .06); --chassis-3: rgb(255 255 255 / .12); --chassis-4: rgb(255 255 255 / .18);
  --line: rgb(255 255 255 / .1);
  --ink: #f0efe9; --ink-2: #b1b0aa; --ink-3: #7b7a74;
  --key: rgb(255 255 255 / .12); --key-hover: rgb(255 255 255 / .2); --key-lip: rgb(0 0 0 / .45);
  --well: rgb(0 0 0 / .3);
  --shadow-panel: 0 1px 2px rgb(0 0 0 / .4), 0 14px 34px rgb(0 0 0 / .5), inset 0 0 0 1px rgb(255 255 255 / .12);
}

/* ═══════════════════════════════════════════════════════════════════════════
   base
   ═══════════════════════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { height: 100%; }
body {
  margin: 0; overflow: hidden; background: var(--bg); color: var(--desk-ink);
  perspective: 1400px;   /* one vanishing point for every tilted device (lighting.js sets `rotate` on them) */
  font: 13.5px/1.45 var(--font-ui); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility; user-select: none; -webkit-user-select: none; touch-action: none;
}
button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; padding: 0; margin: 0; }
img { display: block; }
img.broken { visibility: hidden; }   /* main.js marks pictures that failed to load */
::selection { background: var(--select-bg); }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--chassis-4); border-radius: 8px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-track { background: transparent; }
.dim { color: var(--ink-3); }
.label, .field-label, .pop-title, .pop-sub, .rail-head, .home-h3, .master-ends, .dim-label, .sheet-count, .store-secret label {
  font-family: var(--font-mono); font-size: var(--label-size); font-weight: 500; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-2);
}
kbd { font: 10.5px var(--font-mono); background: var(--display); color: var(--display-ink); border: 0; border-radius: var(--r-display); padding: 3px 7px; letter-spacing: .04em; box-shadow: var(--cut-glass); }

/* focus: one ring for everything */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-key); }
.display:focus-visible, textarea.text:focus-visible, .num:focus-visible { outline-offset: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   materials
   ═══════════════════════════════════════════════════════════════════════════ */
/* MATTE device body */
.panel {
  position: relative;
  background: var(--chassis); color: var(--ink);
  border: 0; padding: 1px; border-radius: var(--r-panel);
  box-shadow: var(--hi), var(--drop-panel);
  -webkit-backdrop-filter: blur(var(--panel-blur)); backdrop-filter: blur(var(--panel-blur));
}
/* the pointer's light on a device (round 20): lighting.js tells each device where the pointer is — --hx / --hy, the
   edge that faces it (a second 1px line of light there: the side the pointer is on, the top when it is above, never the
   bottom — a lamp is never under the desk), --hn how near it is (0 far away … 1 over the device: the line's strength) —
   and every key inside inherits the same light, so the caps glint on the side the pointer is on. The light from above
   stays as it is; at rest (--hn 0) the second line is not there. */
.panel, #readout {
  --hi: inset 0 1px 0 rgb(255 255 255 / .55), inset var(--hx, 0px) var(--hy, 0px) 0 rgb(255 255 255 / calc(.6 * var(--hn, 0)));
  --hi-accent: inset 0 1px 0 rgb(255 255 255 / .28), inset var(--hx, 0px) var(--hy, 0px) 0 rgb(255 255 255 / calc(.3 * var(--hn, 0)));
  --hi-glass: inset 0 1px 0 rgb(255 255 255 / .09), inset var(--hx, 0px) var(--hy, 0px) 0 rgb(255 255 255 / calc(.1 * var(--hn, 0)));
}
:root[data-chassis="black"] .panel, :root[data-chassis="black"] #readout {
  --hi: inset 0 1px 0 rgb(255 255 255 / .07), inset var(--hx, 0px) var(--hy, 0px) 0 rgb(255 255 255 / calc(.1 * var(--hn, 0)));
  --hi-accent: inset 0 1px 0 rgb(255 255 255 / .22), inset var(--hx, 0px) var(--hy, 0px) 0 rgb(255 255 255 / calc(.26 * var(--hn, 0)));
  --hi-glass: inset 0 1px 0 rgb(255 255 255 / .06), inset var(--hx, 0px) var(--hy, 0px) 0 rgb(255 255 255 / calc(.08 * var(--hn, 0)));
}
:root[data-chassis="clear"] .panel { box-shadow: var(--shadow-panel); }
/* "clear" chassis shows its internals: a faint trace pattern under the surface */
:root[data-chassis="clear"] .panel::before { content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 0; opacity: .12;
  background-image: linear-gradient(90deg, transparent 0 14px, currentColor 14px 15px, transparent 15px 100%), linear-gradient(transparent 0 14px, currentColor 14px 15px, transparent 15px 100%);
  background-size: 48px 48px, 48px 48px; mask-image: radial-gradient(120% 90% at 30% 20%, #000 20%, transparent 75%); -webkit-mask-image: radial-gradient(120% 90% at 30% 20%, #000 20%, transparent 75%); }
.panel > * { position: relative; z-index: 1; }
/* reactive lighting: a soft sheen that follows the pointer across every device (quieter since round 20 — the light shows
   on the edges). The sheen is a real element (lighting.js puts one in each device it tracks) moved by its
   background-position: no custom property, so a pointer move re-resolves the style of that one element and nothing
   inside the device (a property that inherits, written on a device, re-resolved everything in it — every field, every
   key — on every move). */
/* THE SHEEN IS A CLIP; THE PICTURE IS A CHILD, MOVED BY TRANSFORM. It used to be a background moved by
   background-position — a paint property, so every pointer frame re-rastered a panel-sized gradient (and, before
   the sheen had its own layer, the whole panel under it). A transform on a childless leaf is composited: the
   same picture, the same place, and nothing repaints. lighting.js moves the `b`. */
.panel > .sheen, #readout > .sheen { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 2; overflow: hidden; opacity: var(--lit, 0); transition: opacity .35s;
  background: radial-gradient(440px circle at center, rgb(255 255 255 / .08), rgb(255 255 255 / .025) 45%, transparent 75%) no-repeat 50% 0% / 880px 880px; }
:root[data-chassis="black"] .panel > .sheen > b { background-image: radial-gradient(440px circle at center, rgb(255 255 255 / .045), rgb(255 255 255 / .015) 45%, transparent 75%); }
:root[data-chassis="clear"] .panel > .sheen > b { background-image: radial-gradient(480px circle at center, rgb(255 255 255 / .3), rgb(255 255 255 / .08) 45%, transparent 75%); background-size: 960px 960px; }
/* the displays catch it too: a thin glint across black glass */
#readout > .sheen > b { background-image: radial-gradient(300px circle at center, rgb(255 255 255 / .07), transparent 65%); background-size: 600px 600px; }
.panel > .sheen > b, #readout > .sheen > b { position: absolute; left: 0; top: 0; width: 880px; height: 880px; background-repeat: no-repeat; will-change: transform; }
:root[data-chassis="clear"] .panel > .sheen > b { width: 960px; height: 960px; }
#readout > .sheen > b { width: 600px; height: 600px; }
:root[data-lighting="off"] .sheen { display: none; }

/* KEY — anything you press: a cap on a lip — the lip drawn inside the box, along its bottom edge, the face padded up by
   as much (so a key is as tall as it looks); the light along the top edge; pressed, the lip goes and the cap drops onto it */
.key, .tool, .btn, .chip, .seg, .dock-app, .dock-add, .sm, .pop-item, .home-tab, .swatch, .slot-btn, .batch-btn, .knob, .lb-x, .lb-prev, .lb-next, .lb-dl, .card-menu-btn {
  transition: background var(--t-fast) var(--ease), color var(--t-fast), transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), border-color var(--t-fast);
}
.key { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 1px 1px calc(var(--press) + 1px); --face: var(--key); color: var(--ink); border: 0; border-radius: var(--r-key); box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); }
.key:hover { --face: var(--key-hover); }
.key:active, .key.pressed { transform: translateY(var(--press)); box-shadow: var(--hi); }
.key.on { --face: var(--accent); color: var(--accent-ink); box-shadow: var(--hi-accent), inset 0 calc(-1 * var(--press)) 0 var(--accent-lip, #b83700), var(--contact); }
.key.on:active { box-shadow: var(--hi-accent); }
.key:disabled { opacity: .5; cursor: default; transform: none; }

/* DISPLAY — dark glass with ivory type (readouts, labels) */
.display, #readout, .batch {
  background: var(--display); color: var(--display-ink); border: 0; border-radius: var(--r-display);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
/* ── THE LCD READOUTS ────────────────────────────────────────────────────────────────────────
   The status bar and the plates used a warm LED on a near-black well; the companion's screen is an
   LCD — dark pixels on pale olive. Two dot grids at the same 3px pitch that looked nothing like
   each other. This is the companion's screen, applied to both.

   It is gated on `data-screen` so the old look is one attribute away while it is being judged:
   set `data-screen="led"` on <html> and everything below stops applying.

   THE PANEL IS LIT FROM THE SIDE. The companion's backlight is an ellipse at `50% -3%` — the top
   edge — which is right for a screen that is nearly square. These are 352x44 and 190x20 strips,
   where a top-down wash has no room to travel and just looks like a lighter edge. Same lamp,
   turned a quarter: the ellipse sits at the LEFT edge and the warmth runs along the display. Same
   colours as `.jl-lamp-field`, at a fraction of the strength — the companion's is a backlight you
   look into, this one only has to say the panel is lit.

   The dot grid itself needs no change: `drawGrid` already lays an unlit cell in EVERY column,
   including the ones between characters, so the grid is continuous. */
:root {
  --dm-panel: linear-gradient(145deg, #717962, #626f57 47%, #59694e);
  /* THE INK IS A RAMP, NOT A COLOUR — this is the heart of it.
     A passive LCD's dark segments are not painted dark, they are what the light does when it meets
     them, so on the companion's screen the pixels near the bulb are warm brown and the ones at the
     far corner are cold green. Measured off `.jl-pixels-rest` / `.jl-pixels-lit`: at the bulb the
     INK moves +209% while the panel under it moves +185%; at the middle, +103% against +49%. The
     ink moves MORE than the panel. That is what says the screen is lit, and it is exactly what a
     single flat `--dm-ink` could not do — the letters here never changed at all while the panel
     graded underneath them.

     Both ramps are the companion's own, re-laid along the strip (its lamp is at the top of a nearly
     square screen; ours is at the left of a long one) and re-sampled onto one shared set of offsets
     so they can be mixed stop by stop. REST is `.jl-pixels-rest`'s 135deg gradient evaluated at
     each offset; LIT is `.jl-pixels-lit`'s radial evaluated the same way. They are written as legal
     CSS stop lists on purpose: the progress bar paints straight out of the same token, so the bar
     and the letters above it cannot drift apart, and dotmatrix.js parses them for the canvas. */
  --dm-ink-rest: #243a34 0%, #253930 21%, #26382c 40%, #26382b 47%, #27392b 62%, #2a3c2b 87%, #2b3d2b 100%;
  --dm-ink-lit:  #8e4e1b 0%, #795020 21%, #614b24 40%, #594b26 47%, #494a29 62%, #2c412b 87%, #273b2b 100%;
  /* HOW FAR THIS SCREEN'S LAMP IS TURNED UP, against the companion's. Derived, not chosen: the
     alphas below are .34/.27/.17 where `.jl-lamp-field`'s are .97/.94/.87 — this panel was
     deliberately built at about a third of the companion's, so the ink's warm copy has to come in
     at the same fraction or the pixels claim a brighter room than the panel they sit on.
     THIS IS THE ONE KNOB. 0 is the unlit ink; 1 is the companion's own lit ink.
     It was .35 while the panel's lamp was about a third of the companion's — the two have to be
     turned up together or the pixels claim a brighter room than the panel they sit on. The lamp is
     the companion's own strength now, so this is too, and the RAMP'S REACH rather than its depth is
     what keeps the far end of the strip cold: the ink warms across the lamp's pool and then stops,
     because that is where the light stops. (While the ramp ran the whole 302px and the lamp only
     the first 60, the letters were claiming a light that was not there — which is the same mistake
     in the other direction.) */
  --dm-lamp-k: .9;
  /* …and the flat ink, for the things that take a `color:` and cannot take a gradient — the text
     readout, the tool chips. It is the ramp's middle, not its coldest stop (which is what
     `--dm-ink: #243a34` used to be: the companion's rest ramp sampled at t=0, applied everywhere). */
  --dm-ink: #3d4129;
  --dm-off: transparent;
  /* A LAMP AT THE EDGE, not a beam through the panel. The first attempt was an ellipse 108% of the
     display wide, which is not a light source at all — it is a wash across the whole screen that
     happens to be brighter at one end. A light has a position and a falloff: this one is 18% of the
     width, sitting just off the left edge, and it is gone by about a third of the way across. Same
     colours as the companion's own backlight (`.jl-lamp-field`), a quarter turn round and much
     weaker: its is a backlight you look into, this only has to say which side the light is on. */
  /* IN PIXELS, NOT PERCENT. A lamp is a physical object and the plate banners are a third of the
     width of the status bar — as a percentage the same lamp would have been a third of the size on
     them, and the ink ramp that has to agree with it is measured in px off the canvas's own box. */
  --dm-lamp-x: -3.5px;
  /* A LIGHT IS DIM OR IT IS SMALL. IT IS NOT DILUTED.
     This carried the companion's own colours at about a third of its alphas, and that is not what
     turning a light down does. An overlay at .34 does not dim the amber, it MIXES IT INTO THE
     OLIVE UNDERNEATH: composited, the companion's bulb lands at 85% saturation on hue 40, and a
     third of it landed at 35% on hue 48, drifting to hue 72 — olive — by a third of the way
     across. That is why it read as a pale wash rather than as a light, and why the same numbers
     kept refusing to produce the same look.
     So the alphas are the companion's own now, and the SIZE does the work the alpha was wrongly
     doing: a tight pool, off the left edge, gone by about a third of the strip. Bright where the
     bulb is, nothing where it is not — which is also what was asked for the first time round
     ("only visible on the left side, just for a small distance"), and what it never was. */
  --dm-lamp-reach: 110px;
  --dm-lamp: radial-gradient(ellipse var(--dm-lamp-reach) 150% at var(--dm-lamp-x) 50%,
    rgb(241 172 36 / .95) 0%,
    rgb(230 175 42 / .82) 14%,
    rgb(206 168 55 / .58) 32%,
    rgb(170 155 66 / .30) 55%,
    rgb(126 135 70 / .10) 78%,
    transparent 100%);
  /* THE POLISHED SHOULDER. The companion's is a 628x484 mask PNG whose alpha crests 1.04 css px in
     from the edge and is gone by 3.12 — a lip on a piece of glass is a millimetre, not a
     percentage, which is why the texture cannot simply be stretched onto an 8:1 strip. Rebuilt here
     as a ring of uniform thickness that follows `border-radius` exactly (see `.rd-rim`).
     1px, not 3.12: the profile is only ABOVE .63 between .35px and 1.75px and then falls away to
     nothing, so a uniform ring at the authored alphas has to be the crest's width, not the whole
     reach. At 3.12 it read as a bright outline drawn round the panel rather than light on a lip,
     and at 1.5 it was still reading thick — the eye adds the `--hi-glass` line to it along the top. */
  --dm-lip: 1px;
  /* THE POOL IS SIZED TO THE RIM IT HAS TO LIGHT. The companion's `ellipse 70% 90%` is a RADIUS on
     a 109x84 screen — 76px, wider than the screen itself — so roughly 40% of its rim perimeter is
     brightly lit at once and the rest falls off. A 40px pool on a 352x44 strip lit 10% of a 792px
     perimeter: correct as a number, invisible as a light. 110px puts about a quarter of this rim in
     the pool, which is the most that still reads as travelling rather than as the whole edge
     coming on. */
  --dm-pool: 110px;
  /* THE RIM HAS ONE THICKNESS. It briefly had a second, wider one for the lit state, on the
     reasoning that the companion's mask crests at 1.04px and reaches 3.12 — but that profile is
     FIXED on the companion: the pool travels inside a ring whose shape never changes, and a rim
     that grows under the cursor is not something glass does. The proportion is the other half of
     it: 3.07px on an 84px-tall screen is 3.7% of the height, and 3.7% of this 40px strip is 1.5px,
     so a 3px band here is twice the companion's rim, not the same one. */
  /* THE SURFACE REFLECTION. Separate from the rim, and the companion has it too — `.jl-screen::after`
     under the shipped compact shape is a pointer-POSITIONED radial over the whole glass, not the
     fixed wash it looks like at a glance. Its own numbers (.026 / .006 at `ellipse 160% 210%`) are
     for a near-square screen; on a 352px strip that ellipse would cover the box and stop reading as
     a moving highlight, so this one is a circle sized to travel along it. */
  --dm-glow: 130px;
  /* THE ACTIVE AREA: a slightly darker rectangle over exactly the cells a pixel can appear in,
     lifted from the companion's `.jl-lcd-base::after`.

     WITHOUT ITS SPECKLE, and that is a difference worth writing down rather than an omission. The
     companion carries a per-cell dot texture, and at its size — a 133px-wide art box, a creature
     twenty cells tall — it reads as the surface of the panel. Here the cell is the same 3px but the
     letters are five cells wide and seven tall, so a dot per cell is the same scale as the strokes
     of the type: it stops reading as texture and starts competing with the words. Same material,
     different size, so not every layer of it survives the move. */
  --dm-active: linear-gradient(145deg, rgb(41 61 37 / .16), rgb(29 49 30 / .22));
}
:root:not([data-screen="led"]) .display,
:root:not([data-screen="led"]) #readout,
:root:not([data-screen="led"]) .ph-banner {
  background: var(--dm-lamp), var(--dm-panel);
  color: var(--dm-ink);
}
/* …and the darker rectangle sits on the canvas itself, which is exactly the area a pixel can
   appear in — no wrapper, and it cannot drift out of register with the grid. */
:root:not([data-screen="led"]) .readout-dm,
:root:not([data-screen="led"]) .ph-dm { background: var(--dm-active); }
/* A HALF PIXEL THAT ARRIVED WITH THIS SCREEN. The LED grid is 20px tall (7 rows of 3, less the
   gutter under the last) and centred in the readout's 42px content box at y=11.0. Filling the cells
   makes it 21, and 42 minus 21 is odd: `align-items: center` put the whole grid on y=11.5, so every
   cell edge in the display was antialiased. A 1px margin under it makes the flex item 22px again
   and lands it back on a whole pixel. */
:root:not([data-screen="led"]) .readout-dm { margin-bottom: 1px; }
/* the type that shares these wells — the fallback text readout, the batch figure, the tool chips —
   has to come with it, or half the display is still wearing the old ink */
:root:not([data-screen="led"]) .readout-text { color: var(--dm-ink); }
/* A PASSIVE LCD HAS ONE INK. It cannot make a green dot, an amber one or a red one — every element
   on the panel is the same etched segment, so the indicator becomes one too: the display's own ink,
   6px square on the 3px grid (two cells), no glow, because a segment cannot be brighter than the
   sheet it is cut from.
   The six states were separated entirely by hue, which is the one thing this panel cannot do, so
   they are separated by RHYTHM instead — and the words beside it already say which is which in so
   many letters, which is what makes that affordable. */
/* …and it takes the ramp's WARM end, because it sits at x=13 — inside the lamp's pool. Every state
   rule below carries the same token: change one and not the others and the square shifts hue as the
   run state changes, which is precisely what a one-ink panel cannot do. */
:root:not([data-screen="led"]) #readout .led {
  --led: var(--dm-ink-hot, var(--dm-ink));
  width: 6px; height: 6px; border-radius: 0; box-shadow: none;
}
:root:not([data-screen="led"]) #readout[data-state="queued"] .led { --led: var(--dm-ink-hot, var(--dm-ink)); }
:root:not([data-screen="led"]) #readout[data-state="running"] .led { --led: var(--dm-ink-hot, var(--dm-ink)); }
:root:not([data-screen="led"]) #readout[data-state="error"] .led {
  /* a wink, not a strobe: .28s steps(1) is 3.6 Hz, over the three-flashes-a-second line */
  --led: var(--dm-ink-hot, var(--dm-ink)); animation: blink 1.2s steps(1, end) infinite;
}
/* OFFLINE. This was a hollow square — a 1.5px inset stroke around a transparent middle — and that
   is a shape this screen cannot make. Everything else on the panel is whole 3px cells of one ink;
   a segment is cut or it is not, and there is no such thing as an outlined one. A 1.5px stroke is
   not even on the grid. It also read as "no signal" in the one way an LCD never shows it: bare
   panel is what you get when the POWER is off, and the power plainly is not off, since the rest of
   the display is still on.
   So it is a filled segment like every other state, at the display's own dim level — .38 is not a
   number picked to look right, it is the alpha `drawGrid` already paints level-1 cells at, so a
   half-lit segment is something this screen genuinely does — and it blinks, slowly, because a link
   that is trying to come back is the one state that is actively happening.
   With motion off the blink goes and the dim level carries it alone, which is why it is dim rather
   than merely blinking. */
:root:not([data-screen="led"]) #readout[data-state="offline"] .led {
  --led: var(--dm-ink-hot, var(--dm-ink));
  opacity: .38; box-shadow: none;
  /* its OWN keyframe, not the shared `blink`. `blink` animates opacity to .35 and an animation
     beats a static declaration, so it would have thrown the .38 away and blinked full-strength.
     This one names only 50%, so the ends are taken from the element — .38 down to .12 and back. */
  animation: dm-dim 1.6s steps(1, end) infinite;
}
@keyframes dm-dim { 50% { opacity: .12; } }
:root:not([data-screen="led"]) body.in-team #readout .led { --led: var(--dm-ink-hot, var(--dm-ink)); }

/* THE PROGRESS BAR IS PART OF THE DISPLAY, not something drawn on top of it. An accent fill inside
   a display well is the same mistake the green dot was: a passive panel has one ink, and a smooth
   CSS bar is not something a dot grid can render anyway. So it is the display's own ink, in cells
   of the same 3px pitch as the letters, with the unfilled part of the track a faint tint of the
   same ink rather than white. The height is already exactly one cell. */
:root:not([data-screen="led"]) .readout-bar,
:root:not([data-screen="led"]) .ph-bar {
  background: rgb(36 58 52 / .14);
  border-radius: 0;          /* a cell is a square; a display cannot round its own corners */
}
:root:not([data-screen="led"]) .readout-fill,
:root:not([data-screen="led"]) .ph-bar-fill {
  /* the same ramp as the letters above it, anchored to the same lamp — `--dm-ink-ramp` and its
     geometry are written by _syncRamp() so the bar and the canvas cannot disagree. The flat colour
     underneath is the ramp's far end: the bar is the full width of the readout while the ramp is
     the width of the CANVAS, so a bar past the last stop would otherwise show bare track. */
  background: var(--dm-ink-ramp, none) var(--dm-ramp-x, 0) center / var(--dm-ramp-w, 100%) 100% no-repeat,
              var(--dm-ink-far, var(--dm-ink));
  border-radius: 0;
  /* IT FILLS A CELL AT A TIME, and the width is quantised in JS (see `cellWidth` in dotmatrix.js)
     rather than here: CSS `round()` would do it in one line and Safari only learned it recently.
     The transition goes with it — a cell is on or off, and easing between the two is the same lie
     as a part-lit cell. */
  transition: none;
}

/* ── THE GLASS OVER THE PANEL ─────────────────────────────────────────────────────────────────
   On the companion, under the shape this app actually ships (`data-shape="compact"`), the only
   pointer-driven highlight left on the screen is `.jl-glass-polish`: one soft pool that follows the
   cursor, plus a fixed polish gradient, BOTH MASKED TO A RING that hugs the border. The ring is the
   whole trick — an unmasked pool over a panel is the fog `.sheen` already paints; the same pool
   through a 2.5px rim becomes a bright spot that travels along the edge, which is what "the
   highlight around the screen lights up when we hover" describes.
   (The travelling edge pools at `.jl-screen::after` are NOT ported: app.css overrides that rule's
   whole background away under `data-shape="compact"`, so they are code nobody has seen render.)

   THE RING. The companion's is a mask PNG measured at 628x484, alpha cresting 1.04 css px in from
   the edge and back to zero by 3.12 — physical, not proportional, so it cannot be stretched onto a
   352x44 strip. Four straight gradient bands would reproduce the profile but not the corners: the
   crest point of two bands meeting at a corner falls outside a radius over 3.5px, so a 6px `--r-key`
   corner would read as a hard bright arc against soft straight runs. This is the same ring built
   from the box itself — two mask layers, one clipped to the padding box, composited `exclude` — so
   it follows `border-radius` exactly at any radius. It trades the profile's soft inner edge for a
   uniform 2.5px, which on a rim whose crest is at 1.04 and is down to .13 by 2.4 is close, and the
   static 1px line in `--hi-glass` is still doing the crest along the top.

   TWO HOMES, because they answer to different things. The GLIMMER is on `::after` and is fixed —
   on the companion it has no `var()` in it at all, and that is what makes the screen look like it
   is under glass when the pointer is nowhere near. The POOL is on a leaf element, because its
   position has to move every frame (a background-position on a childless element re-resolves one
   element and nothing else — the mechanism `.sheen` has used since round 20) while its strength
   comes from `--hn`, which arrives in tenths and is REMOVED when the pointer passes 400px. An
   element's opacity can be transitioned across that step; an alpha inside a gradient cannot. */
:root:not([data-screen="led"]) #readout::after,
:root:not([data-screen="led"]) .ph-banner::after,
:root:not([data-screen="led"]) #readout > .rd-rim,
:root:not([data-screen="led"]) .ph-banner > .rd-rim {
  content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none;
  border-radius: inherit; padding: var(--dm-lip);
  -webkit-mask-image: linear-gradient(#000 0 0), linear-gradient(#000 0 0);
  -webkit-mask-clip: content-box, border-box;
  -webkit-mask-composite: xor;
  mask-image: linear-gradient(#000 0 0), linear-gradient(#000 0 0);
  mask-clip: content-box, border-box;
  mask-composite: exclude;
}
/* the glimmer. The companion's is one 145deg sweep, which on a near-square screen says both which
   edge you are on and how far along it — on an 8:1 strip those two jobs separate, so it is two
   layers: one along the rim and one across it. They composite to .636/.210/.325 against the
   companion's own .62/.20/.374. */
:root:not([data-screen="led"]) #readout::after,
:root:not([data-screen="led"]) .ph-banner::after {
  background:
    linear-gradient(100deg, rgb(255 255 255 / .34), rgb(255 255 255 / .2) 26%,
      rgb(255 255 255 / .035) 52% 74%, rgb(255 255 255 / .13)),
    /* the across-the-rim term is much weaker than the companion's .30: its screen has no other
       highlight on it, and this one sits on a panel that already carries `--hi-glass`'s 1px line
       along its top edge. At .30 the two added up and the rim read THICKER along the top and left
       than along the bottom and right — an asymmetry that looks like a mistake rather than a light. */
    linear-gradient(180deg, rgb(255 255 255 / .12), rgb(255 255 255 / 0) 55%, rgb(255 255 255 / .06));
}
/* the pool: the companion's own `.jl-glass-polish` gradient, at its own alphas. Its `ellipse 70% 90%`
   is a RADIUS on a 108.7x83.8px screen — a 75px circle, .90 of that screen's height — so here it is
   .90 of this box's height rather than a percentage of an 8:1 one, which would smear it flat. */
:root:not([data-screen="led"]) #readout > .rd-rim,
:root:not([data-screen="led"]) .ph-banner > .rd-rim {
  z-index: 5;
  background: radial-gradient(closest-side,
    rgb(255 255 255 / .78), rgb(255 255 255 / .3) 40%, transparent 90%)
    no-repeat 0 0 / calc(var(--dm-pool) * 2) calc(var(--dm-pool) * 2);
  opacity: 0; transition: opacity .18s linear;
}
/* THE REFLECTION ON THE SURFACE, the companion's `.jl-screen::after` pool. Not masked to the rim —
   this is the one that moves across the glass. It is the layer that was missing: the strip had a
   `.sheen` doing something like it, but as a 300px-radius blob in a 600px tile on a 352px box it
   had no shape inside the box at all, so it read as the panel getting evenly foggier rather than as
   a light passing over it. Same alphas as the companion's, which are deliberately low — this is a
   reflection on glass, and the rim above is what carries the brightness. */
:root:not([data-screen="led"]) #readout > .rd-glow,
:root:not([data-screen="led"]) .ph-banner > .rd-glow {
  position: absolute; inset: 0; z-index: 5; pointer-events: none;
  border-radius: inherit; overflow: hidden;
  /* the companion's own alphas are .026 / .006, and at that strength on a 352x44 strip the
     reflection is below the threshold where anyone would call it a highlight — its screen is small
     enough that the pool covers the whole thing and you read the falloff against the bezel, where
     here you only ever see the middle of it. Roughly 3.5x, which lands at a ~5% lift at the
     pointer: a soft sheen passing over the panel, still well under the flat 7% fog the `.sheen`
     used to lay across the whole strip. */
  background: radial-gradient(closest-side,
    rgb(255 255 255 / .09), rgb(255 255 255 / .025) 48%, transparent 100%)
    no-repeat 0 0 / calc(var(--dm-glow) * 2) calc(var(--dm-glow) * 2);
  opacity: 0; transition: opacity .18s linear;
}
:root:not([data-screen="led"]) #readout > .rd-glow { opacity: calc(var(--lit, 0) * var(--hn, 0)); will-change: opacity; }
:root:not([data-screen="led"]) .ph-banner:hover > .rd-glow { opacity: 1; }
:root[data-lighting="off"] .rd-glow,
:root[data-screen="led"] .rd-glow { display: none; }
:root[data-motion="off"] .rd-glow { transition: none; }

/* ── THE GLARE ───────────────────────────────────────────────────────────────
   The third layer, and the only one that answers to the PLANE rather than the pointer. On the
   companion it is `.jl-screen::before`: soft near-white ellipses lying on the glass, translated by
   `--glare-angle-x/y`, which device-light.js derives from `plane.rx / plane.ry`. Tilt the desk and
   the reflection slides across; hold still and it sits where it is.

   IT IS ONE BIG REFLECTION, NOT SEVERAL. `ellipse 62% 57%` on the companion is a RADIUS, so its
   primary is 134 x 96 on a 109 x 84 screen — half again as wide as the glass it lies on, anchored
   up and to the left at `30% 19%`, and what you see is the bright shoulder of something much
   bigger than the screen. The first port here read those as diameters and laid three small blobs
   across a wide tile, which is three reflections in a row and not what glass does.

   SO IT IS BUILT THE COMPANION'S WAY: an oversized PICTURE that the box clips, moved by a
   transform — not a background inside the box moved by its position, which is what ran out of
   picture at full sway. `.rd-glare` is the clip; `::before` is the picture, big enough to cover
   the box plus the whole travel, so there is always more of it arriving.

   THE RADII ARE PIXELS, AND THAT IS THE WHOLE LESSON HERE. A percentage in a radial-gradient is
   a percentage of the BOX, so it carries the box's aspect ratio into the shape. The companion's
   `62% 57%` on a 109 x 84 screen is 67 x 48 px — 1.41:1, near enough a circle. The same two
   numbers re-expressed against this strip's picture gave 199 x 23 px: 8.7:1, a thin sausage
   stretched across the display. The percentages were carried over faithfully and produced the
   opposite shape, because the strip is 8:1 and the screen it came from is 1.3:1.

   So they are the companion's own numbers in the unit that survives the move — at TWICE its size,
   and that is a judgement rather than a derivation. A reflection is a physical size: the same room
   in a bigger piece of glass gives the same reflection and more glass around it, not a stretched
   one. But the companion's screen is 109px across and this one is 352, and a 67px reflection that
   fills most of that little screen is a spot on this one. Doubled, it is 136 x 96 — near enough
   the width of the companion's whole screen — and it reads as one big soft source rather than as
   something sitting on the panel. The second is 70 x 70, a true circle, as it is on the companion.

   AND IT IS ANCHORED OFF THE LEFT EDGE, at 5% of the width rather than the companion's 30%, so
   roughly half of it is outside the display. That is the difference between a reflection and a
   blob: you are not meant to see the whole of it. A soft source big enough to matter runs off the
   glass, and the edge you can see is the shoulder of something larger. The small circle keeps its
   position relative to the big one (13 points to its left, as on the companion), which puts it
   almost entirely outside the box — a lift at the very left edge and nothing more.

   THE ALPHAS ARE THE COMPANION'S TOO (.135). They were briefly .26, which was compensation for a
   shape that had smeared the light across 400px and needed brightness to be seen at all. A
   reflection this size does not need it, and the intensity read as a stripe rather than as glare.

   One thing this port cannot copy: the companion is lit from the top and its glare sits left of
   that, so the two do not share a corner. A strip lit from the left edge has the lamp and the
   reflection's home in the same place, so at rest the glare lies over the brightest part of the
   panel rather than beside it. The anchor is the companion's `30% 19%` because that is what was
   asked for; the lever if it wants separating is that pair of numbers.

   THE ARITHMETIC for the rest. The picture is 190% x 180% of the box, offset -45% / -40% — the box
   plus the 45%/40% of travel each way. Centres are given against that picture, so a position of
   `f` of the BOX is written as (f + 45) / 190: the big one at 5% of the box is (5+45)/190 = 26.3%,
   the small one at -8% is (-8+45)/190 = 19.5%, and the vertical stays the companion's 19% and 33%,
   which are (19+40)/180 = 32.8% and (33+40)/180 = 40.6%.

   Not gated on `--lit` or `--hn`: a reflection of the room is there whether or not anyone is
   pointing at it. With `data-motion` off the plane never leaves zero, so it sits at rest. */
:root:not([data-screen="led"]) #readout > .rd-glare {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-radius: inherit; overflow: hidden;
}
:root:not([data-screen="led"]) #readout > .rd-glare::before {
  content: ""; position: absolute;
  left: -45%; top: -40%; width: 190%; height: 180%;
  background:
    radial-gradient(ellipse 136px 96px at 26.3% 32.8%,
      rgb(236 239 235 / .135) 0 25%,
      rgb(234 237 233 / .105) 44%,
      rgb(234 237 233 / .045) 63%,
      rgb(234 237 233 / .012) 77%, transparent 88%),
    radial-gradient(ellipse 70px 70px at 19.5% 40.6%,
      rgb(227 232 228 / .06) 0 18%,
      rgb(227 232 228 / .04) 43%,
      rgb(227 232 228 / .012) 64%, transparent 81%);
  /* the leaf is childless, so an inheriting property written on it reaches this pseudo-element and
     invalidates nothing else — which is the only way JS can move a ::before at all (a registered
     `inherits: false` property does NOT reach a pseudo from its originating element; that is what
     has had the sticker gloss pinned since it was written) */
  transform: translate3d(var(--glare-x, 0px), var(--glare-y, 0px), 0);
}
:root[data-lighting="off"] .rd-glare,
:root[data-screen="led"] .rd-glare { display: none; }

/* the status bar has the desk's own light, so the pool follows the pointer across the whole room */
:root:not([data-screen="led"]) #readout > .rd-rim { opacity: calc(var(--lit, 0) * var(--hn, 0)); will-change: opacity; }
/* a plate does not: it is not a tracked panel and cannot cheaply be one (it rides a plane that pans
   and zooms, so a cached rect is wrong the moment the desk moves). It lights on hover instead, and
   NOT on `--lit` — that is the desk's 2.6s idle timer, which would fade the gloss out from under a
   cursor that is still sitting on the plate. */
:root:not([data-screen="led"]) .ph-banner:hover > .rd-rim { opacity: 1; }
:root[data-lighting="off"] .rd-rim,
:root[data-screen="led"] .rd-rim { display: none; }
:root[data-motion="off"] .rd-rim { transition: none; }
/* ONE POINTER LIGHT, NOT TWO. `--hi-glass` already carries a 1px line that brightens on the edge
   facing the pointer, scaled by the same `--hn`. Left in, the strip would wear two pointer-driven
   rim highlights with different geometry — one offset toward the near edge, one centred on the
   cursor — which is the class of thing this round is fixing. The companion does the same removal:
   its compact rule drops the `--edge-light` box-shadow and leaves the polish as the only one. The
   static top line stays; it is the crest. */
:root:not([data-screen="led"]) #readout { --hi-glass: inset 0 1px 0 rgb(255 255 255 / .09); }
:root[data-scheme="dark"]:not([data-screen="led"]) #readout { --hi-glass: inset 0 1px 0 rgb(255 255 255 / .06); }
/* …and the fog goes with it. `.sheen` is a 300px-radius, 7%-white blob with no edge inside a 352x44
   box: wherever the pointer is it covers the whole strip at near-uniform alpha, and it paints at
   z-index 2 OVER the dot matrix, lifting the dark letters by the same 4% it lifts the panel. On the
   old near-black display that read as glass. On dark ink over a pale panel it just flattens the
   letters, and it is the reason a rim light would have looked blunt. */
:root:not([data-screen="led"]) #readout > .sheen { display: none; }
/* the progress bar is ink on the panel and the polish is glass above it — but the rim's bottom edge
   and the bar are the same three pixels, and a .78 pool travelling along them would wipe out the
   bar's leading cells exactly while something is cooking. The bar wins those pixels. */
:root:not([data-screen="led"]) .readout-bar { z-index: 6; }
:root:not([data-screen="led"]) .ph-bar { position: relative; z-index: 6; }


/* PAPER — what you type into: a light sheet on the light desk, the same glass as displays on the dark one */
textarea.text, .num, .select, .text-input, .store-secret-row input, .store-search {
  background: var(--field); color: var(--field-ink); border: 0; border-radius: var(--r-display);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; box-shadow: var(--cut);
}
/* WELL — what you set: a recess in the chassis */
.segmented, .switch-track {
  background: var(--well); color: var(--well-ink); border: 0; border-radius: var(--r-display);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; box-shadow: var(--cut);
}
/* focus on paper: one 2px accent ring, the same for every sheet (the recess stays) */
textarea.text:focus, .num:focus, .select:focus, .text-input:focus, .store-secret-row input:focus, .store-search:focus-within, .dialog-input:focus { outline: none; box-shadow: var(--cut), 0 0 0 2px var(--accent); }

/* ═══════════════════════════════════════════════════════════════════════════
   small controls
   ═══════════════════════════════════════════════════════════════════════════ */
/* tool: a flat, quiet button inside a device (matte; lifts on hover) */
.tool { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 28px; height: 28px; padding: 1px 9px; border-radius: var(--r-key); color: var(--ink-2); font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; border: 0; }
.tool:hover { background: var(--chassis-3); color: var(--ink); }
.tool:active { transform: translateY(1px); background: var(--chassis-4); }
.tool.on { background: var(--accent); color: var(--accent-ink); }
.tool.on:hover { filter: brightness(1.06); }
.tool.danger:hover { color: var(--danger); }
.tool .tool-label { font-size: 10.5px; }
.tool.magic.busy .icon { animation: spin 1s linear infinite; }
/* (the reset pip used to rest dimmer and hover to the accent — with a green accent it was the one tool that went
   green; it is a .tool like the rest now, round 23) */
@keyframes spin { to { transform: rotate(360deg); } }

/* btn: a key with a label */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 34px; padding: 1px 15px calc(var(--press) + 1px); border-radius: var(--r-key); --face: var(--key); color: var(--ink); border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); font-family: var(--font-mono); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; white-space: nowrap; text-decoration: none; }
.btn:hover { --face: var(--key-hover); }
.btn:active { transform: translateY(var(--press)); box-shadow: var(--hi); }
.btn.primary, .btn.on { --face: var(--accent); color: var(--accent-ink); box-shadow: var(--hi-accent), inset 0 calc(-1 * var(--press)) 0 var(--accent-lip, #b83700), var(--contact); }
.btn.primary:hover, .btn.on:hover { filter: brightness(1.06); }
.btn.primary:active, .btn.on:active { box-shadow: var(--hi-accent); }
/* ghost: a quiet key with no lip — a tone on the chassis (it used to be an outlined one) */
.btn.ghost { --face: var(--chassis-3); box-shadow: var(--hi); }
.btn.ghost:hover { --face: var(--chassis-4); }
.btn.ghost:active { transform: translateY(1px); }
.btn.danger:hover { color: var(--danger); --face: rgb(255 74 74 / .14); }
/* ═══════════════════════════════════════════════════════════════════════════
   LIT KEYS (round 23) — the companion's lighting, on every bevelled thing.
   Ben approved this on a mockup of the make-it key ("Make It, Lit") and asked for it on all of them.

   `background` for a key is declared HERE and nowhere else. Every rule that used to paint a key's
   background — its base, its :hover, its .on, its :disabled — now sets `--face` instead, and this one
   block turns `--face` into the finished surface. That is what lets hover and on and disabled keep
   their own specificity without the lit stack ever having to out-rank them.

   The stack, top layer first:
     the sheen     a soft pool centred where the pointer is (`--klx/--kly`, lighting.js writes them on
                   the few keys within reach — a property set on a KEY re-resolves a handful of nodes;
                   set on a panel it would re-resolve every key inside, which is the thing lighting.js
                   was built to avoid). Strength is nearness, so it is not there until you approach.
     the top edge  a 1px hairline hugging y=0, pooled under the pointer across two thirds of the width —
                   a rim catches light along a line, not a band.
     the lower     the same, on the row just above the lip (`--lip`, default `--press`): the edge where
     edge          the top face turns down.
     the walls     one hairline on each side, FIXED — a wall is where it is. What changes is how lit each
                   one is: the left by how much the light is from the left (`--hxn` > 0), the right by
                   how much from the right. Driving the position from the light put the pool in the
                   middle of the cap whenever the pointer was near the centre.
     the surface   a gradient too slight to read as one, and it shifts HUE, not just lightness: the top
                   lighter (+.05 L, a touch less chroma — a highlight on plastic washes toward white) and
                   warm, the bottom a hair darker and cool. Mixed in OKLCH toward a warm yellow and a
                   cool blue so the drift goes the right way for ANY face colour; one ramp, interpolated
                   in OKLCH so lightness and hue move evenly all the way down. Measured on the green:
                   L .777 → .705, hue 156° → 160°.
   The plain `background: var(--face)` before the stack is the fallback: a browser without relative
   colour syntax (Safari < 16.4) keeps a flat key rather than none.
   ═══════════════════════════════════════════════════════════════════════════ */
.key, .btn, .chip, .dock-app, .dock-add, .knob, .make, .home-tab, .plus-key, .lb-dl, .magic-ref, .switch-knob, .asst-send {
  --kedge: var(--kn, 0);   /* `--kn` is only ever written while the desk is lit and is taken away on sleep, so `--lit` added nothing here — and reading it kept the keys dependent on a document-wide property */
  /* HOW BRIGHT. The pools were tuned on the green accent key and were far too much on a grey one — white on
     dark grey is a different thing from white on a saturated mid-tone. So a plain key glows at `--key-glow`
     (the scheme's own ratio, .35 dark / .5 light), and the accent keys below at the full approved strength. */
  --kglow: var(--key-glow, .35);
  background: var(--face);
  background: radial-gradient(ellipse 110% 150% at var(--klx, 50%) var(--kly, 40%), rgb(255 255 255 / calc(var(--kedge) * var(--kglow) * .17)), rgb(255 255 255 / calc(var(--kedge) * var(--kglow) * .035)) 46%, transparent 100%),
    radial-gradient(ellipse closest-side, rgb(255 255 255 / calc((.12 + var(--kedge) * .62) * var(--kglow))), transparent 100%) var(--klx, 50%) 0 / 66% 1px no-repeat,
    radial-gradient(ellipse closest-side, rgb(255 255 255 / calc(var(--kedge) * var(--kglow) * .30)), transparent 100%) var(--klx, 50%) calc(100% - var(--lip, var(--press))) / 66% 1px no-repeat,
    radial-gradient(ellipse closest-side, rgb(255 255 255 / calc(var(--kedge) * var(--kglow) * max(0, var(--hxn, 0)) / 1.2 * .42)), transparent 100%) 0 var(--kly, 50%) / 1px 58% no-repeat,
    radial-gradient(ellipse closest-side, rgb(255 255 255 / calc(var(--kedge) * var(--kglow) * max(0, -1 * var(--hxn, 0)) / 1.2 * .42)), transparent 100%) 100% var(--kly, 50%) / 1px 58% no-repeat,
    linear-gradient(180deg in oklch, oklch(from color-mix(in oklch, var(--face) 97%, oklch(0.86 0.14 95) 3%) calc(l + 0.05) calc(c * 0.92) h) 0%, oklch(from color-mix(in oklch, var(--face) 97.5%, oklch(0.52 0.11 255) 2.5%) calc(l - 0.012) c h) 100%);
}
.key.on, .btn.primary, .btn.on, .btn.danger-key, .chip.on, .dock-app.on, .home-tab.on, .make, .plus-key, .lb-dl, .asst-send,
.switch input:checked + .switch-track .switch-knob { --kglow: 1; }
.make:disabled, .canvas-card.new.join-room .plus-key { --kglow: var(--key-glow, .35); }
/* round, or too small for a rim to read: the sheen and the surface only */
.knob, .switch-knob {
  background: var(--face);
  background: radial-gradient(ellipse 110% 150% at var(--klx, 50%) var(--kly, 40%), rgb(255 255 255 / calc(var(--kedge) * var(--kglow) * .17)), rgb(255 255 255 / calc(var(--kedge) * var(--kglow) * .035)) 46%, transparent 100%), linear-gradient(180deg in oklch, oklch(from color-mix(in oklch, var(--face) 97%, oklch(0.86 0.14 95) 3%) calc(l + 0.05) calc(c * 0.92) h) 0%, oklch(from color-mix(in oklch, var(--face) 97.5%, oklch(0.52 0.11 255) 2.5%) calc(l - 0.012) c h) 100%);
}
/* where the lip is not `--press` tall */
.make, .plus-key { --lip: 3px; }
.chip, .magic-ref, .switch-knob { --lip: 1px; }

/* THE LEGEND ON THE MAKE-IT KEY, CUT IN. Two more copies of the legend (ui-shell.js builds them), carried by
   transform — float, never pixel-snapped — the pale one toward the far wall of the cut (+hx), the shade toward
   the near one. Only on this key: the copies are markup, and this is the one key built in one place. */
.make > .legend { grid-area: 1 / 1; display: inline-flex; align-items: center; gap: 8px; position: relative; z-index: 1; }
.make > .ghost { pointer-events: none; z-index: 0; will-change: transform; }
.make > .ghost .make-led { background: currentColor; }
.make > .ghost.pale { color: rgb(255 255 255 / calc(.26 + var(--kedge) * .34)); transform: translate(calc(var(--hxn, 0) * .6px), calc(.4px + var(--hyn, 0) * .6px)); filter: blur(.3px); }
.make > .ghost.dark { color: rgb(0 0 0 / .32); transform: translate(calc(var(--hxn, 0) * -.45px), calc(-.3px - var(--hyn, 0) * .45px)); filter: blur(.35px); }

.btn.small { height: 28px; padding: 1px 11px calc(var(--press) + 1px); font-size: 10.5px; }
.btn.iconbtn { width: 34px; padding: 1px 1px calc(var(--press) + 1px); }
.btn:disabled { opacity: .5; cursor: default; }

/* switch: a well with a key for a knob (a 1px lip — a low cap) */
.switch { display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track { width: 44px; height: 24px; box-sizing: border-box; position: relative; border-radius: var(--r-display); transition: background var(--t-mid) var(--ease); }
.switch-knob { position: absolute; top: 3px; left: 3px; width: 20px; height: 18px; box-sizing: border-box; border-radius: 3px; --face: var(--key); border: 0; box-shadow: var(--hi), inset 0 -1px 0 var(--key-lip), var(--contact); transition: transform var(--t-mid) var(--spring);   /* NOT background: the pointer pools rewrite it every frame (LIT KEYS) */ }
.switch:hover .switch-knob { --face: var(--key-hover); }
.switch:active .switch-knob { transform: scaleX(.94); }
.switch input:checked + .switch-track .switch-knob { transform: translateX(18px); --face: var(--accent); box-shadow: var(--hi-accent), inset 0 -1px 0 var(--accent-lip, #b83700), var(--contact); }   /* 44 − 2·3 − 20 = 18: the same 3 px inset on the right */
.switch input:focus-visible + .switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }

/* segmented: a well, the segment that is on is an accent key */
.segmented { display: inline-flex; padding: 4px; gap: 2px; }
.seg { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 11px; border-radius: 3px; color: var(--well-ink); font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; }
.seg:hover { color: var(--ink); background: var(--field-hover); }
.seg:active { transform: translateY(1px); }
.seg.on { background: var(--accent); color: var(--accent-ink); box-shadow: none; }
.segmented.mini .seg { height: 22px; padding: 0 8px; font-size: 10px; }

/* text: a sheet you type on */
textarea.text { width: 100%; min-height: 76px; resize: none; padding: 11px 13px; line-height: 1.5; font-size: 12.5px; user-select: text; -webkit-user-select: text; transition: background var(--t-fast), box-shadow var(--t-fast); }
textarea.text::placeholder { color: var(--field-ink-3); }
/* the magic prompt is on: the sheet takes a wash of the accent (no outline — a state is a tone here) */
textarea.text.magic-on { background: color-mix(in srgb, var(--field), var(--accent) 7%); }
.text-input { width: 100%; padding: 9px 11px; margin-bottom: 10px; user-select: text; -webkit-user-select: text; }
.text-wrap { position: relative; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.text-wrap textarea { align-self: stretch; }
/* the reference for the magic prompt: a low key (1px lip) — with a picture in it, a lit one */
.magic-ref { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 1px 10px 2px 6px; border-radius: 3px; --face: var(--key); border: 0; box-shadow: var(--hi), inset 0 -1px 0 var(--key-lip), var(--contact); font: 500 10px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-2); cursor: pointer; transition: color var(--t-fast), box-shadow var(--t-fast);   /* NOT background — see LIT KEYS */ }
.magic-ref img { width: 16px; height: 16px; border-radius: 2px; object-fit: cover; display: none; }
.magic-ref.has img { display: block; }
.magic-ref.has { --face: var(--key-hover); color: var(--ink); }
.magic-ref-x { display: none; color: var(--ink-3); }
.magic-ref.has .magic-ref-x { display: inline-flex; }
.magic-ref:hover { --face: var(--key-hover); color: var(--ink); }
.magic-ref.drop-over { box-shadow: var(--hi), inset 0 -1px 0 var(--key-lip), var(--contact), 0 0 0 2px var(--accent); color: var(--ink); }

/* number: a small display */
.num { width: 76px; height: 30px; padding: 1px 9px; text-align: center; font-size: 12px; letter-spacing: .04em; -moz-appearance: textfield; user-select: text; -webkit-user-select: text; transition: background var(--t-fast), box-shadow var(--t-fast); }
.num::-webkit-outer-spin-button, .num::-webkit-inner-spin-button { -webkit-appearance: none; }
.num.dim { color: var(--field-ink-2); }

/* fader (round 20): a groove cut into the chassis (a well, 6px), the part set filled in the accent INSIDE the groove —
   under the same recess shading, so it reads as the groove lit up to the cap, not a bar laid over it — the scale printed
   on the chassis beneath the groove (from the cap's first stop to its last), a key for a cap */
.slider-row { display: flex; align-items: center; gap: 10px; }
.range-wrap { position: relative; flex: 1; height: 28px; display: flex; align-items: center;
  /* the scale: ten divisions between the cap's first stop (7px in) and its last (7px from the end), the ends and the middle a touch longer */
  background: repeating-linear-gradient(90deg, var(--scale) 0 1px, transparent 1px calc((100% - 1px) / 10)) no-repeat 7px 21px / calc(100% - 13px) 3px,
              repeating-linear-gradient(90deg, var(--scale) 0 1px, transparent 1px calc((100% - 1px) / 2)) no-repeat 7px 19px / calc(100% - 13px) 5px; }
.range-wrap::before { content: ''; position: absolute; left: 0; right: 0; top: 11px; height: 6px; border-radius: 3px; background: var(--well); }
.range-fill { position: absolute; left: 0; top: 11px; height: 6px; border-radius: 3px 0 0 3px; background: var(--accent); pointer-events: none; width: 0; max-width: 100%; }
.range-wrap::after { content: ''; position: absolute; left: 0; right: 0; top: 11px; height: 6px; border-radius: 3px; box-shadow: var(--cut); pointer-events: none; }
/* THE CAP SITS ON THE WELL, NOT IN IT. `.range-wrap::after` carries the well's cut shadow and, as
   a pseudo-element, is generated after every real child — so with everything at `z-index: auto`
   the painting order was fill, input, cut, and the cut landed on top of the cap. The cap wears its
   own `--hi` and `--contact`; a second object's shadow over it read as a smear across the middle
   of the button. One step up puts the input above the cut and leaves the fill below it, which is
   the right order for all three: ink in the well, the well's edge over the ink, the cap over both. */
input.range { position: relative; z-index: 1; width: 100%; margin: 0; background: transparent; -webkit-appearance: none; appearance: none; height: 28px; cursor: ew-resize; }
input.range::-webkit-slider-runnable-track { height: 6px; background: transparent; }
input.range::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 26px; margin-top: -10px; border-radius: 3px; background: linear-gradient(var(--scale), var(--scale)) no-repeat center / 1px 10px, var(--key); border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); transition: background var(--t-fast); }
input.range:hover::-webkit-slider-thumb { background: linear-gradient(var(--scale), var(--scale)) no-repeat center / 1px 10px, var(--key-hover); }   /* the cap carries a line, like the scale's */
input.range:active::-webkit-slider-thumb { background: linear-gradient(var(--scale), var(--scale)) no-repeat center / 1px 10px, var(--key-hover); box-shadow: var(--hi), inset 0 -1px 0 var(--key-lip); }
input.range::-moz-range-track { height: 6px; background: transparent; }
input.range::-moz-range-thumb { width: 14px; height: 26px; border-radius: 3px; background: linear-gradient(var(--scale), var(--scale)) no-repeat center / 1px 10px, var(--key); border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); }
input.range:focus-visible { outline: none; }
input.range:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }

/* chips (aspect ratios, resolutions) — low keys (a 1px lip), the accent when on */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.chip { height: 24px; padding: 1px 10px 2px; border-radius: 3px; --face: var(--key); border: 0; box-shadow: var(--hi), inset 0 -1px 0 var(--key-lip), var(--contact); font: 500 10px var(--font-mono); letter-spacing: .04em; color: var(--ink-2); }
.chip:hover { --face: var(--key-hover); color: var(--ink); }
.chip:active { transform: translateY(1px); box-shadow: var(--hi); }
.chip.on { --face: var(--accent); color: var(--accent-ink); box-shadow: var(--hi-accent), inset 0 -1px 0 var(--accent-lip, #b83700), var(--contact); }
.chip.on:active { box-shadow: var(--hi-accent); }
/* a dropdown's options as chips you pick one from (aspect ratios, resolutions); a ratio carries a frame in its own proportion */
.chips.pick { margin-top: 0; }
.chips.pick .chip { display: inline-flex; align-items: center; gap: 6px; }
.chip-frame { display: block; flex: 0 0 auto; border: 1.5px solid currentColor; border-radius: 1.5px; opacity: .7; }
.chip.on .chip-frame { opacity: 1; }
/* a tag is read, not pressed: a small well */
.tag { display: inline-flex; align-items: center; height: 22px; padding: 1px 9px; border-radius: 3px; background: var(--well); border: 0; box-shadow: var(--cut); font: 500 10px var(--font-mono); letter-spacing: .04em; text-transform: uppercase; color: var(--ink-2); }
.badge { display: inline-flex; align-items: center; height: 18px; padding: 0 6px; border-radius: 3px; background: var(--accent); color: var(--accent-ink); font: 600 9.5px var(--font-mono); letter-spacing: .06em; text-transform: uppercase; }

/* select: a sheet with a chevron */
.select-wrap { position: relative; }
.select { width: 100%; height: 34px; padding: 1px 35px 1px 13px; appearance: none; -webkit-appearance: none; font-size: 12px; cursor: pointer; }
.select-wrap .icon { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--field-ink-2); }

/* spinner (progress ring in accent) */
.spinner { width: 18px; height: 18px; border-radius: 50%; border: 2px solid rgb(var(--accent-rgb) / .25); border-top-color: var(--accent); animation: spin .8s linear infinite; }

/* popovers */
.popover { position: fixed; z-index: 500; padding: 11px; min-width: 248px; box-shadow: var(--hi), var(--drop-pop); animation: pop-in .18s var(--spring); }
.pop-title { padding: 4px 8px 8px; }
.pop-sub { padding: 10px 8px 4px; color: var(--ink-3); font-size: 10px; }
.pop-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 6px 8px; font-size: 12.5px; color: var(--ink-2); min-height: 34px; }
.pop-item { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 10px; border-radius: var(--r-key); font-size: 12.5px; color: var(--ink); border: var(--bw) solid transparent; }
a.pop-item { color: inherit; text-decoration: none; box-sizing: border-box; }   /* a menu item that is a link (the admin's usage page) */
.pop-item:hover { background: var(--chassis-3); }
.pop-item:active { transform: translateY(1px); }
.pop-item.danger:hover { color: var(--danger); }
.pop-actions { display: flex; gap: 8px; padding: 10px 8px 4px; }
.help-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 5px 8px; font-size: 12px; color: var(--ink-2); }
.help-row kbd { min-width: 84px; text-align: center; }
@keyframes pop-in { from { opacity: 0; transform: translateY(-4px) scale(.98); } to { opacity: 1; transform: none; } }

/* the prompt library (round 24): opens BESIDE the field it was called from, so the prompt stays in view as saved
   pieces are added to it. No lines anywhere — the list is separated from the save key by a step in tone. */
.prompt-lib { width: 320px; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.prompt-lib .text-input { margin-bottom: 0; }
.prompt-lib > .pl-search, .prompt-lib > .pl-foot { flex: none; }
.pl-list { flex: 1 1 auto; min-height: 0; max-height: min(460px, 56vh); overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 2px; }   /* shrinks when the popover is shortened to fit above or below its field */
.pl-item { display: flex; align-items: flex-start; gap: 6px; padding: 8px 6px 8px 10px; border-radius: var(--r-key); cursor: pointer; transition: background var(--t-fast); }
.pl-item.hi { background: var(--chassis-3); }
.pl-item:active:not(:has(.tool:active)) { transform: translateY(1px); }   /* holding the heart, pencil or trash is not pressing the row: :active reaches every ancestor, and the whole row used to sink (and Safari re-rendered its text thinner) */
.pl-item.added { background: rgb(var(--accent-rgb) / .16); }
.pl-main { flex: 1; min-width: 0; }
.pl-name { font-size: 12.5px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pl-text { margin-top: 3px; font: 11px/1.45 var(--font-mono); color: var(--ink-3); overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; word-break: break-word; }
.pl-acts { display: flex; gap: 1px; }
.pl-acts .tool { height: 24px; min-width: 24px; padding: 0 5px; opacity: 0; transition: opacity var(--t-fast); }
.pl-item.hi .pl-acts .tool, .pl-item:focus-within .pl-acts .tool, .pl-item.pinned .pl-pin { opacity: 1; }
@media (hover: none) { .pl-acts .tool { opacity: 1; } }   /* no hover on a touch screen: the actions are always there */
.pl-item.pinned .pl-pin { color: var(--accent); }
.pl-item.pinned .pl-pin .icon { fill: var(--accent); }
.pl-pin .icon { transform: translateY(-1px); transition: transform .25s var(--spring); }   /* a heart's weight is in its lobes: a pixel up reads as centred */
.pl-pin:active .icon { transform: translateY(-1px) scale(.8); }
.pl-group { padding: 10px 10px 3px; }
.pl-group:first-child { padding-top: 3px; }
.pl-empty { padding: 16px 12px; font-size: 12px; line-height: 1.55; color: var(--ink-3); text-align: center; text-wrap: balance; }
.pl-empty-title { display: block; margin-bottom: 4px; color: var(--ink-2); }
.pl-foot { background: var(--chassis-2); border-radius: var(--r-key); padding: 3px; }
.pl-save { color: var(--ink-2); }
.pl-save:disabled { opacity: .5; cursor: default; transform: none; background: none; }

/* scroll fades: a container fades where it can still scroll (data-fade set by fades.js) */
.fade-y { --f: 18px; }
.fade-y[data-fade="end"] { mask-image: linear-gradient(#000 calc(100% - var(--f)), transparent); -webkit-mask-image: linear-gradient(#000 calc(100% - var(--f)), transparent); }
.fade-y[data-fade="start"] { mask-image: linear-gradient(transparent, #000 var(--f)); -webkit-mask-image: linear-gradient(transparent, #000 var(--f)); }
.fade-y[data-fade="both"] { mask-image: linear-gradient(transparent, #000 var(--f), #000 calc(100% - var(--f)), transparent); -webkit-mask-image: linear-gradient(transparent, #000 var(--f), #000 calc(100% - var(--f)), transparent); }
.fade-x { --f: 22px; }
.fade-x[data-fade="end"] { mask-image: linear-gradient(90deg, #000 calc(100% - var(--f)), transparent); -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - var(--f)), transparent); }
.fade-x[data-fade="start"] { mask-image: linear-gradient(90deg, transparent, #000 var(--f)); -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--f)); }
.fade-x[data-fade="both"] { mask-image: linear-gradient(90deg, transparent, #000 var(--f), #000 calc(100% - var(--f)), transparent); -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--f), #000 calc(100% - var(--f)), transparent); }

/* ═══════════════════════════════════════════════════════════════════════════
   fields (the app's inputs)
   ═══════════════════════════════════════════════════════════════════════════ */
.fields { display: flex; flex-direction: column; gap: 10px; }
/* a field (one input with its label and tools): a step down in tone from the device, no line around it */
.field { background: var(--chassis-2); border: var(--bw) solid transparent; border-radius: var(--r-key); padding: 12px; }
.field-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; min-height: 24px; }
.field-tools { display: flex; align-items: center; gap: 2px; }
.field-tools .tool { height: 24px; min-width: 24px; padding: 0 6px; }
/* the ↺ pip keeps its slot while hidden, so a field never changes size when it appears */
.field-tools .tool.reset[hidden], .field-inline .tool.reset[hidden] { display: inline-flex !important; visibility: hidden; pointer-events: none; }
.tool.reset { transition: opacity var(--t-mid), transform var(--t-mid) var(--spring); }
.tool.reset[hidden] { opacity: 0; transform: scale(.7); }
.field-inline { display: flex; align-items: center; gap: 8px; padding: 10px 12px; }
.field-inline .field-label { flex: 1; }
.field-inline .tool.reset { height: 24px; min-width: 24px; padding: 0 6px; }
.seed-row { display: flex; align-items: center; gap: 6px; }
.seed-row .num { flex: 1; text-align: left; }
.master { display: flex; flex-direction: column; gap: 4px; }
.master-ends { display: flex; justify-content: space-between; color: var(--ink-3); font-size: 10px; }
.dims { display: flex; flex-direction: column; gap: 6px; }
.dim-row { display: flex; align-items: center; gap: 10px; }
.dim-label { width: 14px; color: var(--ink-3); }
.dim-row .slider-row { flex: 1; }

/* image slots: a well you drop into — a tone, an icon and a word say so (no dashes); a wash of the accent under the pointer */
.image-wrap { display: flex; justify-content: center; }
.slot { position: relative; overflow: hidden; border-radius: var(--r-display); background: var(--well); border: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: var(--cut); transition: background .28s var(--ease), box-shadow var(--t-fast); }
.slot-lg { width: 100%; aspect-ratio: 4 / 3; max-height: 260px; }
.slot-sm { aspect-ratio: 1; }
.slot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transform: scale(1.04); pointer-events: none; transition: opacity .28s var(--ease), transform .36s var(--spring); }
.slot.has img { opacity: 1; transform: none; pointer-events: auto; z-index: 1; }
.slot.has { background: var(--display); box-shadow: none; }
.slot-empty { display: flex; flex-direction: column; align-items: center; gap: 6px; color: var(--well-ink); font: 500 10px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; transition: opacity .22s var(--ease), color var(--t-fast); }
.slot.has .slot-empty { opacity: 0; pointer-events: none; }
.slot:hover { background: rgb(var(--accent-rgb) / .14); }
.slot:hover .slot-empty { color: var(--ink); }
.slot.has:hover { background: var(--display); }
.slot.drop-over { box-shadow: var(--cut), 0 0 0 2px var(--accent); background: rgb(var(--accent-rgb) / .18); }
.slot-busy { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgb(0 0 0 / .5); }
.slot.busy .slot-busy { display: flex; }
.slot-actions { position: absolute; top: 6px; right: 6px; display: none; gap: 4px; z-index: 2; }
.slot.has:hover .slot-actions { display: flex; }
.slot-btn { width: 26px; height: 26px; border-radius: 3px; background: rgb(0 0 0 / .6); color: #fff; display: inline-flex; align-items: center; justify-content: center; border: var(--bw) solid transparent; }
.slot-btn:hover { background: var(--accent); color: var(--accent-ink); }
.slot-btn:active { transform: translateY(1px); }
.slots-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }

/* ═══════════════════════════════════════════════════════════════════════════
   the stage
   ═══════════════════════════════════════════════════════════════════════════ */
#stage { position: fixed; inset: 0; overflow: hidden; background: var(--bg); cursor: default; }
#stage.space { cursor: grab; }
#stage.panning { cursor: grabbing !important; }
#stage.dragging { cursor: grabbing; }
/* the grid (grid-fx.js): a WebGL canvas whose points are the dots — the effects (breathe, trail, spotlight, the
   water ripple, the cooking glow) move and shade those same dots. Its opacity fades zoomed out (canvas.js). */
.grid-bg { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
/* without WebGL: the old CSS dot pattern, oversized by one cell and moved by transform (canvas.js sets --gs) */
.grid-bg.nogl { left: calc(-1 * var(--gs, 20px)); top: calc(-1 * var(--gs, 20px)); right: calc(-1 * var(--gs, 20px)); bottom: calc(-1 * var(--gs, 20px)); background-image: radial-gradient(circle, var(--dot) 1.1px, transparent 1.4px); background-size: var(--gs, 20px) var(--gs, 20px); will-change: transform; }
.plane-mover { position: absolute; left: 0; top: 0; z-index: 1; will-change: transform; }
/* the plane is its own composited layer, always (will-change): an eased wheel zoom (round 21) scales it as a whole from
   its baked raster — no re-raster of every print a frame — and the bake (the plane's CSS zoom, once the gesture ends)
   re-lays it out crisp. Always, not for the gesture only: promoting and demoting a layer for every zoom is a change of
   the layer tree each time, and on Windows that showed as a frame with everything somewhere else (round 21). */
.plane { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }
/* while a wheel zoom eases, the ghost boxes follow every frame without their size transition (it made them drift apart
   and overlap on the way) */
#stage.zooming .gcur { transition: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   layout — the devices on the desk
   ═══════════════════════════════════════════════════════════════════════════ */
#dock, #controls, #rail, #readout, #viewbar, #user, #logo, .edge-toggle { position: fixed; z-index: 20; opacity: 0; pointer-events: none; transition: opacity var(--t-mid) var(--ease), transform var(--t-slow) var(--spring), translate .55s var(--spring), box-shadow .3s var(--ease); }
/* while the camera pans, the devices are dragged a few px along and spring back when it stops — plane.js writes each
   device's translate (the dock's keeps its -50%); the translate transition above is the spring back */
body.in-canvas #dock, body.in-canvas #controls, body.in-canvas #rail, body.in-canvas #readout, body.in-canvas #viewbar, body.in-canvas #user, body.in-canvas #logo { opacity: 1; pointer-events: auto; }
body.in-canvas .edge-toggle.show { opacity: 1; pointer-events: auto; }

#logo { top: 16px; left: 18px; height: 72px; display: flex; align-items: center; }   /* 72px (round 20 — 56 was small), still on the dock's centre line (14 + 76 / 2 = 52) */
#logo img { height: 72px; width: auto; filter: var(--logo-filter); opacity: .92; transition: transform var(--t-mid) var(--spring); }
#logo:hover img { transform: rotate(-4deg) scale(1.04); }
#logo:hover img { opacity: 1; }
#dock { top: 14px; left: 50%; translate: -50% 0; transform: translateY(-8px); display: flex; align-items: center; gap: 8px; padding: 1px 15px; max-width: min(66vw, 980px); height: 76px; }
body.in-canvas #dock { transform: none; }
#controls { top: 102px; left: 14px; bottom: 74px; width: 352px; display: flex; flex-direction: column; transform: translateX(-12px); }
body.in-canvas #controls { transform: none; }
#controls.collapsed { transform: translateX(calc(-100% - 20px)) !important; opacity: 0; pointer-events: none; }
#rail { top: 102px; right: 14px; bottom: 74px; width: 108px; display: flex; flex-direction: column; transform: translateX(12px); }
body.in-canvas #rail { transform: none; }
#rail.collapsed { transform: translateX(calc(100% + 20px)) !important; opacity: 0; pointer-events: none; }
#readout { position: fixed; left: 14px; bottom: 14px; height: 44px; width: 352px; max-width: calc(100vw - 340px); display: flex; align-items: center; gap: 10px; padding: 1px 13px; border-radius: var(--r-key); overflow: hidden; box-shadow: var(--hi-glass), var(--drop-panel); }
#viewbar { right: 14px; bottom: 14px; height: 44px; display: flex; align-items: center; gap: 2px; padding: 1px 7px; }
#user { top: 14px; right: 14px; height: 44px; display: flex; align-items: center; gap: 8px; padding: 1px 13px 1px 6px; cursor: pointer; }
#user:hover { background: var(--chassis-3); }
#user:active { transform: translateY(1px); }
.edge-toggle { top: 50%; width: 30px; height: 48px; margin-top: -24px; display: flex; align-items: center; justify-content: center; background: var(--chassis-solid); border: 0; color: var(--ink-2); box-shadow: var(--shadow-panel); }
.edge-toggle.left { left: 0; border-left: 0; border-radius: 0 var(--r-key) var(--r-key) 0; }
.edge-toggle.right { right: 0; border-right: 0; border-radius: var(--r-key) 0 0 var(--r-key); }
.edge-toggle:hover { color: var(--ink); background: var(--chassis-3); }

/* ═══════════════════════════════════════════════════════════════════════════
   dock — numbered pads (persistent: the active one morphs open; under a mouse they magnify)
   ═══════════════════════════════════════════════════════════════════════════ */
.dock-scroll { display: flex; align-items: center; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; padding: 11px 4px; max-width: 100%; scroll-behavior: smooth; }   /* the pads sit on the bar's centre line: the same air above and below, and they magnify from the middle; a magnified pad pushes its neighbours by what it grows (--push, ui-shell.js), so the row's edges never crop one */
.dock-scroll::-webkit-scrollbar { display: none; }
.dock-scroll.pulling { cursor: grabbing; scroll-behavior: auto; }
.dock-scroll.pulling .dock-app { pointer-events: none; }
.dock-app { position: relative; flex: 0 0 auto; display: flex; align-items: center; height: 54px; padding: 5px 7px 15px; min-width: 50px; margin: 0 calc(4px + var(--push, 0px)); border-radius: var(--r-key); --face: var(--key); color: var(--ink); border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact);
  transform: translateY(var(--press-y, 0px)) scale(var(--mag, 1)); transform-origin: 50% 50%;
  transition: background .28s var(--ease), color .28s var(--ease), border-color .28s var(--ease), box-shadow .28s var(--ease), transform .1s linear, margin .1s linear; }
.dock-scroll.settling .dock-app { transition: background .28s var(--ease), color .28s var(--ease), border-color .28s var(--ease), box-shadow .28s var(--ease), transform .45s var(--spring), margin .45s var(--spring); }
.dock-app:hover { --face: var(--key-hover); }
.dock-app:active { --press-y: var(--press); box-shadow: var(--hi); }
.dock-app.new { animation: pad-in .4s var(--spring) both; }
@keyframes pad-in { from { opacity: 0; transform: scale(.6); } }
.dock-app::after { content: attr(data-n); position: absolute; left: 0; right: 0; bottom: 5px; text-align: center; font: 600 8px/1 var(--font-mono); color: var(--ink-3); letter-spacing: .06em; }
.dock-app img { width: 36px; height: 36px; border-radius: 3px; object-fit: cover; background: var(--chassis-2); flex: 0 0 auto; }
/* the text grows out of the pad when it becomes the active one (0fr → 1fr animates to the content's width) */
.dock-app .dock-text { display: block; width: 0; overflow: hidden; margin-left: 0; opacity: 0; transition: width .42s var(--spring), margin-left .42s var(--spring), opacity .22s var(--ease); }
.dock-app .dock-text-in { width: var(--tw, 160px); max-width: 200px; display: flex; flex-direction: column; justify-content: center; padding-right: 6px; line-height: 1.2; text-align: left; }
.dock-app .dock-name { font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dock-app .dock-desc { font-size: 11px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.dock-app.on { --face: var(--accent); color: var(--accent-ink); box-shadow: var(--hi-accent), inset 0 calc(-1 * var(--press)) 0 var(--accent-lip, #b83700), var(--contact); z-index: 300 !important; }
.dock-app.on:active { box-shadow: var(--hi-accent); }
.dock-app.on .dock-text { width: var(--tw, 160px); margin-left: 10px; opacity: 1; transition-delay: 0s, 0s, .12s; }
.dock-app.on .dock-desc { color: var(--accent-ink); opacity: .85; }
.dock-app.on::after { color: var(--accent-ink); opacity: .85; }
.dock-app.fav::before { content: ''; position: absolute; right: 4px; top: 4px; width: 6px; height: 6px; border-radius: 1px; background: var(--accent); z-index: 2; }
.dock-app.on.fav::before { background: var(--accent-ink); }
/* hover label for the pads that don't show their name */
.dock-tip { position: fixed; z-index: 30; padding: 8px 11px; background: var(--display); color: var(--display-ink); border: 0; border-radius: var(--r-display); box-shadow: var(--shadow-glass-pop); font: 600 10px var(--font-mono); text-transform: uppercase; letter-spacing: .08em; white-space: nowrap; pointer-events: none; transform: translate(-50%, 8px); animation: tip-in .16s var(--ease) both; }
.dock-tip small { display: block; font: 400 11px var(--font-ui); color: var(--display-ink-2); text-transform: none; letter-spacing: 0; margin-top: 3px; max-width: 280px; white-space: normal; }
@keyframes tip-in { from { opacity: 0; } to { opacity: 1; } }
.dock-add { flex: 0 0 auto; width: 50px; height: 54px; margin: 0 4px 0 8px; padding: 1px 1px calc(var(--press) + 1px); border-radius: var(--r-key); display: inline-flex; align-items: center; justify-content: center; color: var(--ink-2); --face: var(--key); border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); }
.dock-add:hover { color: var(--ink); --face: var(--key-hover); }
.dock-add:active { transform: translateY(var(--press)); box-shadow: var(--hi); }
.dock-empty { display: inline-flex; align-items: center; gap: 8px; height: 54px; padding: 0 14px; border-radius: var(--r-key); color: var(--ink-2); font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; }
.dock-empty:hover { background: var(--chassis-3); color: var(--ink); }

/* ═══════════════════════════════════════════════════════════════════════════
   controls — the main instrument
   ═══════════════════════════════════════════════════════════════════════════ */
.controls-head { display: flex; align-items: center; gap: 12px; padding: 12px 14px 12px; border-bottom: var(--bw) solid var(--line); }
.controls-head .app-icon img { width: 42px; height: 42px; border-radius: 3px; object-fit: cover; background: var(--chassis-2); border: 0; box-shadow: var(--shadow-pic); }
.controls-head .app-meta { flex: 1; min-width: 0; }
.controls-head .app-name { font: 600 12px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.controls-head .app-desc { font-size: 12px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.controls-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 12px 14px; overscroll-behavior: contain; scrollbar-width: none; }
.controls-body::-webkit-scrollbar { display: none; }
.controls-body.swap-out { animation: cart-out .14s var(--ease) forwards; }
@keyframes cart-out { to { opacity: 0; transform: translateY(6px); } }
.controls-empty { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 40px 20px; text-align: center; color: var(--ink-2); font-size: 12.5px; }
.controls-foot { display: flex; align-items: center; gap: 10px; padding: 10px 14px 10px; border-top: var(--bw) solid var(--line); }
/* batch: a display with a rotary knob */
.batch { display: inline-flex; align-items: center; gap: 4px; height: 46px; padding: 1px 7px 1px 5px; box-shadow: var(--cut-glass); }
.knob { position: relative; width: 36px; height: 36px; border-radius: 50%; --face: var(--key); border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); cursor: ns-resize; touch-action: none; }
.knob::after { content: ''; position: absolute; left: 50%; top: 3px; width: 3px; height: 10px; margin-left: -1.5px; border-radius: 2px; background: var(--accent); transform-origin: 50% 15px; transform: rotate(var(--knob, -60deg)); transition: transform var(--t-mid) var(--spring); }
.knob:hover { --face: var(--key-hover); }
.knob:active { box-shadow: var(--hi); transform: translateY(var(--press)); }
.batch-n { min-width: 16px; text-align: center; font: 700 14px var(--font-mono); color: var(--display-ink); }
.batch-label { font: 500 8px var(--font-mono); text-transform: uppercase; letter-spacing: .1em; color: var(--display-ink-2); writing-mode: vertical-rl; transform: rotate(180deg); margin: 0 2px; }
.batch-btn { display: none; }
/* make it — the big key */
.make { flex: 1; position: relative; display: grid; place-items: center; height: 46px; padding: 1px 1px 4px; border-radius: var(--r-key); --face: var(--accent); color: var(--accent-ink); border: 0; box-shadow: var(--hi-accent), inset 0 -3px 0 var(--accent-lip, #b83700), var(--contact), 0 8px 18px rgb(0 0 0 / .18); font: 700 12px var(--font-mono); text-transform: uppercase; letter-spacing: .14em; transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), filter var(--t-fast); }
.make:hover { filter: brightness(1.06); }
.make:disabled { --face: var(--chassis-3); color: var(--ink-3); box-shadow: var(--hi), inset 0 -3px 0 var(--key-lip), var(--contact); cursor: not-allowed; filter: none; }
.make:disabled .make-led { background: var(--ink-3); box-shadow: none; }
.batch.zero .batch-n { color: var(--ink-3); }
.make:active, .make.pressed { transform: translateY(3px); box-shadow: var(--hi-accent), 0 2px 8px rgb(0 0 0 / .18); }
.make-led { width: 7px; height: 7px; border-radius: 2px; background: var(--accent-ink); opacity: .85; }
#controls.private .make { background: var(--private); box-shadow: var(--hi-accent), inset 0 -3px 0 #793db2, var(--contact), 0 8px 18px rgb(0 0 0 / .18); }
#controls.private .make .make-label::after { content: ' · private'; font-weight: 500; opacity: .8; }

/* ═══════════════════════════════════════════════════════════════════════════
   rail (outputs)
   ═══════════════════════════════════════════════════════════════════════════ */
.rail-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 8px 8px 14px; border-bottom: var(--bw) solid var(--line); }
.rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 12px 10px; display: flex; flex-direction: column; gap: 8px; scrollbar-width: none; }
.rail-list::-webkit-scrollbar { display: none; }
.rail-item { position: relative; flex: 0 0 auto; aspect-ratio: 1; width: 100%; border-radius: 3px; overflow: hidden; background: var(--display); border: 0; box-shadow: var(--shadow-pic); cursor: pointer; transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast); }
.rail-item img { width: 100%; height: 100%; object-fit: cover; cursor: grab; }
.rail-item:hover { transform: translateY(-2px); box-shadow: 0 6px 14px rgb(0 0 0 / .3); }
.rail-item:active { transform: none; }
.rail-item.st-pending { border: var(--bw) dashed var(--ink-3); }   /* a state: queued, not printed yet */
.rail-item.st-failed, .rail-item.st-cancelled { opacity: .45; }
.rail-ph { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--display-ink-2); }
.rail-badge { position: absolute; right: 4px; bottom: 4px; background: rgb(0 0 0 / .6); color: #fff; border-radius: 3px; padding: 2px; display: inline-flex; }
.rail-empty { display: none; flex-direction: column; align-items: center; gap: 6px; padding: 30px 8px; color: var(--ink-3); font: 500 10px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; text-align: center; }
#rail.empty .rail-empty { display: flex; }
#rail.empty .rail-list { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   readout (LED + dot matrix) · viewbar · user
   ═══════════════════════════════════════════════════════════════════════════ */
/* the lamp's colour is one token, so the light and its glow always agree (round 21: cooking and live set the lamp's
   colour alone and kept the green glow of 'ready' under an accent lamp) */
.led { --led: var(--ok); width: 8px; height: 8px; border-radius: 2px; background: var(--led); box-shadow: 0 0 8px var(--led); flex: 0 0 auto; transition: background var(--t-mid), box-shadow var(--t-mid); }
#readout[data-state="queued"] .led { --led: var(--warn); animation: blink 1s ease-in-out infinite; }
#readout[data-state="running"] .led { --led: var(--accent); animation: blink .7s ease-in-out infinite; }
#readout[data-state="error"] .led { --led: var(--danger); }
#readout[data-state="offline"] .led { --led: #3a3a3a; box-shadow: none; }
@keyframes blink { 50% { opacity: .35; } }
/* motion off means motion off — this sheet gates animation rule by rule and the readout had no gate */
:root[data-motion="off"] #readout .led { animation: none; }
.readout-text { flex: 1; font: 500 11px var(--font-mono); text-transform: uppercase; letter-spacing: .1em; color: var(--display-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 60px; }
/* NO HEIGHT. The canvas is 20px tall by construction — 7 rows at a 3px pitch, less the last
   gutter — and this used to force it to 16, which is a scale of 0.8: every 3px cell became 2.4px
   and every edge in the display landed mid-pixel. That is where the half pixels were coming from.
   At 1:1 a cell is a whole 3 device pixels, the same as the companion's screen. */
.readout-dm { display: block; image-rendering: pixelated; flex: 0 0 auto; }
:root[data-readout="matrix"] .readout-text { display: none; }
:root:not([data-readout="matrix"]) .readout-dm { display: none; }
/* progress: a thin line along the bottom edge (no layout shift while cooking) */
.readout-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: transparent; overflow: hidden; opacity: 0; transition: opacity var(--t-mid) var(--ease); }
#readout.busy .readout-bar { opacity: 1; }
.readout-fill { height: 100%; width: 0; background: var(--accent); transition: width .2s linear; box-shadow: none; }
/* the stop key keeps its slot at the end so the matrix never has to shrink */
.readout-tail { flex: 0 0 auto; width: 30px; display: flex; justify-content: flex-end; margin-left: auto; }
/* THE STOP KEY IS HARDWARE, NOT INK. Everything else on this panel is a segment cut from one sheet
   — which is why the indicator and the progress bar are the display's own colour — but a button you
   press is a part you press, and a part is allowed to be red.
   It was a 12% red wash, which on the olive panel (#626f57) composites to #756b55: nineteen points
   of red away from the surface it sits on, so the key had no edges at all and only the glyph was
   doing any work. It is a solid key now, built the same way as the assistant's stop — its own lip,
   its own contact shadow, and `padding-bottom` so the square centres on the key's FACE rather than
   on the whole box (bevels count toward a control's height, CLAUDE.md §6). */
#readout .stop {
  display: inline-flex; width: 30px; min-width: 0;
  padding: 0 0 var(--press);
  color: #fff; background: var(--danger);
  box-shadow: var(--hi-accent), inset 0 calc(-1 * var(--press)) 0 var(--danger-lip), var(--contact);
  opacity: 0; pointer-events: none; transform: scale(.8);
  /* The entrance keeps `transform`; the PRESS is given to `translate`, an individual transform
     property that composes ahead of it. Sharing one property would put the 2px press on --spring
     (cubic-bezier(.3,1.3,.5,1)), which overshoots — a key that dips ~2.6px and settles back. */
  transition: opacity var(--t-mid), transform var(--t-mid) var(--spring),
              translate var(--t-fast) var(--ease), filter var(--t-fast);
}
#readout .stop:hover { filter: brightness(1.07); }
#readout .stop span { display: none; }
#readout.busy .stop { opacity: 1; pointer-events: auto; transform: none; }
/* The press has to be written out here: `.tool:active` (0,2,0) loses to `#readout.busy .stop`'s
   (1,2,0) `transform: none`, so this key has never depressed — invisible while the key itself was,
   and a dead control the moment it grows a 2px lip. The cap drops onto the lip, as `.btn:active`. */
#readout.busy .stop:active { translate: 0 var(--press); box-shadow: var(--hi-accent); }
#viewbar .tool { height: 32px; }
#viewbar .sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
#viewbar .home-btn { max-width: 190px; }
#viewbar .home-btn .canvas-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-transform: none; letter-spacing: 0; font-family: var(--font-ui); font-size: 12.5px; }
#user .avatar { width: 32px; height: 32px; border-radius: 3px; background: var(--display); color: var(--accent); border: 0; display: inline-flex; align-items: center; justify-content: center; font: 700 12px var(--font-mono); box-shadow: var(--cut-glass); }
#user .user-name { font-size: 13px; font-weight: 500; }
.swatches { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
/* a swatch is a small key in its own colour: its lip is its own shade */
.swatch { width: 20px; height: 20px; border-radius: 3px; border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 rgb(0 0 0 / .32), var(--contact); }
.swatch:hover { filter: brightness(1.08); }
.swatch:active { transform: translateY(var(--press)); box-shadow: var(--hi); }
.swatch.on { outline: 2px solid var(--ink); outline-offset: 2px; }
.swatch-pick { width: 22px; height: 22px; padding: 0; border: 0; border-radius: 3px; background: none; cursor: pointer; }
.swatch-pick::-webkit-color-swatch-wrapper { padding: 0; }
.swatch-pick::-webkit-color-swatch { border: var(--bw) dashed var(--ink-3); border-radius: 3px; }   /* dashes: 'any colour' */
.opt-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 5px 8px; min-height: 36px; font-size: 12.5px; color: var(--ink-2); }
.opt-row .segmented { flex: 0 0 auto; }
/* a fader in a settings row. The track is a FIXED width on purpose: `.range-wrap` is `flex: 1`, which collapses to
   nothing inside `.opt-row`, and the popover is `min-width` — it grows to its widest child, so an elastic one would
   widen the whole menu. */
.opt-row .opt-slider { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }
.opt-row .opt-slider .range-wrap { flex: 0 0 132px; }

/* ═══════════════════════════════════════════════════════════════════════════
   canvas items
   ═══════════════════════════════════════════════════════════════════════════ */
.item { position: absolute; border-radius: var(--r-item); background: var(--chassis-2); box-shadow: 0 2px 6px rgb(0 0 0 / .25), 0 12px 32px rgb(0 0 0 / .25); overflow: visible; cursor: grab; transition: box-shadow .2s var(--ease), transform .2s var(--spring); transform-origin: center center; contain: layout style; }   /* contain (round 21): a piece's layout is its own — a bake (the plane's zoom) lays the pieces out, not their insides against each other */
.item.offscreen { display: none; }   /* out of the view (plus a margin): out of layout and paint altogether (round 20 — hidden, it still cost a re-layout and a paint pass at every zoom step) */
.item:active { cursor: grabbing; }
/* hover: a 1% lift with a little overshoot; carrying: no transitions (the physics drive the transform) */
.item:hover:not(.lifted):not(.item-placeholder) { transform: scale(1.01); box-shadow: 0 4px 10px rgb(0 0 0 / .3), 0 16px 36px rgb(0 0 0 / .28); }
#stage.panning .item, #stage.dragging .item { transition: none; }
.item.lifted { transition: none; transform-style: preserve-3d; cursor: grabbing; z-index: 99999 !important; }
.drag-shine { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 4; mix-blend-mode: overlay; }
.item > img, .item > video { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; display: block; pointer-events: none; }
/* the picture itself takes the pointer, so a right-click on a print gets the browser's own image menu — copy image,
   save, open — the way the classic desk had it. The item still handles every press (events bubble up to it); the
   picture must just never start a native drag of its own, which draggable="false" and user-drag see to. */
.item > img { pointer-events: auto; -webkit-user-drag: none; user-select: none; -webkit-user-select: none; }
.item-image > img { opacity: 0; transition: opacity .25s; }
.item-image.loaded > img { opacity: 1; }
/* "print" reveal: a scan line sweeps the image in (wild motion only) */
:root[data-motion="on"] .item-image.printing > img { animation: print-in .55s var(--ease) both; }
:root[data-motion="on"] .item-image.printing::after { content: ''; position: absolute; left: 0; right: 0; height: 2px; background: var(--accent); box-shadow: none; border-radius: 2px; animation: print-line .55s var(--ease) both; pointer-events: none; z-index: 2; }
@keyframes print-in { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes print-line { from { top: 0; opacity: 1; } 90% { opacity: 1; } to { top: 100%; opacity: 0; } }
.item-loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.item.loaded .item-loading { display: none; }
/* an output whose file is gone: no broken-image icon — a dashed slot with our own mark, the label only while it is big enough to read */
.item.missing { border: var(--bw) dashed var(--ink-3); background: var(--chassis-2); overflow: hidden; box-shadow: none; }
.item.missing > img { visibility: hidden; }
.item.missing::after { content: 'missing'; position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 8px; color: var(--ink-3); font: 500 10px var(--font-mono); text-transform: uppercase; letter-spacing: .08em; overflow: hidden;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.41 10.41a2 2 0 1 1-2.83-2.83'/%3E%3Cline x1='13.5' x2='6' y1='13.5' y2='21'/%3E%3Cline x1='18' x2='21' y1='12' y2='15'/%3E%3Cpath d='M3.59 3.59A1.99 1.99 0 0 0 3 5v14a2 2 0 0 0 2 2h14c.55 0 1.052-.22 1.41-.59'/%3E%3Cpath d='M21 15V5a2 2 0 0 0-2-2H9'/%3E%3Cline x1='2' x2='22' y1='2' y2='22'/%3E%3C/svg%3E") center calc(50% - 12px) / 22px 22px no-repeat; padding-top: 30px; }
.plane.far .item.missing::after { color: transparent; background-size: 0 0; }
.item::before { content: ''; position: absolute; inset: -3px; border-radius: calc(var(--r-item) + 2px); border: 2px solid transparent; pointer-events: none; transition: border-color var(--t-fast); }
.item:hover::before { border-color: rgb(var(--accent-rgb) / .45); }
.item.selected::before { border-color: var(--accent); box-shadow: 0 0 0 4px rgb(var(--accent-rgb) / .2); }
.handle { position: absolute; width: 12px; height: 12px; border-radius: 2px; background: var(--key); border: 2px solid var(--accent); display: none; z-index: 3; }
.item.selected .handle { display: block; }
/* carried: no frame and no handles — they used to fly about with a tilting item; these two sit after the hover and
   selected rules on purpose (same specificity: the later wins — round 20c, they were above them and lost) */
.item.lifted::before { border-color: transparent; box-shadow: none; }
.item.lifted .handle { display: none; }
.handle[data-corner="nw"] { left: -7px; top: -7px; cursor: nwse-resize; }
.handle[data-corner="ne"] { right: -7px; top: -7px; cursor: nesw-resize; }
.handle[data-corner="sw"] { left: -7px; bottom: -7px; cursor: nesw-resize; }
.handle[data-corner="se"] { right: -7px; bottom: -7px; cursor: nwse-resize; }

/* placeholder: a plate (--plate: black on the dark desk, a shade of the desk on the light one) with an LED banner — the
   state in dot-matrix letters and a thin progress line — in the middle until the sampler's previews arrive, then along
   the bottom so the preview shows whole. No dashes, no glare. */
.item-placeholder { background: var(--plate); border: 0; border-radius: 3px; overflow: hidden; box-shadow: var(--plate-shadow); }
.item-placeholder .handle { display: none !important; }   /* a plate cannot be resized: no handles (round 21 — the plate's overflow: hidden showed the inner quarter of each, peeking at the corners) */
.item-placeholder::before { border-radius: 3px; }
.ph-banner { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2; padding: 9px 13px 8px; max-width: calc(100% - 16px); background: var(--display); border: 0; border-radius: var(--r-display); box-shadow: var(--hi-glass), 0 6px 18px rgb(0 0 0 / .35); transition: top .45s var(--spring), transform .45s var(--spring); }
.item-placeholder.has-preview .ph-banner { top: calc(100% - 12px); transform: translate(-50%, -100%); }
/* zoomed out, the banner's inside is scaled back up (canvas.js sets --ph-k = 0.9 / zoom, at least 1) so the letters
   keep a readable size on screen; canvas.js gives it as many columns as then fit the plate */
.ph-in { display: flex; flex-direction: column; align-items: center; gap: 6px; zoom: var(--ph-k, 1); }
.ph-dm { display: block; image-rendering: pixelated; }
#stage.zo .ph-dm { image-rendering: auto; }   /* scaled down, nearest-neighbour drops rows of LEDs: let it filter */
.ph-bar { width: 100%; height: 3px; border-radius: 2px; background: rgb(255 255 255 / .1); overflow: hidden; position: relative; }
.ph-bar-fill { height: 100%; width: 0; background: var(--accent); transition: width .2s linear; }
.ph-preview { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity .3s; z-index: 1; }
.ph-preview.in { opacity: 1; }
.ph-cancel { position: absolute; top: 8px; right: 8px; width: 26px; height: 26px; border-radius: 3px; background: rgb(0 0 0 / .6); color: #fff; border: var(--bw) solid transparent; display: none; align-items: center; justify-content: center; z-index: 4; }
.item-placeholder:hover .ph-cancel { display: inline-flex; }
.item-placeholder.remote { cursor: default; }
.item-placeholder.remote.stuck { cursor: grab; }   /* someone else's plate, five minutes on: it may be stuck, and can be taken off the desk (round 21) */
.ph-cancel:hover { background: var(--danger); }

/* notes & stickers */
.item-note { background: var(--note, #fff9c4); color: #222; box-shadow: 0 6px 18px rgb(0 0 0 / .25); border-radius: 2px; padding: 12px; cursor: grab; }
.item-note .note-body { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; }
/* the words are the note's face until you are writing in them: no text cursor, and a drag carries the note rather
   than sweeping a selection through it (round 22) */
.item-note .note-text { width: 100%; max-height: 100%; outline: none; font: 600 16px/1.15 var(--font-ui); text-align: center; white-space: pre-wrap; overflow-wrap: anywhere; overflow: hidden; cursor: inherit; user-select: none; -webkit-user-select: none; caret-color: #222; }
.item-note.editing .note-text { cursor: text; user-select: text; -webkit-user-select: text; }
.item-note.editing { box-shadow: 0 6px 18px rgb(0 0 0 / .25), 0 0 0 2px rgb(var(--accent-rgb) / .6); }
.note-color { width: 18px; height: 18px; border-radius: 2px; border: 0; box-shadow: inset 0 -1px 0 rgb(0 0 0 / .28), var(--contact); }
.note-color:hover { transform: translateY(-1px); }
.note-color.on { outline: 2px solid var(--ink); outline-offset: 1px; }
.sm-colors { display: flex; align-items: center; gap: 4px; padding: 0 8px 0 5px; border-right: var(--bw) solid var(--line); margin-right: 3px; }
.note-preview { position: absolute; border: 2px solid rgb(0 0 0 / .25); background: #fff9c4; opacity: .85; border-radius: 2px; pointer-events: none; z-index: 10; box-shadow: 0 6px 18px rgb(0 0 0 / .2); }
/* stickers (round 20): die-cut prints — Fluent 3D emoji with a white border baked into the picture (no stacked
   drop-shadow stroke, which pixelated) — on the desk with a contact shadow and a soft one; a gloss that follows the
   pointer, masked to the sticker's own shape (--sticker, set on the item); carried, they hang in the air with a deeper
   shadow — and move exactly like a print (round 20c: the peel and the film spring of round 20 threw a big sticker
   about). The sheets are 640 px with a round die-cut (round 20b), so the border stays smooth when a sticker is made big
   or the desk zoomed in. */
.item-sticker { background: transparent; box-shadow: none; }
.item-sticker img { object-fit: contain; padding: 4%; box-sizing: border-box; filter: drop-shadow(0 1px 1px rgb(0 0 0 / .22)) drop-shadow(0 4px 8px rgb(0 0 0 / .18)); transition: filter .35s var(--ease); }
.item-sticker:hover:not(.lifted):not(.item-placeholder) { box-shadow: none; }
.item-sticker.lifted img { filter: drop-shadow(0 2px 2px rgb(0 0 0 / .22)) drop-shadow(0 18px 26px rgb(0 0 0 / .34)); }
.item-sticker::before { border-radius: 8px; }
.item-sticker::after { content: ''; position: absolute; inset: 4%; pointer-events: none; z-index: 3; opacity: 0; transition: opacity .25s var(--ease);
  -webkit-mask: var(--sticker) center / contain no-repeat; mask: var(--sticker) center / contain no-repeat;
  background: radial-gradient(48% 42% at var(--gx, 35%) var(--gy, 28%), rgb(255 255 255 / .62), rgb(255 255 255 / .14) 55%, transparent 82%); }
.item-sticker:hover::after, .item-sticker.lifted::after { opacity: 1; }

/* ghost cursor */
.gcur { position: absolute; pointer-events: none; border-radius: var(--r-item); border: 1.5px dashed rgb(var(--accent-rgb) / .8); background: rgb(var(--accent-rgb) / .1); display: flex; align-items: center; justify-content: center; z-index: 5; transition: width .12s var(--ease), height .12s var(--ease); }
.gcur-tag { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 1px 11px 3px; border-radius: 3px; background: var(--accent); color: var(--accent-ink); border: 0; font: 700 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .12em; box-shadow: var(--hi-accent), inset 0 -2px 0 var(--accent-lip, #b83700), var(--contact), 0 6px 16px rgb(0 0 0 / .18); white-space: nowrap; }
.gcur.compact .gcur-tag { transform: scale(.8); }
.gcur-extra { border-style: dotted; opacity: .6; }
/* shift held: the ghost is a small sticky note */
.gcur-note { border: 0; background: #fff9c4; opacity: .82; box-shadow: 0 6px 18px rgb(0 0 0 / .2); border-radius: 2px; }
.gcur-note .gcur-tag { background: var(--ink); color: var(--bg); box-shadow: var(--hi-glass), inset 0 -2px 0 rgb(0 0 0 / .4), var(--contact); }
.stage.note-mode { cursor: crosshair; }
/* shift held: the pointer carries a 'select' tag (a drag on the desk draws a marquee, a click on an item adds it) */
.stage.select-mode { cursor: crosshair; }
.gcur-select { position: absolute; pointer-events: none; z-index: 5; display: flex; transform: translate(14px, 16px); }
.gcur-select .gcur-tag { background: var(--ink); color: var(--bg); box-shadow: var(--hi-glass), inset 0 -2px 0 rgb(0 0 0 / .4), var(--contact); }
.marquee { position: absolute; border: 1px solid rgb(var(--accent-rgb) / .8); background: rgb(var(--accent-rgb) / .1); border-radius: 2px; pointer-events: none; z-index: 30; }

/* selection toolbar: a small device that floats above the selection */
/* the selection toolbar (round 21): a strip of keys hinged on the edge that faces the piece. It swings up off the desk
   when a piece is picked, overshoots a hair and settles, and its keys flick up one after the other (--i is a key's place
   in the row, numbered in canvas.js); letting the piece go, or picking it up, folds it back down. Before, it was the
   popovers' 4px fade in — and nothing at all on the way out: it simply stopped being there. */
.sel-menu { position: absolute; z-index: 40; display: none; padding: 6px; gap: 2px; border-radius: var(--r-key); background: var(--chassis-solid); border: 0; box-shadow: var(--shadow-pop); transform-origin: 50% 100%; }
.sel-menu.below { transform-origin: 50% 0; }   /* under the piece (no room above): hinged on its top edge instead */
.sel-menu.in { animation: sm-up .32s var(--spring) both; }
.sel-menu.out { animation: sm-down .13s var(--ease) both; }
.sel-menu.below.in { animation-name: sm-up-below; }
.sel-menu.below.out { animation-name: sm-down-below; }
@keyframes sm-up {
  0% { opacity: 0; transform: perspective(600px) rotateX(-64deg) translateY(6px) scale(.97); }
  55% { opacity: 1; transform: perspective(600px) rotateX(7deg) translateY(0) scale(1.015); }
  100% { opacity: 1; transform: perspective(600px) rotateX(0deg) translateY(0) scale(1); }
}
@keyframes sm-up-below {
  0% { opacity: 0; transform: perspective(600px) rotateX(64deg) translateY(-6px) scale(.97); }
  55% { opacity: 1; transform: perspective(600px) rotateX(-7deg) translateY(0) scale(1.015); }
  100% { opacity: 1; transform: perspective(600px) rotateX(0deg) translateY(0) scale(1); }
}
@keyframes sm-down {
  0% { opacity: 1; transform: perspective(600px) rotateX(0deg) translateY(0) scale(1); }
  100% { opacity: 0; transform: perspective(600px) rotateX(-44deg) translateY(4px) scale(.98); }
}
@keyframes sm-down-below {
  0% { opacity: 1; transform: perspective(600px) rotateX(0deg) translateY(0) scale(1); }
  100% { opacity: 0; transform: perspective(600px) rotateX(44deg) translateY(-4px) scale(.98); }
}
.sel-menu.in .sm-wrap > * { animation: sm-key .3s var(--spring) backwards; animation-delay: calc(70ms + var(--i, 0) * 26ms); }
@keyframes sm-key { 0% { opacity: 0; transform: translateY(6px) scale(.86); } 100% { opacity: 1; transform: translateY(0) scale(1); } }
:root[data-motion="off"] .sel-menu.in, :root[data-motion="off"] .sel-menu.out, :root[data-motion="off"] .sel-menu.in .sm-wrap > * { animation: none; }
.sm-wrap { display: flex; gap: 2px; }
.sm { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 9px; border-radius: 3px; font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; color: var(--ink); border: var(--bw) solid transparent; white-space: nowrap; }
.sm:hover { background: var(--chassis-3); }
.sm:active { transform: translateY(1px); }
.sm.danger:hover { color: var(--danger); }

/* ═══════════════════════════════════════════════════════════════════════════
   home
   ═══════════════════════════════════════════════════════════════════════════ */
#home { position: fixed; inset: 0; z-index: 60; overflow-y: auto; background: var(--bg); opacity: 0; transition: opacity var(--t-mid) var(--ease); color: var(--desk-ink); }
#home.in { opacity: 1; }
#home::before { content: ''; position: fixed; inset: 0; background-image: radial-gradient(circle, var(--dot) 1.1px, transparent 1.4px); background-size: 20px 20px; pointer-events: none; }
.home-inner { position: relative; z-index: 1; max-width: 1180px; margin: 0 auto; padding: clamp(40px, 8vh, 90px) 40px 80px; }
.home-head { display: flex; align-items: center; gap: 22px; margin-bottom: 34px; }
.home-logo img { width: 74px; height: 74px; object-fit: contain; filter: var(--logo-filter); }
.home-title { margin: 0; font: 600 clamp(26px, 3.6vw, 36px)/1.05 var(--font-mono); text-transform: uppercase; letter-spacing: -.01em; }
.home-title em { font-style: normal; color: var(--accent); }
.home-sub { margin: 8px 0 0; color: var(--ink-2); font-size: 14.5px; }
:root[data-scheme="dark"] .home-sub, :root[data-scheme="dark"] .home-h3 { color: #a8a7a1; }
.home-tabs { display: flex; gap: 6px; margin-bottom: 20px; }
.home-tab { height: 32px; padding: 1px 13px calc(var(--press) + 1px); border-radius: var(--r-key); color: var(--ink-2); font: 600 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .08em; --face: var(--key); border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); }
.home-tab:hover { --face: var(--key-hover); color: var(--ink); }
.home-tab:active { transform: translateY(var(--press)); box-shadow: var(--hi); }
.home-tab.on { --face: var(--accent); color: var(--accent-ink); box-shadow: var(--hi-accent), inset 0 calc(-1 * var(--press)) 0 var(--accent-lip, #b83700), var(--contact); }
.home-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 16px; align-items: start; }
/* a canvas card: a small device on the desk — the same shadow as the panels, and it lifts under the pointer */
.canvas-card { position: relative; display: block; text-align: left; background: var(--chassis-solid); color: var(--ink); border: 0; border-radius: var(--r-panel); overflow: hidden; cursor: pointer; transition: transform var(--t-mid) var(--spring), box-shadow var(--t-mid); box-shadow: var(--shadow-panel); text-decoration: none; }
.canvas-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-pop); }
.canvas-card:active { transform: translateY(0); box-shadow: var(--shadow-panel); }
/* the new-canvas card is a card like the others: its picture is an empty desk (the dot grid) with a + key on it */
.canvas-card.new .canvas-thumbs.plus { background: var(--chassis-2); color: var(--accent-ink); }   /* a plain face, one shade in from the card */
.canvas-card.new .plus-key { width: 46px; height: 46px; border-radius: var(--r-key); --face: var(--accent); color: var(--accent-ink); padding: 1px 1px 4px; border: 0; box-shadow: var(--hi-accent), inset 0 -3px 0 var(--accent-lip, #b83700), var(--contact), 0 8px 18px rgb(0 0 0 / .18); display: inline-flex; align-items: center; justify-content: center; transition: transform var(--t-mid) var(--spring), box-shadow var(--t-mid); }
.canvas-card.new:hover .plus-key { transform: translateY(-2px); box-shadow: var(--hi-accent), inset 0 -3px 0 var(--accent-lip, #b83700), var(--contact), 0 12px 22px rgb(0 0 0 / .2); }
.canvas-card.new:active .plus-key { transform: translateY(2px); box-shadow: var(--hi-accent), inset 0 -1px 0 var(--accent-lip, #b83700); }
.canvas-card.new.join-room .plus-key { --face: var(--key); color: var(--ink); box-shadow: var(--hi), inset 0 -3px 0 var(--key-lip), var(--contact), 0 8px 18px rgb(0 0 0 / .18); }
/* thumbnails: always a 4:3 window — tall outputs are cropped, never stretch the card */
.canvas-thumbs { aspect-ratio: 4 / 3; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); gap: 2px; background: var(--display); overflow: hidden; }
.canvas-thumbs.n1 { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr); }
.canvas-thumbs.n2 { grid-template-rows: minmax(0, 1fr); }
.canvas-thumbs img { width: 100%; height: 100%; min-width: 0; min-height: 0; object-fit: cover; object-position: center 30%; }
.canvas-thumbs.n3 img:first-child { grid-row: span 2; }
.canvas-thumbs.plus, .canvas-thumbs-empty { display: flex; align-items: center; justify-content: center; color: var(--display-ink-2); grid-column: 1 / -1; grid-row: 1 / -1; }
.canvas-thumbs.plus { background: transparent; color: inherit; }
.canvas-card-info { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px; }
.canvas-card-name { font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; }
.canvas-card-meta { font-size: 12px; color: var(--ink-2); }
.canvas-card-menu { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 3px; background: rgb(0 0 0 / .55); color: #fff; border: var(--bw) solid transparent; display: inline-flex; align-items: center; justify-content: center; opacity: 0; transition: opacity var(--t-fast), background var(--t-fast); }
.canvas-card:hover .canvas-card-menu { opacity: 1; }
.canvas-card-menu:hover { background: var(--accent); color: var(--accent-ink); }
.home-h3 { margin: 40px 0 12px; }
.home-none, .home-loading { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 60px 20px; color: var(--ink-2); text-align: center; }
.home-none p { margin: 0; }
.home-note { margin: 0 0 12px; font-size: 12.5px; }

/* ═══════════════════════════════════════════════════════════════════════════
   overlays: store · lightbox · toasts · splash · gate
   ═══════════════════════════════════════════════════════════════════════════ */
.overlay { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center; background: rgb(0 0 0 / .5); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); opacity: 0; transition: opacity var(--t-mid); padding: 24px; }
.overlay.in { opacity: 1; }
.sheet { width: min(1040px, 100%); height: min(720px, 100%); background: var(--chassis-solid); color: var(--ink); border: 0; padding: 1px; border-radius: var(--r-panel); box-shadow: var(--shadow-panel); display: flex; flex-direction: column; overflow: hidden; transform: translateY(12px) scale(.985); transition: transform var(--t-slow) var(--spring); -webkit-backdrop-filter: blur(var(--panel-blur)); backdrop-filter: blur(var(--panel-blur)); }
.overlay.in .sheet { transform: none; }
/* small dialogs (dialog.js): a title, a line, a field, two keys — the chassis, on the same scrim as the sheets */
.dialog-scrim { z-index: 600; }   /* above the popovers (500): the prompt library stays open under the dialogs it opens, and a modal must never be hidden behind the thing that called it */
.dialog { width: min(380px, 100%); padding: 19px 19px 17px; display: flex; flex-direction: column; gap: 10px; background: var(--chassis-solid); transform: translateY(10px) scale(.98); transition: transform var(--t-mid) var(--spring); }
.overlay.in .dialog { transform: none; }
.dialog-title { font: 600 12px var(--font-mono); text-transform: uppercase; letter-spacing: .1em; color: var(--ink); }
.dialog-text { margin: -2px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-2); }
.dialog-input { margin: 4px 0 0; font-size: 13px; }
.dialog-keys { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; }
.dialog-wide { width: min(560px, 100%); }
.dialog-label { margin: 4px 0 -4px; }
textarea.text.dialog-textarea { min-height: 150px; max-height: 50vh; overflow-y: auto; }   /* textarea.text sets 76px and out-ranks a bare class */
.dialog-hint, .dialog-count { align-self: center; font: 10.5px var(--font-mono); color: var(--ink-3); letter-spacing: .04em; }
.dialog-hint { margin-right: auto; }
@media (hover: none) { .dialog-hint { display: none; } }   /* no keyboard to press it on */
.btn.danger-key { --face: var(--danger); color: #fff; box-shadow: var(--hi-accent), inset 0 calc(-1 * var(--press)) 0 #b83535, var(--contact); }
.btn.danger-key:hover { filter: brightness(1.06); }
.btn.danger-key:active { box-shadow: var(--hi-accent); }
.sheet-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 16px 14px 20px; border-bottom: var(--bw) solid var(--line); }
.sheet-title { display: flex; align-items: center; gap: 10px; }
.sheet-title h2 { margin: 0; font: 600 14px var(--font-mono); text-transform: uppercase; letter-spacing: .08em; }
.sheet-count { margin-left: 6px; color: var(--ink-3); }
.store-body { flex: 1; display: grid; grid-template-columns: 340px 1fr; min-height: 0; }
.store-list { display: flex; flex-direction: column; border-right: var(--bw) solid var(--line); min-height: 0; }
.store-search { display: flex; align-items: center; gap: 8px; margin: 12px 12px 8px; padding: 1px 13px; height: 36px; color: var(--field-ink-2); }
.store-search input { flex: 1; background: none; border: 0; outline: none; color: var(--field-ink); font: 12px var(--font-mono); user-select: text; -webkit-user-select: text; }
.store-search input::placeholder { color: var(--field-ink-3); }
.store-filter { margin: 0 12px 8px; }
.store-items { flex: 1; overflow-y: auto; padding: 4px 8px 12px; display: flex; flex-direction: column; gap: 3px; scrollbar-width: none; }
.store-items::-webkit-scrollbar { display: none; }
.store-row { display: flex; align-items: center; gap: 12px; padding: 8px 10px; border-radius: var(--r-key); text-align: left; color: var(--ink); width: 100%; border: var(--bw) solid transparent; transition: background var(--t-fast), box-shadow var(--t-fast); }
.store-row:hover { background: var(--chassis-3); }
.store-row:active { transform: translateY(1px); }
.store-row.on { background: var(--key-hover); box-shadow: var(--shadow-pic); }   /* the row that is open: a lit card, a hair above the list */
.store-row img { width: 42px; height: 42px; border-radius: 3px; object-fit: cover; background: var(--chassis-2); border: 0; box-shadow: var(--shadow-pic); flex: 0 0 auto; }
.store-row-text { flex: 1; min-width: 0; }
.store-row-name { font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .05em; }
.store-row-desc { font-size: 12px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.store-row-badges { display: flex; align-items: center; gap: 6px; }
.store-none { padding: 30px; text-align: center; color: var(--ink-3); }
.store-secret { padding: 12px; border-top: var(--bw) solid var(--line); }
.store-secret-row { display: flex; gap: 6px; margin-top: 6px; }
.store-secret-row input { flex: 1; height: 30px; padding: 1px 11px; outline: none; user-select: text; -webkit-user-select: text; font-size: 12px; }
.store-secret-err { font-size: 11px; color: var(--danger); margin-top: 6px; min-height: 14px; font-family: var(--font-mono); }
.store-detail { padding: 26px 28px; overflow-y: auto; }
.store-hero { display: flex; gap: 18px; align-items: flex-start; margin-bottom: 18px; }
.store-hero-icon { width: 96px; height: 96px; border-radius: var(--r-key); object-fit: cover; background: var(--chassis-2); border: 0; box-shadow: 0 1px 2px rgb(20 20 10 / .14), 0 10px 26px rgb(20 20 10 / .2); flex: 0 0 auto; }
.store-hero h3 { margin: 4px 0 4px; font: 600 16px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; }
.store-hero p { margin: 0 0 10px; color: var(--ink-2); font-size: 13.5px; }
.store-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.store-actions { display: flex; align-items: center; gap: 8px; margin: 10px 0 20px; }
.store-long { color: var(--ink-2); font-size: 13.5px; line-height: 1.6; user-select: text; -webkit-user-select: text; }
.store-long img { max-width: 100%; border-radius: var(--r-key); border: 0; box-shadow: var(--shadow-pic); }
.store-empty { color: var(--ink-3); padding: 40px; text-align: center; }
.store-edit { display: flex; flex-direction: column; gap: 6px; }
@media (max-width: 760px) { .store-body { grid-template-columns: 1fr; } .store-detail { display: none; } }

/* ── team settings (admin) ─────────────────────────────────────────────────
   Same sheet as the store, narrower rail: the access rules and one row per
   team on the left, the thing you picked on the right. */
.teams.sheet { width: min(920px, 100%); height: min(660px, 100%); }
.teams-head-actions { display: flex; align-items: center; gap: 10px; }
.teams-body { flex: 1; display: grid; grid-template-columns: 268px 1fr; min-height: 0; }
.teams-rail { display: flex; flex-direction: column; gap: 2px; padding: 8px 8px 12px; overflow-y: auto; border-right: var(--bw) solid var(--line); scrollbar-width: none; }
.teams-rail::-webkit-scrollbar { display: none; }
.teams-sub { padding: 12px 10px 4px; }
.teams-sub:first-child { padding-top: 4px; }
.teams-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border-radius: var(--r-key); text-align: left; color: var(--ink); border: 0; transition: background var(--t-fast), box-shadow var(--t-fast); }
.teams-item:hover { background: var(--chassis-3); }
.teams-item:active { transform: translateY(1px); }
.teams-item.on { background: var(--key-hover); box-shadow: var(--shadow-pic); }
.teams-item .icon { color: var(--ink-3); flex: 0 0 auto; }
.teams-item.on .icon { color: var(--accent); }
.teams-item.on .teams-item-text small { color: var(--ink-2); }   /* the lit row sits brighter; --ink-3 disappears into it */
.teams-item-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.teams-item-text strong { font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .05em; }
.teams-item-text small { font-size: 11px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.teams-add { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 6px; padding: 9px; border-radius: var(--r-key); color: var(--ink-3); font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; border: 0; background: var(--chassis-2); }
.teams-add:hover { background: var(--chassis-3); color: var(--ink); }
.teams-add:active { transform: translateY(1px); }
.teams-detail { padding: 24px 28px 30px; overflow-y: auto; }
.teams-form { display: flex; flex-direction: column; gap: 16px; max-width: 620px; }
.teams-lead h3 { margin: 0 0 4px; font: 600 15px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; }
.teams-lead p { margin: 0; font-size: 13px; line-height: 1.5; }
.teams-field { display: flex; flex-direction: column; gap: 5px; }
.teams-field .text-input { margin-bottom: 0; }
.teams-note { margin: -2px 0 2px; font-size: 11.5px; line-height: 1.45; }
.teams-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: end; }
/* notes wrap to different heights; the controls still line up across the row */
.teams-row .teams-field { display: grid; grid-template-rows: auto 1fr auto; height: 100%; }
.teams-actions { display: flex; justify-content: flex-end; margin-top: 4px; }
.teams-loading { padding: 40px; text-align: center; color: var(--ink-3); font-family: var(--font-mono); font-size: 11.5px; }
@media (max-width: 760px) { .teams-body { grid-template-columns: 1fr; } .teams-detail { display: none; } }

#lightbox { position: fixed; inset: 0; z-index: 90; background: rgb(0 0 0 / .86); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); display: flex; align-items: center; justify-content: center; animation: fade .18s; }
@keyframes fade { from { opacity: 0; } }
.lb-stage { max-width: 92vw; max-height: 86vh; display: flex; align-items: center; justify-content: center; }
.lb-stage img, .lb-stage video { max-width: 92vw; max-height: 86vh; border-radius: 4px; box-shadow: 0 30px 80px rgb(0 0 0 / .6); object-fit: contain; animation: pop-in .22s var(--spring); }
.lb-x, .lb-prev, .lb-next { position: fixed; width: 44px; height: 44px; border-radius: var(--r-key); padding: 1px 1px calc(var(--press) + 1px); background: #2b2b2b; color: #fff; border: 0; box-shadow: var(--hi-glass), inset 0 calc(-1 * var(--press)) 0 #111, var(--contact); display: inline-flex; align-items: center; justify-content: center; }
.lb-x:hover, .lb-prev:hover, .lb-next:hover { background: #3a3a3a; }
.lb-x:active, .lb-prev:active, .lb-next:active { transform: translateY(var(--press)); box-shadow: var(--hi-glass); }
.lb-x { top: 18px; right: 18px; }
.lb-prev { left: 18px; top: 50%; margin-top: -22px; }
.lb-next { right: 18px; top: 50%; margin-top: -22px; }
#lightbox.single .lb-prev, #lightbox.single .lb-next { display: none; }
.lb-bar { position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 12px; padding: 6px 6px 6px 14px; border-radius: var(--r-key); background: #131313; border: var(--bw) solid transparent; color: var(--display-ink); font: 500 11px var(--font-mono); text-transform: uppercase; letter-spacing: .08em; box-shadow: 0 2px 4px rgb(0 0 0 / .4), 0 14px 34px rgb(0 0 0 / .5); }
.lb-dl { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 1px 13px calc(var(--press) + 1px); border-radius: 3px; --face: var(--accent); color: var(--accent-ink); border: 0; box-shadow: var(--hi-accent), inset 0 calc(-1 * var(--press)) 0 var(--accent-lip, #b83700), var(--contact); font: 700 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .1em; }
.lb-dl:active { transform: translateY(var(--press)); box-shadow: var(--hi-accent); }

#toasts { position: fixed; left: 50%; bottom: 72px; transform: translateX(-50%); z-index: 100; display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none; }
.toast { display: flex; align-items: center; gap: 8px; padding: 9px 13px; border-radius: var(--r-display); background: var(--display); border: 0; color: var(--display-ink); font: 500 11px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; box-shadow: var(--shadow-glass-pop); opacity: 0; transform: translateY(8px) scale(.96); transition: opacity var(--t-mid), transform var(--t-slow) var(--spring); max-width: 70vw; }
.toast.in { opacity: 1; transform: none; }
.toast.out { opacity: 0; transform: translateY(-6px); }
.toast-error { color: var(--danger); }
.toast-ok .icon { color: var(--ok); }
/* something under way (an import): a spinner where the icon goes, and a line under the words that fills as it goes */
.toast-progress { flex-wrap: wrap; row-gap: 7px; }
.toast-progress .spinner { width: 12px; height: 12px; border-width: 2px; border-color: color-mix(in srgb, var(--display-ink) 22%, transparent); border-top-color: var(--display-ink); flex: none; }
.toast-bar { flex-basis: 100%; height: 3px; background: color-mix(in srgb, var(--display-ink) 14%, transparent); overflow: hidden; }
.toast-bar[hidden] { display: none; }
.toast-bar > i { display: block; height: 100%; width: 0; background: var(--display-ink); transition: width .2s linear; }

#splash { position: fixed; inset: 0; z-index: 200; background: var(--bg); color: var(--desk-ink); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; transition: opacity .35s; }
#splash.out { opacity: 0; pointer-events: none; }
.splash-mark img { width: 84px; height: 84px; object-fit: contain; filter: var(--logo-filter); animation: bob 1.6s ease-in-out infinite; }
@keyframes bob { 50% { transform: translateY(-6px); } }
.splash-text { font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .12em; opacity: .6; }
.gate { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--bg); color: var(--desk-ink); z-index: 300; }
.gate-box { text-align: center; max-width: 380px; padding: 40px; }
.gate-box h2 { font: 600 16px var(--font-mono); text-transform: uppercase; letter-spacing: .08em; margin: 0 0 10px; }
.gate-box p { opacity: .75; margin: 0 0 20px; line-height: 1.5; }

/* emoji (SerenityOS pixel emoji from the classic set; whole-number scales only, see icon.js) */
.emoji { image-rendering: pixelated; display: inline-block; vertical-align: middle; }

/* ═══════════════════════════════════════════════════════════════════════════
   small screens — the instrument folds into a bottom sheet
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1100px) { #controls { width: 300px; } #dock { max-width: 58vw; } }
@media (max-width: 820px) {
  :root { --sheet-h: 70px; }
  #logo { display: none; }
  #dock { top: 10px; left: 10px; right: 64px; transform: none; translate: 0 0 !important; max-width: none; height: 64px; padding: 0 10px; align-items: center; }
  .dock-scroll { padding: 5px 4px; align-items: center; }
  .dock-app { margin: 0 4px; transform: none; }
  .dock-add { margin: 0 4px; }
  body.in-canvas #dock { transform: none; }
  .dock-app .dock-text { max-width: 150px; }
  #user { top: 10px; right: 10px; height: 44px; width: 44px; padding: 0; justify-content: center; }
  #user .user-name { display: none; }
  #controls { top: auto; left: 0; right: 0; bottom: 0; width: auto; height: var(--sheet-h); border-radius: var(--r-panel) var(--r-panel) 0 0; border-bottom: 0; transform: none !important; transition: height var(--t-slow) var(--spring), opacity var(--t-mid); }
  body.sheet-open #controls { height: min(76vh, 640px); }
  #controls.collapsed { transform: none !important; opacity: 1; pointer-events: auto; }
  #controls .controls-foot { order: 0; border-top: 0; border-bottom: var(--bw) solid var(--line); padding: 12px 14px 10px; }
  #controls .controls-head { order: 1; padding: 12px 14px; }
  #controls .controls-body { order: 2; }
  #controls .collapse { display: none; }
  .controls-foot .grip { display: inline-flex; }
  .edge-toggle.left { display: none; }
  #rail { top: 84px; bottom: calc(var(--sheet-h) + 10px); width: 84px; right: 10px; }
  .rail-list { padding: 8px; }
  #readout { bottom: calc(var(--sheet-h) + 10px); left: 10px; min-width: 0; max-width: calc(100vw - 180px); height: 40px; padding: 0 10px; transition: bottom var(--t-slow) var(--spring), opacity var(--t-mid); }
  body.sheet-open #readout, body.sheet-open #viewbar { bottom: calc(min(76vh, 640px) + 10px); }
  body.sheet-open .edge-toggle.right { opacity: 0; pointer-events: none; }   /* the sheet covers it */
  body.sheet-open #rail { opacity: 0; pointer-events: none; }
  #readout .readout-bar { display: none; }
  #viewbar { bottom: calc(var(--sheet-h) + 10px); right: 10px; height: 40px; transition: bottom var(--t-slow) var(--spring), opacity var(--t-mid); }
  #viewbar .fit, #viewbar .help, #viewbar .sep { display: none; }
  .tool { min-height: 32px; }
  .sm { height: 36px; }
  textarea.text { font-size: 16px; }   /* stops iOS zooming into the field */
  .home-inner { padding: 32px 18px 60px; }
  .home-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
  .sel-menu { flex-wrap: wrap; max-width: calc(100vw - 20px); }
  .sm-wrap { flex-wrap: wrap; }
}
.grip { display: none; width: 44px; height: 46px; align-items: center; justify-content: center; color: var(--ink-3); flex: 0 0 auto; border-radius: var(--r-key); }
.grip:hover { background: var(--chassis-3); color: var(--ink); }

/* ═══════════════════════════════════════════════════════════════════════════
   motion preference
   ═══════════════════════════════════════════════════════════════════════════ */
/* motion off (the switch in the avatar menu): what you hover or carry keeps still, the dock does not magnify, the plane does
   not sway (data-tilt follows), the grid's dots are plain (grid-fx.js); the short state transitions stay — they are not motion.
   The system's reduced-motion preference is this switch's default (prefs.js / index.html), not a rule of its own any more
   (round 21: it used to stop every transition and the grid's effects outright, whatever the switch said). */
:root[data-motion="off"] .item:hover:not(.lifted):not(.item-placeholder) { transform: none; }
:root[data-motion="off"] .dock-app { --mag: 1 !important; }
:root[data-motion="off"] #logo.sizzle img, :root[data-motion="off"] #viewbar .zoom.notch { animation: none; }

.fav-icon { color: var(--accent); }
.store-row-badges .icon { flex: 0 0 auto; }

/* ═══════════════════════════════════════════════════════════════════════════
   editors (crop · markup) — a device that opens over the desk
   ═══════════════════════════════════════════════════════════════════════════ */
.ed-overlay { position: fixed; inset: 0; z-index: 85; display: flex; align-items: center; justify-content: center; background: rgb(0 0 0 / .55); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); opacity: 0; transition: opacity var(--t-mid); padding: 20px; }
.ed-overlay.in { opacity: 1; }
.ed-device { display: flex; flex-direction: column; max-width: min(92vw, 1400px); max-height: 92vh; background: var(--chassis-solid); transform: translateY(10px) scale(.985); transition: transform var(--t-slow) var(--spring); }
.ed-overlay.in .ed-device { transform: none; }
.ed-head { display: flex; align-items: center; gap: 12px; padding: 14px 14px 12px 16px; border-bottom: var(--bw) solid var(--line); }
.ed-title { font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .1em; color: var(--ink); }
.ed-tools { flex: 1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ed-sep { width: 1px; height: 18px; background: var(--line); margin: 0 2px; }
.ed-stage { position: relative; flex: 1; min-height: 120px; display: flex; align-items: center; justify-content: center; padding: 20px; background: var(--bg); background-image: radial-gradient(circle, var(--dot) 1.1px, transparent 1.4px); background-size: 20px 20px; overflow: auto; }
.ed-loading { padding: 80px; }
.ed-foot { display: flex; align-items: center; gap: 10px; padding: 12px 14px 14px; border-top: var(--bw) solid var(--line); }
.ed-spacer { flex: 1; }
.ed-dim { font: 500 10.5px var(--font-mono); letter-spacing: .06em; color: var(--ink-3); }
.ed-chips { margin: 0; }
.ed-img { display: block; user-select: none; -webkit-user-select: none; }
/* crop */
.crop-frame { position: relative; box-shadow: 0 10px 30px rgb(0 0 0 / .35); }
.crop-rect { position: absolute; box-shadow: 0 0 0 9999px rgb(0 0 0 / .55); outline: 1.5px solid var(--accent); cursor: move; touch-action: none; }
.crop-grid { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity var(--t-fast); background-image: linear-gradient(90deg, transparent calc(33.3% - .5px), rgb(255 255 255 / .55) calc(33.3% - .5px) calc(33.3% + .5px), transparent calc(33.3% + .5px), transparent calc(66.6% - .5px), rgb(255 255 255 / .55) calc(66.6% - .5px) calc(66.6% + .5px), transparent calc(66.6% + .5px)), linear-gradient(transparent calc(33.3% - .5px), rgb(255 255 255 / .55) calc(33.3% - .5px) calc(33.3% + .5px), transparent calc(33.3% + .5px), transparent calc(66.6% - .5px), rgb(255 255 255 / .55) calc(66.6% - .5px) calc(66.6% + .5px), transparent calc(66.6% + .5px)); }
.crop-rect.active .crop-grid { opacity: 1; }
.crop-h { position: absolute; width: 14px; height: 14px; background: var(--key); border: 2px solid var(--accent); border-radius: 2px; }
.crop-h[data-c="nw"] { left: -8px; top: -8px; cursor: nwse-resize; } .crop-h[data-c="ne"] { right: -8px; top: -8px; cursor: nesw-resize; }
.crop-h[data-c="sw"] { left: -8px; bottom: -8px; cursor: nesw-resize; } .crop-h[data-c="se"] { right: -8px; bottom: -8px; cursor: nwse-resize; }
.crop-h[data-c="n"] { left: calc(50% - 12px); top: -4px; width: 24px; height: 6px; cursor: ns-resize; } .crop-h[data-c="s"] { left: calc(50% - 12px); bottom: -4px; width: 24px; height: 6px; cursor: ns-resize; }
.crop-h[data-c="w"] { top: calc(50% - 12px); left: -4px; width: 6px; height: 24px; cursor: ew-resize; } .crop-h[data-c="e"] { top: calc(50% - 12px); right: -4px; width: 6px; height: 24px; cursor: ew-resize; }
/* markup */
.mk-wrap { position: relative; box-shadow: 0 10px 30px rgb(0 0 0 / .35); line-height: 0; }
.mk-wrap[data-tool="text"] { cursor: text; } .mk-wrap[data-tool="eyedropper"] { cursor: crosshair; }
.mk-range { width: 120px; height: 24px; }
.mk-range::before { height: 8px; } .mk-range .range-fill { height: 6px; }
.mk-num { font: 500 10.5px var(--font-mono); color: var(--ink-2); min-width: 34px; }
.mk-color { width: 30px; height: 26px; padding: 1px; border: 0; border-radius: 3px; background: var(--key); cursor: pointer; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 var(--key-lip), var(--contact); }
.mk-color::-webkit-color-swatch-wrapper { padding: 2px; } .mk-color::-webkit-color-swatch { border: 0; border-radius: 2px; }
@media (max-width: 820px) { .ed-overlay { padding: 8px; } .ed-device { max-width: 100%; max-height: 100%; } .ed-tools { gap: 6px; } }

/* stickers */
.sticker-pop { padding: 8px; min-width: 0; }
.sticker-grid { display: grid; grid-template-columns: repeat(6, 36px); gap: 4px; }
.sticker-opt { width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center; border-radius: 3px; border: var(--bw) solid transparent; }
/* the options are cells of one small sheet (--sticker-sheet, set on the popover; 6 across, 32 px each here — round 20b: one
   picture, one decode, no filter per option: 36 separate sheets left the picker half-empty while they loaded) */
.sticker-thumb { display: block; width: 32px; height: 32px; background: var(--sticker-sheet) calc(var(--col, 0) * -32px) calc(var(--row, 0) * -32px) / 192px auto no-repeat; transition: transform var(--t-fast) var(--spring); }
.sticker-opt:hover .sticker-thumb { transform: scale(1.15); }
.sticker-opt:hover { background: var(--chassis-3); }
.sticker-opt:active { transform: translateY(1px); }

/* home: import + drop */
.home-tabs { align-items: center; }
.home-tabs-spacer { flex: 1; }
#home.drop::after { content: 'drop to import'; position: fixed; inset: 16px; border: 2px dashed var(--accent); border-radius: 12px; display: flex; align-items: center; justify-content: center; font: 600 12px var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--accent); background: rgb(var(--accent-rgb) / .06); pointer-events: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   team canvases — presence
   ═══════════════════════════════════════════════════════════════════════════ */
.remote-cursors { position: absolute; inset: 0; pointer-events: none; z-index: 35; overflow: hidden; }
.remote-cursor { position: absolute; left: 0; top: 0; display: flex; align-items: flex-start; gap: 2px; transition: transform .12s linear, opacity .4s; will-change: transform; }
.remote-cursor.idle { opacity: .35; }
.rc-arrow { display: block; line-height: 0; filter: drop-shadow(0 1px 2px rgb(0 0 0 / .4)); }
.rc-name { margin-top: 12px; padding: 3px 7px; border-radius: 3px; color: #fff; font: 600 10px var(--font-mono); letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; box-shadow: 0 1px 3px rgb(0 0 0 / .35); }
.team-slot:empty { display: none; }
.team-bar { display: flex; align-items: center; gap: 4px; padding: 0 6px; }
.team-avatars { display: flex; align-items: center; gap: 4px; }
.team-avatar { width: 26px; height: 26px; border-radius: 3px; color: #fff; font: 700 10px var(--font-mono); padding: 1px 1px calc(var(--press) + 1px); display: inline-flex; align-items: center; justify-content: center; border: 0; box-shadow: var(--hi), inset 0 calc(-1 * var(--press)) 0 rgb(0 0 0 / .32), var(--contact); text-shadow: 0 1px 1px rgb(0 0 0 / .35); }
.team-avatar:active { transform: translateY(var(--press)); box-shadow: var(--hi); }
.team-more { font: 500 10px var(--font-mono); color: var(--ink-2); }
#viewbar.team .home-btn .icon { color: var(--accent); }
.presence { position: absolute; left: 10px; top: 10px; display: flex; gap: 4px; }
.presence-dot { width: 22px; height: 22px; border-radius: 3px; color: #fff; font: 700 10px var(--font-mono); display: inline-flex; align-items: center; justify-content: center; border: var(--bw) solid transparent; box-shadow: 0 1px 3px rgb(0 0 0 / .35); }
.presence-more { font: 600 10px var(--font-mono); color: #fff; background: rgb(0 0 0 / .6); border-radius: 3px; padding: 0 5px; display: inline-flex; align-items: center; }
body.in-team #readout .led { --led: var(--accent); }
/* live rooms (share-by-code) */
#viewbar .tool.live.on { animation: live-pulse 2s ease-out infinite; }
@keyframes live-pulse { 0% { box-shadow: 0 0 0 0 rgb(var(--accent-rgb) / .55); } 70% { box-shadow: 0 0 0 9px rgb(var(--accent-rgb) / 0); } 100% { box-shadow: 0 0 0 0 rgb(var(--accent-rgb) / 0); } }
#livepop { width: 280px; }
.pop-text { margin: 0; padding: 2px 8px 6px; font-size: 11.5px; line-height: 1.45; color: var(--ink-2); }
.code-row { display: flex; gap: 8px; padding: 6px 8px 4px; align-items: center; }
.code-input { flex: 1; min-width: 0; height: 34px; padding: 1px 11px; font: 600 15px var(--font-mono); letter-spacing: .28em; text-transform: uppercase; color: var(--display-ink); background: var(--display); border: 0; border-radius: var(--r-display); outline: none; box-shadow: var(--cut-glass); }
.code-input:focus { box-shadow: var(--cut-glass), 0 0 0 2px rgb(var(--accent-rgb) / .35); }
.code-input::placeholder { color: var(--display-ink); opacity: .3; letter-spacing: .28em; }
.code-input.shake { animation: code-shake .4s; }
@keyframes code-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 50% { transform: translateX(4px); } 75% { transform: translateX(-3px); } }
:root[data-motion="off"] #viewbar .tool.live.on { animation: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   arrivals — nothing pops: children of a .rise container fade up, staggered
   ═══════════════════════════════════════════════════════════════════════════ */
.rise > * { animation: rise .46s var(--ease) both; animation-delay: calc(var(--i, 0) * var(--stagger, 26ms)); }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.home-loading { animation: fade-in .3s var(--ease) both; animation-delay: .12s; }
@keyframes fade-in { from { opacity: 0; } }
:root[data-motion="off"] .rise > * { animation: none; }

/* favourite key in the controls header: a filled heart when on */
.controls-head .fav-btn { flex: 0 0 auto; }
.controls-head .fav-btn.on { color: var(--accent); background: transparent; border-color: transparent; }
.controls-head .fav-btn.on .icon { fill: var(--accent); }
.controls-head .fav-btn.on:hover { background: var(--chassis-3); filter: none; }
.controls-head .fav-btn .icon { transform: translateY(-1px); transition: transform .25s var(--spring); }   /* a heart's weight is in its lobes: a pixel up reads as centred */
.controls-head .fav-btn:active .icon { transform: translateY(-1px) scale(.8); }

/* ═══════════════════════════════════════════════════════════════════════════
   small delights (all quiet, all short)
   ═══════════════════════════════════════════════════════════════════════════ */
/* a finished output drops into the rail */
:root[data-motion="on"] .rail-item.drop-in { animation: drop-in .5s var(--spring) both; }
@keyframes drop-in { from { opacity: 0; transform: translateY(-14px) scale(.9); } to { opacity: 1; transform: none; } }
/* the 100% detent on the zoom readout */
#viewbar .zoom.notch { animation: notch .35s var(--ease); }
@keyframes notch { 30% { color: var(--accent); transform: scale(1.12); } }
/* hold the logo: it sizzles */
#logo.sizzle img { animation: sizzle 1s linear; }
@keyframes sizzle { 0%, 100% { transform: rotate(0); } 10% { transform: rotate(-3deg) translateY(-1px); } 20% { transform: rotate(3deg) translateY(1px); } 30% { transform: rotate(-4deg); } 40% { transform: rotate(4deg) translateY(-1px); } 50% { transform: rotate(-2deg); } 60% { transform: rotate(3deg) translateY(1px); } 70% { transform: rotate(-3deg); } 80% { transform: rotate(2deg); } 90% { transform: rotate(-1deg); } }
/* a fresh output wears a small tag for a few seconds */
.fresh-tag { position: absolute; top: 8px; left: 8px; z-index: 3; padding: 3px 6px 4px; border-radius: 3px; background: var(--accent); color: var(--accent-ink); font: 700 9px var(--font-mono); letter-spacing: .12em; text-transform: uppercase; box-shadow: var(--hi-accent), inset 0 -1px 0 var(--accent-lip, #b83700); pointer-events: none; animation: fresh-tag 4s var(--ease) both; }
@keyframes fresh-tag { 0% { opacity: 0; transform: translateY(-4px); } 10%, 80% { opacity: 1; transform: none; } 100% { opacity: 0; } }
/* the app name types itself into the panel header when you switch */
.controls-head .app-name.typed { animation: type-in .38s steps(14, end) both; }
@keyframes type-in { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
:root[data-motion="off"] .rail-item.drop-in, :root[data-motion="off"] .fresh-tag, :root[data-motion="off"] .controls-head .app-name.typed { animation: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   round 5 delight: steam, thermal print, photo-booth strips, dust
   ═══════════════════════════════════════════════════════════════════════════ */
/* particles (fx.js) */
.fx-layer { position: fixed; inset: 0; z-index: 45; pointer-events: none; }




/* ═══════════════════════════════════════════════════════════════════════════
   the assistant — a device on the desk, bottom right (js/ui-assistant.js)

   It obeys the same material rules as the rest: a matte chassis, paper
   for what you type into, dark glass for the little displays (the tool chips). The one decision
   worth stating: a *user* message is a sheet of paper — it is a thing they wrote — and the
   assistant's answer is not a bubble at all, just type on the chassis, because it is the device
   talking and the device has no paper. Two speakers, one look, no chat-app candy.

   It sits clear of the outputs rail (right: 14 + 108 wide) and above the view bar (bottom: 14 +
   44 tall), so the desk's own furniture is never covered.
   ═══════════════════════════════════════════════════════════════════════════ */
#asst-launch, #assistant { position: fixed; z-index: 30; opacity: 0; pointer-events: none; }
/* a layer of its own at rest. Without it the 3D rotate of every sway promoted the case — a fresh raster of three
   42-unit shadows — and demoted it again at settle. The transform rule below still scopes `transform` to the moving
   states; this only keeps the texture. */
#asst-launch { will-change: transform; }
/* the two other devices with no backdrop-filter: without a layer of their own the sway promoted and demoted them at
   every gesture, like the companion */
#logo, #readout { will-change: transform; }
/* on the desk only, and turned on explicitly - never left to fall back to an initial value */
body.in-canvas #asst-launch:not(.gone) { opacity: 1; pointer-events: auto; }
body.in-canvas #asst-launch.off:not(.gone) { opacity: .5; filter: saturate(.35); }
body.in-canvas #assistant.in { opacity: 1; pointer-events: auto; }

/* ═══════════════════════════════════════════════════════════════════════════
   the companion — the little machine on the desk (js/ui-assistant.js, js/companion.js)

   BEN'S DESIGN, PORTED, NOT INTERPRETED. This is his own file: the block was exported to him as a
   standalone study, he reshaped the whole device in it, and the device half comes back unedited.
   Only three things were done to it — the study's page furniture and creature lab were dropped,
   `#companion` was renamed to `#asst-launch`, and the one line he marked STUDY was put back to
   parking it in the corner. Every colour, unit and mask below is his.

   THE SHAPE is `compact`: 223 × 213 units, a rounded-square top and a much rounder bottom
   (`--case-outline`), no exposed screws, one polished oval key in a real aperture cut through the
   front cover, and the name cut into the casing above the screen. `<html data-shape="compact">`
   selects it — without that attribute the CSS falls back to the older shell, so prefs.js sets it
   before first paint alongside the scheme and the chassis.

   THE ONE NUMBER is `--jl-size` (180px here); everything derives from `--unit`, which is
   `--jl-size / 260` — 260 being the grid the design is drawn on, not the size it is shown at.

   THE HUE is `--jl-h`, from the user's accent (prefs.js). Every tinted colour is written
   `hsl(calc(var(--jl-h) + Ndeg) S% L% / A)`, so the accent turns the whole machine and changes
   nothing else. Pure whites and blacks carry no hue on purpose: a specular highlight and a mask
   are not tinted by the plastic they land on. The LCD keeps its own olive whatever the case is,
   because that is what an LCD is.

   THE SCREEN is `.jl-screen`, above the front cover so no grain and no blur ever touch it, and it
   is where the mascot lives. The creature is painted as a CSS mask (`--creature-mask`, written by
   companion.js from the rig in creature.js) through two ink layers — `.jl-pixels-rest` for the
   dark LCD ink and `.jl-pixels-lit` under `.jl-screen-lamp` for the lit one. `data-lamp` on
   `.jl-screen` is the backlight: on while the chat is open.

   TEXTURES are in ../tex. Four of them are masks baked at the shell's exact geometry, so they are
   shape-specific: `bevel-mask-compact` / `rounded-wash-mask-compact` for this shell and the
   unsuffixed pair for the older one, plus `cap-aperture-mask` (the hole the key sits in) and
   `glass-shoulder-mask` (the polished lip around the screen).
   ═══════════════════════════════════════════════════════════════════════════ */
#asst-launch {
  --jl-size: var(--dev-size, 190px);
  --jl-h: var(--jelly-h, 143deg);
  /* The two numbers the accent lends the casing besides its hue (prefs.js).

     --jl-sat  0..1   how colourful. Every tinted saturation below is a multiple of it, so the
                      material desaturates as a whole and the spread between its layers — which is
                      what reads as clear plastic — survives intact.
     --jl-lum  -1..1  how pale. Positive moves every tinted lightness proportionally toward white,
                      negative toward black. Proportionally, so the shading survives too: a white
                      device still has darker corners rather than going flat.

     Both are 0-effect at their defaults, and the curves that produce them have a deadband around
     the middle, so a vivid accent leaves the whole thing exactly as Ben drew it. */
  --jl-sat: var(--jelly-sat, 1);
  --jl-lum: var(--jelly-lum, 0);
  --lx: 50%; --ly: 40%;          /* the pointer, in the device's own box (lighting.js) */
  --depth-x: 0px; --depth-y: 0px;   /* the parallax, and the cast shadow that swings with it */
  --cast-x: 0px; --cast-y: 0px;     /* — both written by ui-assistant.js from the plane's angle */
  position: fixed; right: 136px; bottom: 74px; z-index: 30;
  /* THE HAND-LINK BELONGS TO THE ONE THING THAT BEHAVES LIKE A LINK, which is the button. The rest
     of this is an object sitting on a desk: it gets the open hand, and you can pick it up. It used
     to say `pointer` over the whole device, which was a promise it could not keep — nothing is
     bound to the casing at all, only to `.jl-key`.

     `cursor` inherits, and only three things in the whole enclosure ever receive the pointer — the
     casing, the key (with its transparent hit slop), and `.jl-glass`, the screen. Everything else
     is `pointer-events: none` and falls through. So two rules cover every pixel. */
  cursor: grab;
  user-select: none; -webkit-user-select: none;
  touch-action: none;              /* a drag is ours, not the browser's scroll (as .asst-grip does) */
  transition: opacity var(--t-mid) var(--ease);
}
#asst-launch .jl-key { cursor: pointer; }
/* Once it is actually moving, the key stops being a button. That is what lets you pick the device
   up BY the button — which is the natural place to grab a small thing, and on the phone shell the
   key's hit area is most of the object. A press that never travels is untouched, so a tap on the
   key still opens the chat exactly as before. */
#asst-launch.dragging .jl-key { pointer-events: none; }
/* `dragging` is the press; `flying` is the spring home. The z-index belongs to "not on the desk
   yet", so it spans both: #assistant shares z-index 30 and is later in the DOM, so a device pulled
   up more than 12px would otherwise slide underneath the chat window — and the moment of release,
   when it is furthest out, is exactly the moment you are looking at it. */
#asst-launch.dragging, #asst-launch.flying { z-index: 31; }
/* TRANSLATION, AND A TILT ABOUT ITS OWN CENTRE — only while it is off its corner.
   `transform-origin` is one shared origin for `transform`, `translate`, `rotate` and `scale`, and
   plane.js points it at the viewport centre so every device on the desk turns about one pivot —
   hundreds of pixels outside this element. Translation does not care. Anything else does: measured
   on the real page, `scale(.94)` alone slid the device 7.5px sideways, and a rotation about that
   pivot would swing it across the desk. So the tilt is wrapped: move to the device's own centre
   (plane.js writes where the pivot is, in `--po-x/--po-y`), rotate there, move back — the pivot the
   desk sway uses is untouched, and the case tips in place. `perspective()` inside the wrap is what
   makes it read as a trapezoid rather than a squash; four device-widths keeps it subtle (Ben).
   device-drag.js writes the angles from the pull; the spring overshooting is the bounce.
   Scoped to the states so the device's transform is `none` at rest, which keeps it from being a
   containing block for fixed descendants and from holding a compositing layer all day. */
#asst-launch.dragging, #asst-launch.flying, #asst-launch.hopping, #asst-launch.shaking {
  transform:
    translate3d(
      calc(var(--jl-drag-x, 0px) + var(--jl-hop-x, 0px)),
      calc(var(--jl-drag-y, 0px) + var(--jl-hop-y, 0px)), 0)
    translate(calc(50% - var(--po-x, 50%)), calc(50% - var(--po-y, 50%)))
    perspective(calc(var(--jl-size, 180px) * 4))
    rotateX(var(--jl-tilt-x, 0deg)) rotateY(var(--jl-tilt-y, 0deg))
    translate(calc(var(--po-x, 50%) - 50%), calc(var(--po-y, 50%) - 50%));
}

/* ── the little jump while it is talking ────────────────────────────────────
   It crouches, hops about four units, lands, and squashes once — then holds still for the rest of
   the cycle. FIFTY-SIX PERCENT OF EVERY CYCLE IS MOTIONLESS, and that is the whole argument that
   this is a thing that jumped rather than a widget that bobs: a continuous float would read as a
   gimmick, which is the class of effect that has been cut from this desk before.

   The rise is 13 units — about 9px on the full-size shell, 6% of its own height. The first version
   was 4 units and Ben read it as the device moving "forward and backwards" rather than up and down,
   which is what a 2.8px translation on a case with parallax inside it actually looks like: not a
   jump, an ambiguous shimmer. A hop has to be unmistakably a hop or it is just noise. It is still
   under a tenth of the device and it still spends half of every cycle completely still.

   The rise and the fall are the same length and opposite curves, because a thing thrown up and
   falling back is symmetric: decelerating into the apex, accelerating out of it. That is gravity,
   and it is what separates a hop from a wobble.

   It animates `--jl-hop-y` rather than `transform`, so it composes with a drag instead of
   replacing it — one `transform` declaration, one property per writer (see above). The period is
   the talking mouth's own cycle (3 frames x 0.4s in the rig), so the two never drift apart. */
@keyframes jl-hop {
  0%   { --jl-hop-y: 0px; }
  8%   { --jl-hop-y: calc(var(--unit) * 3); }      /* crouch — anticipation */
  22%  { --jl-hop-y: calc(var(--unit) * -13); }    /* apex */
  36%  { --jl-hop-y: 0px; }                        /* down */
  41%  { --jl-hop-y: calc(var(--unit) * 1.6); }    /* one squash on landing */
  48%  { --jl-hop-y: 0px; }
  100% { --jl-hop-y: 0px; }                        /* …and still, for half the cycle */
}
#asst-launch.hopping { animation: jl-hop 1200ms linear infinite; }

/* ── poked ──────────────────────────────────────────────────────────────────
   A press on the casing that goes nowhere. It is the same little motion the talking hop is made of
   — one rise, one landing squash — with a shiver across it, so that being prodded reads as
   something you did to it rather than as the thing it does while it speaks. One shot, 460ms.

   The horizontal part is the shake and it decays: 2 units, then 1.3, then 0.6. A wobble that does
   not visibly die out reads as a broken loop rather than as a thing settling. */
@keyframes jl-shake {
  0%   { --jl-hop-x: 0px;                        --jl-hop-y: 0px; }
  9%   { --jl-hop-x: calc(var(--unit) * -2);     --jl-hop-y: calc(var(--unit) * 1.5); }
  24%  { --jl-hop-x: calc(var(--unit) * 1.3);    --jl-hop-y: calc(var(--unit) * -5); }
  42%  { --jl-hop-x: calc(var(--unit) * -0.6);   --jl-hop-y: calc(var(--unit) * -2); }
  60%  { --jl-hop-x: calc(var(--unit) * 0.3);    --jl-hop-y: calc(var(--unit) * 1.2); }
  78%  { --jl-hop-x: calc(var(--unit) * -0.15);  --jl-hop-y: 0px; }
  100% { --jl-hop-x: 0px;                        --jl-hop-y: 0px; }
}
#asst-launch.shaking { animation: jl-shake 460ms cubic-bezier(.32, .72, .38, 1) 1; }
/* …and while it is actually talking, the talking hop keeps the floor: being poked mid-answer should
   not stop the thing it is doing. */
#asst-launch.hopping.shaking { animation: jl-hop 1200ms linear infinite; }
:root[data-motion="off"] #asst-launch.shaking { animation: none; --jl-hop-x: 0px; --jl-hop-y: 0px; }
/* The switch is the authority, and off means gone rather than shortened. */
:root[data-motion="off"] #asst-launch.hopping { animation: none; --jl-hop-y: 0px; }
/* A drag outruns the device — with the pointer captured you can be anywhere on the page and still
   be holding it. Same trick as #stage.panning. */
:root.jl-dragging, :root.jl-dragging * { cursor: grabbing !important; }
/* (unused: nothing sets `.gone`. Left as written, including its scale, which has the origin
    problem described above and would need the same treatment if it were ever wired up.) */
#asst-launch.gone { opacity: 0; transform: scale(.94); pointer-events: none; }
/* his layers are positioned against the device box */
@property --jl-drag-x { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --jl-drag-y { syntax: '<length>'; inherits: false; initial-value: 0px; }
/* Registered, because the hop is ANIMATED and an unregistered custom property cannot be — it would
   flip between keyframes instead of tweening. `inherits: false` keeps the change from re-resolving
   the forty-odd elements inside the case on every frame. */
@property --jl-hop-y { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --jl-hop-x { syntax: '<length>'; inherits: false; initial-value: 0px; }
#asst-launch .jl-rear > *, #asst-launch .jl-front, #asst-launch .jl-glare,
#asst-launch .jl-grain, #asst-launch .jl-post, #asst-launch .jl-screen { position: absolute; }
#asst-launch .jl-internals span, #asst-launch .jl-cradle,
#asst-launch .jl-cradle span { position: absolute; display: block; pointer-events: none; }
#asst-launch .jl-wall { position: absolute; }
#asst-launch .jl-rear, #asst-launch .jl-front, #asst-launch .jl-glare, #asst-launch .jl-grain,
#asst-launch .jl-internals, #asst-launch .jl-post, #asst-launch .jl-wall { pointer-events: none; }
/* Structure only — the declarations his rules inherit from the shared `.device` sheet his study
   sits on, which we do not carry over: position, stacking and layout, no colour and no size. His
   v2 rules put every measurement in percentages on top of these. */
#asst-launch .jl-screen { overflow: hidden; z-index: 3; }
#asst-launch .jl-key { position: absolute; display: grid; place-items: center; z-index: 7; }
#asst-launch .jl-socket, #asst-launch .jl-face { position: absolute; pointer-events: none; }
#asst-launch .jl-face { display: grid; place-items: center; }
#asst-launch .jl-screw { position: absolute; }
#asst-launch .jl-key, #asst-launch .jl-key * { box-sizing: border-box; }
/* the screen's placeholder, until the mascot goes in */
#asst-launch .jl-glass { display: block; width: 100%; height: 100%; border-radius: inherit;
  background: var(--lcd); }
/* the pointer's sheen, the same element lighting.js puts in every device it tracks */
#asst-launch > .sheen { position: absolute; inset: 0; border-radius: var(--shell-radius);
  pointer-events: none; z-index: 5; opacity: var(--lit, 0); transition: opacity .35s;
  overflow: hidden; }
/* the picture, moved by transform (device-light.js) — the clip above is masked, so it lands exactly where the
   background-position used to put it and nothing repaints when it moves; it is invisible until the pointer lights it */
#asst-launch > .sheen > b { position: absolute; left: 0; top: 0; width: 880px; height: 880px; will-change: transform;
  background: radial-gradient(440px circle at center, rgb(255 255 255 / .10), rgb(255 255 255 / .03) 45%, transparent 75%); }
#asst-launch {
  --unit:calc(var(--jl-size) / 260);
  --shell-radius:calc(var(--jl-size) * .255);
  --radius:var(--shell-radius);
  --blur:calc(var(--unit) * .9);
  --screen-left:calc(var(--unit) * 39);
  --screen-top:calc(var(--unit) * 41);
  --screen-width:calc(var(--unit) * 182);
  --screen-height:calc(var(--unit) * 128);
  --screen-radius:calc(var(--unit) * 25);
  --screen-surround:calc(var(--unit) * 9);
  --plastic-grain:url('../tex/plastic-grain.png');
  --lcd:hsl(calc(var(--jl-h) + 7.0deg) calc(41.4% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))));
  --lcd-ink:hsl(calc(var(--jl-h) + -40.3deg) calc(37.6% * var(--jl-sat)) calc(69.2% + (100% - 69.2%) * max(0, var(--jl-lum)) + 69.2% * min(0, var(--jl-lum))));
  --key-side:hsl(calc(var(--jl-h) + 72.2deg) calc(76.9% * var(--jl-sat)) calc(40.8% + (100% - 40.8%) * max(0, var(--jl-lum)) + 40.8% * min(0, var(--jl-lum))) / 0.471);
  --key-travel:calc(var(--unit) * 2);
  width:var(--jl-size);
  height:calc(var(--jl-size) * .9692307692);
  border:0;
  border-radius:var(--shell-radius);
  background:hsl(calc(var(--jl-h) + 1.4deg) calc(75.6% * var(--jl-sat)) calc(30.6% + (100% - 30.6%) * max(0, var(--jl-lum)) + 30.6% * min(0, var(--jl-lum))));
  overflow:visible;
  filter:none;
  -webkit-backdrop-filter:none;
  backdrop-filter:none;
  box-shadow:calc(-1 * var(--jl-hop-x)) calc(var(--unit) * 3 - var(--jl-hop-y)) calc(var(--unit) * 4) hsl(calc(var(--jl-h) + 3.2deg) calc(100.0% * var(--jl-sat)) calc(7.6% + (100% - 7.6%) * max(0, var(--jl-lum)) + 7.6% * min(0, var(--jl-lum))) / 0.291),
    calc(var(--cast-x) * .4 - var(--jl-hop-x)) calc(var(--cast-y) * .4 - var(--jl-hop-y)) calc(var(--unit) * 14) calc(var(--unit) * -3) hsl(calc(var(--jl-h) + 4.7deg) calc(79.6% * var(--jl-sat)) calc(9.6% + (100% - 9.6%) * max(0, var(--jl-lum)) + 9.6% * min(0, var(--jl-lum))) / 0.291),
    calc(var(--cast-x) - var(--jl-hop-x)) calc(var(--cast-y) - var(--jl-hop-y)) calc(var(--unit) * 29) calc(var(--unit) * -7) hsl(calc(var(--jl-h) + 14.8deg) calc(62.8% * var(--jl-sat)) calc(8.4% + (100% - 8.4%) * max(0, var(--jl-lum)) + 8.4% * min(0, var(--jl-lum))) / 0.255),
    calc(var(--cast-x) * 1.35 - var(--jl-hop-x)) calc(var(--cast-y) * 1.35 - var(--jl-hop-y)) calc(var(--unit) * 42) calc(var(--unit) * -12) hsl(calc(var(--jl-h) + 10.8deg) calc(64.0% * var(--jl-sat)) calc(9.8% + (100% - 9.8%) * max(0, var(--jl-lum)) + 9.8% * min(0, var(--jl-lum))) / 0.128);
}
:root[data-scheme="dark"] #asst-launch {
  box-shadow:calc(-1 * var(--jl-hop-x)) calc(var(--unit) * 3 - var(--jl-hop-y)) calc(var(--unit) * 5) hsl(0deg 0% 0.0% / 0.464),
    calc(var(--cast-x) * .4 - var(--jl-hop-x)) calc(var(--cast-y) * .4 - var(--jl-hop-y)) calc(var(--unit) * 15) calc(var(--unit) * -3) hsl(0deg 0% 0.0% / 0.541),
    calc(var(--cast-x) - var(--jl-hop-x)) calc(var(--cast-y) - var(--jl-hop-y)) calc(var(--unit) * 30) calc(var(--unit) * -6) hsl(0deg 0% 0.0% / 0.464),
    calc(var(--cast-x) * 1.35 - var(--jl-hop-x)) calc(var(--cast-y) * 1.35 - var(--jl-hop-y)) calc(var(--unit) * 42) calc(var(--unit) * -10) hsl(0deg 0% 0.0% / 0.348),
    0 0 calc(var(--unit) * .65) hsl(var(--jl-h) calc(75% * var(--jl-sat)) calc(55% + (100% - 55%) * max(0, var(--jl-lum)) + 55% * min(0, var(--jl-lum))) / 0.070);
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) {
  #asst-launch {
    background:hsl(calc(var(--jl-h) + -0.8deg) calc(92.7% * var(--jl-sat)) calc(26.9% + (100% - 26.9%) * max(0, var(--jl-lum)) + 26.9% * min(0, var(--jl-lum))) / 0.31);
  }
  #asst-launch .jl-front {
    -webkit-backdrop-filter:blur(var(--blur)) saturate(1.2);
    backdrop-filter:blur(var(--blur)) saturate(1.2);
  }
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) {
  #asst-launch .jl-rear > *, #asst-launch .jl-internals > * {--fb:var(--blur)}
}
#asst-launch::before,
#asst-launch::after {content:none}
#asst-launch .jl-brand {display:none}
#asst-launch .jl-rear {
  position:absolute;inset:0;z-index:1;pointer-events:none;
  /* the parallax arrives as an inline `translate` from device-light.js (perf, round 23); it composes with any transform */
}
#asst-launch .jl-back {
  /* Pulled in hard at the top and the sides and left hanging at the bottom, so that once the
     rear slides down-right the only edge of it you ever see is the one along the bottom-right —
     and its corners keep the cover's radius on a smaller box, which reads as a wider curve. */
  inset:calc(var(--unit) * 5) calc(var(--unit) * 2.5) calc(var(--unit) * -3);
  border:calc(var(--unit) * 2) solid hsl(calc(var(--jl-h) + 1.1deg) calc(76.0% * var(--jl-sat)) calc(32.7% + (100% - 32.7%) * max(0, var(--jl-lum)) + 32.7% * min(0, var(--jl-lum))) / 0.541);
  border-radius:calc(var(--shell-radius) + var(--unit) * 2);
  background:linear-gradient(140deg,hsl(calc(var(--jl-h) + -5.4deg) calc(59.5% * var(--jl-sat)) calc(50.6% + (100% - 50.6%) * max(0, var(--jl-lum)) + 50.6% * min(0, var(--jl-lum))) / 0.141),hsl(calc(var(--jl-h) + -1.8deg) calc(100.0% * var(--jl-sat)) calc(26.1% + (100% - 26.1%) * max(0, var(--jl-lum)) + 26.1% * min(0, var(--jl-lum))) / 0.349) 67%,hsl(calc(var(--jl-h) + 2.1deg) calc(96.5% * var(--jl-sat)) calc(33.5% + (100% - 33.5%) * max(0, var(--jl-lum)) + 33.5% * min(0, var(--jl-lum))) / 0.2));
  transform:none;
  box-shadow:inset 0 calc(var(--unit) * 1) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 15.3deg) calc(100.0% * var(--jl-sat)) calc(86.5% + (100% - 86.5%) * max(0, var(--jl-lum)) + 86.5% * min(0, var(--jl-lum))) / 0.702),
    inset calc(var(--hx, 0px) * 1.6) calc(var(--hy, 0px) * 1.6) calc(var(--unit) * 4) hsl(calc(var(--jl-h) + -23.0deg) calc(100.0% * var(--jl-sat)) calc(90.8% + (100% - 90.8%) * max(0, var(--jl-lum)) + 90.8% * min(0, var(--jl-lum))) / 0.451),
    inset calc(var(--unit) * -1) calc(var(--unit) * -3) calc(var(--unit) * 3) hsl(calc(var(--jl-h) + 0.1deg) calc(100.0% * var(--jl-sat)) calc(20.4% + (100% - 20.4%) * max(0, var(--jl-lum)) + 20.4% * min(0, var(--jl-lum))) / 0.569),
    0 calc(var(--unit) * 2) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 11.6deg) calc(90.8% * var(--jl-sat)) calc(12.7% + (100% - 12.7%) * max(0, var(--jl-lum)) + 12.7% * min(0, var(--jl-lum))) / 0.6);
  z-index:0;
}
#asst-launch .jl-back::after {
  content:"";position:absolute;inset:calc(var(--unit) * 7);
  border:calc(var(--unit) * 2) solid hsl(calc(var(--jl-h) + -1.1deg) calc(97.5% * var(--jl-sat)) calc(32.0% + (100% - 32.0%) * max(0, var(--jl-lum)) + 32.0% * min(0, var(--jl-lum))) / 0.42);
  border-radius:calc(var(--shell-radius) - var(--unit) * 10);
  box-shadow:inset 0 0 calc(var(--unit) * 2) hsl(calc(var(--jl-h) + -7.2deg) calc(100.0% * var(--jl-sat)) calc(85.1% + (100% - 85.1%) * max(0, var(--jl-lum)) + 85.1% * min(0, var(--jl-lum))) / 0.42),0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -7.7deg) calc(100.0% * var(--jl-sat)) calc(78.4% + (100% - 78.4%) * max(0, var(--jl-lum)) + 78.4% * min(0, var(--jl-lum))) / 0.345);
  background:transparent;
}
#asst-launch .jl-wall {
  inset:calc(var(--unit) * 3);
  border:calc(var(--unit) * 8) solid hsl(calc(var(--jl-h) + -4.6deg) calc(80.0% * var(--jl-sat)) calc(47.1% + (100% - 47.1%) * max(0, var(--jl-lum)) + 47.1% * min(0, var(--jl-lum))) / 0.412);
  border-bottom-width:calc(var(--unit) * 9);
  border-radius:calc(var(--shell-radius) - var(--unit) * 3);
  background:transparent;
  /* the wall's fifth of the parallax arrives the same way (device-light.js) */
  box-shadow:inset calc(var(--unit) * 2) calc(var(--unit) * 2) calc(var(--unit) * 3) hsl(calc(var(--jl-h) + -48.5deg) calc(100.0% * var(--jl-sat)) calc(87.1% + (100% - 87.1%) * max(0, var(--jl-lum)) + 87.1% * min(0, var(--jl-lum))) / 0.678),
    inset calc(var(--unit) * -2) calc(var(--unit) * -2) calc(var(--unit) * 4) hsl(calc(var(--jl-h) + -2.7deg) calc(100.0% * var(--jl-sat)) calc(48.6% + (100% - 48.6%) * max(0, var(--jl-lum)) + 48.6% * min(0, var(--jl-lum))) / 0.51),
    0 0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 0.1deg) calc(100.0% * var(--jl-sat)) calc(89.8% + (100% - 89.8%) * max(0, var(--jl-lum)) + 89.8% * min(0, var(--jl-lum))) / 0.361),
    inset 0 0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 4.0deg) calc(92.0% * var(--jl-sat)) calc(14.7% + (100% - 14.7%) * max(0, var(--jl-lum)) + 14.7% * min(0, var(--jl-lum))) / 0.549);
  z-index:1;
}
#asst-launch .jl-wall::before,
#asst-launch .jl-wall::after {
  content:"";position:absolute;pointer-events:none;background:transparent;
}
#asst-launch .jl-wall::before {
  inset:calc(var(--unit) * 5);
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + 0.6deg) calc(76.9% * var(--jl-sat)) calc(40.8% + (100% - 40.8%) * max(0, var(--jl-lum)) + 40.8% * min(0, var(--jl-lum))) / 0.702);
  border-radius:calc(var(--shell-radius) - var(--unit) * 17);
  box-shadow:0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -3.8deg) calc(100.0% * var(--jl-sat)) calc(80.4% + (100% - 80.4%) * max(0, var(--jl-lum)) + 80.4% * min(0, var(--jl-lum))) / 0.502),inset 0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 5.1deg) calc(100.0% * var(--jl-sat)) calc(12.2% + (100% - 12.2%) * max(0, var(--jl-lum)) + 12.2% * min(0, var(--jl-lum))) / 0.502);
}
#asst-launch .jl-wall::after {
  inset:calc(var(--unit) * 9);
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -6.6deg) calc(75.0% * var(--jl-sat)) calc(60.8% + (100% - 60.8%) * max(0, var(--jl-lum)) + 60.8% * min(0, var(--jl-lum))) / 0.522);
  border-radius:calc(var(--shell-radius) - var(--unit) * 21);
  box-shadow:0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 9.2deg) calc(100.0% * var(--jl-sat)) calc(10.6% + (100% - 10.6%) * max(0, var(--jl-lum)) + 10.6% * min(0, var(--jl-lum))) / 0.502);
}
#asst-launch .jl-carrier {
  left:calc(var(--screen-left) - var(--screen-surround));
  top:calc(var(--screen-top) - var(--screen-surround));
  width:calc(var(--screen-width) + var(--screen-surround) * 2);
  height:calc(var(--screen-height) + var(--screen-surround) * 2);
  border:calc(var(--unit) * 2) solid hsl(calc(var(--jl-h) + -1.9deg) calc(100.0% * var(--jl-sat)) calc(32.9% + (100% - 32.9%) * max(0, var(--jl-lum)) + 32.9% * min(0, var(--jl-lum))) / 0.549);
  border-radius:calc(var(--screen-radius) + var(--screen-surround));
  background:linear-gradient(135deg,hsl(calc(var(--jl-h) + -0.9deg) calc(86.1% * var(--jl-sat)) calc(19.8% + (100% - 19.8%) * max(0, var(--jl-lum)) + 19.8% * min(0, var(--jl-lum))) / 0.149),hsl(calc(var(--jl-h) + 9.1deg) calc(82.4% * var(--jl-sat)) calc(13.3% + (100% - 13.3%) * max(0, var(--jl-lum)) + 13.3% * min(0, var(--jl-lum))) / 0.239) 58%,hsl(calc(var(--jl-h) + -0.7deg) calc(56.6% * var(--jl-sat)) calc(67.5% + (100% - 67.5%) * max(0, var(--jl-lum)) + 67.5% * min(0, var(--jl-lum))) / 0.102));
  transform:none;
  box-shadow:inset 0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 3.9deg) calc(100.0% * var(--jl-sat)) calc(79.0% + (100% - 79.0%) * max(0, var(--jl-lum)) + 79.0% * min(0, var(--jl-lum))) / 0.459),
    inset 0 calc(var(--unit) * -1) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + -1.3deg) calc(92.2% * var(--jl-sat)) calc(20.0% + (100% - 20.0%) * max(0, var(--jl-lum)) + 20.0% * min(0, var(--jl-lum))) / 0.502),
    calc(var(--cast-x) * .06) calc(var(--unit) * 2 + var(--cast-y) * .06) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 11.7deg) calc(85.1% * var(--jl-sat)) calc(13.1% + (100% - 13.1%) * max(0, var(--jl-lum)) + 13.1% * min(0, var(--jl-lum))) / 0.451);
  z-index:2;
}
#asst-launch .jl-carrier::before,
#asst-launch .jl-carrier::after {content:"";position:absolute;background:transparent;pointer-events:none}
#asst-launch .jl-carrier::before {
  inset:calc(var(--unit) * 4);border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -5.1deg) calc(80.6% * var(--jl-sat)) calc(65.7% + (100% - 65.7%) * max(0, var(--jl-lum)) + 65.7% * min(0, var(--jl-lum))) / 0.549);
  border-radius:calc(var(--screen-radius) + var(--unit) * 3);box-shadow:0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 5.0deg) calc(81.1% * var(--jl-sat)) calc(14.5% + (100% - 14.5%) * max(0, var(--jl-lum)) + 14.5% * min(0, var(--jl-lum))) / 0.502);
}
#asst-launch .jl-carrier::after {
  inset:calc(var(--unit) * 7);border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + 0.4deg) calc(95.0% * var(--jl-sat)) calc(23.3% + (100% - 23.3%) * max(0, var(--jl-lum)) + 23.3% * min(0, var(--jl-lum))) / 0.6);
  border-radius:var(--screen-radius);box-shadow:inset 0 0 calc(var(--unit) * 3) hsl(calc(var(--jl-h) + -4.2deg) calc(100.0% * var(--jl-sat)) calc(90.0% + (100% - 90.0%) * max(0, var(--jl-lum)) + 90.0% * min(0, var(--jl-lum))) / 0.157);
}
#asst-launch .jl-internals {
  position:absolute;inset:0;z-index:2;pointer-events:none;
  transform:none;
}
#asst-launch .jl-internals span,
#asst-launch .jl-cradle {position:absolute;display:block;pointer-events:none}
#asst-launch .jl-tab {
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -2.4deg) calc(87.1% * var(--jl-sat)) calc(39.4% + (100% - 39.4%) * max(0, var(--jl-lum)) + 39.4% * min(0, var(--jl-lum))) / 0.502);
  background:linear-gradient(90deg,hsl(calc(var(--jl-h) + -25.4deg) calc(100.0% * var(--jl-sat)) calc(85.5% + (100% - 85.5%) * max(0, var(--jl-lum)) + 85.5% * min(0, var(--jl-lum))) / 0.149),hsl(calc(var(--jl-h) + -1.0deg) calc(100.0% * var(--jl-sat)) calc(31.6% + (100% - 31.6%) * max(0, var(--jl-lum)) + 31.6% * min(0, var(--jl-lum))) / 0.58),hsl(calc(var(--jl-h) + -3.9deg) calc(83.3% * var(--jl-sat)) calc(57.8% + (100% - 57.8%) * max(0, var(--jl-lum)) + 57.8% * min(0, var(--jl-lum))) / 0.239));
  box-shadow:inset calc(var(--unit) * 1) 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -27.1deg) calc(100.0% * var(--jl-sat)) calc(85.5% + (100% - 85.5%) * max(0, var(--jl-lum)) + 85.5% * min(0, var(--jl-lum))) / 0.361),0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 10.5deg) calc(89.7% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))) / 0.361);
  border-radius:calc(var(--unit) * .5);
}
#asst-launch .jl-tab.t-seam {left:49.1%;top:.4%;width:1.9%;height:3.5%}
#asst-launch .jl-tab.t-key {left:65.9%;top:3.1%;width:1.9%;height:8.6%}
#asst-launch .jl-tab.t-left {left:1.4%;top:47.9%;width:12.4%;height:1.8%}
#asst-launch .jl-tab.t-right {right:1.4%;top:47.9%;width:12.4%;height:1.8%}
#asst-launch .jl-tab.t-bottom {left:39.3%;bottom:2.4%;width:21.4%;height:2.8%}
#asst-launch .jl-tab.t-rib {top:91.1%;width:1.8%;height:8%}
#asst-launch .jl-tab.t-rib.l {left:40.3%}
#asst-launch .jl-tab.t-rib.r {right:40.3%}
#asst-launch .jl-brace {
  width:6.1%;height:2.4%;border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -4.9deg) calc(71.9% * var(--jl-sat)) calc(46.1% + (100% - 46.1%) * max(0, var(--jl-lum)) + 46.1% * min(0, var(--jl-lum))) / 0.51);
  background:linear-gradient(0deg,hsl(calc(var(--jl-h) + 3.3deg) calc(95.7% * var(--jl-sat)) calc(18.2% + (100% - 18.2%) * max(0, var(--jl-lum)) + 18.2% * min(0, var(--jl-lum))) / 0.302),hsl(calc(var(--jl-h) + -1.9deg) calc(73.3% * var(--jl-sat)) calc(60.4% + (100% - 60.4%) * max(0, var(--jl-lum)) + 60.4% * min(0, var(--jl-lum))) / 0.412),hsl(var(--jl-h) calc(100.0% * var(--jl-sat)) calc(27.6% + (100% - 27.6%) * max(0, var(--jl-lum)) + 27.6% * min(0, var(--jl-lum))) / 0.349));
  box-shadow:inset 0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -7.8deg) calc(100.0% * var(--jl-sat)) calc(84.5% + (100% - 84.5%) * max(0, var(--jl-lum)) + 84.5% * min(0, var(--jl-lum))) / 0.451);
}
#asst-launch .jl-brace.a {left:12.7%;top:16%;transform:rotate(44deg)}
#asst-launch .jl-brace.b {right:12.7%;top:16%;transform:rotate(-44deg)}
#asst-launch .jl-brace.c {left:12.7%;top:66.9%;transform:rotate(-43deg)}
#asst-launch .jl-brace.d {right:12.7%;top:66.9%;transform:rotate(43deg)}
#asst-launch .jl-hole {
  width:4.5%;aspect-ratio:1;border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -11.9deg) calc(50.7% * var(--jl-sat)) calc(60.2% + (100% - 60.2%) * max(0, var(--jl-lum)) + 60.2% * min(0, var(--jl-lum))) / 0.38);border-radius:50%;
  background:radial-gradient(circle at 60% 50%,hsl(calc(var(--jl-h) + 18.0deg) calc(77.4% * var(--jl-sat)) calc(10.4% + (100% - 10.4%) * max(0, var(--jl-lum)) + 10.4% * min(0, var(--jl-lum))) / 0.4) 40%,hsl(calc(var(--jl-h) + 0.2deg) calc(100.0% * var(--jl-sat)) calc(32.9% + (100% - 32.9%) * max(0, var(--jl-lum)) + 32.9% * min(0, var(--jl-lum))) / 0.239) 60%,transparent 66%);
  box-shadow:inset calc(var(--unit) * 1) calc(var(--unit) * 1) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 21.3deg) calc(87.5% * var(--jl-sat)) calc(9.4% + (100% - 9.4%) * max(0, var(--jl-lum)) + 9.4% * min(0, var(--jl-lum))) / 0.349);
}
#asst-launch .jl-hole.a {left:27%;top:6.4%;opacity:.5}
#asst-launch .jl-hole.b {right:26.6%;top:6.4%;opacity:.5}
#asst-launch .jl-hole.c {right:6.6%;top:64.5%;width:4.7%;opacity:.75}
/* Battery parts transmit the enclosure colour; broad reflections stay tinted. */
#asst-launch .jl-cradle {
  left:22.1%;top:75.6%;width:55.8%;height:18.9%;
  border:calc(var(--unit) * 2) solid hsl(var(--jl-h) calc(66% * var(--jl-sat)) calc(55% + (100% - 55%) * max(0, var(--jl-lum)) + 55% * min(0, var(--jl-lum))) / .22);
  border-radius:calc(var(--unit) * 2.5);
  background:linear-gradient(90deg,hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(38% + (100% - 38%) * max(0, var(--jl-lum)) + 38% * min(0, var(--jl-lum))) / .06),hsl(var(--jl-h) calc(77% * var(--jl-sat)) calc(30% + (100% - 30%) * max(0, var(--jl-lum)) + 30% * min(0, var(--jl-lum))) / .09) 46%,hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(38% + (100% - 38%) * max(0, var(--jl-lum)) + 38% * min(0, var(--jl-lum))) / .06));
  box-shadow:inset calc(var(--unit) * 1) 0 calc(var(--unit) * 1) hsl(var(--jl-h) calc(70% * var(--jl-sat)) calc(66% + (100% - 66%) * max(0, var(--jl-lum)) + 66% * min(0, var(--jl-lum))) / .12),
    0 0 0 calc(var(--unit) * 1) hsl(var(--jl-h) calc(72% * var(--jl-sat)) calc(55% + (100% - 55%) * max(0, var(--jl-lum)) + 55% * min(0, var(--jl-lum))) / .07);
}
#asst-launch .jl-cell {
  inset:4% 5% 6%;border-radius:8%;
  background:linear-gradient(0deg,
    hsl(var(--jl-h) calc(58% * var(--jl-sat)) calc(27% + (100% - 27%) * max(0, var(--jl-lum)) + 27% * min(0, var(--jl-lum))) / .11),
    hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(52% + (100% - 52%) * max(0, var(--jl-lum)) + 52% * min(0, var(--jl-lum))) / .17) 18%,
    hsl(var(--jl-h) calc(61% * var(--jl-sat)) calc(37% + (100% - 37%) * max(0, var(--jl-lum)) + 37% * min(0, var(--jl-lum))) / .08) 40%,transparent 49%,
    hsl(var(--jl-h) calc(63% * var(--jl-sat)) calc(34% + (100% - 34%) * max(0, var(--jl-lum)) + 34% * min(0, var(--jl-lum))) / .08) 53%,
    hsl(var(--jl-h) calc(68% * var(--jl-sat)) calc(55% + (100% - 55%) * max(0, var(--jl-lum)) + 55% * min(0, var(--jl-lum))) / .18) 72%,
    hsl(var(--jl-h) calc(58% * var(--jl-sat)) calc(28% + (100% - 28%) * max(0, var(--jl-lum)) + 28% * min(0, var(--jl-lum))) / .12));
  border-bottom:calc(var(--unit) * 1) solid hsl(var(--jl-h) calc(62% * var(--jl-sat)) calc(52% + (100% - 52%) * max(0, var(--jl-lum)) + 52% * min(0, var(--jl-lum))) / .1);
  box-shadow:none;
}
#asst-launch .jl-contact {
  top:11%;bottom:11%;width:4.7%;
  border:calc(var(--unit) * 1) solid hsl(var(--jl-h) calc(45% * var(--jl-sat)) calc(46% + (100% - 46%) * max(0, var(--jl-lum)) + 46% * min(0, var(--jl-lum))) / .2);
  background:linear-gradient(90deg,hsl(var(--jl-h) calc(40% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .1),hsl(var(--jl-h) calc(50% * var(--jl-sat)) calc(59% + (100% - 59%) * max(0, var(--jl-lum)) + 59% * min(0, var(--jl-lum))) / .27),hsl(var(--jl-h) calc(40% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .1));
  box-shadow:none;
}
#asst-launch .jl-contact.l {left:2%}
#asst-launch .jl-contact.r {right:2%}
#asst-launch .jl-spring {
  right:7.7%;top:23%;width:15.5%;height:57%;
  background:repeating-linear-gradient(77deg,transparent 0 calc(var(--unit) * 3),
    hsl(var(--jl-h) calc(42% * var(--jl-sat)) calc(61% + (100% - 61%) * max(0, var(--jl-lum)) + 61% * min(0, var(--jl-lum))) / .4) calc(var(--unit) * 3.5),
    hsl(var(--jl-h) calc(35% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .13) calc(var(--unit) * 4.5),
    hsl(var(--jl-h) calc(46% * var(--jl-sat)) calc(56% + (100% - 56%) * max(0, var(--jl-lum)) + 56% * min(0, var(--jl-lum))) / .3) calc(var(--unit) * 5.3),transparent calc(var(--unit) * 6.5));
  border-radius:25%;opacity:.75;
}
#asst-launch .jl-latch {
  width:13.5%;height:14%;bottom:-9%;border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -20.0deg) calc(62.5% * var(--jl-sat)) calc(74.9% + (100% - 74.9%) * max(0, var(--jl-lum)) + 74.9% * min(0, var(--jl-lum))) / 0.251);
  background:hsl(calc(var(--jl-h) + -17.0deg) calc(46.7% * var(--jl-sat)) calc(58.0% + (100% - 58.0%) * max(0, var(--jl-lum)) + 58.0% * min(0, var(--jl-lum))) / 0.129);box-shadow:inset calc(var(--unit) * 1) 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -34.0deg) calc(81.6% * var(--jl-sat)) calc(82.9% + (100% - 82.9%) * max(0, var(--jl-lum)) + 82.9% * min(0, var(--jl-lum))) / 0.22);
}
#asst-launch .jl-latch.a {left:6%}
#asst-launch .jl-latch.b {right:6%}
#asst-launch .jl-boss {
  top:81.4%;width:8%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle at 52% 52%,hsl(calc(var(--jl-h) + 2.8deg) calc(85.7% * var(--jl-sat)) calc(16.5% + (100% - 16.5%) * max(0, var(--jl-lum)) + 16.5% * min(0, var(--jl-lum))) / 0.38) 0 30%,hsl(calc(var(--jl-h) + 21.0deg) calc(100.0% * var(--jl-sat)) calc(8.8% + (100% - 8.8%) * max(0, var(--jl-lum)) + 8.8% * min(0, var(--jl-lum))) / 0.612) 33% 45%,hsl(calc(var(--jl-h) + -4.6deg) calc(74.5% * var(--jl-sat)) calc(36.9% + (100% - 36.9%) * max(0, var(--jl-lum)) + 36.9% * min(0, var(--jl-lum))) / 0.478) 48% 58%,hsl(calc(var(--jl-h) + -15.1deg) calc(96.4% * var(--jl-sat)) calc(78.2% + (100% - 78.2%) * max(0, var(--jl-lum)) + 78.2% * min(0, var(--jl-lum))) / 0.38) 62% 69%,hsl(calc(var(--jl-h) + 0.9deg) calc(72.0% * var(--jl-sat)) calc(28.0% + (100% - 28.0%) * max(0, var(--jl-lum)) + 28.0% * min(0, var(--jl-lum))) / 0.278) 72%);
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -6.6deg) calc(56.5% * var(--jl-sat)) calc(46.9% + (100% - 46.9%) * max(0, var(--jl-lum)) + 46.9% * min(0, var(--jl-lum))) / 0.549);
  box-shadow:inset calc(var(--unit) * 1) calc(var(--unit) * 1) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + -36.4deg) calc(100.0% * var(--jl-sat)) calc(81.6% + (100% - 81.6%) * max(0, var(--jl-lum)) + 81.6% * min(0, var(--jl-lum))) / 0.478),
    0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 8.1deg) calc(96.6% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))) / 0.549);
}
#asst-launch .jl-boss.l {left:12.1%}
#asst-launch .jl-boss.r {right:12.1%}
#asst-launch .jl-screen {
  left:var(--screen-left);top:var(--screen-top);width:var(--screen-width);height:var(--screen-height);
  padding:0;display:flex;align-items:center;justify-content:center;
  overflow:hidden;z-index:6;
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + 7.0deg) calc(43.9% * var(--jl-sat)) calc(16.1% + (100% - 16.1%) * max(0, var(--jl-lum)) + 16.1% * min(0, var(--jl-lum))));
  border-radius:var(--screen-radius);
  background:hsl(calc(var(--jl-h) + 7.0deg) calc(41.4% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))));
  box-shadow:inset calc(var(--unit) * 1) calc(var(--unit) * 2) calc(var(--unit) * 3) hsl(0deg 0% 0.0% / 0.867),
    /* THE THIN DARK LINE. Spread with no blur is an OUTLINE, and this one is near-black at 80%: at the
       size the shell was drawn at it is 0.69px and reads as the glass meeting the case, but it scales, so
       by 400px it is a crisp 1.5px ring drawn round the screen. Blurred to about its own width it goes
       back to being the contact shadow it was meant to be, at every size. */
    0 0 calc(var(--unit) * 1.6) calc(var(--unit) * .55) hsl(calc(var(--jl-h) + 16.0deg) calc(83.3% * var(--jl-sat)) calc(4.7% + (100% - 4.7%) * max(0, var(--jl-lum)) + 4.7% * min(0, var(--jl-lum))) / 0.8),
    0 0 0 calc(var(--unit) * 2) hsl(calc(var(--jl-h) + -3.4deg) calc(67.7% * var(--jl-sat)) calc(57.5% + (100% - 57.5%) * max(0, var(--jl-lum)) + 57.5% * min(0, var(--jl-lum))) / 0.42),
    0 0 calc(var(--unit) * 2) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + -3.3deg) calc(100.0% * var(--jl-sat)) calc(85.1% + (100% - 85.1%) * max(0, var(--jl-lum)) + 85.1% * min(0, var(--jl-lum))) / 0.188),
    0 0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 0.2deg) calc(100.0% * var(--jl-sat)) calc(11.2% + (100% - 11.2%) * max(0, var(--jl-lum)) + 11.2% * min(0, var(--jl-lum))) / 0.502);
}
#asst-launch .jl-screen canvas {
  display:block;position:relative;width:100%;height:auto;max-height:100%;
  aspect-ratio:3/2;object-fit:contain;flex:0 1 auto;z-index:1;
  border:0;border-radius:calc(var(--unit) * 17);background:hsl(calc(var(--jl-h) + 7.0deg) calc(41.4% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))));
  image-rendering:pixelated;
}
/* Glass reflections sit above the LCD and apart from the resin cover.
   The reflected area light has a crisp side and a gradual falloff; the thin
   companion reflection and cut edge give it the depth of polished glass. */
#asst-launch .jl-glass {
  position:relative;z-index:1;
  display:block;width:100%;height:100%;border-radius:inherit;
  background:var(--lcd);
}
#asst-launch .jl-screen {
  --glass-edge-x:clamp(8%,var(--glass-pointer-x,50%),92%);
  --glass-edge-y:clamp(8%,var(--glass-pointer-y,40%),92%);
  --edge-light:calc(var(--lit,0) * var(--hn,0));
}
/* An environment reflection changes with the viewing angle only. The pointer
   has a separate, much weaker pool of light in ::after. */
#asst-launch .jl-screen::before {
  content:"";position:absolute;left:6%;top:-24%;width:62%;height:140%;
  z-index:2;pointer-events:none;
  border:0;border-radius:0;
  background:
    linear-gradient(90deg,rgb(255 255 255 / .015),rgb(255 255 255 / .20) 2.5%,rgb(255 255 255 / .15) 11%,rgb(255 255 255 / .075) 67%,rgb(255 255 255 / .035) 98%,transparent) left top / 86% 100% no-repeat,
    linear-gradient(90deg,rgb(255 255 255 / .14),rgb(255 255 255 / .04)) right top / 3% 100% no-repeat;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,#000e 26%,#0005 58%,transparent 88%);
  mask-image:linear-gradient(180deg,#000 0%,#000e 26%,#0005 58%,transparent 88%);
  opacity:.72;
  transform:translate3d(calc((var(--glare-angle-x,0) * 52 + var(--glare-rest-x,0)) * var(--unit)),calc((var(--glare-angle-y,0) * 34 + var(--glare-rest-y,0)) * var(--unit)),0) skewX(-13deg) rotate(-6deg);
  transform-origin:50% 50%;
  box-shadow:none;filter:none;
  transition:none;
}
/* The cut glass edge is sharper and brighter than the reflected panes.
   These highlights respond to the pointer light; the broad reflection above
   responds only to the plane's angle. The large wash is deliberately very faint. */
#asst-launch .jl-screen::after {
  content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  border-radius:inherit;
  background:
    radial-gradient(ellipse 160% 210% at var(--glass-pointer-x,50%) var(--glass-pointer-y,40%),rgb(255 255 255 / calc(var(--lit,0) * .026)),rgb(255 255 255 / calc(var(--lit,0) * .006)) 48%,transparent 100%),
    radial-gradient(ellipse closest-side,rgb(255 255 255 / calc(.18 + var(--edge-light) * .62)),rgb(255 255 255 / calc(.045 + var(--edge-light) * .18)) 48%,transparent 100%) var(--glass-edge-x) 0 / 25% calc(var(--unit) * 2) no-repeat,
    radial-gradient(ellipse closest-side,rgb(255 255 255 / calc(var(--edge-light) * .48)),transparent 100%) 0 var(--glass-edge-y) / calc(var(--unit) * 1.5) 30% no-repeat,
    linear-gradient(90deg,transparent 5%,rgb(255 255 255 / .50) 28%,rgb(255 255 255 / .13) 57%,transparent 85%) left top / 100% calc(var(--unit) * .75) no-repeat,
    linear-gradient(0deg,transparent 8%,rgb(255 255 255 / .15) 43%,rgb(255 255 255 / .37) 80%,transparent 100%) left top / calc(var(--unit) * .75) 100% no-repeat;
  box-shadow:inset 0 calc(var(--unit) * .65) 0 rgb(255 255 255 / .25),
    inset 0 calc(var(--unit) * -.6) 0 rgb(255 255 255 / .14),
    inset calc(var(--hx,0px) * .9) calc(var(--hy,0px) * .9) calc(var(--unit) * .7) rgb(255 255 255 / calc(var(--edge-light) * .48)),
    inset 0 0 0 calc(var(--unit) * .35) rgb(0 0 0 / .30);
  filter:none;
}
:root[data-motion="off"] #asst-launch .jl-screen::before {transition:none}
#asst-launch .jl-front {
  inset:0;border-radius:var(--shell-radius);z-index:5;
  background:radial-gradient(ellipse at var(--lx) var(--ly),hsl(calc(var(--jl-h) + -3.5deg) calc(100.0% * var(--jl-sat)) calc(91.6% + (100% - 91.6%) * max(0, var(--jl-lum)) + 91.6% * min(0, var(--jl-lum))) / 0.11),transparent 63%),linear-gradient(113deg,hsl(calc(var(--jl-h) + -31.6deg) calc(100.0% * var(--jl-sat)) calc(91.8% + (100% - 91.8%) * max(0, var(--jl-lum)) + 91.8% * min(0, var(--jl-lum))) / 0.0784),transparent 20% 81%,hsl(calc(var(--jl-h) + -5.7deg) calc(86.6% * var(--jl-sat)) calc(49.6% + (100% - 49.6%) * max(0, var(--jl-lum)) + 49.6% * min(0, var(--jl-lum))) / 0.0902));
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -4.3deg) calc(100.0% * var(--jl-sat)) calc(91.2% + (100% - 91.2%) * max(0, var(--jl-lum)) + 91.2% * min(0, var(--jl-lum))) / 0.722);
  border-top-color:hsl(calc(var(--jl-h) + -32.4deg) calc(100.0% * var(--jl-sat)) calc(93.7% + (100% - 93.7%) * max(0, var(--jl-lum)) + 93.7% * min(0, var(--jl-lum))) / 0.82);border-right-color:hsl(calc(var(--jl-h) + 3.9deg) calc(100.0% * var(--jl-sat)) calc(79.0% + (100% - 79.0%) * max(0, var(--jl-lum)) + 79.0% * min(0, var(--jl-lum))) / 0.588);border-bottom-color:hsl(calc(var(--jl-h) + -6.2deg) calc(74.5% * var(--jl-sat)) calc(70.8% + (100% - 70.8%) * max(0, var(--jl-lum)) + 70.8% * min(0, var(--jl-lum))) / 0.659);
  box-shadow:inset 0 calc(var(--unit) * 2) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + -47.0deg) calc(100.0% * var(--jl-sat)) calc(93.1% + (100% - 93.1%) * max(0, var(--jl-lum)) + 93.1% * min(0, var(--jl-lum))) / 0.702),
    inset calc(var(--unit) * 3) 0 calc(var(--unit) * 4) hsl(calc(var(--jl-h) + -25.2deg) calc(100.0% * var(--jl-sat)) calc(89.2% + (100% - 89.2%) * max(0, var(--jl-lum)) + 89.2% * min(0, var(--jl-lum))) / 0.471),
    inset calc(var(--unit) * -2) calc(var(--unit) * -3) calc(var(--unit) * 4) hsl(calc(var(--jl-h) + -2.0deg) calc(87.3% * var(--jl-sat)) calc(53.7% + (100% - 53.7%) * max(0, var(--jl-lum)) + 53.7% * min(0, var(--jl-lum))) / 0.549),
    inset calc(var(--hx, 0px) * 1.6) calc(var(--hy, 0px) * 1.6) calc(var(--unit) * 4) hsl(calc(var(--jl-h) + -28.6deg) calc(100.0% * var(--jl-sat)) calc(91.6% + (100% - 91.6%) * max(0, var(--jl-lum)) + 91.6% * min(0, var(--jl-lum))) / 0.341),
    0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -5.3deg) calc(100.0% * var(--jl-sat)) calc(86.1% + (100% - 86.1%) * max(0, var(--jl-lum)) + 86.1% * min(0, var(--jl-lum))) / 0.671);
}
#asst-launch .jl-front::before {
  content:"";position:absolute;inset:calc(var(--unit) * 3);
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -4.5deg) calc(93.2% * var(--jl-sat)) calc(76.9% + (100% - 76.9%) * max(0, var(--jl-lum)) + 76.9% * min(0, var(--jl-lum))) / 0.549);
  border-top-color:hsl(calc(var(--jl-h) + -38.9deg) calc(100.0% * var(--jl-sat)) calc(93.3% + (100% - 93.3%) * max(0, var(--jl-lum)) + 93.3% * min(0, var(--jl-lum))) / 0.741);border-left-color:hsl(calc(var(--jl-h) + -13.2deg) calc(100.0% * var(--jl-sat)) calc(91.6% + (100% - 91.6%) * max(0, var(--jl-lum)) + 91.6% * min(0, var(--jl-lum))) / 0.788);
  border-radius:calc(var(--shell-radius) - var(--unit) * 4);
  background:transparent;box-shadow:0 calc(var(--unit) * 1) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 0.6deg) calc(100.0% * var(--jl-sat)) calc(22.9% + (100% - 22.9%) * max(0, var(--jl-lum)) + 22.9% * min(0, var(--jl-lum))) / 0.329);
}
#asst-launch .jl-front::after {
  content:"";position:absolute;inset:calc(var(--unit) * 6);
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -6.7deg) calc(100.0% * var(--jl-sat)) calc(77.6% + (100% - 77.6%) * max(0, var(--jl-lum)) + 77.6% * min(0, var(--jl-lum))) / 0.412);
  border-radius:calc(var(--shell-radius) - var(--unit) * 7);
  background:transparent;box-shadow:inset calc(var(--unit) * 1) calc(var(--unit) * 1) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + -12.8deg) calc(100.0% * var(--jl-sat)) calc(89.6% + (100% - 89.6%) * max(0, var(--jl-lum)) + 89.6% * min(0, var(--jl-lum))) / 0.361);
}
#asst-launch .jl-glare,
#asst-launch .jl-grain {
  position:absolute;inset:calc(var(--unit) * 1);pointer-events:none;
  border-radius:calc(var(--shell-radius) - var(--unit) * 2);
}
#asst-launch .jl-glare {
  background:radial-gradient(ellipse at 12% 4%,hsl(calc(var(--jl-h) + -60.3deg) calc(100.0% * var(--jl-sat)) calc(92.7% + (100% - 92.7%) * max(0, var(--jl-lum)) + 92.7% * min(0, var(--jl-lum))) / 0.149),transparent 55%);
  box-shadow:inset calc(var(--unit) * 7) calc(var(--unit) * 9) calc(var(--unit) * 9) calc(var(--unit) * -6) hsl(calc(var(--jl-h) + -53.0deg) calc(100.0% * var(--jl-sat)) calc(95.3% + (100% - 95.3%) * max(0, var(--jl-lum)) + 95.3% * min(0, var(--jl-lum))) / 0.49),
    inset calc(var(--unit) * 2) calc(var(--unit) * 2) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -31.2deg) calc(100.0% * var(--jl-sat)) calc(95.7% + (100% - 95.7%) * max(0, var(--jl-lum)) + 95.7% * min(0, var(--jl-lum))) / 0.278);
}
#asst-launch .jl-grain {
  background-image:url('../tex/plastic-grain.png');background-repeat:repeat;background-size:calc(var(--unit) * 128) calc(var(--unit) * 128);
  opacity:.14;mix-blend-mode:soft-light;
  -webkit-mask-image:linear-gradient(var(--la, 135deg),hsl(0deg 0% 0.0%),hsl(0deg 0% 0.0% / 0.4) 38%,hsl(0deg 0% 0.0% / 0.0667) 70%,hsl(0deg 0% 0.0% / 0.333));
  mask-image:linear-gradient(var(--la, 135deg),hsl(0deg 0% 0.0%),hsl(0deg 0% 0.0% / 0.4) 38%,hsl(0deg 0% 0.0% / 0.0667) 70%,hsl(0deg 0% 0.0% / 0.333));
}
#asst-launch .jl-post {
  width:7.9%;height:auto;aspect-ratio:1;border-radius:50%;z-index:6;
  background:radial-gradient(circle,hsl(calc(var(--jl-h) + 15.6deg) calc(100.0% * var(--jl-sat)) calc(11.6% + (100% - 11.6%) * max(0, var(--jl-lum)) + 11.6% * min(0, var(--jl-lum))) / 0.502) 0 46%,hsl(calc(var(--jl-h) + -1.0deg) calc(55.7% * var(--jl-sat)) calc(34.5% + (100% - 34.5%) * max(0, var(--jl-lum)) + 34.5% * min(0, var(--jl-lum))) / 0.502) 51% 59%,hsl(calc(var(--jl-h) + -7.3deg) calc(51.2% * var(--jl-sat)) calc(75.1% + (100% - 75.1%) * max(0, var(--jl-lum)) + 75.1% * min(0, var(--jl-lum))) / 0.439) 64% 69%,hsl(calc(var(--jl-h) + 2.7deg) calc(77.8% * var(--jl-sat)) calc(22.9% + (100% - 22.9%) * max(0, var(--jl-lum)) + 22.9% * min(0, var(--jl-lum))) / 0.42) 74%);
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + -2.6deg) calc(57.7% * var(--jl-sat)) calc(38.0% + (100% - 38.0%) * max(0, var(--jl-lum)) + 38.0% * min(0, var(--jl-lum))) / 0.6);
  box-shadow:inset 0 calc(var(--unit) * .8) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -10.2deg) calc(54.0% * var(--jl-sat)) calc(82.9% + (100% - 82.9%) * max(0, var(--jl-lum)) + 82.9% * min(0, var(--jl-lum))) / 0.549),
    0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 12.8deg) calc(90.5% * var(--jl-sat)) calc(12.4% + (100% - 12.4%) * max(0, var(--jl-lum)) + 12.4% * min(0, var(--jl-lum))) / 0.502);
  transform:none;
}
#asst-launch .jl-post::after {content:none}
#asst-launch .jl-post.a {left:6.45%;top:7.25%;right:auto;bottom:auto;--screw-angle:11deg}
#asst-launch .jl-post.b {right:6.45%;top:7.25%;left:auto;bottom:auto;--screw-angle:-13deg}
#asst-launch .jl-post.c {left:6.45%;top:70.75%;right:auto;bottom:auto;--screw-angle:15deg}
#asst-launch .jl-post.d {right:6.45%;top:70.75%;left:auto;bottom:auto;--screw-angle:-9deg}
#asst-launch .jl-screw {
  position:absolute;inset:auto;left:50%;top:50%;width:70%;height:70%;
  display:block;margin:0;padding:0;border-radius:50%;font-style:normal;
  border:calc(var(--unit) * .55) solid hsl(calc(var(--jl-h) + -7.0deg) calc(12.6% * var(--jl-sat)) calc(23.3% + (100% - 23.3%) * max(0, var(--jl-lum)) + 23.3% * min(0, var(--jl-lum))));
  transform:translate(-50%,-50%);
  background:radial-gradient(ellipse at 25% 20%,hsl(calc(var(--jl-h) + -53.0deg) calc(100.0% * var(--jl-sat)) calc(97.6% + (100% - 97.6%) * max(0, var(--jl-lum)) + 97.6% * min(0, var(--jl-lum)))) 0% 12%,hsl(calc(var(--jl-h) + -58.5deg) calc(37.9% * var(--jl-sat)) calc(88.6% + (100% - 88.6%) * max(0, var(--jl-lum)) + 88.6% * min(0, var(--jl-lum))) / 0.902) 20%,hsl(calc(var(--jl-h) + -51.1deg) calc(23.2% * var(--jl-sat)) calc(72.9% + (100% - 72.9%) * max(0, var(--jl-lum)) + 72.9% * min(0, var(--jl-lum))) / 0.439) 32%,transparent 58%),
    linear-gradient(140deg,hsl(0deg 0% 44.5%),hsl(0deg 0% 23.5%) 38%,hsl(calc(var(--jl-h) + 4.7deg) calc(17.8% * var(--jl-sat)) calc(14.3% + (100% - 14.3%) * max(0, var(--jl-lum)) + 14.3% * min(0, var(--jl-lum)))) 67%,hsl(calc(var(--jl-h) + -33.0deg) calc(12.3% * var(--jl-sat)) calc(28.6% + (100% - 28.6%) * max(0, var(--jl-lum)) + 28.6% * min(0, var(--jl-lum)))));
  box-shadow:inset calc(var(--unit) * .35) calc(var(--unit) * .4) calc(var(--unit) * .6) hsl(calc(var(--jl-h) + -47.4deg) calc(52.9% * var(--jl-sat)) calc(90.0% + (100% - 90.0%) * max(0, var(--jl-lum)) + 90.0% * min(0, var(--jl-lum))) / 0.4),
    inset 0 calc(var(--unit) * -.55) calc(var(--unit) * .65) hsl(calc(var(--jl-h) + 13.0deg) calc(27.3% * var(--jl-sat)) calc(10.8% + (100% - 10.8%) * max(0, var(--jl-lum)) + 10.8% * min(0, var(--jl-lum))) / 0.8),
    0 calc(var(--unit) * .6) calc(var(--unit) * .75) hsl(calc(var(--jl-h) + 16.0deg) calc(76.9% * var(--jl-sat)) calc(10.2% + (100% - 10.2%) * max(0, var(--jl-lum)) + 10.2% * min(0, var(--jl-lum))) / 0.451);
}
#asst-launch .jl-slot {
  /* The SVG recess and silver dome share the same exact centre. The recess
     has four identical arms; its lighting never shifts its painted bounds. */
  position:absolute;inset:auto;left:50%;top:50%;width:70%;height:70%;
  display:block;margin:0;padding:0;border:0;border-radius:0;
  background:none;fill:none;stroke:none;overflow:visible;
  transform:translate(-50%,-50%) rotate(var(--screw-angle));
  transform-origin:50% 50%;filter:none;
}
#asst-launch .jl-recess {
  fill:hsl(calc(var(--jl-h) + 12.0deg) calc(20.7% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))));stroke:hsl(0deg 0% 58.6% / 0.459);stroke-width:.55;stroke-linejoin:round;
}
#asst-launch .jl-edge {
  fill:none;stroke:hsl(calc(var(--jl-h) + -53.0deg) calc(24.2% * var(--jl-sat)) calc(75.7% + (100% - 75.7%) * max(0, var(--jl-lum)) + 75.7% * min(0, var(--jl-lum))) / 0.588);stroke-width:.75;stroke-linecap:butt;stroke-linejoin:round;
}
#asst-launch .jl-key {
  width:26%;height:21%;left:37%;top:74.4%;bottom:auto;
  padding:0;border:0;border-radius:50%;background:transparent;box-shadow:none;
  color:hsl(calc(var(--jl-h) + 74.1deg) calc(73.3% * var(--jl-sat)) calc(29.4% + (100% - 29.4%) * max(0, var(--jl-lum)) + 29.4% * min(0, var(--jl-lum))));z-index:8;filter:none;transform:none;transition:none;
}
#asst-launch .jl-key:hover,
#asst-launch .jl-key:active {filter:none;transform:none}
#asst-launch .jl-key:focus-visible {outline:calc(var(--unit) * 2) solid hsl(calc(var(--jl-h) + 64.1deg) calc(100.0% * var(--jl-sat)) calc(87.8% + (100% - 87.8%) * max(0, var(--jl-lum)) + 87.8% * min(0, var(--jl-lum))));outline-offset:calc(var(--unit) * 3)}
#asst-launch .jl-socket {
  inset:2% 0 0;border-radius:50%;
  /* The centre is open to the actual hardware below the button. Only the
     rounded green collar and its small contact shadow are painted here. */
  background:radial-gradient(ellipse closest-side,transparent 0 66%,hsl(calc(var(--jl-h) + 3.5deg) calc(55.3% * var(--jl-sat)) calc(51.8% + (100% - 51.8%) * max(0, var(--jl-lum)) + 51.8% * min(0, var(--jl-lum))) / 0.0706) 72%,hsl(calc(var(--jl-h) + 9.1deg) calc(83.9% * var(--jl-sat)) calc(17.1% + (100% - 17.1%) * max(0, var(--jl-lum)) + 17.1% * min(0, var(--jl-lum))) / 0.471) 81%,hsl(calc(var(--jl-h) + 3.2deg) calc(73.2% * var(--jl-sat)) calc(81.0% + (100% - 81.0%) * max(0, var(--jl-lum)) + 81.0% * min(0, var(--jl-lum))) / 0.341) 88%,hsl(calc(var(--jl-h) + 7.0deg) calc(79.4% * var(--jl-sat)) calc(24.7% + (100% - 24.7%) * max(0, var(--jl-lum)) + 24.7% * min(0, var(--jl-lum))) / 0.329) 95%,transparent 100%);
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + 3.2deg) calc(50.0% * var(--jl-sat)) calc(56.9% + (100% - 56.9%) * max(0, var(--jl-lum)) + 56.9% * min(0, var(--jl-lum))) / 0.502);
  box-shadow:inset 0 calc(var(--unit) * 1.5) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 15.6deg) calc(80.0% * var(--jl-sat)) calc(13.7% + (100% - 13.7%) * max(0, var(--jl-lum)) + 13.7% * min(0, var(--jl-lum))) / 0.38),
    inset calc(var(--unit) * 1) calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 7.5deg) calc(100.0% * var(--jl-sat)) calc(88.0% + (100% - 88.0%) * max(0, var(--jl-lum)) + 88.0% * min(0, var(--jl-lum))) / 0.612),
    0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -5.3deg) calc(65.6% * var(--jl-sat)) calc(81.8% + (100% - 81.8%) * max(0, var(--jl-lum)) + 81.8% * min(0, var(--jl-lum))) / 0.471),
    0 0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 6.6deg) calc(60.7% * var(--jl-sat)) calc(22.9% + (100% - 22.9%) * max(0, var(--jl-lum)) + 22.9% * min(0, var(--jl-lum))) / 0.4);
}
#asst-launch .jl-face {
  left:8.5%;right:8.5%;top:8%;height:77%;border-radius:50%;
  border:calc(var(--unit) * 1) solid hsl(calc(var(--jl-h) + 69.7deg) calc(64.4% * var(--jl-sat)) calc(62.5% + (100% - 62.5%) * max(0, var(--jl-lum)) + 62.5% * min(0, var(--jl-lum))) / 0.702);
  background:radial-gradient(ellipse closest-side,hsl(calc(var(--jl-h) + 81.1deg) calc(83.5% * var(--jl-sat)) calc(45.3% + (100% - 45.3%) * max(0, var(--jl-lum)) + 45.3% * min(0, var(--jl-lum))) / 0.18) 0 49%,hsl(calc(var(--jl-h) + 77.0deg) calc(94.2% * var(--jl-sat)) calc(47.5% + (100% - 47.5%) * max(0, var(--jl-lum)) + 47.5% * min(0, var(--jl-lum))) / 0.31) 70%,hsl(calc(var(--jl-h) + 67.9deg) calc(100.0% * var(--jl-sat)) calc(65.7% + (100% - 65.7%) * max(0, var(--jl-lum)) + 65.7% * min(0, var(--jl-lum))) / 0.612) 87%,hsl(calc(var(--jl-h) + 82.8deg) calc(79.3% * var(--jl-sat)) calc(39.8% + (100% - 39.8%) * max(0, var(--jl-lum)) + 39.8% * min(0, var(--jl-lum))) / 0.678) 100%),
    linear-gradient(155deg,hsl(calc(var(--jl-h) + 67.9deg) calc(100.0% * var(--jl-sat)) calc(79.8% + (100% - 79.8%) * max(0, var(--jl-lum)) + 79.8% * min(0, var(--jl-lum))) / 0.169),hsl(calc(var(--jl-h) + 75.7deg) calc(82.9% * var(--jl-sat)) calc(45.9% + (100% - 45.9%) * max(0, var(--jl-lum)) + 45.9% * min(0, var(--jl-lum))) / 0.506) 48%,hsl(calc(var(--jl-h) + 77.4deg) calc(84.0% * var(--jl-sat)) calc(39.2% + (100% - 39.2%) * max(0, var(--jl-lum)) + 39.2% * min(0, var(--jl-lum))) / 0.4));
  box-shadow:inset 0 calc(var(--unit) * 1) calc(var(--unit) * 1.4) hsl(calc(var(--jl-h) + 59.8deg) calc(100.0% * var(--jl-sat)) calc(94.3% + (100% - 94.3%) * max(0, var(--jl-lum)) + 94.3% * min(0, var(--jl-lum))) / 0.839),
    inset calc(var(--unit) * 1.1) 0 calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 66.5deg) calc(100.0% * var(--jl-sat)) calc(74.9% + (100% - 74.9%) * max(0, var(--jl-lum)) + 74.9% * min(0, var(--jl-lum))) / 0.549),
    inset 0 calc(var(--unit) * -1.6) calc(var(--unit) * 2.5) hsl(calc(var(--jl-h) + 80.0deg) calc(75.0% * var(--jl-sat)) calc(31.4% + (100% - 31.4%) * max(0, var(--jl-lum)) + 31.4% * min(0, var(--jl-lum))) / 0.478),
    inset 0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 71.9deg) calc(93.8% * var(--jl-sat)) calc(38.0% + (100% - 38.0%) * max(0, var(--jl-lum)) + 38.0% * min(0, var(--jl-lum))) / 0.612),
    0 calc(var(--unit) * 2) 0 var(--key-side),
    0 calc(var(--unit) * 3) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 24.5deg) calc(82.8% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))) / 0.4),
    0 calc(var(--unit) * 4) calc(var(--unit) * 3) hsl(calc(var(--jl-h) + 28.4deg) calc(75.0% * var(--jl-sat)) calc(11.0% + (100% - 11.0%) * max(0, var(--jl-lum)) + 11.0% * min(0, var(--jl-lum))) / 0.302);
  transform:translateY(0);transition:transform 75ms ease-out,box-shadow 75ms ease-out;
}
#asst-launch .jl-face::before {
  content:"";position:absolute;inset:calc(var(--unit) * 1);border-radius:50%;pointer-events:none;
  border:calc(var(--unit) * .8) solid hsl(calc(var(--jl-h) + 67.9deg) calc(100.0% * var(--jl-sat)) calc(68.8% + (100% - 68.8%) * max(0, var(--jl-lum)) + 68.8% * min(0, var(--jl-lum))) / 0.361);
  border-top-color:hsl(calc(var(--jl-h) + 60.1deg) calc(100.0% * var(--jl-sat)) calc(94.9% + (100% - 94.9%) * max(0, var(--jl-lum)) + 94.9% * min(0, var(--jl-lum))) / 0.722);border-left-color:hsl(calc(var(--jl-h) + 65.3deg) calc(100.0% * var(--jl-sat)) calc(85.9% + (100% - 85.9%) * max(0, var(--jl-lum)) + 85.9% * min(0, var(--jl-lum))) / 0.612);
  box-shadow:inset 0 calc(var(--unit) * .7) calc(var(--unit) * 1.7) hsl(calc(var(--jl-h) + 61.8deg) calc(100.0% * var(--jl-sat)) calc(87.6% + (100% - 87.6%) * max(0, var(--jl-lum)) + 87.6% * min(0, var(--jl-lum))) / 0.251);
}
#asst-launch .jl-face::after {
  content:"";position:absolute;inset:3%;border-radius:50%;pointer-events:none;
  background:radial-gradient(ellipse at 29% 3%,hsl(calc(var(--jl-h) + 61.4deg) calc(100.0% * var(--jl-sat)) calc(94.7% + (100% - 94.7%) * max(0, var(--jl-lum)) + 94.7% * min(0, var(--jl-lum))) / 0.522),transparent 49%),
    radial-gradient(ellipse at 73% 98%,hsl(calc(var(--jl-h) + 61.5deg) calc(100.0% * var(--jl-sat)) calc(54.3% + (100% - 54.3%) * max(0, var(--jl-lum)) + 54.3% * min(0, var(--jl-lum))) / 0.439),transparent 47%);
  box-shadow:inset calc(var(--unit) * .8) calc(var(--unit) * 1.1) calc(var(--unit) * 2.3) hsl(calc(var(--jl-h) + 63.8deg) calc(100.0% * var(--jl-sat)) calc(85.1% + (100% - 85.1%) * max(0, var(--jl-lum)) + 85.1% * min(0, var(--jl-lum))) / 0.239);
}
#asst-launch .jl-key .jl-mark {
  position:relative;z-index:2;width:54%;height:86%;overflow:visible;stroke-width:1.9;fill:none;
  stroke-linecap:round;stroke-linejoin:round;
}
#asst-launch .jl-mark .hi {stroke:hsl(calc(var(--jl-h) + 71.2deg) calc(100.0% * var(--jl-sat)) calc(93.0% + (100% - 93.0%) * max(0, var(--jl-lum)) + 93.0% * min(0, var(--jl-lum))) / 0.88)}
#asst-launch .jl-mark .lo {stroke:hsl(calc(var(--jl-h) + 75.2deg) calc(80.0% * var(--jl-sat)) calc(19.0% + (100% - 19.0%) * max(0, -1 * var(--jl-lum))) / 0.78)}
#asst-launch .jl-key:hover .jl-face {
  border-color:hsl(calc(var(--jl-h) + 68.2deg) calc(100.0% * var(--jl-sat)) calc(88.0% + (100% - 88.0%) * max(0, var(--jl-lum)) + 88.0% * min(0, var(--jl-lum))) / 0.92);
  transform:translateY(calc(var(--key-travel) * -.35));
  box-shadow:inset 0 calc(var(--unit) * 1) calc(var(--unit) * 1.4) hsl(calc(var(--jl-h) + 59.8deg) calc(100.0% * var(--jl-sat)) calc(96.0% + (100% - 96.0%) * max(0, var(--jl-lum)) + 96.0% * min(0, var(--jl-lum))) / 0.92),
    inset calc(var(--unit) * 1.1) 0 calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 66.5deg) calc(100.0% * var(--jl-sat)) calc(79.0% + (100% - 79.0%) * max(0, var(--jl-lum)) + 79.0% * min(0, var(--jl-lum))) / 0.62),
    inset 0 calc(var(--unit) * -1.6) calc(var(--unit) * 2.5) hsl(calc(var(--jl-h) + 80.0deg) calc(75.0% * var(--jl-sat)) calc(31.4% + (100% - 31.4%) * max(0, var(--jl-lum)) + 31.4% * min(0, var(--jl-lum))) / 0.478),
    inset 0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 71.9deg) calc(93.8% * var(--jl-sat)) calc(38.0% + (100% - 38.0%) * max(0, var(--jl-lum)) + 38.0% * min(0, var(--jl-lum))) / 0.612),
    0 calc(var(--unit) * 2.7) 0 var(--key-side),
    0 calc(var(--unit) * 4) calc(var(--unit) * 3) hsl(calc(var(--jl-h) + 24.5deg) calc(82.8% * var(--jl-sat)) calc(11.4% + (100% - 11.4%) * max(0, var(--jl-lum)) + 11.4% * min(0, var(--jl-lum))) / 0.44),
    0 calc(var(--unit) * 6) calc(var(--unit) * 5) hsl(calc(var(--jl-h) + 28.4deg) calc(75.0% * var(--jl-sat)) calc(11.0% + (100% - 11.0%) * max(0, var(--jl-lum)) + 11.0% * min(0, var(--jl-lum))) / 0.34);
}
#asst-launch .jl-key:hover .jl-face::after {
  background:radial-gradient(ellipse at 29% 3%,hsl(calc(var(--jl-h) + 61.4deg) calc(100.0% * var(--jl-sat)) calc(97.0% + (100% - 97.0%) * max(0, var(--jl-lum)) + 97.0% * min(0, var(--jl-lum))) / 0.72),transparent 52%),
    radial-gradient(ellipse at 73% 98%,hsl(calc(var(--jl-h) + 61.5deg) calc(100.0% * var(--jl-sat)) calc(58.0% + (100% - 58.0%) * max(0, var(--jl-lum)) + 58.0% * min(0, var(--jl-lum))) / 0.5),transparent 47%);
}
#asst-launch .jl-key:hover .jl-socket {
  box-shadow:inset 0 calc(var(--unit) * 2) calc(var(--unit) * 2.6) hsl(calc(var(--jl-h) + 15.6deg) calc(80.0% * var(--jl-sat)) calc(13.7% + (100% - 13.7%) * max(0, var(--jl-lum)) + 13.7% * min(0, var(--jl-lum))) / 0.5),
    inset calc(var(--unit) * 1) calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 7.5deg) calc(100.0% * var(--jl-sat)) calc(88.0% + (100% - 88.0%) * max(0, var(--jl-lum)) + 88.0% * min(0, var(--jl-lum))) / 0.612),
    0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -5.3deg) calc(65.6% * var(--jl-sat)) calc(81.8% + (100% - 81.8%) * max(0, var(--jl-lum)) + 81.8% * min(0, var(--jl-lum))) / 0.471),
    0 0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 6.6deg) calc(60.7% * var(--jl-sat)) calc(22.9% + (100% - 22.9%) * max(0, var(--jl-lum)) + 22.9% * min(0, var(--jl-lum))) / 0.4);
}
#asst-launch .jl-socket {transition:box-shadow 110ms ease-out}
#asst-launch .jl-key:hover .jl-mark .hi {stroke:hsl(calc(var(--jl-h) + 71.2deg) calc(100.0% * var(--jl-sat)) calc(97.0% + (100% - 97.0%) * max(0, var(--jl-lum)) + 97.0% * min(0, var(--jl-lum))) / 1)}
#asst-launch .jl-key:active .jl-face {
  transform:translateY(var(--key-travel));
  box-shadow:inset 0 calc(var(--unit) * 1) calc(var(--unit) * 1.4) hsl(calc(var(--jl-h) + 62.7deg) calc(100.0% * var(--jl-sat)) calc(91.8% + (100% - 91.8%) * max(0, var(--jl-lum)) + 91.8% * min(0, var(--jl-lum))) / 0.678),
    inset 0 calc(var(--unit) * -1) calc(var(--unit) * 2) hsl(calc(var(--jl-h) + 78.6deg) calc(70.7% * var(--jl-sat)) calc(30.8% + (100% - 30.8%) * max(0, var(--jl-lum)) + 30.8% * min(0, var(--jl-lum))) / 0.522),
    inset 0 0 calc(var(--unit) * 1) hsl(calc(var(--jl-h) + 73.1deg) calc(80.3% * var(--jl-sat)) calc(39.8% + (100% - 39.8%) * max(0, var(--jl-lum)) + 39.8% * min(0, var(--jl-lum))) / 0.612),
    0 calc(var(--unit) * .5) 0 var(--key-side),0 calc(var(--unit) * 1) calc(var(--unit) * 1.5) hsl(calc(var(--jl-h) + 18.5deg) calc(81.2% * var(--jl-sat)) calc(12.5% + (100% - 12.5%) * max(0, var(--jl-lum)) + 12.5% * min(0, var(--jl-lum))) / 0.471);
}
#asst-launch .jl-key:active .jl-socket {
  box-shadow:inset 0 calc(var(--unit) * 2.2) calc(var(--unit) * 2.3) hsl(calc(var(--jl-h) + 19.7deg) calc(90.8% * var(--jl-sat)) calc(12.7% + (100% - 12.7%) * max(0, var(--jl-lum)) + 12.7% * min(0, var(--jl-lum))) / 0.549),
    0 calc(var(--unit) * 1) calc(var(--unit) * 1) hsl(calc(var(--jl-h) + -5.7deg) calc(62.1% * var(--jl-sat)) calc(81.4% + (100% - 81.4%) * max(0, var(--jl-lum)) + 81.4% * min(0, var(--jl-lum))) / 0.522);
}
@media(prefers-reduced-transparency:reduce) {
  #asst-launch {background:hsl(calc(var(--jl-h) + 2.6deg) calc(76.4% * var(--jl-sat)) calc(28.2% + (100% - 28.2%) * max(0, var(--jl-lum)) + 28.2% * min(0, var(--jl-lum))));-webkit-backdrop-filter:none;backdrop-filter:none}
  #asst-launch .jl-front {-webkit-backdrop-filter:none;backdrop-filter:none}
  #asst-launch .jl-internals,
  #asst-launch .jl-carrier {filter:blur(var(--blur))}
}
/* Motion is the app's switch, never the OS preference — `data-motion` is the authority and
   prefers-reduced-motion is only its default when nothing is stored (round 21: a Windows machine
   with animation effects off got a dead desk and no way to explain it). His study gated this on
   the media query, which here would have been a veto.

   Only the SWAY is motion. The resting offset is not — it is which way the case is being looked
   at, and it stays whatever the switch says; ui-assistant.js drops the sway term on its own. */
:root[data-motion="off"] #asst-launch .jl-face {transition:none}
/* small screens: the desk is the app, so the companion shrinks — one number */
/* Two separate optical distances. This pane samples only the desk; the
   mouldings are painted above it and softened by the front cover separately. */
#asst-launch {
  --inside-blur:calc(var(--unit) * .95);
  --desk-blur:calc(var(--unit) * 4.8);
}
#asst-launch .jl-transmission {
  position:absolute;inset:0;z-index:0;border-radius:inherit;
  pointer-events:none;
}

/* The half of the shadow `box-shadow` cannot draw.
   ------------------------------------------------
   An outer box-shadow is clipped to the OUTSIDE of the border box — it is never painted beneath
   the element casting it. On an opaque object nobody notices. This one is plastic you can see the
   desk through, so the shadow visibly stopped dead at the outline and the desk ran on underneath
   it unshaded.

   It is a pseudo-element, not a background, and that is not a style preference: `.jl-transmission`
   already has a `background` AND a `backdrop-filter` set on it inside an `@supports` block further
   down the file. Putting this on the background of the same element loses to that rule in every
   browser that supports backdrop-filter, which is all of them. `::before` is a surface nothing
   else claims.

   It sits above the desk's own blur and below every piece of hardware, so `.jl-front`'s backdrop
   blur still softens it on the way out — which is what stops it reading as a dark sticker stuck to
   the back of the case. Offset along the same `--cast-x/--cast-y` as the outer shadow, so the two
   halves agree about where the light is, and faded out well before the edge so they never stack. */
#asst-launch .jl-transmission::before {
  content:"";
  position:absolute;inset:0;
  border-radius:inherit;
  pointer-events:none;
  /* …and it stays on the desk when the device hops or is poked, like the cast layers outside it. */
  transform:translate(calc(-1 * var(--jl-hop-x)), calc(-1 * var(--jl-hop-y)));
  background:
    radial-gradient(118% 112% at calc(50% + var(--cast-x, 0px) * 1.1) calc(50% + var(--cast-y, 0px) * 1.1),
      rgb(0 0 0 / .26) 0 34%,
      rgb(0 0 0 / .14) 62%,
      rgb(0 0 0 / .04) 84%,
      transparent 100%);
}
:root[data-scheme="dark"] #asst-launch .jl-transmission::before {
  background:
    radial-gradient(118% 112% at calc(50% + var(--cast-x, 0px) * 1.1) calc(50% + var(--cast-y, 0px) * 1.1),
      rgb(0 0 0 / .38) 0 34%,
      rgb(0 0 0 / .22) 62%,
      rgb(0 0 0 / .07) 84%,
      transparent 100%);
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) {
  #asst-launch {background:transparent}
  #asst-launch .jl-transmission {
    background:hsl(var(--jl-h) calc(92% * var(--jl-sat)) calc(27% + (100% - 27%) * max(0, var(--jl-lum)) + 27% * min(0, var(--jl-lum))) / .31);
    -webkit-backdrop-filter:blur(var(--desk-blur)) saturate(1.16);
    backdrop-filter:blur(var(--desk-blur)) saturate(1.16);
  }
  /* ONE MORE LAYER OF BODY, and it is a new surface rather than a bigger number on an existing one.
     The tint above cannot simply be raised: it is the layer that carries `backdrop-filter`, so its
     alpha is also how much desk comes through the resin, and taking it up mutes the blur it exists
     to show. The parts below it are worse — every piece of hardware has its own alpha on purpose
     ("Distance changes softness, not a shared opacity for the whole assembly"), and raising any one
     of them would move that piece forward relative to the others instead of thickening the shell.

     So: a flat fill of the case's own colour, on the one free surface in the stack. It sits above
     the transmission's tint and above the shadow the body casts on the desk beneath it — both of
     those belong to what you see THROUGH the resin — and below `.jl-rear` (z-index 1), so every
     tab, brace, hole and cradle still reads in front of it. Nothing above the internals is touched,
     which is why the screen, the foil and the key look exactly as they did.

     Inside the @supports block on purpose: without backdrop-filter `#asst-launch` keeps its own
     opaque background, and a second fill over that would only darken an already solid case. */
  #asst-launch .jl-transmission::after {
    content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
    /* THICKEST AT THE WALLS, THINNEST THROUGH THE MIDDLE — a flat fill was the wrong shape for what
       this layer is. A moulded shell is a shallow dish: the light takes a short path through the
       middle of the face and a long one out through the sides and corners, so a solid object made
       of this stuff is always most transmissive in the centre. Running the alpha the other way up
       (.15 in the middle, .50 at the edge) is what makes it read as a piece with walls rather than
       as a sheet of tinted film laid behind the hardware.
       The ellipse is 78% x 74% so the dense ring reaches the corners before it reaches the sides,
       which is where the walls actually meet. */
    background:radial-gradient(ellipse 78% 74% at 50% 48%,
      hsl(calc(var(--jl-h) + 1.4deg) calc(75.6% * var(--jl-sat)) calc(30.6% + (100% - 30.6%) * max(0, var(--jl-lum)) + 30.6% * min(0, var(--jl-lum))) / var(--jl-body-mid, .15)) 0 18%,
      hsl(calc(var(--jl-h) + 1.4deg) calc(75.6% * var(--jl-sat)) calc(30.6% + (100% - 30.6%) * max(0, var(--jl-lum)) + 30.6% * min(0, var(--jl-lum))) / calc(var(--jl-body-mid, .15) + (var(--jl-body-edge, .5) - var(--jl-body-mid, .15)) * .42)) 62%,
      hsl(calc(var(--jl-h) + 1.4deg) calc(75.6% * var(--jl-sat)) calc(30.6% + (100% - 30.6%) * max(0, var(--jl-lum)) + 30.6% * min(0, var(--jl-lum))) / var(--jl-body-edge, .5)) 100%);
    /* it stays with the desk when the device hops or is poked, like the shadow it sits on */
    transform:translate(calc(-1 * var(--jl-hop-x)), calc(-1 * var(--jl-hop-y)));
  }
  #asst-launch .jl-front {
    -webkit-backdrop-filter:blur(var(--inside-blur)) saturate(1.12);
    backdrop-filter:blur(var(--inside-blur)) saturate(1.12);
  }
}
/* Distance changes softness, not a shared opacity for the whole assembly.
   Every material below has its own alpha, so intersecting walls accumulate
   colour while the openings between them remain transmissive. */
#asst-launch .jl-rear > *,
#asst-launch .jl-internals > * {--part-blur:.65;filter:blur(calc(var(--unit) * var(--part-blur) + var(--fb,0px)))}
#asst-launch .jl-internals {filter:none}
#asst-launch .jl-back {--part-blur:1.2}
#asst-launch .jl-carrier {--part-blur:.8}
#asst-launch .jl-hole {--part-blur:1.1}
#asst-launch .jl-boss {--part-blur:.45}
#asst-launch .jl-tab.t-rib,
#asst-launch .jl-tab.t-bottom {--part-blur:.35}
#asst-launch .jl-cradle {--part-blur:.6}
#asst-launch .jl-back {
  border-color:hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(32% + (100% - 32%) * max(0, var(--jl-lum)) + 32% * min(0, var(--jl-lum))) / .59);
  background:linear-gradient(140deg,hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(48% + (100% - 48%) * max(0, var(--jl-lum)) + 48% * min(0, var(--jl-lum))) / .09),hsl(var(--jl-h) calc(94% * var(--jl-sat)) calc(27% + (100% - 27%) * max(0, var(--jl-lum)) + 27% * min(0, var(--jl-lum))) / .24) 67%,hsl(var(--jl-h) calc(92% * var(--jl-sat)) calc(33% + (100% - 33%) * max(0, var(--jl-lum)) + 33% * min(0, var(--jl-lum))) / .15));
}
#asst-launch .jl-carrier {
  border-color:hsl(var(--jl-h) calc(92% * var(--jl-sat)) calc(30% + (100% - 30%) * max(0, var(--jl-lum)) + 30% * min(0, var(--jl-lum))) / .68);
  background:linear-gradient(135deg,hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(25% + (100% - 25%) * max(0, var(--jl-lum)) + 25% * min(0, var(--jl-lum))) / .20),hsl(var(--jl-h) calc(84% * var(--jl-sat)) calc(20% + (100% - 20%) * max(0, var(--jl-lum)) + 20% * min(0, var(--jl-lum))) / .36) 58%,hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(54% + (100% - 54%) * max(0, var(--jl-lum)) + 54% * min(0, var(--jl-lum))) / .16));
}
#asst-launch .jl-tab {
  border-color:hsl(var(--jl-h) calc(83% * var(--jl-sat)) calc(43% + (100% - 43%) * max(0, var(--jl-lum)) + 43% * min(0, var(--jl-lum))) / .38);
  background:linear-gradient(90deg,hsl(var(--jl-h) calc(75% * var(--jl-sat)) calc(68% + (100% - 68%) * max(0, var(--jl-lum)) + 68% * min(0, var(--jl-lum))) / .12),hsl(var(--jl-h) calc(95% * var(--jl-sat)) calc(30% + (100% - 30%) * max(0, var(--jl-lum)) + 30% * min(0, var(--jl-lum))) / .31) 50%,hsl(var(--jl-h) calc(85% * var(--jl-sat)) calc(53% + (100% - 53%) * max(0, var(--jl-lum)) + 53% * min(0, var(--jl-lum))) / .19));
  box-shadow:inset calc(var(--unit) * .8) 0 calc(var(--unit) * .8) hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(72% + (100% - 72%) * max(0, var(--jl-lum)) + 72% * min(0, var(--jl-lum))) / .28);
}
#asst-launch .jl-tab.t-key,
#asst-launch .jl-tab.t-rib,
#asst-launch .jl-tab.t-bottom {
  border-color:hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(38% + (100% - 38%) * max(0, var(--jl-lum)) + 38% * min(0, var(--jl-lum))) / .64);
  background:linear-gradient(90deg,hsl(var(--jl-h) calc(82% * var(--jl-sat)) calc(58% + (100% - 58%) * max(0, var(--jl-lum)) + 58% * min(0, var(--jl-lum))) / .31),hsl(var(--jl-h) calc(98% * var(--jl-sat)) calc(28% + (100% - 28%) * max(0, var(--jl-lum)) + 28% * min(0, var(--jl-lum))) / .62) 45%,hsl(var(--jl-h) calc(90% * var(--jl-sat)) calc(46% + (100% - 46%) * max(0, var(--jl-lum)) + 46% * min(0, var(--jl-lum))) / .4));
}
#asst-launch .jl-brace {
  border-color:hsl(var(--jl-h) calc(76% * var(--jl-sat)) calc(43% + (100% - 43%) * max(0, var(--jl-lum)) + 43% * min(0, var(--jl-lum))) / .55);
  background:linear-gradient(0deg,hsl(var(--jl-h) calc(94% * var(--jl-sat)) calc(24% + (100% - 24%) * max(0, var(--jl-lum)) + 24% * min(0, var(--jl-lum))) / .5),hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(58% + (100% - 58%) * max(0, var(--jl-lum)) + 58% * min(0, var(--jl-lum))) / .34),hsl(var(--jl-h) calc(97% * var(--jl-sat)) calc(30% + (100% - 30%) * max(0, var(--jl-lum)) + 30% * min(0, var(--jl-lum))) / .58));
}
#asst-launch .jl-boss {
  background:radial-gradient(circle at 52% 52%,transparent 0 27%,hsl(var(--jl-h) calc(94% * var(--jl-sat)) calc(21% + (100% - 21%) * max(0, var(--jl-lum)) + 21% * min(0, var(--jl-lum))) / .64) 33% 45%,hsl(var(--jl-h) calc(82% * var(--jl-sat)) calc(36% + (100% - 36%) * max(0, var(--jl-lum)) + 36% * min(0, var(--jl-lum))) / .76) 48% 58%,hsl(var(--jl-h) calc(85% * var(--jl-sat)) calc(68% + (100% - 68%) * max(0, var(--jl-lum)) + 68% * min(0, var(--jl-lum))) / .44) 62% 69%,hsl(var(--jl-h) calc(83% * var(--jl-sat)) calc(30% + (100% - 30%) * max(0, var(--jl-lum)) + 30% * min(0, var(--jl-lum))) / .4) 72%);
  border-color:hsl(var(--jl-h) calc(66% * var(--jl-sat)) calc(44% + (100% - 44%) * max(0, var(--jl-lum)) + 44% * min(0, var(--jl-lum))) / .63);
  box-shadow:inset calc(var(--unit) * 1) calc(var(--unit) * 1) calc(var(--unit) * 2) hsl(var(--jl-h) calc(90% * var(--jl-sat)) calc(74% + (100% - 74%) * max(0, var(--jl-lum)) + 74% * min(0, var(--jl-lum))) / .34);
}
/* A clear tray with thicker end walls. These paint over the cell beneath,
   instead of using a dark drop shadow to suggest a cavity. */
#asst-launch .jl-cradle {
  border-color:hsl(var(--jl-h) calc(80% * var(--jl-sat)) calc(43% + (100% - 43%) * max(0, var(--jl-lum)) + 43% * min(0, var(--jl-lum))) / .48);
  border-left-width:calc(var(--unit) * 2.6);
  border-right-width:calc(var(--unit) * 2.6);
  background:linear-gradient(90deg,hsl(var(--jl-h) calc(88% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .29),hsl(var(--jl-h) calc(84% * var(--jl-sat)) calc(38% + (100% - 38%) * max(0, var(--jl-lum)) + 38% * min(0, var(--jl-lum))) / .06) 15% 85%,hsl(var(--jl-h) calc(90% * var(--jl-sat)) calc(32% + (100% - 32%) * max(0, var(--jl-lum)) + 32% * min(0, var(--jl-lum))) / .34));
  box-shadow:inset calc(var(--unit) * .7) 0 calc(var(--unit) * 1) hsl(var(--jl-h) calc(80% * var(--jl-sat)) calc(65% + (100% - 65%) * max(0, var(--jl-lum)) + 65% * min(0, var(--jl-lum))) / .25);
}
#asst-launch .jl-cradle::before,
#asst-launch .jl-cradle::after {
  content:"";position:absolute;top:-4%;bottom:-3%;width:4%;z-index:2;
  pointer-events:none;border-radius:calc(var(--unit) * .8);
  border:calc(var(--unit) * .7) solid hsl(var(--jl-h) calc(81% * var(--jl-sat)) calc(53% + (100% - 53%) * max(0, var(--jl-lum)) + 53% * min(0, var(--jl-lum))) / .4);
  background:linear-gradient(90deg,hsl(var(--jl-h) calc(90% * var(--jl-sat)) calc(27% + (100% - 27%) * max(0, var(--jl-lum)) + 27% * min(0, var(--jl-lum))) / .46),hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(48% + (100% - 48%) * max(0, var(--jl-lum)) + 48% * min(0, var(--jl-lum))) / .24));
}
#asst-launch .jl-cradle::before {left:0}
#asst-launch .jl-cradle::after {right:0}
#asst-launch .jl-cell {
  background:linear-gradient(0deg,
    hsl(var(--jl-h) calc(63% * var(--jl-sat)) calc(25% + (100% - 25%) * max(0, var(--jl-lum)) + 25% * min(0, var(--jl-lum))) / .64),
    hsl(var(--jl-h) calc(56% * var(--jl-sat)) calc(43% + (100% - 43%) * max(0, var(--jl-lum)) + 43% * min(0, var(--jl-lum))) / .58) 18%,
    hsl(var(--jl-h) calc(62% * var(--jl-sat)) calc(31% + (100% - 31%) * max(0, var(--jl-lum)) + 31% * min(0, var(--jl-lum))) / .53) 40%,
    hsl(var(--jl-h) calc(75% * var(--jl-sat)) calc(22% + (100% - 22%) * max(0, var(--jl-lum)) + 22% * min(0, var(--jl-lum))) / .67) 49%,
    hsl(var(--jl-h) calc(69% * var(--jl-sat)) calc(28% + (100% - 28%) * max(0, var(--jl-lum)) + 28% * min(0, var(--jl-lum))) / .57) 54%,
    hsl(var(--jl-h) calc(60% * var(--jl-sat)) calc(47% + (100% - 47%) * max(0, var(--jl-lum)) + 47% * min(0, var(--jl-lum))) / .62) 72%,
    hsl(var(--jl-h) calc(70% * var(--jl-sat)) calc(26% + (100% - 26%) * max(0, var(--jl-lum)) + 26% * min(0, var(--jl-lum))) / .68));
  border-bottom-color:hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(33% + (100% - 33%) * max(0, var(--jl-lum)) + 33% * min(0, var(--jl-lum))) / .38);
}
#asst-launch .jl-contact {
  border-color:hsl(var(--jl-h) calc(43% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .58);
  background:linear-gradient(90deg,hsl(var(--jl-h) calc(38% * var(--jl-sat)) calc(29% + (100% - 29%) * max(0, var(--jl-lum)) + 29% * min(0, var(--jl-lum))) / .6),hsl(var(--jl-h) calc(45% * var(--jl-sat)) calc(53% + (100% - 53%) * max(0, var(--jl-lum)) + 53% * min(0, var(--jl-lum))) / .72),hsl(var(--jl-h) calc(42% * var(--jl-sat)) calc(32% + (100% - 32%) * max(0, var(--jl-lum)) + 32% * min(0, var(--jl-lum))) / .64));
}
#asst-launch .jl-spring {opacity:.92}
#asst-launch .jl-latch {
  border-color:hsl(var(--jl-h) calc(75% * var(--jl-sat)) calc(53% + (100% - 53%) * max(0, var(--jl-lum)) + 53% * min(0, var(--jl-lum))) / .48);
  background:hsl(var(--jl-h) calc(84% * var(--jl-sat)) calc(36% + (100% - 36%) * max(0, var(--jl-lum)) + 36% * min(0, var(--jl-lum))) / .38);
  box-shadow:inset calc(var(--unit) * .7) 0 calc(var(--unit) * .7) hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(65% + (100% - 65%) * max(0, var(--jl-lum)) + 65% * min(0, var(--jl-lum))) / .27);
}
@media(prefers-reduced-transparency:reduce) {
  #asst-launch {background:hsl(var(--jl-h) calc(76% * var(--jl-sat)) calc(28% + (100% - 28%) * max(0, var(--jl-lum)) + 28% * min(0, var(--jl-lum))))}
  #asst-launch .jl-transmission,
  #asst-launch .jl-front {-webkit-backdrop-filter:none;backdrop-filter:none}
}
#asst-launch {
  --bevel-mask:url('../tex/bevel-mask.png');
  --rounded-wash-mask:url('../tex/rounded-wash-mask.png');
}
/* Append after the device and screen-glass styles. The pointer lights the
   button and casing; the broad screen reflection has its own camera motion.
   These overrides leave the cap's colour, extrusion, lift and press intact. */
#asst-launch {
  --surface-pointer-x:clamp(0%,var(--lx,50%),100%);
  --surface-pointer-y:clamp(0%,var(--ly,0%),100%);
  --surface-light:calc(clamp(0,var(--lit,0),1) * clamp(0,var(--hn,0),1));   /* fallback only: device-light.js writes the same number straight onto the readers (see TARGETS there) */
}
/* Polished acrylic: the pointer reflection is clipped to the curved lip, so
   it travels around the cap rather than painting a bright spot in its centre.
   Correct local coordinates are supplied by the light script. */
#asst-launch .jl-face {
  --cap-reflection:.22;
  --cap-pointer-x:clamp(0%,var(--key-pointer-x,50%),100%);
  --cap-pointer-y:clamp(0%,var(--key-pointer-y,0%),100%);
}
#asst-launch .jl-key:hover .jl-face {--cap-reflection:.28}
#asst-launch .jl-key:active .jl-face {--cap-reflection:.20}
#asst-launch .jl-face::before {
  padding:calc(var(--unit) * .4);
  background:
    radial-gradient(ellipse 54% 70% at var(--cap-pointer-x) var(--cap-pointer-y),rgb(255 255 255 / calc(.08 + var(--surface-light) * .88)),rgb(255 255 255 / calc(var(--surface-light) * .35)) 42%,transparent 100%);
  border-color:hsl(calc(var(--jl-h) + 67.9deg) calc(100% * var(--jl-sat)) calc(68.8% + (100% - 68.8%) * max(0, var(--jl-lum)) + 68.8% * min(0, var(--jl-lum))) / .25);
  border-top-color:rgb(255 255 255 / .31);
  border-left-color:hsl(calc(var(--jl-h) + 65.3deg) calc(100% * var(--jl-sat)) calc(85.9% + (100% - 85.9%) * max(0, var(--jl-lum)) + 85.9% * min(0, var(--jl-lum))) / .25);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask-composite:exclude;
  box-shadow:none;
}
#asst-launch .jl-face::after,
#asst-launch .jl-key:hover .jl-face::after {
  background:
    radial-gradient(ellipse 55% 65% at var(--cap-pointer-x) var(--cap-pointer-y),rgb(255 255 255 / calc(var(--surface-light) * .19)),transparent 100%),
    radial-gradient(ellipse at 35% 2%,rgb(255 255 255 / var(--cap-reflection)),rgb(255 255 255 / .045) 31%,transparent 57%),
    radial-gradient(ellipse at 73% 98%,hsl(calc(var(--jl-h) + 61.5deg) calc(100% * var(--jl-sat)) calc(54.3% + (100% - 54.3%) * max(0, var(--jl-lum)) + 54.3% * min(0, var(--jl-lum))) / .36),transparent 47%);
  box-shadow:inset calc(var(--unit) * .55) calc(var(--unit) * .8) calc(var(--unit) * 2.1) rgb(255 255 255 / .16);
}
/* The outer shoulder is a continuous light profile, not a clipped ring.
   The same inward-distance curve is used for straight sides and round corners.
   Its crest is 9 units inward, with a soft rise and a long fade into the face.
   A cached distance-field mask keeps curved/straight joins truly continuous. */
#asst-launch {
  --bevel-radius:calc(var(--shell-radius) - var(--unit));
  --bevel-width:calc(var(--unit) * 32);

}
#asst-launch .jl-front {
  border-color:hsl(var(--jl-h) calc(72% * var(--jl-sat)) calc(48% + (100% - 48%) * max(0, var(--jl-lum)) + 48% * min(0, var(--jl-lum))) / .3);
  border-top-color:hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(72% + (100% - 72%) * max(0, var(--jl-lum)) + 72% * min(0, var(--jl-lum))) / .32);
  border-left-color:hsl(var(--jl-h) calc(66% * var(--jl-sat)) calc(63% + (100% - 63%) * max(0, var(--jl-lum)) + 63% * min(0, var(--jl-lum))) / .3);
  border-right-color:hsl(var(--jl-h) calc(75% * var(--jl-sat)) calc(39% + (100% - 39%) * max(0, var(--jl-lum)) + 39% * min(0, var(--jl-lum))) / .32);
  border-bottom-color:hsl(var(--jl-h) calc(73% * var(--jl-sat)) calc(37% + (100% - 37%) * max(0, var(--jl-lum)) + 37% * min(0, var(--jl-lum))) / .36);
  background:linear-gradient(113deg,hsl(calc(var(--jl-h) - 28deg) calc(92% * var(--jl-sat)) calc(89% + (100% - 89%) * max(0, var(--jl-lum)) + 89% * min(0, var(--jl-lum))) / .045),transparent 25% 78%,hsl(var(--jl-h) calc(87% * var(--jl-sat)) calc(44% + (100% - 44%) * max(0, var(--jl-lum)) + 44% * min(0, var(--jl-lum))) / .06));
  box-shadow:inset 0 0 calc(var(--unit) * 4) hsl(var(--jl-h) calc(85% * var(--jl-sat)) calc(22% + (100% - 22%) * max(0, var(--jl-lum)) + 22% * min(0, var(--jl-lum))) / .10);
}
/* Broad stationary area light follows the roll all the way around the corner.
   The actual front remains unmasked so its backdrop blur samples the internals. */
#asst-launch .jl-front::before {
  inset:0;border:0;border-radius:inherit;
  background:linear-gradient(135deg,
    hsl(calc(var(--jl-h) - 25deg) calc(65% * var(--jl-sat)) calc(96% + (100% - 96%) * max(0, var(--jl-lum)) + 96% * min(0, var(--jl-lum))) / .65),
    hsl(calc(var(--jl-h) - 12deg) calc(70% * var(--jl-sat)) calc(91% + (100% - 91%) * max(0, var(--jl-lum)) + 91% * min(0, var(--jl-lum))) / .39) 33%,
    hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(78% + (100% - 78%) * max(0, var(--jl-lum)) + 78% * min(0, var(--jl-lum))) / .10) 65%,
    hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(59% + (100% - 59%) * max(0, var(--jl-lum)) + 59% * min(0, var(--jl-lum))) / .13));
  -webkit-mask:var(--bevel-mask) center / 100% 100% no-repeat;mask:var(--bevel-mask) center / 100% 100% no-repeat;
  box-shadow:none;filter:none;
}
#asst-launch .jl-front::after {content:none}
#asst-launch .jl-glare {
  inset:0;border:0;border-radius:inherit;
  background:none;box-shadow:none;
}
#asst-launch > .sheen,
#asst-launch .jl-glare::before {
  -webkit-mask:var(--rounded-wash-mask) center / 100% 100% no-repeat;mask:var(--rounded-wash-mask) center / 100% 100% no-repeat;
}
#asst-launch .jl-glare::before {
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(ellipse 45% 42% at var(--surface-pointer-x) var(--surface-pointer-y),rgb(255 255 255 / calc(var(--surface-light) * .10)),rgb(255 255 255 / calc(var(--surface-light) * .025)) 48%,transparent 100%);
  box-shadow:none;
}
/* The cursor uses the same convex profile as the stationary area light.
   There is no smaller clipping edge for either highlight to terminate on. */
#asst-launch .jl-glare::after {
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(ellipse 54% 58% at var(--surface-pointer-x) var(--surface-pointer-y),rgb(255 255 255 / calc(var(--surface-light) * .44)),rgb(255 255 255 / calc(var(--surface-light) * .11)) 52%,transparent 100%);
  -webkit-mask:var(--bevel-mask) center / 100% 100% no-repeat;mask:var(--bevel-mask) center / 100% 100% no-repeat;
  box-shadow:none;
}
/* Underlying mouldings keep their tint and thickness, without pale outlines
   that could cut across the cover's continuous highlight. */
#asst-launch .jl-back {
  box-shadow:
    inset 0 calc(var(--unit) * 1) calc(var(--unit) * 2) hsl(var(--jl-h) calc(80% * var(--jl-sat)) calc(65% + (100% - 65%) * max(0, var(--jl-lum)) + 65% * min(0, var(--jl-lum))) / .10),
    inset calc(var(--unit) * -1) calc(var(--unit) * -3) calc(var(--unit) * 3) hsl(var(--jl-h) calc(98% * var(--jl-sat)) calc(21% + (100% - 21%) * max(0, var(--jl-lum)) + 21% * min(0, var(--jl-lum))) / .35),
    0 calc(var(--unit) * 2) calc(var(--unit) * 1) hsl(var(--jl-h) calc(90% * var(--jl-sat)) calc(13% + (100% - 13%) * max(0, var(--jl-lum)) + 13% * min(0, var(--jl-lum))) / .36);
}
#asst-launch .jl-back::after {
  border-color:hsl(var(--jl-h) calc(87% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .3);
  box-shadow:inset 0 0 calc(var(--unit) * 3) hsl(var(--jl-h) calc(83% * var(--jl-sat)) calc(65% + (100% - 65%) * max(0, var(--jl-lum)) + 65% * min(0, var(--jl-lum))) / .11);
}
#asst-launch .jl-wall {
  border-color:hsl(var(--jl-h) calc(83% * var(--jl-sat)) calc(45% + (100% - 45%) * max(0, var(--jl-lum)) + 45% * min(0, var(--jl-lum))) / .28);
  box-shadow:inset calc(var(--unit) * -2) calc(var(--unit) * -2) calc(var(--unit) * 5) hsl(var(--jl-h) calc(92% * var(--jl-sat)) calc(31% + (100% - 31%) * max(0, var(--jl-lum)) + 31% * min(0, var(--jl-lum))) / .18);
}
#asst-launch .jl-wall::before {
  border-color:hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(42% + (100% - 42%) * max(0, var(--jl-lum)) + 42% * min(0, var(--jl-lum))) / .32);
  box-shadow:none;
}
#asst-launch .jl-wall::after {
  border-color:hsl(var(--jl-h) calc(76% * var(--jl-sat)) calc(54% + (100% - 54%) * max(0, var(--jl-lum)) + 54% * min(0, var(--jl-lum))) / .22);
  box-shadow:none;
}
:root[data-shape="compact"] #asst-launch {--bevel-mask:url('../tex/bevel-mask-compact.png');--rounded-wash-mask:url('../tex/rounded-wash-mask-compact.png');--cap-aperture-mask:url('../tex/cap-aperture-mask.png');}
:root[data-shape="compact"] #asst-launch {--glass-shoulder-mask:url('../tex/glass-shoulder-mask.png');}
/* Compact moulding, using the original unit scale. The smaller native display
   retains its pixel pitch, while the polished key keeps its physical size.
   Append after the standard device, optical and surface styles. Shape-specific
   bevel/wash masks are supplied separately by the comparison page. */
:root[data-shape="compact"] #asst-launch {
  --jl-size:var(--dev-size, 180px);
  --shell-radius:calc(var(--unit) * 48);
  --shell-bottom-radius:calc(var(--unit) * 76);
  --case-outline:var(--shell-radius) var(--shell-radius) var(--shell-bottom-radius) var(--shell-bottom-radius);
  --radius:var(--shell-radius);
  --screen-left:calc(var(--unit) * 33);
  --screen-top:calc(var(--unit) * 33);
  --screen-width:calc(var(--unit) * 157);
  --screen-height:calc(var(--unit) * 121);
  --screen-radius:calc(var(--unit) * 25);
  --screen-surround:calc(var(--unit) * 6);
  width:calc(var(--unit) * 223);
  height:calc(var(--unit) * 213);
  border-radius:var(--case-outline);
}
#asst-launch .jl-name,
#asst-launch .jl-key-reflection,
#asst-launch .jl-key-polish,
#asst-launch .jl-switch {display:none}
/* The matte surface has a real aperture beneath the polished button. The
   desk still receives its stronger, separate blur behind the whole assembly. */
:root[data-shape="compact"] #asst-launch .jl-front {
  border-radius:var(--case-outline);
  -webkit-mask:var(--cap-aperture-mask) 0 0 / 100% 100% no-repeat;
  mask:var(--cap-aperture-mask) 0 0 / 100% 100% no-repeat;
}
:root[data-shape="compact"] #asst-launch > .sheen {
  border-radius:var(--case-outline);
}
:root[data-shape="compact"] #asst-launch .jl-grain {
  border-radius:
    calc(var(--shell-radius) - var(--unit) * 2)
    calc(var(--shell-radius) - var(--unit) * 2)
    calc(var(--shell-bottom-radius) - var(--unit) * 2)
    calc(var(--shell-bottom-radius) - var(--unit) * 2);
}
/* The bare casing has no exposed fasteners or spare screw mounting rings. */
:root[data-shape="compact"] #asst-launch .jl-post,
:root[data-shape="compact"] #asst-launch .jl-brace,
:root[data-shape="compact"] #asst-launch .jl-hole,
:root[data-shape="compact"] #asst-launch .jl-boss {
  display:none;
}
/* A slightly broader cap stays easy to press at the 180 default scale.
   Its optical centre still meets the rear switch at (111.5,181.6). */
:root[data-shape="compact"] #asst-launch .jl-key {
  left:calc(var(--unit) * 72.5);
  top:calc(var(--unit) * 154.8625);
  right:auto;bottom:auto;
  width:calc(var(--unit) * 78);
  height:calc(var(--unit) * 57.5);
}
/* Transparent hit slop adds a little forgiveness around the small oval. */
:root[data-shape="compact"] #asst-launch .jl-key::before {
  content:"";position:absolute;left:50%;top:50%;
  width:max(100%,56px);height:max(100%,44px);
  transform:translate(-50%,-50%);
  border-radius:50%;background:transparent;
}
/* Slightly quieter whites retain the continuous matte shoulder. */
:root[data-shape="compact"] #asst-launch .jl-front::before {
  background:linear-gradient(135deg,
    hsl(calc(var(--jl-h) - 25deg) calc(65% * var(--jl-sat)) calc(96% + (100% - 96%) * max(0, var(--jl-lum)) + 96% * min(0, var(--jl-lum))) / .55),
    hsl(calc(var(--jl-h) - 12deg) calc(70% * var(--jl-sat)) calc(91% + (100% - 91%) * max(0, var(--jl-lum)) + 91% * min(0, var(--jl-lum))) / .33) 33%,
    hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(78% + (100% - 78%) * max(0, var(--jl-lum)) + 78% * min(0, var(--jl-lum))) / .085) 65%,
    hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(59% + (100% - 59%) * max(0, var(--jl-lum)) + 59% * min(0, var(--jl-lum))) / .11));
}
:root[data-shape="compact"] #asst-launch .jl-glare::before {
  background:radial-gradient(ellipse 45% 42% at var(--surface-pointer-x) var(--surface-pointer-y),
    rgb(255 255 255 / calc(var(--surface-light) * .085)),
    rgb(255 255 255 / calc(var(--surface-light) * .021)) 48%,transparent 100%);
}
:root[data-shape="compact"] #asst-launch .jl-glare::after {
  background:radial-gradient(ellipse 54% 58% at var(--surface-pointer-x) var(--surface-pointer-y),
    rgb(255 255 255 / calc(var(--surface-light) * .38)),
    rgb(255 255 255 / calc(var(--surface-light) * .095)) 52%,transparent 100%);
}
/* Thinner internal walls make room for the screen. The rear cover ends
   at the base rather than extending into a thick lip below the smaller shell. */
:root[data-shape="compact"] #asst-launch .jl-back {
  /* Concentric, and far enough in that the sway can never carry it out past an edge: the parallax
     reaches 6 units and this is 8, leaving two units of margin at the extreme of the travel. */
  inset:calc(var(--unit) * 8);
  border-width:calc(var(--unit) * 1.6);
  border-radius:
    calc(var(--shell-radius) - var(--unit) * 8)
    calc(var(--shell-radius) - var(--unit) * 8)
    calc(var(--shell-bottom-radius) - var(--unit) * 8)
    calc(var(--shell-bottom-radius) - var(--unit) * 2);
}
:root[data-shape="compact"] #asst-launch .jl-back::after {
  inset:calc(var(--unit) * 5);
  border-width:calc(var(--unit) * 1.2);
  border-radius:
    calc(var(--shell-radius) - var(--unit) * 8)
    calc(var(--shell-radius) - var(--unit) * 8)
    calc(var(--shell-bottom-radius) - var(--unit) * 8)
    calc(var(--shell-bottom-radius) - var(--unit) * 8);
}
:root[data-shape="compact"] #asst-launch .jl-wall {
  inset:calc(var(--unit) * 2.5);
  border-width:calc(var(--unit) * 6);
  border-bottom-width:calc(var(--unit) * 5);
  border-radius:
    calc(var(--shell-radius) - var(--unit) * 2.5)
    calc(var(--shell-radius) - var(--unit) * 2.5)
    calc(var(--shell-bottom-radius) - var(--unit) * 2.5)
    calc(var(--shell-bottom-radius) - var(--unit) * 2.5);
}
:root[data-shape="compact"] #asst-launch .jl-wall::before {
  border-radius:
    calc(var(--shell-radius) - var(--unit) * 14.5)
    calc(var(--shell-radius) - var(--unit) * 14.5)
    calc(var(--shell-bottom-radius) - var(--unit) * 14.5)
    calc(var(--shell-bottom-radius) - var(--unit) * 14.5);
}
:root[data-shape="compact"] #asst-launch .jl-wall::after {
  border-radius:
    calc(var(--shell-radius) - var(--unit) * 18.5)
    calc(var(--shell-radius) - var(--unit) * 18.5)
    calc(var(--shell-bottom-radius) - var(--unit) * 18.5)
    calc(var(--shell-bottom-radius) - var(--unit) * 18.5);
}
/* The lower assembly sits directly behind the key. Its cell, contacts and
   spring inherit the existing translucent material treatment. */
:root[data-shape="compact"] #asst-launch .jl-cradle {
  left:calc(var(--unit) * 39);
  top:calc(var(--unit) * 152);
  width:calc(var(--unit) * 145);
  height:calc(var(--unit) * 43);
}
:root[data-shape="compact"] #asst-launch .jl-latch {
  height:calc(var(--unit) * 4.5);
  bottom:calc(var(--unit) * -2);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-seam {
  left:calc(var(--unit) * 110);top:calc(var(--unit) * 4);
  width:calc(var(--unit) * 3);height:calc(var(--unit) * 5);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-key {
  left:calc(var(--unit) * 144.5);top:calc(var(--unit) * 7);
  width:calc(var(--unit) * 3);height:calc(var(--unit) * 8);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-left,
:root[data-shape="compact"] #asst-launch .jl-tab.t-right {
  top:calc(var(--unit) * 99.5);
  width:calc(var(--unit) * 26);height:calc(var(--unit) * 3);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-left {
  left:calc(var(--unit) * 5);right:auto;
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-right {
  right:calc(var(--unit) * 5);left:auto;
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-bottom {
  left:calc(var(--unit) * 92.5);bottom:calc(var(--unit) * 8);
  width:calc(var(--unit) * 38);height:calc(var(--unit) * 3.5);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-rib {
  top:calc(var(--unit) * 185);
  width:calc(var(--unit) * 3);height:calc(var(--unit) * 10);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-rib.l {
  left:calc(var(--unit) * 94.5);right:auto;
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-rib.r {
  right:calc(var(--unit) * 94.5);left:auto;
}
/* Interior mouldings for the 223 × 213 compact shell. Append after compact
   geometry, optical depth and surface lighting; the outer light profile stays
   untouched. These parts use the existing rear assembly and its parallax. */
:root[data-shape="compact"] #asst-launch .jl-boss {
  display:block;
  top:calc(var(--unit) * 177);
  bottom:auto;
  width:calc(var(--unit) * 16);
  height:calc(var(--unit) * 16);
  border-width:calc(var(--unit) * .8);
  --part-blur:.45;
}
:root[data-shape="compact"] #asst-launch .jl-boss.l {
  left:calc(var(--unit) * 34);right:auto;
}
:root[data-shape="compact"] #asst-launch .jl-boss.r {
  left:calc(var(--unit) * 173);right:auto;
}
/* Small locator depressions, distinct from the still-hidden screw collars. */
:root[data-shape="compact"] #asst-launch .jl-hole {
  display:block;
  width:calc(var(--unit) * 7);
  height:calc(var(--unit) * 7);
  border-width:calc(var(--unit) * .6);
  opacity:.32;
  --part-blur:.7;
}
:root[data-shape="compact"] #asst-launch .jl-hole.a {
  left:calc(var(--unit) * 48.5);right:auto;
  top:calc(var(--unit) * 19);
}
:root[data-shape="compact"] #asst-launch .jl-hole.b {
  right:calc(var(--unit) * 48.5);left:auto;
  top:calc(var(--unit) * 19);
}
:root[data-shape="compact"] #asst-launch .jl-hole.c {
  right:calc(var(--unit) * 12);left:auto;
  top:calc(var(--unit) * 154);
  opacity:.4;
}
/* Four diagonal supports connect the screen carrier's rounded corners to
   the rear moulding. They share the internals' full rear-pane movement. */
:root[data-shape="compact"] #asst-launch .jl-brace {
  display:block;
  width:calc(var(--unit) * 17);
  height:calc(var(--unit) * 3.6);
  border-width:calc(var(--unit) * .75);
  border-color:hsl(var(--jl-h) calc(77% * var(--jl-sat)) calc(42% + (100% - 42%) * max(0, var(--jl-lum)) + 42% * min(0, var(--jl-lum))) / .64);
  background:linear-gradient(0deg,
    hsl(var(--jl-h) calc(95% * var(--jl-sat)) calc(23% + (100% - 23%) * max(0, var(--jl-lum)) + 23% * min(0, var(--jl-lum))) / .58),
    hsl(var(--jl-h) calc(79% * var(--jl-sat)) calc(55% + (100% - 55%) * max(0, var(--jl-lum)) + 55% * min(0, var(--jl-lum))) / .42) 55%,
    hsl(var(--jl-h) calc(95% * var(--jl-sat)) calc(31% + (100% - 31%) * max(0, var(--jl-lum)) + 31% * min(0, var(--jl-lum))) / .61));
  box-shadow:inset 0 calc(var(--unit) * .5) calc(var(--unit) * .65)
    hsl(var(--jl-h) calc(76% * var(--jl-sat)) calc(68% + (100% - 68%) * max(0, var(--jl-lum)) + 68% * min(0, var(--jl-lum))) / .29);
  opacity:.92;
  --part-blur:.4;
}
:root[data-shape="compact"] #asst-launch .jl-brace.a,
:root[data-shape="compact"] #asst-launch .jl-brace.b {top:calc(var(--unit) * 33)}
:root[data-shape="compact"] #asst-launch .jl-brace.c,
:root[data-shape="compact"] #asst-launch .jl-brace.d {top:calc(var(--unit) * 151)}
:root[data-shape="compact"] #asst-launch .jl-brace.a,
:root[data-shape="compact"] #asst-launch .jl-brace.c {
  left:calc(var(--unit) * 27);right:auto;
}
:root[data-shape="compact"] #asst-launch .jl-brace.b,
:root[data-shape="compact"] #asst-launch .jl-brace.d {
  right:calc(var(--unit) * 27);left:auto;
}
:root[data-shape="compact"] #asst-launch .jl-brace.a,
:root[data-shape="compact"] #asst-launch .jl-brace.d {transform:rotate(44deg)}
:root[data-shape="compact"] #asst-launch .jl-brace.b,
:root[data-shape="compact"] #asst-launch .jl-brace.c {transform:rotate(-44deg)}
/* Nested tinted rails remain below the cover's continuous soft shoulder. */
:root[data-shape="compact"] #asst-launch .jl-back::after {
  border-width:calc(var(--unit) * .8);
  border-color:hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(33% + (100% - 33%) * max(0, var(--jl-lum)) + 33% * min(0, var(--jl-lum))) / .4);
  box-shadow:inset 0 calc(var(--unit) * .5) calc(var(--unit) * 1.1)
    hsl(var(--jl-h) calc(77% * var(--jl-sat)) calc(64% + (100% - 64%) * max(0, var(--jl-lum)) + 64% * min(0, var(--jl-lum))) / .15);
}
:root[data-shape="compact"] #asst-launch .jl-wall::before {
  border-width:calc(var(--unit) * .7);
  border-color:hsl(var(--jl-h) calc(82% * var(--jl-sat)) calc(36% + (100% - 36%) * max(0, var(--jl-lum)) + 36% * min(0, var(--jl-lum))) / .42);
  box-shadow:inset 0 calc(var(--unit) * .45) calc(var(--unit) * .9)
    hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(61% + (100% - 61%) * max(0, var(--jl-lum)) + 61% * min(0, var(--jl-lum))) / .13);
}
:root[data-shape="compact"] #asst-launch .jl-wall::after {
  border-width:calc(var(--unit) * .6);
  border-color:hsl(var(--jl-h) calc(76% * var(--jl-sat)) calc(51% + (100% - 51%) * max(0, var(--jl-lum)) + 51% * min(0, var(--jl-lum))) / .24);
  box-shadow:0 calc(var(--unit) * .45) calc(var(--unit) * .7)
    hsl(var(--jl-h) calc(88% * var(--jl-sat)) calc(19% + (100% - 19%) * max(0, var(--jl-lum)) + 19% * min(0, var(--jl-lum))) / .19);
}
/* Shorter cell tray, with its contacts, spring and latches contained beneath
   the key. Its last fittings stop above y=207 in the shell's unit space. */
:root[data-shape="compact"] #asst-launch .jl-cradle {
  --part-blur:.23;
  left:calc(var(--unit) * 52.5);
  top:calc(var(--unit) * 168);
  width:calc(var(--unit) * 118);
  height:calc(var(--unit) * 33);
  border-top-width:calc(var(--unit) * 1.3);
  border-bottom-width:calc(var(--unit) * 1.3);
  border-left-width:calc(var(--unit) * 2.1);
  border-right-width:calc(var(--unit) * 2.1);
}
:root[data-shape="compact"] #asst-launch .jl-cradle::before,
:root[data-shape="compact"] #asst-launch .jl-cradle::after {
  top:0;bottom:0;
  width:calc(var(--unit) * 3.5);
}
:root[data-shape="compact"] #asst-launch .jl-cell {inset:8% 6%}
:root[data-shape="compact"] #asst-launch .jl-contact {top:15%;bottom:15%;width:4%}
:root[data-shape="compact"] #asst-launch .jl-spring {top:25%;height:50%;width:16%;right:8%}
:root[data-shape="compact"] #asst-launch .jl-latch {
  height:calc(var(--unit) * 3.5);
  bottom:calc(var(--unit) * -1.5);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-left,
:root[data-shape="compact"] #asst-launch .jl-tab.t-right {
  top:calc(var(--unit) * 109.5);
  width:calc(var(--unit) * 26);
  height:calc(var(--unit) * 3.5);
  border-width:calc(var(--unit) * .65);
  border-color:hsl(var(--jl-h) calc(81% * var(--jl-sat)) calc(39% + (100% - 39%) * max(0, var(--jl-lum)) + 39% * min(0, var(--jl-lum))) / .63);
  background:linear-gradient(90deg,
    hsl(var(--jl-h) calc(73% * var(--jl-sat)) calc(63% + (100% - 63%) * max(0, var(--jl-lum)) + 63% * min(0, var(--jl-lum))) / .25),
    hsl(var(--jl-h) calc(95% * var(--jl-sat)) calc(27% + (100% - 27%) * max(0, var(--jl-lum)) + 27% * min(0, var(--jl-lum))) / .6) 53%,
    hsl(var(--jl-h) calc(84% * var(--jl-sat)) calc(48% + (100% - 48%) * max(0, var(--jl-lum)) + 48% * min(0, var(--jl-lum))) / .41));
  --part-blur:.4;
}
/* Long and short side ribs are part of these existing rear tabs, rather
   than outlines painted on the front. Two thicknesses keep the depth legible. */
:root[data-shape="compact"] #asst-launch .jl-tab.t-left::before,
:root[data-shape="compact"] #asst-launch .jl-tab.t-right::before,
:root[data-shape="compact"] #asst-launch .jl-tab.t-left::after,
:root[data-shape="compact"] #asst-launch .jl-tab.t-right::after {
  content:"";
  position:absolute;
  pointer-events:none;
  border-radius:calc(var(--unit) * 1);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-left::before,
:root[data-shape="compact"] #asst-launch .jl-tab.t-right::before {
  top:calc(var(--unit) * -49.5);
  width:calc(var(--unit) * 1.6);
  height:calc(var(--unit) * 76);
  background:hsl(var(--jl-h) calc(84% * var(--jl-sat)) calc(37% + (100% - 37%) * max(0, var(--jl-lum)) + 37% * min(0, var(--jl-lum))) / .7);
  box-shadow:inset calc(var(--unit) * .45) 0 calc(var(--unit) * .6)
    hsl(var(--jl-h) calc(74% * var(--jl-sat)) calc(66% + (100% - 66%) * max(0, var(--jl-lum)) + 66% * min(0, var(--jl-lum))) / .3);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-left::after,
:root[data-shape="compact"] #asst-launch .jl-tab.t-right::after {
  top:calc(var(--unit) * -40.5);
  width:calc(var(--unit) * 1);
  height:calc(var(--unit) * 57);
  background:hsl(var(--jl-h) calc(79% * var(--jl-sat)) calc(47% + (100% - 47%) * max(0, var(--jl-lum)) + 47% * min(0, var(--jl-lum))) / .48);
  box-shadow:calc(var(--unit) * .35) 0 calc(var(--unit) * .5)
    hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(25% + (100% - 25%) * max(0, var(--jl-lum)) + 25% * min(0, var(--jl-lum))) / .26);
}
:root[data-shape="compact"] #asst-launch .jl-tab.t-left::before {left:calc(var(--unit) * 14.5)}
:root[data-shape="compact"] #asst-launch .jl-tab.t-right::before {right:calc(var(--unit) * 14.5)}
:root[data-shape="compact"] #asst-launch .jl-tab.t-left::after {left:calc(var(--unit) * 8.5)}
:root[data-shape="compact"] #asst-launch .jl-tab.t-right::after {right:calc(var(--unit) * 8.5)}
:root[data-shape="compact"] #asst-launch .jl-tab.t-rib {
  top:calc(var(--unit) * 194);
  height:calc(var(--unit) * 11);
}
/* A dark cut into the front face, with a subpixel lip catching the same
   moving light as the casing. The lettering does not inherit rear blur. */
:root[data-shape="compact"] #asst-launch .jl-name {
  position:absolute;
  left:calc(var(--unit) * 21.5);
  top:calc(var(--unit) * 8);
  width:calc(var(--unit) * 180);
  height:calc(var(--unit) * 20);
  display:flex;align-items:center;justify-content:center;
  z-index:7;
  pointer-events:none;user-select:none;-webkit-user-select:none;
  overflow:hidden;white-space:nowrap;
  font-family:inherit;
  font-size:calc(var(--unit) * 15);
  font-weight:750;
  line-height:1;
  letter-spacing:calc(var(--unit) * .8);
  text-indent:calc(var(--unit) * .8);
  color:hsl(var(--jl-h) calc(81% * var(--jl-sat)) calc(18% + (100% - 18%) * max(0, -1 * var(--jl-lum))) / .8);
  /* Engraved. The lit and shaded walls are two more copies of the text (`::before` / `::after`, from
     `data-name`), CARRIED BY TRANSFORM rather than drawn as text-shadows. A shadow offset is pixel-snapped
     when it is painted; a transform is a float matrix, and text under it is positioned sub-pixel. At the
     shipped size the shadows were ~0.7px on 10px letters, so the light had three places it could be, and
     it went between them in steps. The copies glide. Offsets are in `--unit`s, so they scale with the case. */
}
:root[data-shape="compact"] #asst-launch .jl-name::before,
:root[data-shape="compact"] #asst-launch .jl-name::after {
  content:attr(data-name);
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  pointer-events:none;
  z-index:-1;
  will-change:transform;
}
:root[data-shape="compact"] #asst-launch .jl-name::before {
  color:hsl(var(--jl-h) calc(68% * var(--jl-sat)) calc(76% + (100% - 76%) * max(0, var(--jl-lum)) + 76% * min(0, var(--jl-lum))) / calc(.22 + var(--surface-light,0) * .26));
  transform:translate(calc(var(--hxn,0) * var(--unit) * .85), calc(var(--unit) * .42 + var(--hyn,0) * var(--unit) * .85));
  filter:blur(calc(var(--unit) * .35));
}
:root[data-shape="compact"] #asst-launch .jl-name::after {
  color:hsl(var(--jl-h) calc(94% * var(--jl-sat)) calc(11% + (100% - 11%) * max(0, var(--jl-lum)) + 11% * min(0, var(--jl-lum))) / .33);
  transform:translate(calc(var(--hxn,0) * var(--unit) * -.6), calc(var(--unit) * -.3 - var(--hyn,0) * var(--unit) * .6));
  filter:blur(calc(var(--unit) * .4));
}
/* A small mechanical switch on the rear assembly, visible through the cap.
   Place .jl-switch inside .jl-internals after the battery cradle. Its centre
   is (111.5,181.6) in the compact shell's unit space. */
:root[data-shape="compact"] #asst-launch .jl-switch {
  position:absolute;
  left:calc(var(--unit) * 94.5);
  top:calc(var(--unit) * 169.6);
  width:calc(var(--unit) * 34);
  height:calc(var(--unit) * 24);
  display:block;
  box-sizing:border-box;
  pointer-events:none;
  z-index:3;
  --part-blur:.25;
  filter:blur(calc(var(--unit) * var(--part-blur) + var(--fb,0px)));
  border:calc(var(--unit) * .7) solid hsl(var(--jl-h) calc(66% * var(--jl-sat)) calc(40% + (100% - 40%) * max(0, var(--jl-lum)) + 40% * min(0, var(--jl-lum))) / .54);
  border-radius:calc(var(--unit) * 3);
  background:linear-gradient(150deg,
    hsl(var(--jl-h) calc(62% * var(--jl-sat)) calc(45% + (100% - 45%) * max(0, var(--jl-lum)) + 45% * min(0, var(--jl-lum))) / .3),
    hsl(var(--jl-h) calc(88% * var(--jl-sat)) calc(27% + (100% - 27%) * max(0, var(--jl-lum)) + 27% * min(0, var(--jl-lum))) / .43) 64%,
    hsl(var(--jl-h) calc(77% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .28));
  box-shadow:inset 0 calc(var(--unit) * .45) calc(var(--unit) * .65)
    hsl(var(--jl-h) calc(60% * var(--jl-sat)) calc(64% + (100% - 64%) * max(0, var(--jl-lum)) + 64% * min(0, var(--jl-lum))) / .2);
}
/* Narrow folded contacts, tinted by the case rather than bright silver. */
:root[data-shape="compact"] #asst-launch .jl-switch::before,
:root[data-shape="compact"] #asst-launch .jl-switch::after {
  content:"";
  position:absolute;
  top:calc(var(--unit) * 5.5);
  width:calc(var(--unit) * 2.5);
  height:calc(var(--unit) * 11.5);
  box-sizing:border-box;
  pointer-events:none;
  z-index:2;
  border:calc(var(--unit) * .45) solid hsl(var(--jl-h) calc(26% * var(--jl-sat)) calc(42% + (100% - 42%) * max(0, var(--jl-lum)) + 42% * min(0, var(--jl-lum))) / .55);
  border-radius:calc(var(--unit) * .7);
  background:linear-gradient(90deg,
    hsl(var(--jl-h) calc(24% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .53),
    hsl(var(--jl-h) calc(21% * var(--jl-sat)) calc(69% + (100% - 69%) * max(0, var(--jl-lum)) + 69% * min(0, var(--jl-lum))) / .48) 47%,
    hsl(var(--jl-h) calc(33% * var(--jl-sat)) calc(44% + (100% - 44%) * max(0, var(--jl-lum)) + 44% * min(0, var(--jl-lum))) / .45) 71%);
}
:root[data-shape="compact"] #asst-launch .jl-switch::before {left:calc(var(--unit) * 1.6)}
:root[data-shape="compact"] #asst-launch .jl-switch::after {right:calc(var(--unit) * 1.6)}
/* The contact carrier is a low rectangle under the circular actuator. */
:root[data-shape="compact"] #asst-launch .jl-switch-foot {
  position:absolute;
  left:calc(var(--unit) * 4.3);
  right:calc(var(--unit) * 4.3);
  top:calc(var(--unit) * 4);
  bottom:calc(var(--unit) * 4);
  display:block;
  box-sizing:border-box;
  pointer-events:none;
  border:calc(var(--unit) * .55) solid hsl(var(--jl-h) calc(51% * var(--jl-sat)) calc(47% + (100% - 47%) * max(0, var(--jl-lum)) + 47% * min(0, var(--jl-lum))) / .4);
  border-radius:calc(var(--unit) * 1.7);
  background:hsl(var(--jl-h) calc(75% * var(--jl-sat)) calc(24% + (100% - 24%) * max(0, var(--jl-lum)) + 24% * min(0, var(--jl-lum))) / .32);
  box-shadow:inset 0 calc(var(--unit) * .35) calc(var(--unit) * .5)
    hsl(var(--jl-h) calc(46% * var(--jl-sat)) calc(59% + (100% - 59%) * max(0, var(--jl-lum)) + 59% * min(0, var(--jl-lum))) / .18);
}
/* Concentric moulded shoulders and a small solid core read as hardware,
   without borrowing the cap's icon or adding a shadow beneath the assembly. */
:root[data-shape="compact"] #asst-launch .jl-switch-hub {
  position:absolute;
  left:calc(var(--unit) * 7.3);
  top:calc(var(--unit) * 2.3);
  width:calc(var(--unit) * 18);
  height:calc(var(--unit) * 18);
  display:block;
  box-sizing:border-box;
  pointer-events:none;
  z-index:3;
  border:calc(var(--unit) * .7) solid hsl(var(--jl-h) calc(58% * var(--jl-sat)) calc(42% + (100% - 42%) * max(0, var(--jl-lum)) + 42% * min(0, var(--jl-lum))) / .61);
  border-radius:50%;
  background:radial-gradient(circle,
    hsl(var(--jl-h) calc(66% * var(--jl-sat)) calc(27% + (100% - 27%) * max(0, var(--jl-lum)) + 27% * min(0, var(--jl-lum))) / .58) 0 30%,
    hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(20% + (100% - 20%) * max(0, var(--jl-lum)) + 20% * min(0, var(--jl-lum))) / .55) 34% 43%,
    hsl(var(--jl-h) calc(61% * var(--jl-sat)) calc(44% + (100% - 44%) * max(0, var(--jl-lum)) + 44% * min(0, var(--jl-lum))) / .59) 47% 58%,
    hsl(var(--jl-h) calc(54% * var(--jl-sat)) calc(60% + (100% - 60%) * max(0, var(--jl-lum)) + 60% * min(0, var(--jl-lum))) / .35) 61% 68%,
    hsl(var(--jl-h) calc(73% * var(--jl-sat)) calc(28% + (100% - 28%) * max(0, var(--jl-lum)) + 28% * min(0, var(--jl-lum))) / .43) 72%);
  box-shadow:inset calc(var(--unit) * .35) calc(var(--unit) * .5)
    calc(var(--unit) * .8) hsl(var(--jl-h) calc(48% * var(--jl-sat)) calc(66% + (100% - 66%) * max(0, var(--jl-lum)) + 66% * min(0, var(--jl-lum))) / .2);
}
:root[data-shape="compact"] #asst-launch .jl-switch-hub::after {
  content:"";
  position:absolute;
  inset:calc(var(--unit) * 5.1);
  box-sizing:border-box;
  pointer-events:none;
  border:calc(var(--unit) * .5) solid hsl(var(--jl-h) calc(38% * var(--jl-sat)) calc(53% + (100% - 53%) * max(0, var(--jl-lum)) + 53% * min(0, var(--jl-lum))) / .42);
  border-radius:50%;
  background:linear-gradient(145deg,
    hsl(var(--jl-h) calc(40% * var(--jl-sat)) calc(49% + (100% - 49%) * max(0, var(--jl-lum)) + 49% * min(0, var(--jl-lum))) / .53),
    hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(25% + (100% - 25%) * max(0, var(--jl-lum)) + 25% * min(0, var(--jl-lum))) / .67));
}
/* A clear lens in the enclosure's own hue. Append after the compact and
   surface styles. A clear aperture in the front exposes the actual hardware;
   these transparent layers never blur or replace that optical opening. */
:root[data-shape="compact"] #asst-launch {
  --key-side:hsl(var(--jl-h) calc(77% * var(--jl-sat)) calc(36% + (100% - 36%) * max(0, var(--jl-lum)) + 36% * min(0, var(--jl-lum))) / .48);
}
:root[data-shape="compact"] #asst-launch .jl-key {
  color:hsl(var(--jl-h) calc(74% * var(--jl-sat)) calc(24% + (100% - 24%) * max(0, var(--jl-lum)) + 24% * min(0, var(--jl-lum))));
}
:root[data-shape="compact"] #asst-launch .jl-key:focus-visible {
  outline-color:hsl(var(--jl-h) calc(87% * var(--jl-sat)) calc(89% + (100% - 89%) * max(0, var(--jl-lum)) + 89% * min(0, var(--jl-lum))));
}
:root[data-shape="compact"] #asst-launch .jl-socket,
:root[data-shape="compact"] #asst-launch .jl-key:hover .jl-socket {
  background:radial-gradient(ellipse closest-side,
    transparent 0 68%,hsl(var(--jl-h) calc(78% * var(--jl-sat)) calc(25% + (100% - 25%) * max(0, var(--jl-lum)) + 25% * min(0, var(--jl-lum))) / .08) 74%,
    hsl(var(--jl-h) calc(76% * var(--jl-sat)) calc(22% + (100% - 22%) * max(0, var(--jl-lum)) + 22% * min(0, var(--jl-lum))) / .33) 83%,
    hsl(var(--jl-h) calc(71% * var(--jl-sat)) calc(80% + (100% - 80%) * max(0, var(--jl-lum)) + 80% * min(0, var(--jl-lum))) / .08) 90%,
    hsl(var(--jl-h) calc(72% * var(--jl-sat)) calc(32% + (100% - 32%) * max(0, var(--jl-lum)) + 32% * min(0, var(--jl-lum))) / .19) 96%,transparent 100%);
  border-color:hsl(var(--jl-h) calc(58% * var(--jl-sat)) calc(47% + (100% - 47%) * max(0, var(--jl-lum)) + 47% * min(0, var(--jl-lum))) / .23);
  box-shadow:
    inset 0 calc(var(--unit) * 1.1) calc(var(--unit) * 1.6) hsl(var(--jl-h) calc(84% * var(--jl-sat)) calc(14% + (100% - 14%) * max(0, var(--jl-lum)) + 14% * min(0, var(--jl-lum))) / .24),
    inset calc(var(--unit) * .55) calc(var(--unit) * .65) calc(var(--unit) * 1) hsl(var(--jl-h) calc(70% * var(--jl-sat)) calc(88% + (100% - 88%) * max(0, var(--jl-lum)) + 88% * min(0, var(--jl-lum))) / .10),
    0 calc(var(--unit) * .6) calc(var(--unit) * 1) hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(78% + (100% - 78%) * max(0, var(--jl-lum)) + 78% * min(0, var(--jl-lum))) / .08);
}
:root[data-shape="compact"] #asst-launch .jl-face,
:root[data-shape="compact"] #asst-launch .jl-key:hover .jl-face {
  background:transparent;
  border-color:hsl(var(--jl-h) calc(65% * var(--jl-sat)) calc(57% + (100% - 57%) * max(0, var(--jl-lum)) + 57% * min(0, var(--jl-lum))) / .27);
  color:hsl(var(--jl-h) calc(74% * var(--jl-sat)) calc(24% + (100% - 24%) * max(0, var(--jl-lum)) + 24% * min(0, var(--jl-lum))));
  transform:translateY(0);
  box-shadow:
    inset 0 0 calc(var(--unit) * 1.6) hsl(var(--jl-h) calc(70% * var(--jl-sat)) calc(36% + (100% - 36%) * max(0, var(--jl-lum)) + 36% * min(0, var(--jl-lum))) / .10),
    inset 0 calc(var(--unit) * -.9) calc(var(--unit) * 1.8) hsl(var(--jl-h) calc(81% * var(--jl-sat)) calc(28% + (100% - 28%) * max(0, var(--jl-lum)) + 28% * min(0, var(--jl-lum))) / .25),
    0 calc(var(--unit) * 1.6) 0 var(--key-side),
    0 calc(var(--unit) * 3.1) calc(var(--unit) * 2.2) hsl(var(--jl-h) calc(80% * var(--jl-sat)) calc(10% + (100% - 10%) * max(0, var(--jl-lum)) + 10% * min(0, var(--jl-lum))) / .30);
}
:root[data-shape="compact"] #asst-launch .jl-key:hover .jl-face {
  border-color:hsl(var(--jl-h) calc(74% * var(--jl-sat)) calc(72% + (100% - 72%) * max(0, var(--jl-lum)) + 72% * min(0, var(--jl-lum))) / .34);
}
/* The cap's tint is painted above the hardware, with a lightly coloured
   centre and a denser curved shoulder. The outer edge eases back down, rather
   than ending in a thick dark ring. No opaque fill covers the aperture. */
:root[data-shape="compact"] #asst-launch .jl-face::before {
  content:"";position:absolute;inset:0;z-index:2;
  padding:0;border:0;border-radius:inherit;pointer-events:none;
  background:
    radial-gradient(ellipse closest-side,
      hsl(var(--jl-h) calc(80% * var(--jl-sat)) calc(46% + (100% - 46%) * max(0, var(--jl-lum)) + 46% * min(0, var(--jl-lum))) / .105) 0 50%,
      hsl(var(--jl-h) calc(82% * var(--jl-sat)) calc(45% + (100% - 45%) * max(0, var(--jl-lum)) + 45% * min(0, var(--jl-lum))) / .13) 62%,
      hsl(var(--jl-h) calc(82% * var(--jl-sat)) calc(45% + (100% - 45%) * max(0, var(--jl-lum)) + 45% * min(0, var(--jl-lum))) / .24) 74%,
      hsl(var(--jl-h) calc(85% * var(--jl-sat)) calc(48% + (100% - 48%) * max(0, var(--jl-lum)) + 48% * min(0, var(--jl-lum))) / .40) 85%,
      hsl(var(--jl-h) calc(83% * var(--jl-sat)) calc(46% + (100% - 46%) * max(0, var(--jl-lum)) + 46% * min(0, var(--jl-lum))) / .50) 93%,
      hsl(var(--jl-h) calc(80% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .36) 100%),
    linear-gradient(160deg,hsl(var(--jl-h) calc(74% * var(--jl-sat)) calc(89% + (100% - 89%) * max(0, var(--jl-lum)) + 89% * min(0, var(--jl-lum))) / .045),transparent 42%,hsl(var(--jl-h) calc(79% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .045));
  -webkit-mask:none;mask:none;
  box-shadow:none;filter:none;
}
/* The whole oval carries a continuous convex light profile. Its crest is
   about three units inside the top edge, fading smoothly toward both the
   centre and silhouette. These subdued resting glints describe the broad
   curvature; the sharper pointer reflection has its own layer below. There
   is no inset clipping edge or solid white outline. */
:root[data-shape="compact"] #asst-launch .jl-face::after,
:root[data-shape="compact"] #asst-launch .jl-key:hover .jl-face::after {
  content:"";position:absolute;inset:0;z-index:2;
  padding:0;
  border:0;border-radius:inherit;pointer-events:none;
  background:
    radial-gradient(ellipse 23% 13% at 33% 13%,rgb(255 255 255 / .235),rgb(255 255 255 / .065) 39%,transparent 100%),
    linear-gradient(145deg,rgb(255 255 255 / .37),rgb(255 255 255 / .22) 28%,rgb(255 255 255 / .0375) 49%,transparent 61% 72%,hsl(var(--jl-h) calc(63% * var(--jl-sat)) calc(87% + (100% - 87%) * max(0, var(--jl-lum)) + 87% * min(0, var(--jl-lum))) / .12));
  -webkit-mask:radial-gradient(ellipse closest-side,
    transparent 0 40%,#0001 48%,#0004 57%,#0009 67%,#000d 76%,
    #000f 82%,#000f 84%,#000d 89%,#0007 94%,#0002 98%,transparent 100%);
  mask:radial-gradient(ellipse closest-side,
    transparent 0 40%,#0001 48%,#0004 57%,#0009 67%,#000d 76%,
    #000f 82%,#000f 84%,#000d 89%,#0007 94%,#0002 98%,transparent 100%);
  box-shadow:none;filter:none;
}
/* A large light source illuminates an extended arc around the oval. Only its
   cross-section is narrow: the specular crest sits at 84% of the ellipse's
   radius and fades on both sides. This makes the curved reflection crisp
   without reducing the pointer light to a small circular hotspot. */
:root[data-shape="compact"] #asst-launch .jl-key-polish {
  position:absolute;inset:0;z-index:2;display:block;
  padding:0;border:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(ellipse 75% 95% at var(--cap-pointer-x,50%) var(--cap-pointer-y,0%),
    rgb(255 255 255 / calc(var(--surface-light,0) * .78)),
    rgb(255 255 255 / calc(var(--surface-light,0) * .42)) 27%,
    rgb(255 255 255 / calc(var(--surface-light,0) * .10)) 58%,transparent 92%);
  -webkit-mask:radial-gradient(ellipse closest-side,
    transparent 0 77%,#0002 80%,#000a 82%,#000f 84%,#000d 86%,#0003 89%,transparent 92%);
  mask:radial-gradient(ellipse closest-side,
    transparent 0 77%,#0002 80%,#000a 82%,#000f 84%,#000d 86%,#0003 89%,transparent 92%);
  box-shadow:none;filter:none;
}
/* The clear cap keeps its polished shoulder and pointer light, without the
   diagonal camera-reflection streak. The LCD has its own separate glare. */
:root[data-shape="compact"] #asst-launch .jl-key-reflection {
  display:none;
}
:root[data-shape="compact"] #asst-launch .jl-key-reflection::before {
  content:none;
}
:root[data-shape="compact"] #asst-launch .jl-key .jl-mark {
  z-index:3;width:50%;height:80%;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;
}
:root[data-shape="compact"] #asst-launch .jl-mark .hi,
:root[data-shape="compact"] #asst-launch .jl-key:hover .jl-mark .hi {
  stroke:hsl(var(--jl-h) calc(37% * var(--jl-sat)) calc(97% + (100% - 97%) * max(0, var(--jl-lum)) + 97% * min(0, var(--jl-lum))) / .86);
}
:root[data-shape="compact"] #asst-launch .jl-mark .lo {
  stroke:hsl(var(--jl-h) calc(77% * var(--jl-sat)) calc(20% + (100% - 20%) * max(0, -1 * var(--jl-lum))) / .24);
}
/* A two-unit press collapses the small coloured side and contact shadow.
   Hover brightens the edge without lifting or enlarging the transparent cap. */
:root[data-shape="compact"] #asst-launch .jl-key:active .jl-face {
  transform:translateY(var(--key-travel));
  border-color:hsl(var(--jl-h) calc(68% * var(--jl-sat)) calc(55% + (100% - 55%) * max(0, var(--jl-lum)) + 55% * min(0, var(--jl-lum))) / .26);
  box-shadow:
    inset 0 0 calc(var(--unit) * 1.6) hsl(var(--jl-h) calc(70% * var(--jl-sat)) calc(32% + (100% - 32%) * max(0, var(--jl-lum)) + 32% * min(0, var(--jl-lum))) / .13),
    inset 0 calc(var(--unit) * -1) calc(var(--unit) * 1.5) hsl(var(--jl-h) calc(82% * var(--jl-sat)) calc(28% + (100% - 28%) * max(0, var(--jl-lum)) + 28% * min(0, var(--jl-lum))) / .32),
    0 calc(var(--unit) * .35) 0 var(--key-side),
    0 calc(var(--unit) * .8) calc(var(--unit) * 1.2) hsl(var(--jl-h) calc(83% * var(--jl-sat)) calc(10% + (100% - 10%) * max(0, var(--jl-lum)) + 10% * min(0, var(--jl-lum))) / .35);
}
:root[data-shape="compact"] #asst-launch .jl-key:active .jl-socket {
  box-shadow:
    inset 0 calc(var(--unit) * 1.6) calc(var(--unit) * 2) hsl(var(--jl-h) calc(84% * var(--jl-sat)) calc(14% + (100% - 14%) * max(0, var(--jl-lum)) + 14% * min(0, var(--jl-lum))) / .34),
    0 calc(var(--unit) * .55) calc(var(--unit) * .8) hsl(var(--jl-h) calc(66% * var(--jl-sat)) calc(78% + (100% - 78%) * max(0, var(--jl-lum)) + 78% * min(0, var(--jl-lum))) / .075);
}
/* Reflective LCD, pixel ink, concealed edge lamp, then the bezel's cast
   shadow. All of these remain below the existing screen-glass reflections. */
#asst-launch .jl-lcd-base {
  position:absolute;inset:0;
  display:block;z-index:0;
  pointer-events:none;border-radius:inherit;
  background:linear-gradient(145deg,#717962,#626f57 47%,#59694e);
}
/* One shared viewport keeps the pixel masks, active matrix bed and texture
   aligned. A cropped native mask can supply its aspect and cell counts. */
/* The grid sits on WHOLE DEVICE PIXELS. `--pixel-*` are written by companion.js `_snap()`, which
   picks the largest integer cell that fits and rounds the position the same way — a box centred
   with `translate(-50%,-50%)` lands on a half pixel whenever its size is odd, and a cell that is
   3.09 pixels wide has both its edges antialiased. The old rule sized this from a percentage and
   an aspect ratio, which almost never divides evenly by 30.
   The fallbacks are the old behaviour exactly, for the frame before the first measurement. */
#asst-launch .jl-pixel-art,
#asst-launch .jl-lamp-grain,
#asst-launch .jl-lcd-base::after,
#asst-launch .jl-lamp-field::after {
  position:absolute;
  left:50%;top:50%;
  width:calc(100% - var(--unit) * 20);
  height:auto;aspect-ratio:var(--pixel-aspect,3/2);
  display:block;
  transform:translate(-50%,-50%);
  pointer-events:none;
}
/* …and once `_snap()` has measured, the exact box replaces all of it: whole-pixel size, whole-pixel
   position, and no centring transform (which is itself a half pixel whenever the size is odd). */
#asst-launch.snapped .jl-pixel-art,
#asst-launch.snapped .jl-lamp-grain,
#asst-launch.snapped .jl-lcd-base::after,
/* `.snapped`, not `[style*="--pixel-x"]`: an attribute selector on `style` made EVERY inline-style write in the
   document schedule a descendant-invalidation walk — and the pointer light writes a dozen a frame (perf, round 23) */
#asst-launch.snapped .jl-lamp-field::after {
  left:var(--pixel-x);top:var(--pixel-y);
  width:var(--pixel-w);height:var(--pixel-h);
  aspect-ratio:auto;transform:none;
}
#asst-launch .jl-pixel-art {
  -webkit-mask:var(--creature-mask,linear-gradient(transparent,transparent)) center / 100% 100% no-repeat;
  mask:var(--creature-mask,linear-gradient(transparent,transparent)) center / 100% 100% no-repeat;
  image-rendering:pixelated;
}
#asst-launch .jl-lcd-base::after {
  content:"";
  background-image:
    radial-gradient(ellipse at center,rgb(28 46 30 / .1) 0 10%,transparent 17%),
    linear-gradient(145deg,rgb(41 61 37 / .18),rgb(29 49 30 / .25));
  background-size:calc(100% / var(--pixel-columns,36)) calc(100% / var(--pixel-rows,24)),100% 100%;
}
/* The active matrix is a nearly square-cornered inset in the LCD substrate,
   independent of the large rounded glass corners and reflected highlights. */
#asst-launch .jl-lcd-base::after,
#asst-launch .jl-lamp-field::after {
  border-radius:calc(var(--unit) * .3);
  box-shadow:inset 0 0 0 calc(var(--unit) * .4) rgb(29 47 28 / .16),
    0 calc(var(--unit) * .35) 0 rgb(170 186 132 / .07);
}
#asst-launch .jl-pixels-rest {
  z-index:2;
  background:linear-gradient(135deg,#243a34,#26382b 47%,#2b3d2b);
}
#asst-launch .jl-screen-lamp {
  position:absolute;inset:0;
  display:block;z-index:3;
  overflow:hidden;border-radius:inherit;
  pointer-events:none;
  opacity:0;
  transition:opacity 600ms ease-out;
}
#asst-launch .jl-screen[data-lamp="on"] .jl-screen-lamp {
  opacity:1;
  transition-duration:130ms;
}
/* Amber strikes the centre of the upper edge. The panel reflects less light
   toward its lower/right corner, which remains a subdued olive surface. */
#asst-launch .jl-lamp-field {
  position:absolute;inset:0;
  display:block;z-index:0;
  pointer-events:none;border-radius:inherit;
  background:
    linear-gradient(130deg,transparent 24%,rgb(21 32 10 / .025) 58%,rgb(13 27 12 / .17)),
    radial-gradient(ellipse 87% 105% at 50% -3%,
      rgb(241 172 36 / .97) 0%,
      rgb(222 177 47 / .94) 11%,
      rgb(187 162 69 / .87) 25%,
      rgb(150 146 72 / .78) 44%,
      rgb(114 128 67 / .63) 65%,
      rgb(81 103 51 / .3) 87%,
      transparent 100%);
}
#asst-launch .jl-lamp-field::after {
  content:"";
  background:linear-gradient(145deg,rgb(69 81 24 / .17),rgb(37 64 29 / .24));
}
#asst-launch .jl-pixels-lit {
  z-index:2;
  background:radial-gradient(ellipse 86% 110% at 50% -7%,
    #8e4e1b 0%,#795020 21%,#614b24 40%,#494a29 62%,#2c412b 87%,#273b2b);
}
#asst-launch .jl-lamp-grain {
  z-index:3;
  background-image:radial-gradient(ellipse at center,rgb(63 53 16 / .2) 0 10%,transparent 17%);
  background-size:calc(100% / var(--pixel-columns,36)) calc(100% / var(--pixel-rows,24));
  -webkit-mask-image:linear-gradient(135deg,#000 0%,#000b 55%,transparent 100%);
  mask-image:linear-gradient(135deg,#000 0%,#000b 55%,transparent 100%);
}
/* A short, saturated pool under the top lip distinguishes the bulb's local
   reach from the broad, weak illumination of the rest of the panel. */
#asst-launch .jl-screen-lamp::after {
  content:"";
  position:absolute;inset:0;
  z-index:1;pointer-events:none;border-radius:inherit;
  background:
    radial-gradient(ellipse 47% 55% at 50% -2%,
      rgb(255 164 30 / .54),
      rgb(244 157 26 / .27) 24%,
      rgb(188 144 24 / .065) 55%,transparent 84%),
    linear-gradient(90deg,transparent 27%,rgb(255 186 52 / .22) 42%,
      rgb(255 196 65 / .52) 50%,rgb(243 168 37 / .2) 58%,transparent 73%)
      left top / 100% calc(var(--unit) * 1.4) no-repeat;
}
#asst-launch .jl-screen-lamp::before {
  content:"";
  position:absolute;
  left:calc(50% - var(--unit) * 7.5);
  top:calc(var(--unit) * -1.7);
  width:calc(var(--unit) * 15);
  height:calc(var(--unit) * 4.5);
  z-index:4;pointer-events:none;border-radius:50%;
  background:radial-gradient(ellipse at 50% 51%,
    rgb(255 229 117 / .99) 0 10%,
    rgb(255 187 49 / .96) 25%,
    rgb(234 130 18 / .74) 48%,
    rgb(124 71 16 / .25) 70%,transparent 84%);
}
/* This belongs to the physical bezel, not to the bulb. It shades both the
   reflective substrate and its pixel ink, including when the lamp is off.
   The stronger first few units soften into a faint upper/left cast band. */
#asst-launch .jl-lcd-shadow {
  position:absolute;inset:0;
  display:block;z-index:4;
  pointer-events:none;border-radius:inherit;
  --bezel-shadow-background:
    linear-gradient(180deg,
      rgb(12 28 25 / .58),
      rgb(14 30 26 / .44) calc(var(--unit) * 2),
      rgb(17 33 27 / .3) calc(var(--unit) * 5),
      rgb(20 35 27 / .12) calc(var(--unit) * 10),
      rgb(20 35 27 / .035) calc(var(--unit) * 18),
      transparent calc(var(--unit) * 29)),
    linear-gradient(90deg,
      rgb(13 31 29 / .48),
      rgb(16 34 30 / .28) calc(var(--unit) * 3.5),
      rgb(20 38 31 / .1) calc(var(--unit) * 8),
      transparent calc(var(--unit) * 18));
  background:none;
}
/* The small internal source fills part of the ambient cast shadow directly
   beneath it. Two fixed shadow treatments crossfade with the lamp so the
   centre's relief never snaps between an unmasked and a masked surface. */
#asst-launch .jl-lcd-shadow::before,
#asst-launch .jl-lcd-shadow::after {
  content:"";
  position:absolute;inset:0;
  pointer-events:none;border-radius:inherit;
  background:var(--bezel-shadow-background);
  transition:opacity 600ms ease-out;
}
#asst-launch .jl-lcd-shadow::before {opacity:1}
#asst-launch .jl-lcd-shadow::after {
  opacity:0;
  -webkit-mask-image:radial-gradient(ellipse 34% 46% at 50% 0%,#0002 0 6%,#0008 28%,#000 74%);
  mask-image:radial-gradient(ellipse 34% 46% at 50% 0%,#0002 0 6%,#0008 28%,#000 74%);
}
#asst-launch .jl-screen[data-lamp="on"] .jl-lcd-shadow::before,
#asst-launch .jl-screen[data-lamp="on"] .jl-lcd-shadow::after {transition-duration:130ms}
#asst-launch .jl-screen[data-lamp="on"] .jl-lcd-shadow::before {opacity:0}
#asst-launch .jl-screen[data-lamp="on"] .jl-lcd-shadow::after {opacity:1}
:root[data-motion="off"] #asst-launch .jl-screen-lamp,
:root[data-motion="off"] #asst-launch .jl-lcd-shadow::before,
:root[data-motion="off"] #asst-launch .jl-lcd-shadow::after {transition:none}
/* His study gated this on the OS preference. Here the app's own switch is the authority and the
   OS preference is only its default — see the note on `data-motion` above, and round 21. */
:root[data-motion="off"] #asst-launch .jl-screen-lamp,
:root[data-motion="off"] #asst-launch .jl-lcd-shadow::before,
:root[data-motion="off"] #asst-launch .jl-lcd-shadow::after {transition:none}
/* A wider polished shoulder remains legible against the olive LCD. The
   continuous rounded-rectangle profile fades into the glass on both sides. */
#asst-launch .jl-glass-polish {display:none}
:root[data-shape="compact"] #asst-launch .jl-glass-polish {
  display:block;position:absolute;inset:0;z-index:4;
  border-radius:inherit;pointer-events:none;
  background:
    radial-gradient(ellipse 70% 90% at var(--glass-pointer-x,50%) var(--glass-pointer-y,0%),
      rgb(255 255 255 / calc(var(--edge-light,0) * .78)),
      rgb(255 255 255 / calc(var(--edge-light,0) * .3)) 40%,transparent 90%),
    linear-gradient(145deg,rgb(255 255 255 / .62),rgb(255 255 255 / .33) 28%,
      rgb(255 255 255 / .035) 53% 72%,rgb(255 255 255 / .2));
  -webkit-mask:var(--glass-shoulder-mask) center / 100% 100% no-repeat;
  mask:var(--glass-shoulder-mask) center / 100% 100% no-repeat;
}
:root[data-shape="compact"] #asst-launch .jl-screen::after {
  background:radial-gradient(ellipse 160% 210% at var(--glass-pointer-x,50%) var(--glass-pointer-y,40%),
    rgb(255 255 255 / calc(var(--lit,0) * .026)),
    rgb(255 255 255 / calc(var(--lit,0) * .006)) 48%,transparent 100%);
  box-shadow:
    inset 0 calc(var(--unit) * .6) 0 rgb(255 255 255 / .17),
    inset 0 calc(var(--unit) * -.5) 0 rgb(255 255 255 / .1),
    inset 0 0 0 calc(var(--unit) * .35) rgb(0 0 0 / .3);
}
/* A broad, soft environment reflection sits above the LCD. Overlapping
   rounded falloffs give the upper-left wash a gently uneven lower boundary,
   leaving the lower/right glass dark. This layer has no pointer dependency;
   the existing plane angles carry it opposite the pull and back on release. */
:root[data-shape="compact"] #asst-launch .jl-screen::before {
  content:"";position:absolute;
  left:-28%;top:-28%;right:auto;bottom:auto;
  width:150%;height:150%;z-index:2;
  border:0;border-radius:0;pointer-events:none;
  background:
    radial-gradient(ellipse 62% 57% at 30% 19%,
      rgb(236 239 235 / .135) 0 25%,
      rgb(234 237 233 / .105) 44%,
      rgb(234 237 233 / .045) 63%,
      rgb(234 237 233 / .012) 77%,transparent 88%),
    radial-gradient(ellipse 32% 42% at 17% 33%,
      rgb(227 232 228 / .06) 0 18%,
      rgb(227 232 228 / .04) 43%,
      rgb(227 232 228 / .012) 64%,transparent 81%);
  -webkit-mask:none;mask:none;
  opacity:1;
  transform:translate3d(
    calc((var(--glare-angle-x,0) * 52 + var(--glare-rest-x,0)) * var(--unit)),
    calc((var(--glare-angle-y,0) * 34 + var(--glare-rest-y,0)) * var(--unit)),0);
  transform-origin:50% 50%;
  box-shadow:none;filter:none;transition:none;
}

/* small screens: the desk is the app, so the companion shrinks — one number.
   Written at the shape's own specificity and placed last, because a media query adds none of its
   own and `:root[data-shape="compact"] #asst-launch` would otherwise keep it at full size. */
@media (max-width: 820px) {
  :root #asst-launch,
  /* `--dev-size-sm` is the chosen size already capped to 132 (prefs.js) — a smaller choice is kept, a larger one is not */
  :root[data-shape="compact"] #asst-launch { --jl-size: var(--dev-size-sm, 132px); right: 14px; bottom: 68px; }
}

/* ══ the glossy finish ═══════════════════════════════════════════════════════
   A second material for the same enclosure: polished resin with foil suspended in it, instead of
   the matte plastic above. Everything here is additive — `data-finish="glitter"` on <html> turns it
   on and nothing below is edited, so the matte casing is one attribute away and still correct.

   The three things it changes about the light are worth knowing:

     THE HAZE COMES OFF. The matte cover's grain and its wide sheen are hidden, and the internal
     blurs drop to about a fifth. Clear resin shows you the mouldings inside it and their real
     parallax; frosted resin is what the matte finish is for.

     THE HIGHLIGHT GETS AN EDGE. A polished surface reflects the ROOM — rectangular sources with
     defined, tapered edges, bent over the shoulders of the case. That is the SVG in
     `.jl-gloss-source`, which slides with the viewing angle, and the tight pointer pool in
     `.jl-glare::after`. The matte finish's soft white hairline along the silhouette is removed,
     because on a gloss it reads as an outline rather than as a reflection.

     THE FOIL IS DRAWN, NOT STYLED. `.jl-glitter` is a canvas; see `js/glitter.js` for why.

   Both new optical masks (`--gloss-shell-mask`, `--gloss-pointer-mask`) hold the light off the LCD
   and the key, so neither ever sits under a reflection you are trying to read through.

   Tinted colours here carry the same `--jl-sat` / `--jl-lum` as the rest of the casing, so the
   finish follows the user's accent — his study had one fixed green and did not need them. */
:root[data-finish="glitter"] #asst-launch {--gloss-shell-mask:url('../tex/gloss-shell-mask.png');--gloss-pointer-mask:url('../tex/gloss-pointer-mask.png');}
/* Separate polished-resin finish. Append after the compact device styles;
   the matte file remains unchanged. All optical masks are supplied by the
   glossy builder in the current shell's own coordinate system. */
:root[data-finish="glitter"] #asst-launch {
  --inside-blur:calc(var(--unit) * .18);
  /* twice what it was (2.1). This is the ONE that is in effect — the 4.8 further up belongs to the
     matte finish and is overridden here, and the polished resin is still the clearer of the two
     (4.2 against 4.8), which is the relationship that was already there. A thicker body wants a
     less legible desk behind it, or the shell reads as solid and the desk behind it as sharp glass. */
  --desk-blur:calc(var(--unit) * 4.2);
}
:root[data-finish="glitter"] #asst-launch .jl-grain,
:root[data-finish="glitter"] #asst-launch > .sheen {
  display:none;
}
:root[data-finish="glitter"] #asst-launch .jl-transmission {
  background:hsl(var(--jl-h) calc(91% * var(--jl-sat)) calc(26% + (100% - 26%) * max(0, var(--jl-lum)) + 26% * min(0, var(--jl-lum))) / .30);
}

/* Clear resin reveals the existing mouldings and their actual parallax.
   The modest residual softness describes depth without frosting the cover. */
:root[data-finish="glitter"] #asst-launch .jl-rear > *,
:root[data-finish="glitter"] #asst-launch .jl-internals > * {
  filter:blur(calc(var(--unit) * .18 + var(--fb,0px) * .25));
}
:root[data-finish="glitter"] #asst-launch .jl-internals {filter:none}
:root[data-finish="glitter"] #asst-launch .jl-back {
  filter:blur(calc(var(--unit) * .32 + var(--fb,0px) * .25));
  border-color:hsl(var(--jl-h) calc(87% * var(--jl-sat)) calc(35% + (100% - 35%) * max(0, var(--jl-lum)) + 35% * min(0, var(--jl-lum))) / .62);
  background:linear-gradient(140deg,
    hsl(var(--jl-h) calc(79% * var(--jl-sat)) calc(56% + (100% - 56%) * max(0, var(--jl-lum)) + 56% * min(0, var(--jl-lum))) / .11),
    hsl(var(--jl-h) calc(95% * var(--jl-sat)) calc(25% + (100% - 25%) * max(0, var(--jl-lum)) + 25% * min(0, var(--jl-lum))) / .26) 65%,
    hsl(var(--jl-h) calc(89% * var(--jl-sat)) calc(40% + (100% - 40%) * max(0, var(--jl-lum)) + 40% * min(0, var(--jl-lum))) / .19));
}
:root[data-finish="glitter"] #asst-launch .jl-wall {
  border-color:hsl(var(--jl-h) calc(85% * var(--jl-sat)) calc(43% + (100% - 43%) * max(0, var(--jl-lum)) + 43% * min(0, var(--jl-lum))) / .34);
}

/* The broader reflected sources describe the polish without a fine white
   outline along the silhouette. Transparent borders retain the geometry. */
:root[data-finish="glitter"] #asst-launch .jl-front {
  border-color:hsl(var(--jl-h) calc(80% * var(--jl-sat)) calc(52% + (100% - 52%) * max(0, var(--jl-lum)) + 52% * min(0, var(--jl-lum))) / .35);
  border-top-color:transparent;
  border-left-color:transparent;
  border-right-color:hsl(var(--jl-h) calc(86% * var(--jl-sat)) calc(34% + (100% - 34%) * max(0, var(--jl-lum)) + 34% * min(0, var(--jl-lum))) / .39);
  border-bottom-color:hsl(var(--jl-h) calc(88% * var(--jl-sat)) calc(30% + (100% - 30%) * max(0, var(--jl-lum)) + 30% * min(0, var(--jl-lum))) / .42);
  background:linear-gradient(135deg,
    hsl(var(--jl-h) calc(73% * var(--jl-sat)) calc(81% + (100% - 81%) * max(0, var(--jl-lum)) + 81% * min(0, var(--jl-lum))) / .045),
    transparent 27% 64%,
    hsl(var(--jl-h) calc(92% * var(--jl-sat)) calc(42% + (100% - 42%) * max(0, var(--jl-lum)) + 42% * min(0, var(--jl-lum))) / .085));
  box-shadow:inset 0 0 calc(var(--unit) * 1.1) hsl(var(--jl-h) calc(88% * var(--jl-sat)) calc(20% + (100% - 20%) * max(0, var(--jl-lum)) + 20% * min(0, var(--jl-lum))) / .20);
}
:root[data-finish="glitter"] #asst-launch .jl-front::before {
  content:none;
}
/* A broad cursor-light pool washes over the clear face. The rounded mask
   eases it away at the silhouette without reintroducing an edge hairline. */
:root[data-finish="glitter"] #asst-launch .jl-glare::before {
  content:"";position:absolute;inset:0;
  border:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(ellipse 70% 78% at var(--surface-pointer-x,50%) var(--surface-pointer-y,0%),
    rgb(255 255 255 / calc(var(--surface-light,0) * .17)),
    rgb(255 255 255 / calc(var(--surface-light,0) * .085)) 30%,
    rgb(255 255 255 / calc(var(--surface-light,0) * .025)) 58%,transparent 88%);
  -webkit-mask:var(--rounded-wash-mask) center / 100% 100% no-repeat;
  mask:var(--rounded-wash-mask) center / 100% 100% no-repeat;
  box-shadow:none;filter:none;
}
:root[data-finish="glitter"] #asst-launch .jl-glare::after {
  content:"";position:absolute;inset:0;
  border:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(ellipse 52% 58% at var(--surface-pointer-x,50%) var(--surface-pointer-y,0%),
    rgb(255 255 255 / calc(var(--surface-light,0) * .72)) 0 12%,
    rgb(255 255 255 / calc(var(--surface-light,0) * .54)) 28%,
    rgb(255 255 255 / calc(var(--surface-light,0) * .055)) 41%,transparent 54%);
  -webkit-mask:var(--gloss-pointer-mask) center / 100% 100% no-repeat;
  mask:var(--gloss-pointer-mask) center / 100% 100% no-repeat;
  box-shadow:none;filter:none;
}

/* Flakes are drawn individually by the finish runtime. Their canvas sits
   behind the polished cover, avoiding both the display and the clear key. */
:root[data-finish="glitter"] #asst-launch .jl-glitter {
  position:absolute;inset:0;width:100%;height:100%;
  display:block;z-index:4;border-radius:var(--case-outline);
  pointer-events:none;opacity:1;mix-blend-mode:normal;
  -webkit-mask:var(--gloss-shell-mask) center / 100% 100% no-repeat;
  mask:var(--gloss-shell-mask) center / 100% 100% no-repeat;
}

/* Broad bent softbox reflections have defined, tapered edges like the
   reference. Their stationary shell mask keeps them off the LCD and key. */
:root[data-finish="glitter"] #asst-launch .jl-gloss-reflection {
  position:absolute;inset:0;display:block;z-index:5;
  border-radius:var(--case-outline);pointer-events:none;
  -webkit-mask:var(--gloss-shell-mask) center / 100% 100% no-repeat;
  mask:var(--gloss-shell-mask) center / 100% 100% no-repeat;
}
:root[data-finish="glitter"] #asst-launch .jl-gloss-reflection::before {content:none}
:root[data-finish="glitter"] #asst-launch .jl-gloss-source {
  position:absolute;inset:0;width:100%;height:100%;display:block;overflow:visible;
  transform:translate3d(
    calc((var(--glare-angle-x,0) * 3.6 + var(--glare-rest-x,0) * .22) * var(--unit)),
    calc((var(--glare-angle-y,0) * 3 + var(--glare-rest-y,0) * .22) * var(--unit)),0);
  box-shadow:none;filter:none;transition:none;
}

/* Recessed marks use the same translucent resin and moving etched lip as
   Milo's lettering, instead of opaque black wells. */
:root[data-finish="glitter"] #asst-launch .jl-boss {display:none}
:root[data-finish="glitter"] #asst-launch .jl-speakers {
  position:absolute;inset:0;z-index:7;pointer-events:none;
}
:root[data-finish="glitter"] #asst-launch .jl-speaker-hole {
  position:absolute;
  left:calc(var(--hole-x) * var(--unit));
  top:calc(var(--hole-y) * var(--unit));
  width:calc(var(--unit) * 4.5);height:calc(var(--unit) * 4.5);
  transform:translate(-50%,-50%);border-radius:50%;
  /* LIT BY A GRADIENT, NOT A SHADOW — the same way the glass is (`.jl-front`, `--glass-pointer-x`).
     A shadow is a SHAPE at an OFFSET, and on a 3px hole an offset of a pixel has only a handful of places it
     can land: as the light swept, the crescent could sit top, left, right or bottom and nothing in between,
     however smoothly the numbers underneath were changing. A gradient is intensity per pixel: move its
     centre by a tenth of a pixel and every pixel changes by a tenth. That is why the case's reflection
     glides and this used to jump.

     Bounded by construction, too. The pale spot's RADIUS is fixed at 2.2 units and only its CENTRE moves,
     by at most 20% of the hole either way (`--hxn` never leaves -1.2..1.2), so it cannot thicken as the
     pointer goes far — which the offset version did, saturating at its widest exactly when you were not
     near it. Positions are positional, so no inset sign-flip here: light from the left is +hx, and the
     far wall it lights is the right, at 50% + hx. */
  background:
    radial-gradient(circle calc(var(--unit) * 2.2) at
        calc(50% + var(--hxn,0) * 20%) calc(62% + var(--hyn,0) * 20%),
      hsl(var(--jl-h) calc(68% * var(--jl-sat)) calc(76% + (100% - 76%) * max(0, var(--jl-lum)) + 76% * min(0, var(--jl-lum))) / calc(.22 + var(--surface-light,0) * .26)) 0%, transparent 100%),
    radial-gradient(circle calc(var(--unit) * 2) at
        calc(50% - var(--hxn,0) * 16%) calc(38% - var(--hyn,0) * 16%),
      hsl(var(--jl-h) calc(94% * var(--jl-sat)) calc(11% + (100% - 11%) * max(0, var(--jl-lum)) + 11% * min(0, var(--jl-lum))) / .33) 0%, transparent 100%),
    hsl(var(--jl-h) calc(81% * var(--jl-sat)) calc(18% + (100% - 18%) * max(0, var(--jl-lum)) + 18% * min(0, var(--jl-lum))) / .58);
}

/* solid, not the translucent chassis the fixed devices wear: this one floats over the middle of the
   desk and is a surface you READ. A print behind it showed straight through the type. `.sheet` (the
   store, team settings) already makes the same call for the same reason. */
/* and no backdrop blur: `.panel` sets one so the translucent chassis can blur what is behind it.
   With an opaque background there is nothing to blur, so it only costs a compositing layer. */
/* `bottom` is the resting value; ui-assistant.js `_place()` moves it to just above the device,
   which is measured rather than assumed because its height follows `--jl-size`. */
/* THE MOUNT. The window rides the desk's tilt like everything else on it, and this is what the
   plane turns: it holds the geometry, keeps a box even while the window is hidden (the plane skips
   anything with no width when it re-measures pivots), and passes the pointer straight through.
   The window inside keeps its own `transform` and its own origin for the way it opens — one
   element can only have one `transform-origin`, and the plane has already claimed the mount's. */
#asst-mount { position: fixed; right: 136px; bottom: 238px; width: 380px; height: 520px;
  z-index: 30; pointer-events: none; }
#asst-mount > * { pointer-events: auto; }

#assistant { position: absolute; inset: 0; right: 0; bottom: 0; width: auto; height: auto; display: flex; flex-direction: column; overflow: hidden;
  background: var(--chassis-solid); box-shadow: var(--hi), var(--drop-pop);
  -webkit-backdrop-filter: none; backdrop-filter: none;
  /* IT GROWS OUT OF THE MACHINE. Ben asked for something more like a speech bubble, and the honest
     way to say that here is not a tail or an outline — this design system has neither, and the note
     at the top of this block already settled that an answer is type on a chassis rather than
     chat-app candy. It is WHERE IT GROWS FROM. `--from-x` is the middle of the key you pressed and
     `--from-y` is the bottom edge, both measured in _place() because the device moves and resizes;
     so the window unfolds from the button, and folds back into it. */
  transform-origin: var(--from-x, 100%) var(--from-y, 100%);
  transform: translateY(6px) scale(.9);
  transition: opacity var(--t-mid) var(--ease), transform var(--t-slow) var(--spring); }
#assistant.in { transform: none; }
/* out is quicker than in and does not overshoot: a thing being put away does not bounce. */
#assistant:not(.in) { transition: opacity var(--t-fast) var(--ease), transform var(--t-mid) var(--ease); }
:root[data-motion="off"] #assistant { transition: none; }
/* two ways a picture arrives: a file dragged from the desktop (`drop-on`, an HTML5 drag) and one
   carried off the canvas (`drop-over`, which canvas.js puts on whatever [data-drop] is under the
   pointer). They look the same because to the user they are the same gesture. */
#assistant.drop-on, #assistant.drop-over { box-shadow: var(--hi), var(--drop-pop), inset 0 0 0 2px var(--accent); }

/* …and while an HTML5 drag is over the window, one element covers the lot. It is not decoration:
   without it the drag reaches the textarea, which is a text drop target, so the browser puts a
   caret in and starts selecting instead of offering to take the picture. */
.asst-drop { position: absolute; inset: 0; z-index: 6; display: none; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  border-radius: inherit; background: color-mix(in srgb, var(--chassis-solid), transparent 8%); color: var(--accent);
  font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .07em; }
#assistant.drop-on .asst-drop { display: flex; }
.asst-drop span { color: var(--ink-2); }

/* the top-left corner resizes it — the only corner that grows the window away from the desk edge */
.asst-grip { position: absolute; left: 0; top: 0; width: 18px; height: 18px; z-index: 4; cursor: nwse-resize; touch-action: none; }

/* Centred on its MARKS, not on its boxes — which is the whole reason it read as off.
   Vertically: the row is 28 tall in a head whose content field is 50, so the padding is 11/11, not
   13/9 (which rode it 2px low). Horizontally: the brand's glyph starts 14px in, while the last
   `.tool` is a 32px box carrying a 14px icon — 9px of it is dead space — so 5px of padding puts
   its visible mark at the same 14. The eye reads the mark; the box is invisible. */
.asst-head { display: flex; align-items: center; gap: 8px; padding: 11px 5px 11px 14px; border-bottom: var(--bw) solid var(--line); flex: 0 0 auto; }
.asst-mark { display: inline-flex; color: var(--accent); }
.asst-title { font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .07em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46%; }
.asst-sub { flex: 1; min-width: 0; font: 500 10.5px var(--font-mono); color: var(--ink-3); text-transform: uppercase; letter-spacing: .05em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.asst-head-tools { display: flex; gap: 2px; flex: 0 0 auto; }

/* ── the transcript ─────────────────────────────────────────────────────── */
.asst-log { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 14px 14px 8px;
  display: flex; flex-direction: column; gap: 13px; scrollbar-width: thin; scrollbar-color: var(--chassis-4) transparent; }
.asst-log::-webkit-scrollbar { width: 8px; }
.asst-log::-webkit-scrollbar-thumb { background: var(--chassis-4); border-radius: 4px; }
.asst-log::-webkit-scrollbar-track { background: transparent; }

.asst-msg { display: flex; flex-direction: column; gap: 6px; min-width: 0; animation: asst-rise .28s var(--ease) both; }
@keyframes asst-rise { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
:root[data-motion="off"] .asst-msg { animation: none; }

/* a user message is a sheet of paper, set in from the left so the two speakers are never confused */
.asst-msg.from-user { align-self: flex-end; align-items: flex-end; max-width: 86%; }
.asst-msg.from-user .asst-body { background: var(--field); color: var(--field-ink); border-radius: var(--r-display);
  box-shadow: var(--cut); padding: 9px 12px; font-size: 12.5px; line-height: 1.55; user-select: text; -webkit-user-select: text; }
/* the assistant has no paper: it is the device talking */
.asst-msg.from-asst { align-self: stretch; }
.asst-msg.from-asst .asst-body { color: var(--ink); font-size: 12.5px; line-height: 1.62; user-select: text; -webkit-user-select: text; }

/* A LINE WITH NOWHERE TO BREAK still has to stay in the window.
   A `dk1:` creature code is 175 characters with not one space in it, and a word with no break
   opportunity sets its own minimum width: the bubble measured 1358px inside a 378px log and hung
   out over the edge of the panel. (It escapes to the LEFT, because a user bubble is right-aligned,
   so the log never even scrolls to tell you.) `anywhere` rather than `break-word` on purpose —
   only `anywhere` counts toward the intrinsic minimum width, which is the number that was wrong;
   `break-word` wraps the painted text and leaves the box just as wide. Long URLs and file names
   were the same bug waiting to happen, so it goes on both speakers' text rather than on the code. */
.asst-body { min-width: 0; max-width: 100%; overflow-wrap: anywhere; }
.asst-body > :first-child { margin-top: 0; }
.asst-body > :last-child { margin-bottom: 0; }
.asst-body p { margin: 0 0 .6em; }
.asst-body h3, .asst-body h4, .asst-body h5, .asst-body h6 { margin: 1em 0 .4em; font: 600 11px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-2); }
.asst-body ul, .asst-body ol { margin: 0 0 .6em; padding-left: 1.15em; }
.asst-body li { margin: .18em 0; }
.asst-body code { font-family: var(--font-mono); font-size: .88em; background: var(--well); border-radius: 3px; padding: .08em .34em; }
.asst-body pre { margin: .5em 0 .7em; padding: 9px 11px; background: var(--display); color: var(--display-ink); border-radius: var(--r-display);
  box-shadow: var(--cut-glass); overflow-x: auto; }
/* …except inside a fenced block, where re-flowing would change what the code says. That one keeps
   its lines and scrolls in its own box, which `pre` above already does. */
.asst-body pre code { background: none; padding: 0; font-size: 11px; line-height: 1.5; color: inherit;
  overflow-wrap: normal; white-space: pre; }
.asst-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.asst-body strong { font-weight: 600; }

/* a long paste, folded to its first lines until it is asked for (_userBubble). The paper fades
   out rather than stopping at a hard edge, so it reads as more below, not as the end. */
.asst-msg.from-user .asst-body.folded { max-height: 13.5em; overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent); mask-image: linear-gradient(to bottom, #000 72%, transparent); }
.asst-more { border: 0; background: none; padding: 0 2px; cursor: pointer;
  font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); }
.asst-more:hover { color: var(--ink-2); }

/* pictures the user attached, shown in their own message */
.asst-pics { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
.asst-pics img { max-width: 128px; max-height: 128px; border-radius: 3px; box-shadow: var(--shadow-pic); }

/* ── a tool call: a small dark-glass readout, one line ───────────────────── */
.asst-tool { display: inline-flex; align-items: center; gap: 8px; align-self: flex-start; max-width: 100%;
  background: var(--display); color: var(--display-ink-2); border-radius: var(--r-display); box-shadow: var(--cut-glass);
  padding: 5px 10px 5px 9px; font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .05em; }
.asst-tool-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--display-ink-3); flex: 0 0 auto; }
.asst-tool.pending .asst-tool-dot { background: var(--accent); animation: blink 1s ease-in-out infinite; }
.asst-tool.done .asst-tool-dot { background: var(--ok); }
.asst-tool.failed .asst-tool-dot { background: var(--danger); }
.asst-tool.failed { color: var(--display-ink); }
.asst-tool-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* what it thought, folded away — there when you want it, silent when you don't */
.asst-thought { align-self: flex-start; max-width: 100%; }
.asst-thought summary { display: flex; align-items: center; gap: 6px; cursor: pointer; list-style: none;
  font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .05em; color: var(--ink-3); }
.asst-thought summary::-webkit-details-marker { display: none; }
.asst-thought summary:hover { color: var(--ink-2); }
.asst-thought[open] summary { margin-bottom: 5px; }
.asst-thought-body { font-size: 11.5px; line-height: 1.55; color: var(--ink-3); padding-left: 18px; user-select: text; -webkit-user-select: text; }
.asst-thought-body p { margin: 0 0 .5em; }
/* …and while it is still thinking, the last line of it, live */
.asst-thought-live { font: 400 11.5px var(--font-ui); color: var(--ink-3); line-height: 1.5; min-height: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* waiting: three dots, the same tempo as the readout's led */
.asst-status { display: flex; align-items: center; gap: 9px; color: var(--ink-3); font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; }
.asst-dots { display: inline-flex; gap: 3px; }
.asst-dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); opacity: .3; animation: asst-dot 1.1s ease-in-out infinite; }
.asst-dots i:nth-child(2) { animation-delay: .16s; }
.asst-dots i:nth-child(3) { animation-delay: .32s; }
@keyframes asst-dot { 0%, 70%, 100% { opacity: .28; transform: none; } 35% { opacity: 1; transform: translateY(-2px); } }
:root[data-motion="off"] .asst-dots i { animation: none; opacity: .6; }

.asst-actions { display: flex; gap: 2px; opacity: 0; transition: opacity var(--t-fast); }
.asst-msg:hover .asst-actions, .asst-msg:focus-within .asst-actions { opacity: 1; }
.asst-act { height: 24px; min-width: 24px; padding: 1px 6px; color: var(--ink-3); }
.asst-stopped { font: 500 10.5px var(--font-mono); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); }

.asst-system { display: flex; align-items: flex-start; gap: 7px; padding: 7px 10px; border-radius: var(--r-display);
  background: rgb(255 74 74 / .1); color: var(--danger); font-size: 11.5px; line-height: 1.45; }
.asst-system .icon { flex: 0 0 auto; margin-top: 1px; }

/* nothing said yet */
.asst-empty { margin: auto 0; display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; padding: 8px 4px 16px; }
.asst-empty-mark { color: var(--accent); opacity: .8; }
.asst-empty p { margin: 0; max-width: 260px; font-size: 12px; line-height: 1.5; color: var(--ink-3); }
.asst-seeds { display: flex; flex-direction: column; gap: 5px; width: 100%; margin-top: 2px; }
.asst-seed { display: block; width: 100%; text-align: left; padding: 7px 11px; border: 0; border-radius: var(--r-display);
  background: var(--chassis-3); color: var(--ink-2); font: 400 11.5px var(--font-ui); cursor: pointer; transition: background var(--t-fast), color var(--t-fast); }
.asst-seed:hover { background: var(--chassis-4); color: var(--ink); }

/* ── the composer ───────────────────────────────────────────────────────── */
.asst-foot { flex: 0 0 auto; padding: 8px 12px 12px; border-top: var(--bw) solid var(--line); }
.asst-strip { display: none; gap: 6px; flex-wrap: wrap; padding-bottom: 8px; }
.asst-strip.has { display: flex; }
.asst-chip { position: relative; width: 46px; height: 46px; border-radius: 3px; overflow: hidden; background: var(--chassis-3); box-shadow: var(--shadow-pic); }
.asst-chip img { width: 100%; height: 100%; object-fit: cover; }
.asst-chip.busy::after { content: ''; position: absolute; inset: 0; background: rgb(0 0 0 / .35);
  background-image: linear-gradient(115deg, transparent 30%, rgb(255 255 255 / .22) 50%, transparent 70%); background-size: 220% 100%;
  animation: asst-sheen 1.1s linear infinite; }
@keyframes asst-sheen { from { background-position: 120% 0; } to { background-position: -120% 0; } }
.asst-chip-ph { width: 100%; height: 100%; background: var(--chassis-4); }
.asst-chip-x { position: absolute; top: 2px; right: 2px; width: 16px; height: 16px; display: grid; place-items: center;
  border: 0; border-radius: 50%; background: rgb(0 0 0 / .58); color: #fff; cursor: pointer; opacity: 0; transition: opacity var(--t-fast); }
.asst-chip:hover .asst-chip-x { opacity: 1; }

.asst-composer { display: flex; align-items: flex-end; gap: 6px; padding: 5px 5px 5px 7px;
  background: var(--field); border-radius: var(--r-display); box-shadow: var(--cut); transition: box-shadow var(--t-fast); }
.asst-composer:focus-within { box-shadow: var(--cut), 0 0 0 2px var(--accent); }
/* 12.5px at 1.5 is an 18.75px line; with 6px of padding each side the scrollHeight came out at 31
   against a 30px box, so the very first keystroke grew the composer by a pixel, every time. 5px
   makes the content exactly fit the box it is given. */
.asst-input { flex: 1; min-width: 0; height: 30px; max-height: 160px; resize: none; border: 0; background: none; outline: none;
  color: var(--field-ink); font: 400 12.5px/1.5 var(--font-ui); padding: 5px 2px; user-select: text; -webkit-user-select: text; }
.asst-input::placeholder { color: var(--field-ink-3); }
/* ONE HEIGHT FOR THE ROW. The composer is `align-items: flex-end` — which has to stay, or the send
   key leaves the bottom as the textarea grows — and this key was 28 tall in a row of 30. Bottom
   aligned, that put its glyph a pixel below the send's and below the first line of type. It was
   never a margin; it was a different height in a row that aligns on the bottom edge. */
.asst-attach { height: 30px; min-width: 30px; padding: 1px 5px; color: var(--field-ink-3); flex: 0 0 auto; }
.asst-attach:hover { background: var(--field-hover); color: var(--field-ink); }
.asst-send { flex: 0 0 auto; width: 30px; height: 30px; display: grid; place-items: center; border: 0; border-radius: var(--r-key);
  --face: var(--accent); color: var(--accent-ink); cursor: pointer;
  /* the lip every other accent key in the workshop has (`.btn.primary`, `.make`) — this was the
     only one without one, so it read flat next to them */
  box-shadow: var(--hi-accent), var(--contact), inset 0 calc(-1 * var(--press)) 0 var(--accent-lip);
  /* THE LIP IS PART OF THE KEY'S HEIGHT. It paints a 2px band across the bottom, so the face you
     actually read is the top 28 of 30 — and a glyph centred in the box sits a pixel below the
     centre of that face. On a paper plane nobody could tell; on the stop button's square, which is
     a perfect symmetrical shape, it is obvious. The padding gives the grid the face to centre in
     rather than the whole box. (`bevels count toward a control's height`, CLAUDE.md §6.) */
  padding-bottom: var(--press);
  transition: filter var(--t-fast), transform var(--t-fast); }
.asst-send:hover { filter: brightness(1.07); }
.asst-send:active { transform: translateY(1px); }
/* Stopping is not the accent action, so it does not wear the accent's lip. The background was
   already switching to red while the lip and the highlight stayed orange — a red key with an
   orange bevel under it. */
#assistant.busy .asst-send { background: var(--danger);
  box-shadow: var(--hi-accent), var(--contact), inset 0 calc(-1 * var(--press)) 0 var(--danger-lip); }

/* a field the assistant just filled says so, briefly */
.field.assistant-touched { animation: asst-touch 1.6s var(--ease) both; }
@keyframes asst-touch {
  0% { box-shadow: inset 0 0 0 2px rgb(var(--accent-rgb) / .85); }
  30% { box-shadow: inset 0 0 0 2px rgb(var(--accent-rgb) / .7); }
  100% { box-shadow: inset 0 0 0 2px rgb(var(--accent-rgb) / 0); }
}
:root[data-motion="off"] .field.assistant-touched { animation-duration: .01s; }

/* small screens: the assistant takes the desk, not a corner of it.
   (The device's own corner and size are set in the companion block, at the shape's specificity.) */
@media (max-width: 820px) {
  #asst-mount { right: 10px; left: 10px; bottom: 10px; width: auto !important; height: min(74vh, 560px) !important; }
  .asst-grip { display: none; }
}
