/* =========================================================================
   studiolab.css — THE SPECIMEN LAB studio theme.

   WHAT THIS IS. Arkadia Survival's live skin studio, ported into webcore as a
   THEME the assembler switches on per owner (`web.studio.theme: "lab"`).
   Owner order 2026-08-17, Frontier Prime: "make it like arkadia instead, like
   actually, design wise, like 1:1 ... same exact look, not just reference."

   ★★★★★ READ THIS BEFORE CHANGING ANYTHING — THE FIRST PORT GOT IT WRONG.
   Arkadia's page loads FIVE sheets over one DOM, and the LAST one wins:
     _components.css -> lab.css -> crt.css -> ark-living.css -> mobile.css
   The first attempt at this port read `crt.css` and stopped there, and
   shipped a cyan phosphor terminal. `crt.css` is NOT the look. `ark-living.css`
   ("ISLA VIVA") loads after it and re-dresses the whole thing: it deletes the
   scanlines, the registration brackets and the `> SPECIMEN LAB_` prompt, drops
   VT323 for Fraunces + Inter, repaints cyan to warm brown / jade / amber, and
   turns the floating chrome into THREE DOCKED COLUMNS. The owner's reply to
   the cyan version was "its not like arkadia at all. like at all dude."

   So this sheet is the FINAL rendered design in one layer, read off
   https://arkadiasurvival.com/skin/ as it actually paints — not off a
   stylesheet in isolation. If you change it, re-render both pages and compare
   the pictures. A sheet read is not a page seen.

   THE SHAPE, top to bottom:
     · a three-column grid — species list | stage | skin controls
     · a warm painted chamber, lit from above, with film grain and a vignette
     · the specimen's name over it in Fraunces, growth pill above that
     · instruments docked top-LEFT of the stage, sliders bottom-centre
     · an amber tool pill across the foot of the stage
     · the right column has TWO FACES behind tabs: Editor and Library

   FOUR THINGS THAT ARE DELIBERATE — Arkadia's own notes, and they hold here:
     1. The controls are DOCKED in grid columns, not floating over the stage,
        so the stage loses exactly the two rail widths and studio.mjs's
        ResizeObserver keeps the camera aspect right for free.
     2. Panels are near-OPAQUE, not glass. The chamber colour is player-chosen
        and one preset is white; text over a live render cannot be certified
        for contrast.
     3. Rail chrome is pinned to jade/amber, NEVER --accent. The chamber
        buttons rewrite --accent, so accent-driven chrome would repaint the
        whole tool pink when a player picks the pink backdrop. The stage
        tints; the tool does not.
     4. NO backdrop-filter and NO mix-blend-mode anywhere. Both force the
        compositor to re-sample a LIVE 3D canvas every frame. The surfaces are
        opaque enough not to need it, and the page holds frame rate on weak
        GPUs because of it.

   SEATING MAP — Arkadia's element -> the webcore element wearing it:
     #slot-list / .slot        -> #channel-list / .channel   (the colour rows)
     #species-rail-list        -> #deck-list                 (the species list)
     .preset-row               -> .design-row                (the library)
     .gcard                    -> .glitch-tile               (the glitch shelf)
     the drawer's import block -> #code-row                  (skin codes)
   ========================================================================= */

/* -------------------------------------------------------------------------
   0. TOKENS — Arkadia's own palette, from its _shared.css.
   The donor's VALUE relationships carry over; nothing else does.
   ------------------------------------------------------------------------- */
body.studiolab {
  --av-serif: 'Fraunces', Georgia, 'Times New Roman', serif;
  --av-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --av-ink: #f5e9d8;
  --av-ink-2: rgba(245, 233, 216, .68);
  --av-ink-3: rgba(245, 233, 216, .42);

  --av-page: #0a0806;
  --av-panel: #17110c;
  --av-panel-2: #1f160f;
  --av-line: #4a3626;
  --av-line-dim: #2e2117;

  --av-jade: #2fbf87;
  --av-jade-lt: #7fe0b8;
  --av-jade-hot: #a8f0d0;
  --av-amber: #c9924e;
  --av-amber-lt: #ffc472;
  --av-focus: #ffb347;

  --av-edge: rgba(127, 224, 184, .17);
  --av-edge-lit: rgba(127, 224, 184, .55);

  --av-rail-w: 336px;      /* skin controls, right */
  --av-species-w: 246px;   /* species list, left  */

  /* The chamber accent. studiolab.js rewrites THIS ONE from the chosen
     chamber colour, and only the stage reads it. */
  --av-chamber: #2fbf87;
  --av-chamber-rgb: 47, 191, 135;

  /* ★★★★★ THE RUNTIME PROPERTIES ARE **NOT** DECLARED HERE, AND THAT IS THE
     WHOLE POINT. studiolab.js publishes --lab-dock-h, --lab-bar and
     --lab-bottom; every read below carries its own `var(..., fallback)`, so a
     page whose script never ran still lays out.

     Declaring them in this block looked tidier and was a BUG: this block sits
     on `body.studiolab`, so a value the script set on the ROOT element was
     SHADOWED for every descendant of body - the sheet reservation computed
     376px, the body's own `--lab-dock-h: 0px` won, and the phone's foot strip
     laid out underneath the sheet with the clip keys behind it. A token
     default and a runtime value must never name the same property on an
     element between the writer and the reader. */
}

/* -------------------------------------------------------------------------
   1. THE PAGE SHELL — three columns, no page scroll.
   ------------------------------------------------------------------------- */
html:has(body.studiolab) { height: 100%; overflow: hidden; }
body.studiolab {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
  height: 100%;
  min-height: 100dvh;
  margin: 0;
  padding: 0 !important;
  overflow: hidden;
  background: var(--av-page) !important;
  color: var(--av-ink);
  font-family: var(--av-sans);
}
/* THE LIVEBAR RESERVATION SURVIVES THE LAB (Jurassic Den 2026-08-18, his
   screenshot: the site's fixed status rail sat OVER the swatch strip and the
   rail's own save / wear / skin-code foot - "you can't see what it say at the
   bottom as the players bar is in the way"). The shell's `padding: 0
   !important` above rightly strips the page chrome, but it also stripped
   site.css's `body.has-livebar { padding-bottom: var(--livebar-h) }`
   reservation, and every 100%-height column then ran under the fixed bar
   (z-livebar 90). Restore exactly that one clearance, at higher specificity.
   body itself is NOT covered by the descendant box-sizing rule below, so say
   border-box here or the padding would grow past height:100%. */
body.studiolab { box-sizing: border-box; }
body.studiolab.has-livebar {
  /* +12px: the bar FLOATS ~10px off the viewport foot, so it occupies
     ~livebar-h + 10 of screen; the bare token left the deck list 7.5px under
     the bar's top edge (measured in the /validate render, cosmetic only). */
  padding-bottom: calc(var(--livebar-h, 44px) + 12px) !important;
}
body.studiolab *, body.studiolab *::before, body.studiolab *::after {
  box-sizing: border-box;
}
body.studiolab > header,
body.studiolab > .site-header,
body.studiolab > #sky-topbar { grid-row: 1; grid-column: 1 / -1; z-index: 32; }
body.studiolab > .skip-link, body.studiolab > .skip { position: absolute; }

#workspace { grid-row: 2; grid-column: 1; position: relative; min-width: 0; min-height: 0; overflow: hidden; }
.lab-main { position: relative; display: flex; flex-direction: column; height: 100%; min-height: 0; margin: 0; padding: 0; }
#workspace, #panel, .viewport-wrap { min-width: 0; }
#panel, #panel * { max-width: 100%; }

/* the page's own heading, for a screen reader. The stage carries no title -
   the specimen's name IS the heading, exactly as on the donor. */
.lab-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }

/* -------------------------------------------------------------------------
   2. THE CHAMBER — a PAINTED STUDIO, never a photograph.
   A graded wall, a warm pool where the specimen stands, one light shaft,
   grain, vignette. Plain gradients: behind a lit 3D model a photograph
   competes with the subject at the same depth.
   ------------------------------------------------------------------------- */
.viewport-wrap {
  position: relative; width: 100%; height: 100%;
  min-width: 0; min-height: 0; overflow: clip; overscroll-behavior: contain;
  border: 1px solid rgba(127, 224, 184, .14);
  border-top: 0; border-bottom: 0;
  background:
    radial-gradient(92% 72% at 50% 2%, rgba(var(--av-chamber-rgb), .13), transparent 62%),
    radial-gradient(66% 56% at 14% 100%, rgba(201, 146, 78, .13), transparent 60%),
    radial-gradient(52% 26% at 50% 92%, rgba(255, 196, 114, .09), transparent 70%),
    linear-gradient(#151009, #0b0806);
}
#stage-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 2; cursor: grab; touch-action: none;
}
#stage-canvas:active { cursor: grabbing; }

/* ── THE ATMOSPHERE ────────────────────────────────────────────────────
   A warm floor haze with three slow puffs, and fine motes rising through the
   light shaft. Both sit BEHIND the specimen (z-index 1) so nothing ever reads
   as dirt on the lens in front of it, and both animate TRANSFORM and OPACITY
   only - no layout, no paint of a live 3D canvas, no compositor read-back.
   studiolab.js builds the motes; the Effects key takes the whole layer away. */
/* the haze spans the CHAMBER, not just the floor - the donor's bokeh sit at
   every height, and a layer pinned low reads as fog on the ground instead of
   light in the room. */
#lab-cloud {
  position: absolute; left: 0; right: 0; top: 18%; bottom: 0;
  z-index: 1; pointer-events: none;
}
#lab-cloud .base {
  position: absolute; left: 50%; bottom: 4%; transform: translateX(-50%);
  width: 56%; height: 66px; border-radius: 50%; filter: blur(18px);
  background: radial-gradient(ellipse, rgba(10, 6, 4, .72), transparent 72%);
}
#lab-cloud .contact {
  position: absolute; left: 50%; bottom: 16%; transform: translateX(-50%);
  width: 30%; height: 62px; border-radius: 50%; filter: blur(14px);
  background: radial-gradient(ellipse, rgba(201, 146, 78, .24), transparent 72%);
}
#lab-cloud .puff {
  position: absolute; border-radius: 50%; filter: blur(26px);
  animation: lab-drift var(--ds, 15s) ease-in-out infinite alternate;
}
#lab-cloud .puff.p1 { left: 6%;  top: 16%; width: 26%; height: 40%; --ds: 15s; --dx: 18px;
  background: radial-gradient(circle, rgba(255, 196, 114, .22), transparent 68%); }
#lab-cloud .puff.p2 { right: 8%; top: 8%;  width: 22%; height: 34%; --ds: 19s; --dx: -15px;
  background: radial-gradient(circle, rgba(127, 224, 184, .15), transparent 68%); animation-delay: -6s; }
#lab-cloud .puff.p3 { left: 34%; top: 46%; width: 30%; height: 40%; --ds: 23s; --dx: 11px;
  background: radial-gradient(circle, rgba(201, 146, 78, .18), transparent 70%); animation-delay: -12s; }
@keyframes lab-drift {
  from { transform: translate(0, 0) scale(1); }
  to   { transform: translate(var(--dx, 12px), -10px) scale(1.06); }
}

#lab-parts { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
#lab-parts .mote {
  position: absolute; border-radius: 50%;
  background: var(--mote, #ffc472);
  box-shadow: 0 0 var(--glow, 8px) calc(var(--glow, 8px) / 2) rgba(255, 196, 114, .16);
  filter: var(--soft, none);
  opacity: 0;
  animation: lab-rise var(--dur, 17s) linear infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes lab-rise {
  0%   { opacity: 0;   transform: translate3d(0, 0, 0) scale(.7); }
  12%  { opacity: var(--peak, .5); }
  80%  { opacity: var(--peak, .5); }
  100% { opacity: 0;   transform: translate3d(var(--dx, 14px), calc(-1 * var(--rise, 260px)), 0) scale(1.05); }
}

/* film grain, blended by OPACITY only — mix-blend-mode over a live canvas
   costs a full compositor read-back every frame */
#lab-grain {
  position: absolute; inset: 0; z-index: 3; pointer-events: none; opacity: .045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.7'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}
/* a soft daylight vignette, not a CRT tunnel */
#lab-vig {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  box-shadow: inset 0 0 210px 58px rgba(6, 4, 3, .62), inset 0 0 80px 10px rgba(0, 0, 0, .28);
}
/* the light shaft from above. The falloff is IN the gradient, so it needs no
   filter:blur() — one less full-layer filter on every repaint. */
#lab-vig::after {
  content: ""; position: absolute; left: -8%; top: -30%; width: 64%; height: 120%;
  transform: rotate(17deg); pointer-events: none;
  background: linear-gradient(90deg,
    rgba(255, 238, 206, .075) 0%, rgba(255, 238, 206, .045) 26%,
    rgba(255, 238, 206, .015) 48%, transparent 68%);
}
.vp-overlay { position: absolute; inset: 0; z-index: 4; pointer-events: none; }

/* -------------------------------------------------------------------------
   3. THE SPECIMEN CAPTION — top middle.
   growth pill, then the saved-look cycler, then the species name with its two
   arrows, as one centred stack above the animal.
   ------------------------------------------------------------------------- */
.stage-growth {
  position: absolute; left: 50%; top: 26px; transform: translateX(-50%);
  z-index: 7; max-width: calc(100% - 32px); pointer-events: auto;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 7px 14px; border-radius: 999px;
  background: rgba(16, 12, 8, .82); border: 1px solid rgba(245, 233, 216, .12);
}
.stage-growth .gr-label, .stage-growth .gr-readout {
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--av-ink-3); white-space: nowrap;
}
.stage-growth .gr-readout { min-width: 92px; text-align: left; }
.stage-growth #growth-pct {
  color: var(--av-jade-lt); font-variant-numeric: tabular-nums;
  display: inline-block; min-width: 34px; text-align: right;
}

.vp-center-stack {
  position: absolute; left: 50%; top: 74px; transform: translateX(-50%);
  z-index: 6; display: flex; flex-direction: column; align-items: center;
  gap: 10px; max-width: calc(100% - 32px); pointer-events: none;
}
.skin-cycler {
  order: 2; display: none; align-items: center; gap: 2px; pointer-events: auto;
  padding: 4px 6px; border-radius: 999px;
  background: rgba(16, 12, 8, .82); border: 1px solid rgba(245, 233, 216, .14);
  box-shadow: 0 10px 26px -16px #000;
}
.skin-cycler.has-options { display: inline-flex; }
.skin-cycler button {
  width: 26px; height: 26px; border-radius: 50%; border: 1px solid transparent;
  background: transparent; color: var(--av-ink-3);
  font-size: 15px; line-height: 1; cursor: pointer; transition: .15s;
}
.skin-cycler button:hover { color: var(--av-ink); background: rgba(47, 191, 135, .18); }
.skin-cycler button[hidden] { display: none; }
#skin-cycler-name {
  font-family: var(--av-sans); font-size: 11px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--av-ink-2); padding: 0 10px;
}

.vp-specnav {
  order: 3; display: inline-flex; align-items: center; gap: 10px;
  background: none; border: 0; box-shadow: none; padding: 0; pointer-events: auto;
}
.vp-specnav .spec-block { display: flex; flex-direction: column; align-items: center; gap: 5px; min-width: 0; }
.vp-specnav .spec-name {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--av-serif); font-weight: 300;
  font-size: clamp(22px, 2.4vw, 34px); line-height: 1; letter-spacing: -.01em;
  text-transform: none; color: var(--av-ink);
  text-shadow: 0 2px 22px rgba(0, 0, 0, .6);
}
.vp-specnav .spec-name .dot {
  width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
  background: var(--av-jade-lt); box-shadow: 0 0 12px rgba(127, 224, 184, .7);
}
.vp-specnav .spec-status {
  font-family: var(--av-sans); font-size: 10.5px; letter-spacing: .26em;
  text-transform: uppercase; color: var(--av-ink-3);
}
.vp-specnav .nav-arrow {
  width: 44px; height: 44px; border-radius: 50%; font: inherit; font-size: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(245, 233, 216, .16); background: rgba(16, 12, 8, .82);
  color: var(--av-ink-2); cursor: pointer; transition: .15s;
}
.vp-specnav .nav-arrow:hover {
  color: var(--av-ink); background: rgba(47, 191, 135, .22); border-color: var(--av-edge-lit);
}
.vp-specnav .nav-arrow:disabled { opacity: .35; cursor: not-allowed; }

/* -------------------------------------------------------------------------
   4. INSTRUMENTS ON THE STAGE — docked top-LEFT.
   ------------------------------------------------------------------------- */
.vp-mesh-status, .vp-loading {
  position: absolute; z-index: 7;
  font-family: var(--av-sans); font-size: 10.5px; letter-spacing: .18em;
  text-transform: uppercase; padding: 8px 11px; border-radius: 9px;
  border: 1px solid rgba(245, 233, 216, .14); background: rgba(16, 12, 8, .88);
  color: var(--av-ink-2);
}
.vp-mesh-status { left: 18px; top: 18px; width: 206px; }
.vp-mesh-status.online { color: var(--av-jade-lt); border-color: var(--av-edge-lit); }
.vp-mesh-status.offline { color: var(--av-amber-lt); border-color: rgba(255, 196, 114, .45); }
.vp-loading { left: 50%; top: 50%; transform: translate(-50%, -50%); transition: opacity .3s ease; }
.vp-loading.hide { opacity: 0; pointer-events: none; visibility: hidden; }

.vp-readout {
  position: absolute; left: 18px; top: 58px; z-index: 6; width: 206px;
  display: flex; flex-direction: column; gap: 4px; padding: 9px 11px;
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--av-ink-3);
  border-radius: 10px; border: 1px solid rgba(245, 233, 216, .12);
  background: rgba(16, 12, 8, .82); font-variant-numeric: tabular-nums;
}
.vp-readout > span { display: flex; justify-content: space-between; gap: 10px; }
.vp-readout .k { color: rgba(127, 224, 184, .72); }
.vp-readout .v { color: var(--av-ink-2); }

.vp-autorotate {
  position: absolute; left: 18px; top: 144px; z-index: 6; width: 206px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--av-sans); font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; padding: 9px 14px; border-radius: 999px;
  border: 1px solid rgba(245, 233, 216, .14); background: rgba(16, 12, 8, .82);
  color: var(--av-ink-2); cursor: pointer; pointer-events: auto; transition: .15s;
}
.vp-autorotate:hover { color: var(--av-ink); border-color: var(--av-edge-lit); }
.vp-autorotate[aria-pressed="true"] {
  color: #06251a; background: linear-gradient(var(--av-jade-lt), var(--av-jade));
  border-color: var(--av-jade-lt);
}
.vp-autorotate[aria-pressed="true"] .ar-ico { color: #06251a; }
.vp-autorotate:disabled { opacity: .4; cursor: not-allowed; }

.vp-rot, .showcase-hint {
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--av-ink-3);
}
.vp-rot { position: absolute; right: 18px; bottom: calc(var(--lab-bottom, 18px) + 4px); z-index: 6; }
.showcase-hint {
  position: absolute; left: 50%; bottom: 40px; transform: translateX(-50%);
  z-index: 6; pointer-events: none; display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 16px; border-radius: 999px;
  background: rgba(16, 12, 8, .82); border: 1px solid rgba(245, 233, 216, .14);
  opacity: 0; visibility: hidden; transition: opacity .38s, visibility 0s linear .38s;
}
.showcase-hint .sh-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--av-jade-lt); box-shadow: 0 0 9px rgba(127, 224, 184, .7);
}
body.showcase .showcase-hint { opacity: 1; visibility: visible; transition: opacity .38s, visibility 0s linear 0s; }

/* the loading spinner: amber + jade rings */
.crt-spinner {
  position: absolute; left: 50%; top: calc(50% + 52px); transform: translate(-50%, -50%);
  z-index: 4; width: 46px; height: 46px; pointer-events: none;
}
.vp-loading.hide + .crt-spinner { display: none; }
.crt-spinner .ring {
  position: absolute; inset: 0; border: 2px solid transparent; border-radius: 50%;
  border-top-color: var(--av-amber-lt);
  animation: lab-spin 1.1s cubic-bezier(.55, .15, .45, .85) infinite;
}
.crt-spinner .ring.r2 {
  inset: 8px; border-top-color: transparent; border-bottom-color: var(--av-jade-lt);
  animation: lab-spin-rev 1.6s linear infinite;
}
.crt-spinner .core {
  position: absolute; left: 50%; top: 50%; width: 6px; height: 6px;
  margin: -3px 0 0 -3px; border-radius: 50%;
  background: var(--av-jade-lt); box-shadow: 0 0 14px rgba(127, 224, 184, .6);
  animation: lab-pulse 1.1s ease-in-out infinite;
}
@keyframes lab-spin { to { transform: rotate(360deg); } }
@keyframes lab-spin-rev { to { transform: rotate(-360deg); } }
@keyframes lab-pulse { 0%, 100% { opacity: .55; transform: scale(.6); } 50% { opacity: 1; transform: scale(1); } }

/* the stage's own note, and the animation keys, bottom-left */
.stage-note {
  position: absolute; left: 0; right: 0; bottom: 122px; z-index: 6;
  text-align: center; margin: 0; padding: 0 18px;
  font-family: var(--av-sans); font-size: 12px; color: var(--av-ink-2);
}
.stage-note.is-error { color: var(--av-amber-lt); }
.stage-anim {
  position: absolute; left: 18px; bottom: calc(var(--lab-bottom, 18px) + 50px); z-index: 6;
  display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap;
  max-width: min(560px, calc(100% - 320px)); pointer-events: auto;
}
.stage-anim[hidden] { display: none; }
.stage-anim-k {
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--av-ink-3);
}

/* the signed-out gate */
.stage-gate {
  position: absolute; inset: 0; z-index: 9;
  display: grid; place-content: center; justify-items: center;
  gap: 14px; text-align: center; padding: 32px;
  background: linear-gradient(180deg, rgba(23, 17, 12, .94), rgba(10, 8, 6, .98));
}
.stage-gate[hidden] { display: none; }
.stage-gate h3 {
  font-family: var(--av-serif); font-weight: 400; font-size: 26px;
  letter-spacing: .01em; color: var(--av-ink); margin: 0;
}
.stage-gate p { max-width: 40ch; color: var(--av-ink-2); font-size: 13.5px; line-height: 1.6; margin: 0; }

/* -------------------------------------------------------------------------
   5. THE BOTTOM BAND — view sliders, then the amber tool pill.
   ------------------------------------------------------------------------- */
.vp-tools {
  position: absolute; left: 50%; bottom: calc(var(--lab-bottom, 18px) + 56px);
  transform: translateX(-50%);
  z-index: 6; display: flex; flex-direction: row; align-items: center; gap: 8px;
  pointer-events: none;
}
.vp-rotate, .vp-exposure {
  display: inline-flex; align-items: center; gap: 10px; pointer-events: auto;
  padding: 7px 13px; border-radius: 999px;
  background: rgba(16, 12, 8, .88); border: 1px solid rgba(245, 233, 216, .14);
  box-shadow: 0 16px 34px -22px #000;
}
.vp-rotate .rot-ico, .vp-exposure .exp-ico { font-size: 13px; color: var(--av-amber-lt); }
.vp-rotate input[type="range"], .vp-exposure input[type="range"],
.stage-growth input[type="range"] {
  -webkit-appearance: none; appearance: none; height: 3px; border-radius: 3px;
  background: rgba(245, 233, 216, .18); outline: none; cursor: pointer;
}
.vp-rotate input[type="range"], .vp-exposure input[type="range"] { width: 104px; }
.stage-growth input[type="range"] { width: 150px; }
.vp-rotate input[type="range"]::-webkit-slider-thumb,
.vp-exposure input[type="range"]::-webkit-slider-thumb,
.stage-growth input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%;
  background: var(--av-amber-lt); border: 2px solid #140e08; cursor: pointer;
}
.vp-rotate input[type="range"]::-moz-range-thumb,
.vp-exposure input[type="range"]::-moz-range-thumb,
.stage-growth input[type="range"]::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--av-amber-lt); border: 2px solid #140e08; cursor: pointer;
}
.vp-rotate input:disabled, .vp-exposure input:disabled { opacity: .4; cursor: not-allowed; }

/* THE AMBER TOOL RAIL — a pill across the foot of the STAGE.
   #lab-rail is a grid-level element, so it is spanned across the stage
   column and its contents centred; `left:50%` would centre it on the WINDOW
   and slide the pill under a docked column. */
/* --lab-bottom is the height of whatever FIXED bar the owner's site pins to
   the foot of the window (Frontier's live strip is one). studiolab.js
   measures it; 18px is the donor's own number for a site with none. */
#lab-rail {
  position: absolute; left: 0; right: 0; bottom: var(--lab-bottom, 18px); z-index: 8;
  display: flex; flex-direction: row; align-items: center; justify-content: center;
  padding: 0 16px; pointer-events: none;
}
.rail-fab { display: none; }
.lab-rail-stack {
  pointer-events: auto; max-width: 100%; flex-wrap: nowrap;
  display: flex; flex-direction: row; align-items: center; justify-content: center;
  gap: 12px; padding: 9px 15px; border-radius: 999px;
  background: linear-gradient(rgba(35, 25, 17, .94), rgba(18, 13, 9, .96));
  border: 1px solid rgba(201, 146, 78, .34);
  box-shadow: 0 24px 52px -26px #000, inset 0 1px 0 rgba(255, 255, 255, .09);
}
.rail-colors { display: flex; gap: 7px; }
.rail-colors button, .rail-custom {
  width: 22px; height: 22px; border-radius: 6px; padding: 0; cursor: pointer;
  position: relative; transition: .15s;
  border: 1px solid rgba(245, 233, 216, .22);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .5);
}
.rail-colors button:hover, .rail-custom:hover { transform: scale(1.12); }
.rail-colors button.active {
  box-shadow: 0 0 0 2px rgba(18, 13, 9, .95), 0 0 0 3px var(--av-jade-lt);
}
.rail-colors button[data-bg="jade"]  { background: radial-gradient(circle at 34% 30%, #a8f0d0, #2fbf87 60%, #12503a); }
.rail-colors button[data-bg="ember"] { background: radial-gradient(circle at 34% 30%, #ff9a58, #ff6a24 60%, #7a2b0c); }
.rail-colors button[data-bg="blue"]  { background: radial-gradient(circle at 34% 30%, #8ec5ff, #4da6ff 60%, #123a6e); }
.rail-colors button[data-bg="green"] { background: radial-gradient(circle at 34% 30%, #93f0bb, #4fe08a 60%, #0f5c33); }
.rail-colors button[data-bg="amber"] { background: radial-gradient(circle at 34% 30%, #ffd28a, #ffb347 60%, #7a5211); }
.rail-colors button[data-bg="white"] { background: radial-gradient(circle at 34% 30%, #ffffff, #e8e4dc 60%, #6e6a62); }
.rail-colors button[data-bg="red"]   { background: radial-gradient(circle at 34% 30%, #ff8a8a, #ff4444 60%, #701414); }
.rail-colors button[data-bg="pink"]  { background: radial-gradient(circle at 34% 30%, #ff9ac8, #ff5fa2 60%, #761040); }
.rail-custom { overflow: hidden; background: conic-gradient(#ff4444, #ffb347, #4fe08a, #4da6ff, #b06ae0, #ff4444); }
.rail-custom input[type="color"] {
  position: absolute; inset: -6px; width: calc(100% + 12px); height: calc(100% + 12px);
  opacity: 0; cursor: pointer; border: 0; padding: 0;
}
.rail-divider { width: 1px; height: 24px; align-self: center; background: rgba(201, 146, 78, .3); }
.rail-action {
  font: inherit; font-family: var(--av-sans); font-size: 12px; font-weight: 400;
  letter-spacing: .02em; padding: 7px 13px; cursor: pointer; text-align: center;
  border-radius: 0;
  clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
  border: 1px solid rgba(127, 224, 184, .34); background: rgba(127, 224, 184, .07);
  color: #eaf4ec; white-space: nowrap; transition: .15s;
}
.rail-action:hover { background: rgba(127, 224, 184, .16); border-color: var(--av-edge-lit); }
.rail-action[aria-pressed="true"] { background: rgba(127, 224, 184, .2); border-color: var(--av-edge-lit); }
.rail-action:disabled { opacity: .4; cursor: not-allowed; }
/* Ultra ON is a STATE, not a press - it gets the jade fill the auto-rotate key
   uses for the same reason. */
#quality-toggle[aria-pressed="true"] {
  background: var(--av-jade-lt); border-color: var(--av-jade-lt); color: #06251a;
}

/* -------------------------------------------------------------------------
   6. THE LEFT COLUMN — the species list.
   ------------------------------------------------------------------------- */
#species-rail {
  grid-row: 2; grid-column: 1; position: relative;
  display: flex; flex-direction: column; height: 100%; min-width: 0;
  overflow: hidden; z-index: 24;
  background: linear-gradient(180deg, rgba(31, 22, 15, .98), rgba(14, 10, 7, 1));
  border-right: 1px solid var(--av-line);
}
#species-rail[hidden] { display: flex; }
.ctx-collapse { display: none; }
.ctx-title {
  display: flex; align-items: center; height: 52px; margin: 0; padding: 0 16px;
  flex: 0 0 auto;
  font-family: var(--av-serif); font-weight: 500; font-size: 16px;
  letter-spacing: .01em; text-transform: none; color: var(--av-ink);
  background: linear-gradient(rgba(41, 29, 19, .94), rgba(23, 17, 12, .94));
  border-bottom: 1px solid var(--av-line-dim);
}
.ctx-body { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.deck-search { flex: 0 0 auto; padding: 10px 12px 0; }
#deck-filter {
  width: 100%; font: inherit; font-family: var(--av-sans); font-size: 12px;
  padding: 8px 10px; border-radius: 0;
  background: rgba(10, 8, 6, .85); border: 1px solid rgba(127, 224, 184, .20);
  color: var(--av-ink); outline: 0;
}
#deck-filter:focus { border-color: var(--av-jade); box-shadow: 0 0 0 1px rgba(47, 191, 135, .40); }
.deck-list {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 10px 12px 16px; display: flex; flex-direction: column; gap: 4px;
}
.deck-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  text-align: left; cursor: pointer; border-radius: 0;
  clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
  border: 1px solid transparent; background: rgba(245, 233, 216, .03);
  font-family: var(--av-sans); font-size: 12px; letter-spacing: .02em;
  color: var(--av-ink); padding: 8px 11px; transition: .14s;
}
.deck-row b { font: inherit; font-weight: 400; color: inherit; }
.deck-row span {
  font-family: var(--av-sans); font-size: 9.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--av-ink-3);
}
.deck-row:hover { border-color: var(--av-line); background: rgba(47, 191, 135, .09); }
.deck-row.on { background: rgba(47, 191, 135, .14); border-color: var(--av-edge-lit); }
.deck-row:focus-visible { outline: 2px solid var(--av-focus); outline-offset: -2px; }
.deck-empty { font-size: 12px; color: var(--av-ink-3); padding: 8px 4px; }

/* -------------------------------------------------------------------------
   7. THE RIGHT COLUMN — the skin controls, and its two faces.
   ------------------------------------------------------------------------- */
#lab-tabs {
  grid-row: 2; grid-column: 3; align-self: start;
  position: relative; z-index: 41; display: flex; align-items: stretch;
  height: 52px; width: 100%;
  background: linear-gradient(rgba(41, 29, 19, .97), rgba(23, 17, 12, .97));
  border-bottom: 1px solid var(--av-line-dim);
  border-left: 1px solid var(--av-line);
}
#lab-tabs .lab-tab {
  flex: 1 1 0; min-width: 0; padding: 0 10px; border: 0; background: none; cursor: pointer;
  font-family: var(--av-serif); font-weight: 500; font-size: 15px; letter-spacing: .01em;
  color: var(--av-ink-3); border-bottom: 2px solid transparent;
  transition: color .15s, border-color .15s;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#lab-tabs .lab-tab::before, #lab-tabs .lab-tab::after { content: none; }
#lab-tabs .lab-tab:hover { color: var(--av-ink-2); }
#lab-tabs .lab-tab.active { color: var(--av-ink); border-bottom-color: var(--av-jade-lt); }
#lab-tabs .lab-tab:focus-visible { outline: 2px solid var(--av-focus); outline-offset: -2px; }

#lab-dock-panel {
  grid-row: 2; grid-column: 3; position: relative;
  display: flex; flex-direction: column; height: 100%; min-width: 0;
  overflow: hidden; z-index: 24; padding-top: 52px;
}
.dock-connector, .dock-handle { display: none; }
.deck {
  /* ★★★★★ RESERVE THE OWNER'S FIXED BAR. The column is `height: 100%` of its
     grid area and that area runs UNDER whatever bar the site pins to the
     bottom of the window - so its last row was drawn behind the live bar and
     cut in half. Owner, 2026-08-17: the import controls were *"buried under
     the bottom bar until you click on the Copy Code button"* - clicking
     scrolled them into view, which is how a control can be present, correct,
     reachable and still, honestly, missing.

     `--lab-bar` is that bar measured at runtime (studiolab.js), so this
     follows an owner whose bar is a different height and costs nothing on one
     who has no bar at all. */
  height: 100%; max-height: none; overflow: hidden;
  display: flex; flex-direction: column; gap: 0;
  /* ★ AFTER the shorthand, never before it. The first cut of this put
     `padding-bottom` above `padding: 0` in the same rule, so the shorthand
     reset it to nothing and the reservation measured as applied while doing
     absolutely nothing - the block still ended exactly at the window's foot,
     under the bar. Same rule, later declaration wins. */
  padding: 0 0 var(--lab-bar, 0px);
  box-sizing: border-box;
  border-left: 1px solid var(--av-line);
  background: linear-gradient(180deg, #221810 0%, var(--av-panel) 46%, #100b07 100%);
}
.deck-row-l { display: flex; align-items: center; gap: 10px; }
.deck > .deck-row-l {
  border-top: 1px solid var(--av-line-dim); padding: 13px 16px; flex-wrap: wrap;
}
.deck > .deck-row-l:first-child { border-top: 0; }
/* ★ nowrap IS LOAD-BEARING. `.deck > .deck-row-l` wraps, and a COLUMN flex
   that also wraps puts its overflow in a SECOND COLUMN - which threw the
   pattern-size note 200px clear of the panel's right edge. */
.deck > .deck-row-l.deck-head { flex-wrap: nowrap; }
.deck-head { flex-direction: column; align-items: stretch; }
.deck-field { display: flex; flex-direction: column; gap: 5px; flex: 1 1 auto; min-width: 0; }
.micro-label, .stage-foot-k {
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--av-ink-3); white-space: nowrap;
}
#design-name {
  width: 100%; height: 32px; font: inherit; font-family: var(--av-serif);
  font-size: 14.5px; font-weight: 500; padding: 5px 11px; border-radius: 0;
  background: rgba(36, 26, 17, .9); border: 1px solid var(--av-line);
  color: var(--av-ink); outline: 0;
}
#design-name::placeholder { color: var(--av-ink-3); }
#design-name:focus { border-color: var(--av-jade); box-shadow: 0 0 0 1px rgba(47, 191, 135, .40); }

.stage-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.chips { display: inline-flex; gap: 5px; flex-wrap: wrap; }
.chip {
  font: inherit; font-family: var(--av-sans); font-size: 11.5px;
  min-width: 32px; padding: 6px 9px; border-radius: 0; cursor: pointer;
  border: 1px solid var(--av-line); background: rgba(245, 233, 216, .04);
  color: var(--av-ink); transition: .15s;
}
.chip:hover:not(:disabled) { border-color: var(--av-edge-lit); }
.chip.on { color: #06251a; background: var(--av-jade-lt); border-color: var(--av-jade-lt); }
.chip:disabled { opacity: .4; cursor: not-allowed; }
.studio-size-note {
  flex-basis: 100%; margin: 2px 0 0;
  font-family: var(--av-sans); font-size: 10.5px; line-height: 1.45; color: var(--av-ink-3);
}

/* ★★★★★ EVERY COLOUR MUST BE ON SCREEN AT ONCE, WITHOUT SCROLLING.
   Owner ruling 2026-08-17: "i should not need to scroll for them. make design
   better." Seven channels is the whole product of this page - a player picks
   them against each other, and a list you have to scroll to see the end of is
   a list you cannot compare. So the head and the foot of this column are
   budgeted to leave the rows room, in this order:

     · the pattern row is ONE line, not four stacked ones (label, chips,
       label, chips read across; they were 126px stacked, 44px across);
     · the size EXPLAINER leaves the page and becomes the row's tooltip -
       studio.mjs already sets it as `title`, and explainer copy is removed
       fleet-wide by owner order anyway;
     · the quick-colour strip is one scrolling line, not a wrapping grid;
     · the action row is compact, and its note is one small line.

   Measured at 1600x950: head 260 -> 106, palette 116 -> 46, actions 156 ->
   112, which buys the seven rows 159px more than they need. */
/* the head is budgeted for a 768-tall laptop, which is where the seven rows
   ran out of room first: name on one line, pattern and size across the next. */
.deck-row-l.deck-head { padding-top: 10px; padding-bottom: 10px; gap: 8px; }
.deck-name .micro-label { font-size: 9.5px; letter-spacing: .14em; }
.deck-name { gap: 4px; }
.deck-pattern.deck-field {
  flex-direction: row; align-items: center; flex-wrap: wrap; gap: 6px 10px;
}
.deck-pattern .chips { flex: 0 1 auto; }
/* studio.mjs appends the SIZE control as key-then-chips siblings of the
   pattern pair, so a plain wrap breaks BETWEEN a caption and the chips it
   names. The size caption starts its own line instead, and the pair stays
   together. */
.deck-pattern #size-chips-k { flex-basis: 100%; }
/* the note is the row's TOOLTIP, not a paragraph in the panel */
.studio-size-note { display: none; }

/* THE COLOUR ROWS. A 336px column cannot carry seven 84px tiles without
   truncating every label, so the wells are a LIST: swatch, name, value. */
.deck-wells { flex-direction: column; align-items: stretch; }
.deck-row-l.deck-wells {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  align-items: flex-start; justify-content: flex-start; flex-wrap: nowrap;
}
#channel-list { display: flex; flex-direction: column; gap: 4px; width: 100%; }
.channel {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px; width: 100%; flex: 0 0 auto; padding: 5px 10px; cursor: pointer;
  border-radius: 0;
  clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
  border: 1px solid transparent; background: rgba(245, 233, 216, .03); transition: .15s;
}
.channel:hover, .channel:focus-within { border-color: var(--av-line); background: rgba(47, 191, 135, .09); }
.channel.cp-on { border-color: var(--av-edge-lit); background: rgba(47, 191, 135, .12); }
/* yields its own box so the picker and the hex become row cells */
.channel-io { display: contents; }
.channel input[type="color"] {
  order: 1; display: block; width: 34px; height: 24px; padding: 0; border-radius: 0;
  background: transparent; cursor: pointer;
  border: 1px solid rgba(245, 233, 216, .22);
}
.channel input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.channel input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 0; }
.channel input[type="color"]::-moz-color-swatch { border: 0; border-radius: 0; }
.channel:hover input[type="color"] { box-shadow: 0 0 0 1px var(--av-edge-lit); }
.channel label {
  order: 2; min-width: 0; text-align: left; cursor: pointer;
  font-family: var(--av-sans); font-size: 11px; font-weight: 400; letter-spacing: .06em;
  color: var(--av-ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.channel:hover label { color: var(--av-ink); }
/* Arkadia's rows carry no hex box; webcore's do, and a control that exists
   must stay reachable - so it reads as the row's VALUE, right-aligned. */
.channel .hex {
  order: 3; width: 9.5ch; padding: 3px 0; margin: 0; border: 0; border-radius: 0;
  background: transparent; color: var(--av-ink-3); text-align: right;
  font-family: var(--av-sans); font-size: 10.5px; letter-spacing: .04em;
  text-transform: lowercase; outline: 0; font-variant-numeric: tabular-nums;
}
.channel .hex:focus { background: rgba(10, 8, 6, .85); color: var(--av-ink); box-shadow: inset 0 0 0 1px var(--av-jade); }

/* the quick-colour strip, aimed at the active row - ONE scrolling line */
.channel-palette {
  width: 100%; margin-top: 7px; padding: 8px 0 2px;
  border-top: 1px solid var(--av-line-dim);
  display: flex; flex-wrap: wrap; align-items: center; gap: 5px 9px; min-width: 0;
  /* on a screen too short for all seven rows the list still scrolls - and the
     strip is aimed at whichever row is SELECTED, so it has to stay on screen
     while you move through them. */
  position: sticky; bottom: 0; background: var(--av-panel);
}
.channel-palette .cp-for {
  flex: 0 0 auto; margin: 0;
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--av-ink-3); white-space: nowrap;
}
/* ★ IT WRAPS, IT NEVER SCROLLS SIDEWAYS. Owner ruling 2026-08-17: "i have to
   scroll to the side for it." A colour you have to scroll to is a colour you
   do not know is there - and the whole point of the strip is seeing the
   choices at a glance. It wraps into as many short rows as it needs; the
   height that costs is taken out of the seats above, not out of the list. */
.cp-grid {
  display: flex; flex-wrap: wrap; gap: 4px; flex: 1 1 100%; min-width: 0;
}
.cp-swatch {
  position: relative; width: 20px; height: 20px; flex: 0 0 auto; padding: 0; cursor: pointer;
  border-radius: 0; border: 1px solid rgba(245, 233, 216, .16); background: var(--cp);
  transition: transform .12s, box-shadow .12s;
}
.cp-swatch:hover { transform: translateY(-1px); box-shadow: 0 0 0 1px var(--av-edge-lit); }
.cp-swatch.on { box-shadow: 0 0 0 2px var(--av-jade-lt); }
.cp-swatch.cp-natural::after { content: ""; position: absolute; inset: 3px; border: 1px solid rgba(255, 255, 255, .5); }

/* the actions, pinned to the foot of the column */
.deck-row-l.deck-actions {
  flex: 0 0 auto; margin-top: 0; padding-top: 10px; padding-bottom: 10px;
  border-top: 1px solid var(--av-line); background: rgba(10, 8, 6, .55);
  display: flex; flex-direction: row; flex-wrap: wrap; align-items: stretch; gap: 6px;
}
.deck-gap { display: none; }
.save-row { display: contents; }
.btn {
  font: inherit; font-family: var(--av-sans); font-size: 11.5px; font-weight: 400;
  letter-spacing: .02em; text-transform: none; cursor: pointer; text-align: center;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 36px; padding: 8px 10px; border-radius: 0; text-decoration: none;
  clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
  border: 1px solid rgba(127, 224, 184, .34); background: rgba(127, 224, 184, .07);
  color: #eaf4ec; white-space: nowrap; transition: .15s;
}
.btn:hover { background: rgba(127, 224, 184, .16); border-color: var(--av-edge-lit); }
.btn:disabled { opacity: .4; cursor: not-allowed; }
.btn::before { content: none; }
.btn:focus-visible, .rail-action:focus-visible { outline: 2px solid var(--av-focus); outline-offset: 2px; }
.btn-primary, .btn.primary {
  background: var(--av-jade-lt); border-color: var(--av-jade-lt);
  color: #06251a; font-weight: 600;
}
.btn-primary:hover, .btn.primary:hover { background: var(--av-jade-hot); border-color: var(--av-jade-hot); }
.btn.save { border-color: rgba(255, 196, 114, .55); color: var(--av-amber-lt); }
.btn-ghost { background: rgba(245, 233, 216, .04); border-color: var(--av-line); }
.btn-sm { min-height: 32px; padding: 6px 9px; font-size: 11px; }
.deck-actions .btn { flex: 1 1 calc(50% - 3px); width: auto; min-width: 0; min-height: 32px; padding: 6px 10px; }
.deck-actions .deck-apply { flex: 1 1 100%; min-height: 38px; padding: 9px 16px; font-size: 12.5px; }
.save-note { flex: 1 1 100%; margin: 0; font-size: 9.5px; color: var(--av-ink-3); line-height: 1.35; }

/* -------------------------------------------------------------------------
   8. THE LIBRARY FACE — docked into the same column, never sliding over.
   ------------------------------------------------------------------------- */
#lab-drawer-scrim { display: none; }
#panel {
  grid-row: 2; grid-column: 3; position: relative;
  width: 100%; height: 100%; min-width: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding: 52px 16px 30px; z-index: 26;
  border-left: 1px solid var(--av-line);
  background: linear-gradient(180deg, rgba(31, 22, 15, .98), rgba(14, 10, 7, 1));
  display: none;
}
body.drawer-open #panel { display: block; }
body.drawer-open #lab-dock-panel { visibility: hidden; }
.drawer-head { display: none; }

body.studiolab #panel h2, body.studiolab #panel h3 {
  display: flex; align-items: center; gap: 8px; margin: 0 0 11px;
  font-family: var(--av-sans); font-size: 10.5px; font-weight: 500;
  letter-spacing: .2em; text-transform: uppercase; color: var(--av-ink-2);
}
body.studiolab #panel h2::before, body.studiolab #panel h3::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; flex: 0 0 auto;
  background: var(--av-jade-lt); box-shadow: 0 0 8px rgba(127, 224, 184, .6);
}
body.studiolab #panel .right { margin-left: auto; font-size: 10px; color: var(--av-ink-3); }
body.studiolab #panel p { font-family: var(--av-sans); font-size: 12px; line-height: 1.6; color: var(--av-ink-2); }
body.studiolab #panel .dim { color: var(--av-ink-3); }
.lab-section { margin-bottom: 20px; }
.lab-section + .lab-section { padding-top: 18px; border-top: 1px solid var(--av-line-dim); }
.lab-lede { margin: 0 0 16px; font-size: 12.5px; line-height: 1.6; color: var(--av-ink-2); }

body.studiolab .studio-tabs {
  display: flex; gap: 4px; padding: 4px; margin-bottom: 16px; border-radius: 0;
  background: rgba(10, 8, 6, .55); border: 1px solid var(--av-line-dim);
}
body.studiolab .studio-tabs [role="tab"] {
  flex: 1; font: inherit; font-family: var(--av-sans); font-size: 12px;
  padding: 8px 10px; border: 0; border-radius: 0; min-height: 36px;
  background: transparent; color: var(--av-ink-3); cursor: pointer; transition: .15s;
}
body.studiolab .studio-tabs [role="tab"]:hover { color: var(--av-ink-2); }
body.studiolab .studio-tabs [role="tab"][aria-selected="true"] {
  color: #06251a; background: var(--av-jade-lt);
}

body.studiolab .studio-panel-label {
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--av-ink-3); margin: 0 0 8px;
}
.presets { display: flex; flex-wrap: wrap; gap: 5px; }
.preset {
  font: inherit; font-family: var(--av-sans); font-size: 11.5px;
  padding: 7px 10px; border-radius: 0; cursor: pointer;
  border: 1px solid var(--av-line); background: rgba(245, 233, 216, .04);
  color: var(--av-ink-2); transition: .15s;
}
.preset:hover { color: var(--av-ink); border-color: var(--av-edge-lit); }

.code-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 4px; }
body.studiolab #code-input, body.studiolab #code-name, body.studiolab .skin-pattern {
  font: inherit; font-family: var(--av-sans); font-size: 12px;
  padding: 8px 10px; border-radius: 0;
  background: rgba(10, 8, 6, .85); border: 1px solid rgba(127, 224, 184, .20);
  color: var(--av-ink); outline: 0;
}
body.studiolab #code-input { flex: 1 1 170px; min-width: 0; }
body.studiolab #code-name { flex: 0 1 130px; min-width: 0; }
body.studiolab #code-input:focus, body.studiolab #code-name:focus, body.studiolab .skin-pattern:focus {
  border-color: var(--av-jade); box-shadow: 0 0 0 1px rgba(47, 191, 135, .40);
}
#code-note { flex-basis: 100%; margin: 0; font-size: 11px; }
.skin-pattern { flex: 0 0 auto; max-width: 44%; }

.designs { border-top: 1px solid var(--av-line-dim); padding-top: 12px; margin-top: 4px; }
.design-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 5px; }
.design-load {
  flex: 1 1 auto; min-width: 9em;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 10px; text-align: left; cursor: pointer; border-radius: 0;
  clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
  background: rgba(245, 233, 216, .03); border: 1px solid transparent;
  color: var(--av-ink-2); transition: .15s;
}
.design-load:hover { border-color: var(--av-line); background: rgba(47, 191, 135, .09); }
.design-load b { font: inherit; font-size: 12.5px; font-weight: 500; color: var(--av-ink); overflow-wrap: anywhere; }
.design-load span { font-family: var(--av-sans); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--av-ink-3); }
.design-row.editing .design-load { border-color: var(--av-edge-lit); background: rgba(47, 191, 135, .14); }
.design-row .btn, .design-row .design-del { flex: 0 0 auto; white-space: nowrap; }
.design-row .btn { min-height: 30px; padding: 5px 9px; font-size: 10.5px; }
.design-row .skin-pattern { max-width: 9em; }
.design-del {
  width: 30px; height: 30px; flex: none; border-radius: 0;
  border: 1px solid rgba(255, 124, 132, .35); background: transparent;
  color: #ff7c84; font: 14px/1 var(--av-sans); cursor: pointer;
}
.design-del:hover { border-color: #ff7c84; background: rgba(255, 124, 132, .12); }
.designs-empty, .preset-empty { font-family: var(--av-sans); font-size: 11.5px; color: var(--av-ink-3); padding: 8px 4px; }
.skin-row .skin-name { cursor: default; }
.skin-row .skin-name:hover { border-color: transparent; background: rgba(245, 233, 216, .03); }
.skin-row .skin-name span { color: var(--av-jade-lt); }

/* the glitch shelf and the typed composer */
.glitch-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
.glitch-tile {
  position: relative; overflow: hidden; display: flex; flex-direction: column; gap: 8px;
  padding: 10px; border-radius: 0;
  clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
  background: rgba(245, 233, 216, .03); border: 1px solid var(--av-line-dim); transition: .15s;
}
.glitch-tile:hover { border-color: var(--av-line); background: rgba(47, 191, 135, .07); }
.glitch-tile.is-owned { border-color: var(--av-edge-lit); }
body.studiolab .glitch-tile h3 { font-family: var(--av-serif); font-weight: 400; font-size: 14px; letter-spacing: 0; color: var(--av-ink); margin: 0; text-transform: none; }
body.studiolab .glitch-tile h3::before { display: none; }
body.studiolab .glitch-tile p { margin: 0; font-size: 11.5px; color: var(--av-ink-2); }
.glitch-prox { display: flex; width: 100%; aspect-ratio: 5 / 4; overflow: hidden; margin: 0; border-radius: 0; }
.glitch-prox i { flex: 1 1 0; min-width: 0; display: block; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .35); }
.glitch-cmd { font-family: var(--av-sans); font-size: 10.5px; color: var(--av-ink-3); }
.glitch-cmd code { font-family: var(--av-sans); color: var(--av-jade-lt); background: rgba(10, 8, 6, .6); padding: 2px 5px; }
.glitch-buy { display: flex; gap: 6px; }
.glitch-buy .btn { flex: 1; justify-content: center; }
.glitch-buy .btn.is-armed { border-color: var(--av-amber-lt); color: var(--av-amber-lt); }
.glitch-note { font-family: var(--av-sans); font-size: 10.5px; margin: 6px 0 0; color: var(--av-ink-2); }
.glitch-note:empty { display: none; }
.glitch-shimmer { display: none; }

.gc-lab { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: flex-end; margin-top: 10px; }
.gc-lab-field { display: grid; gap: 5px; }
.gc-lab-acts { display: flex; gap: 6px; }
.gc-lab-label, .gc-slot-name, .gc-chan-k {
  font-family: var(--av-sans); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--av-ink-3);
}
.gc-chip-row { display: flex; flex-wrap: wrap; gap: 4px; }
/* .gc-chan-row was in the line above until 2026-08-17. It is a GRID now (see
   below) and leaving it here left two rules governing one row, with only
   source order deciding - the kind of pair a later reorder turns back into
   the wrapped alpha column the owner reported. */
.gc-chip {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; padding: 6px 9px; cursor: pointer; border-radius: 0;
  font-family: var(--av-sans); font-size: 11.5px;
  border: 1px solid var(--av-line); background: rgba(245, 233, 216, .04); color: var(--av-ink-2);
}
.gc-chip:hover { border-color: var(--av-edge-lit); color: var(--av-ink); }
.gc-chip.is-on { background: var(--av-jade-lt); border-color: var(--av-jade-lt); color: #06251a; }
.gc-slots { display: grid; gap: 8px; margin-top: 14px; }
.gc-slot-row { display: grid; grid-template-columns: 1fr; gap: 4px; align-items: center; }
.gc-num, body.studiolab #gc-name {
  width: 8ch; padding: 6px 7px; border-radius: 0;
  font-family: var(--av-sans); font-size: 11.5px; text-align: right;
  font-variant-numeric: tabular-nums;
  background: rgba(10, 8, 6, .85); border: 1px solid rgba(127, 224, 184, .20);
  color: var(--av-ink); outline: 0;
}
body.studiolab #gc-name { width: auto; flex: 1 1 130px; text-align: left; }
.gc-num:focus, body.studiolab #gc-name:focus { border-color: var(--av-jade); box-shadow: 0 0 0 1px rgba(47, 191, 135, .40); }
.gc-num.is-bad { border-color: #ff7c84; }
/* ★ THE R G B A HEADER HAD NOTHING UNDER ITS LAST COLUMN.
   The header spans were 8ch each with no padding, while `.gc-num` is 8ch PLUS
   14px of padding and a border - so four values were wider than four headers,
   the flex row wrapped, and the alpha field dropped onto its own line under a
   header that stayed put. Owner, 2026-08-17: "The R G B A at the top is off".

   A header and its values cannot be laid out by two different rules and be
   expected to line up. One grid, four equal tracks, shared by both rows, and
   the inputs fill their track instead of setting their own width - so they
   align at every panel width and can never wrap. */
.gc-chan-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 4px;
  align-items: center;
}
.gc-chan-row .gc-num { width: 100%; min-width: 0; }
.gc-chan-k { width: auto; min-width: 0; text-align: center; }
.gc-chan-head { margin-bottom: 2px; }
.gc-save-row { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.gc-price-note { flex-basis: 100%; }
.gc-try { white-space: nowrap; }

/* the tier gate card studio.mjs draws into #main */
#studio-tier-gate {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 26; width: min(520px, calc(100vw - 40px));
  background: linear-gradient(180deg, rgba(31, 22, 15, .98), rgba(14, 10, 7, 1));
  border: 1px solid var(--av-line);
}
#studio-tier-gate h3 { font-family: var(--av-serif); font-weight: 400; font-size: 22px; color: var(--av-ink); margin: 0; }
#studio-tier-gate p { font-family: var(--av-sans); font-size: 13px; color: var(--av-ink-2); }
body.lab-gated #lab-dock-panel, body.lab-gated #lab-rail, body.lab-gated #species-rail,
body.lab-gated .vp-overlay, body.lab-gated #panel, body.lab-gated #lab-tabs { display: none !important; }

/* EFFECTS OFF — the rail's own switch. It strips the ROOM (grain, vignette,
   light shaft) and leaves the specimen and every control where they were. A
   player checking a colour against a plain ground should not have to leave
   the studio to do it. */
body.lab-fx-off #lab-grain,
body.lab-fx-off #lab-cloud,
body.lab-fx-off #lab-parts,
body.lab-fx-off #lab-vig::after { display: none; }
/* the vignette itself STAYS - it is the room's edge, not an effect, and
   without it the chamber ends in a hard rectangle. */

/* signed out: the name box goes with the save row it belongs to */
body.lab-readonly .deck-name { display: none; }

.toast-host {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  z-index: 80; display: flex; flex-direction: column; gap: 6px; align-items: center; pointer-events: none;
}
.toast {
  padding: 10px 16px; border-radius: 0;
  clip-path: polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px);
  font-family: var(--av-sans); font-size: 12.5px;
  background: rgba(23, 17, 12, .96); border: 1px solid var(--av-edge);
  color: var(--av-ink); box-shadow: 0 18px 50px rgba(0, 0, 0, .5);
  min-width: 220px; text-align: center;
}
.toast.ok { border-color: var(--av-edge-lit); }
.toast.err { border-color: #ff7c84; color: #ffb1b6; }
.toast.warn { border-color: var(--av-amber-lt); color: var(--av-amber-lt); }

/* -------------------------------------------------------------------------
   9. SHOWCASE — the chrome-free stage.
   ------------------------------------------------------------------------- */
#lab-rail, .vp-specnav, .skin-cycler, .vp-tools, .vp-readout, .vp-autorotate,
.vp-rot, .vp-mesh-status, .stage-growth, .stage-anim {
  transition: opacity .38s, visibility 0s linear 0s;
}
body.showcase #lab-rail, body.showcase .vp-specnav, body.showcase .skin-cycler,
body.showcase .vp-tools, body.showcase .vp-readout, body.showcase .vp-autorotate,
body.showcase .vp-rot, body.showcase .vp-mesh-status,
body.showcase .stage-growth, body.showcase .stage-anim {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .38s, visibility 0s linear .38s;
}

/* -------------------------------------------------------------------------
   10. THE COLUMNS — created here, at desktop widths only.
   ------------------------------------------------------------------------- */
@media (min-width: 901px) {
  body.studiolab { grid-template-columns: var(--av-species-w) minmax(0, 1fr) var(--av-rail-w); }
  #workspace { grid-column: 2; }
  /* while orbiting, nothing stands between the eye and the animal */
  body.lab-orbiting .vp-center-stack, body.lab-orbiting .stage-growth { opacity: .12; pointer-events: none; }
}

/* ══ SHORT SCREENS ══ <=900px tall: the seven colours STILL fit whole.
   A 1366x768 laptop is the width this list ran out of room on first, and the
   owner's ruling is that scrolling for the last two colours is the defect -
   so on a short screen the ROOM gets tighter rather than the list getting
   longer. What goes is chrome the chips already say: the "PATTERN" and
   "PATTERN SIZE" captions (A/B/C and SMALL/MEDIUM/LARGE name themselves, and
   the tooltip still carries the sentence), and a few pixels off every seat.
   Nothing that can be pressed is removed. */
@media (min-width: 901px) and (max-height: 900px) {
  .deck-row-l.deck-head { padding-top: 8px; padding-bottom: 8px; gap: 6px; }
  .deck-pattern .stage-foot-k { display: none; }
  .deck-name .micro-label { display: none; }
  #design-name { height: 30px; font-size: 14px; }
  .deck > .deck-row-l { padding-top: 9px; padding-bottom: 9px; }
  .channel { padding: 4px 10px; gap: 10px; }
  .channel input[type="color"] { height: 22px; }
  #channel-list { gap: 3px; }
  .channel-palette { margin-top: 6px; padding-top: 6px; }
  .cp-swatch { width: 18px; height: 18px; }
  .deck-actions .btn { min-height: 30px; }
  .deck-actions .deck-apply { min-height: 36px; }
  .save-note { display: none; }
}

/* ...and one tier tighter again at 720p, the shortest desktop this fleet
   sees. Same rule, same reason: the list stays whole. */
@media (min-width: 901px) and (max-height: 780px) {
  .deck > .deck-row-l { padding-top: 7px; padding-bottom: 7px; }
  .channel { padding: 3px 10px; }
  .channel input[type="color"] { height: 20px; }
  .channel label { font-size: 10.5px; }
  #channel-list { gap: 2px; }
  /* the strip WRAPS now, so at 720p it is the thing that grew - take the
     pixels back out of the swatches, never out of the colour list. */
  .channel-palette { margin-top: 4px; padding-top: 4px; gap: 3px 8px; }
  .cp-swatch { width: 14px; height: 14px; }
  .cp-grid { gap: 3px; }
  .channel-palette .cp-for { font-size: 9px; }
  /* ★ THE CODE BLOCK'S OWN 720p RULES LEFT THIS TIER (2026-08-18). The old
     one-line five-track grid here NEVER APPLIED - the base .deck-codes block
     sits LATER in this sheet, same selectors, and later declaration wins
     (headless-measured: the lead sat on its own line at 1366x760 all along,
     and the two fields were 61px slivers). The block's short-screen
     tightening now lives AFTER the base rules, at the sheet's foot, where it
     actually wins; the wrapping row itself takes the fewest lines the
     owner's own labels physically fit at every height. */
  .cp-swatch { width: 13px; height: 13px; }
  .deck-actions .btn { min-height: 27px; font-size: 11px; }
}
/* ★ 720p AND NARROWER: the code block's controls wrap onto an extra line at
   this width, and the last of the pixels come out of the swatches - the same
   rule the tier above states. Measured after: every one of the seven colour
   rows fully visible, the swatch strip no longer scrolling, and the block
   still clear of the owner's live bar. */
@media (min-width: 901px) and (max-height: 820px) {
  .cp-swatch { width: 12px; height: 12px; }
  .cp-grid { gap: 2px; }
  .channel-palette { margin-top: 2px; padding-top: 2px; gap: 2px 6px; }
  /* .deck-codes' own tightening for this tier lives at the sheet's foot -
     declared here it was shadowed by the later base block (same selector,
     later declaration wins). */
  .deck-actions .deck-apply { min-height: 33px; }
  .deck > .deck-row-l { padding-top: 6px; padding-bottom: 6px; }
  #design-name { height: 28px; }
  #channel-list { gap: 1px; }
}

/* ══ TABLET ══ 1024-1279: the same three columns, tighter */
@media (min-width: 1024px) and (max-width: 1279px) {
  body.studiolab { --av-species-w: 206px; --av-rail-w: 300px; }
  .vp-specnav .spec-name { font-size: 26px; }
}

/* ══ SMALL LAPTOP ══ 901-1023: drop the species column; the list moves into
   the right column's own face, so nothing becomes unreachable. */
@media (min-width: 901px) and (max-width: 1023px) {
  body.studiolab { --av-species-w: 0px; --av-rail-w: 288px;
    grid-template-columns: minmax(0, 1fr) var(--av-rail-w); }
  #workspace { grid-column: 1; }
  #lab-dock-panel, #lab-tabs, #panel { grid-column: 2; }
  #species-rail, #species-rail[hidden] { display: none; }
  .vp-specnav .spec-name { font-size: 24px; }
  .vp-mesh-status, .vp-readout, .vp-autorotate { width: 180px; }
}

/* -------------------------------------------------------------------------
   11. PHONE / TABLET PORTRAIT — one column, the controls become a bottom
   sheet, and NOTHING text-bearing sits over the model band.
   ------------------------------------------------------------------------- */
@media (max-width: 900px) {
  body.studiolab { grid-template-columns: minmax(0, 1fr); }
  #species-rail, #species-rail[hidden], #lab-tabs { display: none; }
  #workspace { grid-column: 1; }
  .lab-main {
    display: flex; flex-direction: column; min-height: 0;
    padding-bottom: var(--lab-dock-h, 0px);
  }
  .viewport-wrap { flex: 1 1 auto; min-height: 0; height: auto; border: 0; }
  .vp-readout, .vp-rot, .showcase-hint { display: none; }
  .vp-mesh-status { left: 12px; top: 12px; width: auto; max-width: calc(100% - 24px); }
  .stage-growth { top: 14px; }
  .vp-center-stack { top: 56px; }
  .vp-specnav .spec-name { font-size: clamp(19px, 5.4vw, 26px); }
  .vp-specnav .spec-status { font-size: 9px; letter-spacing: .2em; }

  /* the stage's own controls move UNDER the band */
  #lab-headstrip { flex: none; position: relative; z-index: 6; padding: 6px 8px 4px; display: flex; justify-content: center; }
  #lab-headstrip[hidden] { display: none; }
  #lab-headstrip .vp-center-stack {
    position: static; transform: none; top: auto; left: auto;
    display: flex; flex-direction: column; align-items: center; gap: 6px; max-width: 100%;
  }
  #lab-footstrip { flex: none; position: relative; z-index: 6; min-width: 0; padding: 2px 8px 4px; }
  #lab-footstrip .fs-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
  #lab-footstrip .stage-anim {
    position: static; max-width: none; flex: 1 1 auto; min-width: 0;
    overflow-x: auto; flex-wrap: nowrap; padding: 3px 0;
  }
  #lab-footstrip .stage-anim .btn, #lab-footstrip .stage-anim .chip { min-height: 38px; flex: none; }
  #lab-footstrip .vp-autorotate {
    position: static; transform: none; flex: none; width: auto;
    padding: 9px 12px; min-height: 38px;
  }
  body.deck-expanded #lab-footstrip { display: none; }
  body.deck-expanded .skin-cycler { display: none; }
  .stage-note { bottom: 12px; }

  /* the sheet */
  #lab-tabs { display: none; }
  /* the sheet sits ON the owner's fixed foot bar, never under it - the Apply
     key is the last thing that may end up behind a live strip. */
  #lab-dock-panel {
    position: absolute; left: 0; right: 0; bottom: var(--lab-bar, 0px); top: auto;
    grid-column: 1; height: auto; padding-top: 0; z-index: 33;
  }
  /* ★ THE SHEET IS CAPPED IN BOTH STATES. An uncapped column of colour rows
     is seven rows tall on a phone and leaves the animal a letterbox - the one
     thing this page must never do. Collapsed shows a few rows and scrolls;
     expanded gets more. The band keeps the rest either way. */
  .deck {
    /* collapsed is the state you LOOK at the animal in, so it takes the
       smaller share; the grab handle buys the rest when you are editing. */
    height: auto; max-height: 34dvh; overflow: hidden;
    border-left: 0; border-top: 1px solid var(--av-edge);
    background: linear-gradient(180deg, rgba(34, 24, 16, .98), rgba(12, 9, 6, 1));
    box-shadow: 0 -18px 40px -24px #000;
    padding-bottom: env(safe-area-inset-bottom);
  }
  #lab-dock-panel.expanded .deck { max-height: 62dvh; }
  .deck-row-l.deck-wells { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .dock-handle {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; width: 100%; height: 28px; padding: 0; border: 0; background: transparent; cursor: pointer;
  }
  .dock-handle span { width: 56px; height: 5px; border-radius: 3px; background: var(--av-ink-3); }
  .dock-handle::after {
    content: ""; width: 8px; height: 8px;
    border-left: 2px solid var(--av-ink-3); border-top: 2px solid var(--av-ink-3);
    transform: rotate(45deg) translateY(1px);
  }
  #lab-dock-panel.expanded .dock-handle::after { transform: rotate(225deg) translateY(1px); }
  /* thumb-first: the wells you paint with come FIRST */
  .deck { display: flex; flex-direction: column; }
  /* ★ THE WELLS ARE THE ONLY THING THAT SCROLLS. The sheet is capped, so if
     this row does not shrink it pushes the Apply key clean off the bottom of
     the sheet - measured at 492x932: actions at y=892 inside a deck ending at
     811. flex:1 1 auto + min-height:0 is what lets a flex child be smaller
     than its content. */
  .deck-row-l.deck-wells { order: 1; flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .deck-head { order: 2; }
  .deck-row-l.deck-actions { order: 3; }
  #lab-dock-panel .stage-growth {
    position: static; transform: none; order: 0; width: 100%; max-width: none;
    margin: 0; padding: 8px 16px 0; justify-content: center;
    background: transparent; border: 0;
  }
  #lab-dock-panel .stage-growth input[type="range"] { width: auto; flex: 1 1 auto; min-width: 0; }
  #lab-dock-panel:not(.expanded) .deck-head { display: none; }
  #lab-dock-panel:not(.expanded) .channel-palette { display: none; }
  #lab-dock-panel:not(.expanded) .save-note { display: none; }
  #lab-dock-panel:not(.expanded) .deck-actions .dock-secondary { display: none; }
  .channel { padding: 8px 10px; }

  /* the tool rail becomes a sheet key over the deck */
  #lab-rail { left: auto; right: 10px; bottom: auto; top: 12px; justify-content: flex-end; padding: 0; }
  #lab-rail:not(.expanded) .lab-rail-stack { display: none; }
  .rail-fab {
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; border-radius: 0;
    clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
    border: 1px solid var(--av-edge); background: rgba(23, 17, 12, .94);
    color: var(--av-ink); font-size: 16px; cursor: pointer; pointer-events: auto;
  }
  #lab-rail.expanded { z-index: 41; }
  #lab-rail.expanded .lab-rail-stack {
    position: fixed; left: 8px; right: 8px; width: auto;
    top: auto; bottom: calc(var(--lab-bar, 0px) + 8px + env(safe-area-inset-bottom));
    max-height: 60dvh; overflow-y: auto; flex-wrap: wrap; border-radius: 0;
    clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
    z-index: 41;
  }
  .vp-tools { display: none; }

  /* the library becomes its own bottom sheet, with a header and a close key */
  #lab-drawer-scrim {
    display: block; position: fixed; inset: 0; z-index: 32;
    background: rgba(6, 4, 3, .62); opacity: 0; pointer-events: none; transition: opacity .24s;
  }
  #lab-drawer-scrim.open { opacity: 1; pointer-events: auto; }
  #panel {
    position: fixed; top: auto; bottom: var(--lab-bar, 0px); left: 0; right: 0;
    width: 100%; max-width: 100%; height: min(78vh, 600px); max-height: 78vh;
    transform: translateY(100%); display: block; z-index: 34;
    border-left: 0; border-top: 1px solid var(--av-edge);
    padding: 12px 16px calc(24px + env(safe-area-inset-bottom));
    transition: transform .24s cubic-bezier(.2, .9, .25, 1);
    box-shadow: 0 -18px 40px -24px #000;
  }
  body.drawer-open #panel { transform: translateY(0); }
  body.drawer-open #lab-dock-panel { visibility: visible; }
  .drawer-head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    position: sticky; top: 0; z-index: 2; margin: 0 -16px 10px; padding: 4px 16px 10px;
    background: linear-gradient(rgba(31, 22, 15, .99), rgba(23, 17, 12, .97));
    border-bottom: 1px solid var(--av-line-dim);
  }
  .drawer-head strong {
    display: block; font-family: var(--av-serif); font-weight: 400; font-size: 20px; color: var(--av-ink);
  }
  #drawer-close {
    min-width: 44px; min-height: 44px; border-radius: 0;
    border: 1px solid var(--av-edge); background: rgba(127, 224, 184, .07);
    color: var(--av-ink); font: 22px/1 var(--av-sans); cursor: pointer;
  }
  .glitch-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 640px) {
  .stage-growth { left: 12px; right: 12px; transform: none; max-width: none; justify-content: center; }
  .stage-growth .gr-label { display: none; }
  #lab-dock-panel .stage-growth .gr-label { display: inline; }
  .glitch-grid { grid-template-columns: 1fr; }
}

/* ══ TOUCH ══ real thumb targets, whatever the width */
@media (pointer: coarse) {
  .channel { min-height: 44px; }
  .deck-actions .btn, .chip, .rail-action, body.studiolab #panel button, .preset { min-height: 44px; }
  .vp-specnav .nav-arrow { width: 44px; height: 44px; }
  /* a 3px range track is a 3px target: pad the control, not the track */
  .stage-growth input[type="range"], .vp-rotate input[type="range"], .vp-exposure input[type="range"] {
    height: 44px; background: transparent;
    background-image: linear-gradient(rgba(245, 233, 216, .18), rgba(245, 233, 216, .18));
    background-size: 100% 3px; background-position: 0 50%; background-repeat: no-repeat;
  }
  .stage-growth { padding-top: 0; padding-bottom: 0; }
}

/* -------------------------------------------------------------------------
   12. REDUCED MOTION
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  body.studiolab *, body.studiolab *::before, body.studiolab *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .vp-loading { transition: none; }
  /* the motes exist to DRIFT; frozen they are specks on the glass, so the
     reader who asked for no motion gets the still chamber instead. */
  #lab-parts { display: none; }
}


/* ---- SKIN CODES, seated in the editor column (2026-08-17; reflowed
   2026-08-18) ----
   It sits under Random / Save / Apply: the presses and the fields share a
   line where the words leave room, and the note speaks underneath. The whole
   row is DISPLAY:NONE when the owner switched codes off, because assemble.py
   removes the markup outright - there is no heading left behind to orphan.

   ★ THE CONTROLS ARE A WRAPPING ROW NOW, NOT GRID TRACKS. The old
   `auto minmax(0,1fr) minmax(0,1fr) auto` grid sized its two auto columns to
   the BUTTON LABELS - and the labels are the owner's own words
   (labels.studio_code*), not ours. jurassicden's "Copy the code" +
   "Paste it in" ate ~300px of the rail's 336, so both minmax(0,1fr) fields
   rendered as slivers a few pixels wide ON A 1920px BROWSER: the crush lives
   in the COLUMN, and the 720px phone tier that was meant to catch it keys on
   the VIEWPORT. A wrapping flex row is sized by the container itself -
   whatever fits a line shares it, whatever does not takes the next line
   whole - so the code box, the row's whole purpose, never drops below its
   floor at any label length in any language, and no media query has to
   guess the panel's width again. */
body.studiolab .deck-codes {
  display: block;
  margin-top: 5px;
  /* ★ AND IT NEVER SHRINKS. `.deck` is a flex COLUMN with overflow hidden,
     so a full panel squeezed this row - the last child, with no shrink guard -
     down toward zero height and then clipped what was left. That is why it
     read as half-buried rather than merely low. */
  flex: 0 0 auto;
  /* ★ AND IT LOOKS LIKE SOMETHING. His word was "super visible. like super."
     A hairline strip of controls at the foot of a long column reads as chrome
     even when it is fully on screen, so the block gets its own lit edge, its
     own ground and its own name. */
  padding: 8px 10px 9px;
  border: 1px solid rgba(127, 224, 184, .34);
  border-left: 3px solid var(--av-jade);
  background: linear-gradient(180deg, rgba(47, 191, 135, .07), rgba(10, 8, 6, .55));
}

body.studiolab .deck-codes .code-row {
  display: flex; flex-wrap: wrap;
  gap: 4px 8px; align-items: center;
  /* the wrapper's padding is the spacing here; .code-row's own-seat rule
     above would add 4px more on top of it */
  margin-top: 0;
}
body.studiolab .deck-codes #code-input,
body.studiolab .deck-codes #code-name {
  min-width: 0;
  padding: 5px 8px; border-radius: 0;
  font-family: var(--av-sans); font-size: 11.5px;
  background: rgba(10, 8, 6, .85);
  border: 1px solid rgba(127, 224, 184, .20);
  color: var(--av-ink); outline: 0;
}
/* The code box is the row's purpose - a player pastes a code INTO it - so it
   holds a real floor (~a code's first 20 characters) and takes first claim on
   a line's spare width. Wrapping means the floor is honoured by taking a
   whole line, never by crushing a neighbour. The name is one short word and
   gives way first. */
body.studiolab .deck-codes #code-input { flex: 2 1 170px; min-width: min(170px, 100%); }
body.studiolab .deck-codes #code-name { flex: 1 1 130px; }
body.studiolab .deck-codes #code-input:focus,
body.studiolab .deck-codes #code-name:focus {
  border-color: var(--av-jade);
  box-shadow: 0 0 0 1px rgba(47, 191, 135, .40);
}
body.studiolab .deck-codes #code-note {
  flex: 0 0 100%; margin: 0;
}
/* No phone tier any more: the wrap above is container-driven, so a narrow
   deck - phone, tablet rail, or a future column width - lays the same row
   out on more lines by itself. */

/* ---- the skin-code block's own name, and its two presses ---- */
body.studiolab .deck-codes .code-lead {
  flex: 0 0 100%;
  font-family: var(--av-sans);
  font-size: 10.5px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--av-jade);
  margin: 0; line-height: 1.15;
}
/* Copy and Import are the two things a player comes here to press, so they
   read as presses rather than as ghosts among the fields. */
body.studiolab .deck-codes #copy-code,
body.studiolab .deck-codes #import-code {
  border-color: var(--av-jade);
  color: var(--av-ink);
  background: rgba(47, 191, 135, .16);
  font-weight: 600;
  white-space: nowrap;
}
body.studiolab .deck-codes #copy-code:hover,
body.studiolab .deck-codes #import-code:hover {
  background: rgba(47, 191, 135, .28);
}
/* An owner who switched codes off has the row REMOVED by assemble.py; without
   this the wrapper would keep drawing its border and its ground around
   nothing. */
body.studiolab .deck-codes:empty { display: none; }
/* ...and the OTHER way the row goes: signed out, studio.mjs hides #code-row
   (hidden + display:none, same both-ways idiom as the staff toggle). Same
   promise as the :empty guard - no lit edge around nothing. Scoped with
   :has() like site.css's nav rules; a browser without it simply keeps
   today's empty strip. */
body.studiolab .deck-codes:has(> #code-row[hidden]) { display: none; }

/* Short desktops (the 720p family): the block gives a few pixels back -
   the same trade the swatch tiers above make. Declared HERE, after the base
   rules, because the base block sits this late in the sheet and same-selector
   precedence is file order: written inside the height tiers above (where the
   old grid's one-line variant also sat) these were silently shadowed and
   never applied - "measured as applied while doing absolutely nothing", the
   exact trap the .deck padding comment names. */
@media (min-width: 901px) and (max-height: 820px) {
  body.studiolab .deck-codes { padding: 5px 8px 6px; }
  body.studiolab .deck-codes .code-row { gap: 3px 6px; }
}
