/* CHAIN — Studio Chain design system.
   Source of truth: docs/design/handover/Studio Chain.dc.html + BUILD-PLAN-CHAIN.md.
   Supersedes bench.css entirely as Studio's visual layer — Bench is a stale, voided design.
   Every value below is lifted from the approved design (§4 of BUILD-PLAN-CHAIN.md). Class prefix cn-. */

@font-face{
  font-family:'Urbanist'; font-style:normal; font-weight:200; font-display:swap;
  src:url('../assets/fonts/urbanist-200.woff2') format('woff2');
}
@font-face{
  font-family:'Urbanist'; font-style:normal; font-weight:300; font-display:swap;
  src:url('../assets/fonts/urbanist-300.woff2') format('woff2');
}
@font-face{
  font-family:'Urbanist'; font-style:normal; font-weight:400; font-display:swap;
  src:url('../assets/fonts/urbanist-400.woff2') format('woff2');
}
@font-face{
  font-family:'Urbanist'; font-style:normal; font-weight:500; font-display:swap;
  src:url('../assets/fonts/urbanist-500.woff2') format('woff2');
}

:root{
  /* ── ground (byte-identical to Bench's — confirmed §4.1) ──────────────── */
  --cn-page:            #070D0E;
  --cn-shell:           radial-gradient(80% 60% at 16% 90%, #16262A 0%, #0E1719 44%, #0B1214 100%);
  --cn-well:            #08100F;

  /* ── ink ────────────────────────────────────────────────────────────── */
  --cn-ink:             #EEF2F1;
  --cn-ink-on-amber:    #1A0D03;
  --cn-ink-on-knob:     #0F1A1C;
  --cn-ink-on-white:    #0B1214;

  /* ── amber / lime (unchanged from Bench) ──────────────────────────────── */
  --cn-amber:           #E8863A;

  /* THE LIME KILL. studio.html alone references var(--lime) 110 times — the onboarding tour's
     "STEP n OF 8" and Next button, the CLIP readout, the hero eyebrow, the cine/full-frame
     buttons, the processing spinner. chain.css re-tokened everything that reads --cn-*, but
     --lime was never remapped, so all 110 kept rendering in the retired #E7FE55 while the rest
     of the app moved to amber. That is the "old branding" that keeps showing up in screenshots.
     Remapped here rather than editing 110 call sites, so it cannot drift back.
     Safe for mono/print/restore: desktop-tokens.css records that those three reference --lime
     exactly zero times (they carry --safe), so this reaches only the lime-family pages. */
  --lime:               var(--cn-amber);
  --lime-ink:           var(--cn-amber);
  --cn-amber-hi:        #F5A44E;
  --cn-amber-glow:      0 0 34px -12px rgba(232,134,58,0.8);
  /* --cn-lime was a literal, un-rebranded lime green (#C6E848) still wired into filmstrip
     active-borders, the cached-CTA dot, histogram peak values, and every -chain.css bridge
     file's eyebrow/status dots — al flagged it as off-brand. Re-pointing it at amber here
     fixes every one of those call sites at once, the same way --lime already does above. */
  --cn-lime:            var(--cn-amber);
  --cn-lime-glow:       0 0 8px var(--cn-amber);

  /* ── the chain-gradient endpoints (§4.2) — every module/stage accent is
     mix(--cn-grad-a, --cn-grad-b, t) where t = position in the 01-23 chain ── */
  --cn-grad-a:          #B9C4C2;
  --cn-grad-b:          #E8863A;

  /* ── surfaces ───────────────────────────────────────────────────────── */
  --cn-surf:            rgba(238,242,241,0.05);
  --cn-surf-off:        rgba(238,242,241,0.028);
  --cn-surf-btn:        rgba(238,242,241,0.08);
  --cn-surf-track:      rgba(238,242,241,0.14);

  /* ── borders ────────────────────────────────────────────────────────── */
  --cn-line-off:        rgba(238,242,241,0.07);
  --cn-line-head:       rgba(238,242,241,0.09);
  --cn-line-div:        rgba(238,242,241,0.10);
  --cn-line:            rgba(238,242,241,0.11);
  --cn-line-2:          rgba(238,242,241,0.12);
  --cn-line-btn:        rgba(238,242,241,0.14);
  --cn-line-chip:       rgba(238,242,241,0.20);
  --cn-line-check:      rgba(238,242,241,0.28);
  --cn-line-knob:       rgba(238,242,241,0.30);
  --cn-line-marks:      rgba(255,255,255,0.35);

  /* ── ink alpha ladder ───────────────────────────────────────────────── */
  --cn-t-16:  rgba(238,242,241,0.16);
  --cn-t-22:  rgba(238,242,241,0.22);
  --cn-t-30:  rgba(238,242,241,0.30);
  --cn-t-35:  rgba(238,242,241,0.35);
  --cn-t-40:  rgba(238,242,241,0.40);
  --cn-t-42:  rgba(238,242,241,0.42);
  --cn-t-45:  rgba(238,242,241,0.45);
  --cn-t-50:  rgba(238,242,241,0.50);
  --cn-t-55:  rgba(238,242,241,0.55);
  --cn-t-60:  rgba(238,242,241,0.60);
  --cn-t-62:  rgba(238,242,241,0.62);
  --cn-t-68:  rgba(238,242,241,0.68);
  --cn-t-70:  rgba(238,242,241,0.70);
  --cn-t-72:  rgba(238,242,241,0.72);
  --cn-t-75:  rgba(238,242,241,0.75);
  --cn-t-80:  rgba(238,242,241,0.80);
  --cn-t-85:  rgba(238,242,241,0.85);
  --cn-t-90:  rgba(238,242,241,0.90);
  --cn-t-92:  rgba(238,242,241,0.92);
  --cn-t-12:  rgba(238,242,241,0.12);

  /* ── hover ladder ───────────────────────────────────────────────────── */
  --cn-hov-08: rgba(238,242,241,0.08);
  --cn-hov-10: rgba(238,242,241,0.10);
  --cn-hov-12: rgba(238,242,241,0.12);
  --cn-hov-14: rgba(238,242,241,0.14);
  --cn-hov-16: rgba(238,242,241,0.16);
  --cn-hov-18: rgba(238,242,241,0.18);
  --cn-hov-20: rgba(238,242,241,0.20);

  /* ── type ───────────────────────────────────────────────────────────── */
  --cn-font: 'Urbanist', system-ui, sans-serif;
  --cn-w-thin: 200;  --cn-w-light: 300;  --cn-w-reg: 400;  --cn-w-med: 500;

  --cn-fs-7:    7px;    --cn-ls-22: 0.22em;
  --cn-fs-75:   7.5px;  --cn-ls-14: 0.14em;
  --cn-fs-8:    8px;    --cn-ls-18: 0.18em;
  --cn-fs-85:   8.5px;  --cn-ls-24: 0.24em;
  --cn-fs-9:    9px;    --cn-ls-20: 0.20em;
  --cn-fs-95:   9.5px;  --cn-ls-16: 0.16em;
  --cn-fs-10:   10px;   --cn-ls-10: 0.10em;
  --cn-fs-105:  10.5px; --cn-ls-12: 0.12em;
  --cn-fs-11:   11px;   --cn-ls-06: 0.06em;
  --cn-fs-115:  11.5px;
  --cn-fs-12:   12px;
  --cn-fs-125:  12.5px;
  --cn-fs-13:   13px;
  --cn-fs-15:   15px;
  --cn-fs-16:   16px;   --cn-ls-n2: -0.02em;
  --cn-fs-21:   21px;   --cn-ls-n3: -0.03em;
  --cn-fs-30:   30px;   --cn-ls-n35: -0.035em;

  /* ── radius ─────────────────────────────────────────────────────────── */
  --cn-r-5:  5px;
  --cn-r-7:  7px;
  --cn-r-8:  8px;
  --cn-r-9:  9px;
  --cn-r-10: 10px;
  --cn-r-11: 11px;
  --cn-r-12: 12px;
  --cn-r-13: 13px;
  --cn-r-14: 14px;
  --cn-r-15: 15px;
  --cn-r-16: 16px;
  --cn-r-18: 18px;
  --cn-r-pill: 999px;

  /* ── layout (§4.3) ──────────────────────────────────────────────────── */
  --cn-bar-h:       62px;
  --cn-bar-pad:     18px;
  --cn-bar-gap:     16px;
  --cn-body-pad:    12px;
  --cn-body-gap:    12px;
  --cn-rail-w:      58px; /* LEFTPANEL.md §2 — reverted from the interim 78px, back to the spec's own number */
  --cn-col-w:       366px;
  --cn-col-gap:     10px;
  --cn-vtool-h:     40px;
  --cn-exif-h:      34px;
  --cn-mod-gap:     6px;
  --cn-dial:        104px;
  --cn-hist-h:      44px;

  /* ── motion / disabled / focus — carried forward from Bench verbatim (decision #27) ── */
  --cn-ease:        cubic-bezier(0.22, 1, 0.36, 1);
  --cn-t-state:     180ms;
  --cn-t-needle:    70ms; /* was 260ms — al: dial needle visibly lagged the tick-strip during a live drag
    (wireChainSliderDrag's liveUpdate already rewrites #cnDialAnchorParent's innerHTML on every single
    pointermove, so the JS-side value/label were already instant — the whole "perceptible lag" was this
    CSS transition re-animating the needle's rotate() on every one of those rewrites). Kept non-zero
    (not 0ms) so chainDialClick's +/- stepper taps still read as a deliberate small nudge rather than a
    hard jump cut; 70ms is fast enough to feel like direct-manipulation during drag while still visible. */
  --cn-disabled-alpha: 0.35;
  --cn-focus-ring:  2px solid rgba(232,134,58,0.6);
  --cn-focus-offset: 2px;
}

@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── press/release choreography (shared by every clickable control) ─────
   One coherent press → release motion instead of an abrupt on/off state
   change: the same duration/easing token (--cn-t-state / --cn-ease) drives
   both directions, so pressing down and springing back read as a single
   gesture rather than two disconnected snaps. transform+filter opt into
   the transition here; each control's own :active rule (below, alongside
   its base styles) supplies the scale/brightness/background delta. */
.cn-cmdbtn, .cn-action, .cn-chip, .cn-cursor, .cn-mod-reset,
.cn-picker-arrow, .cn-picker-val-btn, .cn-swatch{
  transition: transform var(--cn-t-state) var(--cn-ease),
              filter var(--cn-t-state) var(--cn-ease),
              background var(--cn-t-state) var(--cn-ease),
              background-color var(--cn-t-state) var(--cn-ease),
              border-color var(--cn-t-state) var(--cn-ease);
}
.cn-cmdbtn:active, .cn-action:active, .cn-chip:active, .cn-cursor:active,
.cn-picker-arrow:active, .cn-picker-val-btn:active,
.cn-swatch:active{
  transform: scale(0.94);
  filter: brightness(0.90);
}
/* al: "I want the animation removed" — .cn-switch AND .cn-checkrow (the checkbox-style
   toggle, e.g. Zones' "False colour") are both intentionally excluded from the shared
   transition rule above and carry none of their own. They flip state instantly, full
   stop; no press-scale, no colour fade, no knob slide, no checkmark fade-in. */
.cn-cmdbtn[disabled]:active, .cn-action[disabled]:active, .cn-chip[disabled]:active,
.cn-cursor[disabled]:active, .cn-picker-arrow[disabled]:active,
.cn-picker-val-btn[disabled]:active{
  transform: none;
  filter: none;
}
/* .cn-mod-reset already animates a hover rotate() — its :active combines the
   press scale with that same rotation instead of overwriting it. */
.cn-mod-reset:active{ filter: brightness(0.90); }
.cn-mod-reset:hover:active{ transform: rotate(-40deg) scale(0.90); }

/* ══════════════════════════════════════════════════════════════════════
   SHELL
   ══════════════════════════════════════════════════════════════════════ */

html.cn-app, html.cn-app body{ margin:0; padding:0; height:100%; background:var(--cn-page); overflow:hidden; }
html.cn-app body{ font-family:var(--cn-font); -webkit-font-smoothing:antialiased; }
html.cn-app *{ box-sizing:border-box; }
html.cn-app a{ color:var(--cn-amber); text-decoration:none; }
html.cn-app a:hover{ color:var(--cn-amber-hi); }

html.cn-app :focus-visible{ outline:var(--cn-focus-ring); outline-offset:var(--cn-focus-offset); }

/* Same "hide, never delete" treatment BUILD-PLAN.md §6.2 established for Bench, applied twice
   over here: the pre-Bench 19-stage card system AND Bench's own bn-* shell are both dead UI now,
   kept in the DOM because both still carry live ⌘K/tour anchors and side-effect-only functions
   (bindStageCard, initVolSliders, mountFilmBenchBody, renderBenchStack, syncGroundLine, etc).
   Chain hides both layers the same way Bench hid the original one. */
.nav{ display:none !important; }
.preview-assetbar, .preview-head, .pv-ground{ display:none !important; }
.preview-col{ display:none !important; }
.cn-right > .studio{ flex:none; height:0; overflow:hidden; }
/* Bench's own shell, now itself demolished-but-kept-hidden the same way it treated the original. */
.bn-bar, .bn-body{ display:none !important; }

.cn-shell{
  position:relative; display:flex; flex-direction:column;
  width:100%; height:100dvh; overflow:hidden;
  background:var(--cn-shell); color:var(--cn-ink);
  font-family:var(--cn-font); -webkit-font-smoothing:antialiased;
  animation:cnIn .7s var(--cn-ease) both;
}
.cn-grain{
  position:absolute; inset:0; pointer-events:none;
  opacity:.06; mix-blend-mode:overlay;
  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='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:cnGrain 8s steps(3) infinite;
}
@keyframes cnIn   { from{opacity:0; filter:blur(10px)} to{opacity:1; filter:blur(0)} }
@keyframes cnGrain{ 0%{transform:translate(0,0)} 25%{transform:translate(-2%,1%)}
                    50%{transform:translate(1%,-2%)} 75%{transform:translate(-1%,-1%)}
                    100%{transform:translate(0,0)} }
@keyframes cnPulse{ 0%,100%{opacity:1} 50%{opacity:.25} }

::-webkit-scrollbar{ width:5px; }
::-webkit-scrollbar-thumb{ background:rgba(238,242,241,0.16); border-radius:3px; }

.cn-bar{ position:relative; flex:none; height:var(--cn-bar-h);
         display:flex; align-items:center; gap:var(--cn-bar-gap);
         padding:0 var(--cn-bar-pad);
         border-bottom:1px solid var(--cn-line-head); }
.cn-bar .sp{ flex:1; }

/* The bar had no shrink plan at all: every child was intrinsically sized, .sp collapsed to 0 and
   the overflow simply ran off the right edge — which is where the Render CTA lives, so on a
   narrower window the app's primary action was invisible with no scrollbar or hint that anything
   had been cut. Measured at 1121px: content wanted 1133px, and #cnRenderBtn sat at x=1043..1133.
   Priority under pressure, in order: the CTA and the command buttons are the tools, so they never
   shrink; the room links are navigation to OTHER pages, so they give way first (and can scroll if
   it ever comes to that); the wordmark is branding and yields before the tools do. */
.cn-bar{ overflow:hidden; }
.cn-bar .cn-cta, .cn-bar .cn-cmdgrp, .cn-bar .cn-vrule{ flex:0 0 auto; }
.cn-bar .cn-nav{ min-width:0; overflow-x:auto; scrollbar-width:none; }
.cn-bar .cn-nav::-webkit-scrollbar{ display:none; }
.cn-bar .cn-wordmark{ min-width:0; }
/* Tighten before anything has to be clipped — the gap and side padding alone give back ~45px,
   which covers the ordinary laptop widths where the overflow showed up. */
@media (max-width: 1280px){
  .cn-bar{ gap:9px; padding:0 12px; }
  .cn-bar .cn-nav{ gap:2px; }
}

.cn-body{ position:relative; flex:1; min-height:0;
          display:flex; gap:var(--cn-body-gap); padding:var(--cn-body-pad); }

.cn-rail{ flex:none; width:var(--cn-rail-w); display:flex; flex-direction:column; gap:6px; min-height:0; }
.cn-left { flex:none; width:var(--cn-col-w); min-height:0;
           display:flex; flex-direction:column; gap:var(--cn-col-gap); }
.cn-right{ flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--cn-col-gap); }

.cn-vtools{ flex:none; height:var(--cn-vtool-h); display:flex; align-items:center; gap:6px;
            padding:0 12px; border-radius:var(--cn-r-14);
            background:var(--cn-surf); border:1px solid var(--cn-line); }
.cn-vtools .sp{ flex:1; }

/* ══════════════════════════════════════════════════════════════════════
   COMPONENT LAYER
   ══════════════════════════════════════════════════════════════════════ */

/* ── wordmark ─────────────────────────────────────────────────────────── */
.cn-wordmark{ font-size:var(--cn-fs-12); font-weight:var(--cn-w-med);
              letter-spacing:var(--cn-ls-18); padding-right:4px;
              display:flex; align-items:center; }
.cn-wordmark i{ color:var(--cn-amber); font-style:normal; }
.cn-wordmark img{ height:22px; width:auto; display:block; }

/* ── command-bar button ───────────────────────────────────────────────── */
.cn-cmdbtn{ display:flex; flex-direction:column; align-items:center; gap:5px;
            width:52px; padding:6px 0; border-radius:var(--cn-r-10);
            background:transparent; border:0; cursor:pointer; color:inherit;
            font-family:inherit; }
.cn-cmdbtn:hover{ background:var(--cn-hov-10); }
.cn-cmdbtn svg{ width:19px; height:19px; fill:none; stroke:var(--cn-ink);
                stroke-linecap:round; stroke-linejoin:round; }
.cn-cmdbtn span{ font-size:var(--cn-fs-75); letter-spacing:var(--cn-ls-14); color:var(--cn-t-55); }
.cn-cmdbtn[aria-pressed="true"]{ background:var(--cn-hov-14); }
.cn-cmdbtn[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-cmdbtn[disabled]:hover{ background:transparent; }
a.cn-cmdbtn{ text-decoration:none; }
.cn-cmdbtn.icon-only{ width:32px; padding:7px 0; }
.cn-cmdgrp{ display:flex; align-items:center; gap:4px; }
.cn-vrule{ width:1px; height:28px; background:var(--cn-line-div); flex:none; }

/* ── status pill ──────────────────────────────────────────────────────── */
.cn-status{ display:flex; align-items:center; gap:9px; height:27px; padding:0 13px;
            border-radius:var(--cn-r-pill); background:var(--cn-hov-08);
            border:1px solid var(--cn-line-2);
            font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-18); color:var(--cn-t-70); }
.cn-status i{ width:5px; height:5px; border-radius:50%; display:block;
              background:var(--cn-lime); box-shadow:var(--cn-lime-glow);
              animation:cnPulse 2.6s ease-in-out infinite; }

/* ── nav links + primary CTA ──────────────────────────────────────────── */
.cn-nav{ display:flex; gap:4px; font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-20); color:var(--cn-t-45); }
.cn-nav a{ color:inherit; text-decoration:none; display:flex; align-items:center; justify-content:center;
  width:32px; height:32px; border-radius:var(--cn-r-10); }
.cn-nav a svg{ width:18px; height:18px; fill:none; stroke:currentColor; stroke-linecap:round; stroke-linejoin:round; }
.cn-nav a:hover{ color:var(--cn-ink); background:var(--cn-hov-10); }
/* QC sweep: was --cn-ink (white). Four page CSS files (print/credits/custom/about) each
   overrode this to --cn-amber while mono/gallery/studio kept white, so the "you are here"
   marker changed colour depending which room you stood in. Amber was the majority and is the
   stronger signal, so it wins here and the four local copies are deleted. */
/* Icon suite (ICON-SUITE.md): the room word is gone from the row, so "you are here" needs a
   background pill (not just an ink colour swap) to survive the icon-only change. */
.cn-nav a[aria-current="page"]{ color:var(--cn-amber); background:color-mix(in srgb, var(--cn-amber) 16%, transparent); }

.cn-cta{ display:flex; align-items:center; gap:8px; height:31px; padding:0 18px; border:0;
         border-radius:var(--cn-r-pill); background:var(--cn-amber);
         color:var(--cn-ink-on-amber); font-family:inherit;
         font-size:var(--cn-fs-115); font-weight:var(--cn-w-med);
         box-shadow:var(--cn-amber-glow); cursor:pointer;
         transition:background var(--cn-t-fast, .14s) ease, box-shadow var(--cn-t-fast, .14s) ease, opacity var(--cn-t-fast, .14s) ease; }
.cn-cta:hover:not(:disabled){ background:var(--cn-amber-hi); }
.cn-cta:disabled{ opacity:.34; cursor:default; box-shadow:none; }

/* Render CTA state dot — al: "render button needs to be visually connected to a function".
   The tier mirrors the legacy #flattenBtn's own perf classes (studio.html updatePerfIndicator):
   how long the last full-resolution pipeline pass took. Dark ink on amber, not the usual lime,
   because the pill's own background IS the accent — a coloured dot on a coloured pill needs
   contrast against the pill, not against the page. */
.cn-cta-dot{ width:6px; height:6px; border-radius:50%; display:block; flex:0 0 auto;
             background:color-mix(in srgb, var(--cn-ink-on-amber) 42%, transparent); }
.cn-cta.perf-warn .cn-cta-dot,
.cn-cta.perf-hot .cn-cta-dot{ background:var(--cn-ink-on-amber);
             animation:cnPulse 1.8s ease-in-out infinite; }
/* A render checkpoint is live and still valid for the current chain — the button has nothing
   left to do, so it stops advertising itself: flat, quiet, no glow. */
.cn-cta.is-cached{ background:var(--cn-hov-08); color:var(--cn-t-70);
                   border:1px solid var(--cn-line-2); box-shadow:none; }
.cn-cta.is-cached:hover:not(:disabled){ background:var(--cn-hov-12, var(--cn-hov-08)); }
.cn-cta.is-cached .cn-cta-dot{ background:var(--cn-lime); box-shadow:var(--cn-lime-glow); animation:none; }
.cn-cta.is-busy{ pointer-events:none; }
.cn-cta.is-busy .cn-cta-dot{ border-radius:50%; border:1.5px solid color-mix(in srgb, var(--cn-ink-on-amber) 30%, transparent);
             border-top-color:var(--cn-ink-on-amber); background:none; width:11px; height:11px;
             animation:cnSpin .7s linear infinite; }
@keyframes cnSpin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .cn-cta-dot{ animation:none !important; }
}

/* ── card / panel base ─────────────────────────────────────────────────── */
.cn-card{ background:var(--cn-surf); border:1px solid var(--cn-line); }
.cn-panel{ flex:none; border-radius:var(--cn-r-16); padding:12px 14px 11px;
           background:var(--cn-surf); border:1px solid var(--cn-line); }

/* ── histogram panel (new — always visible above the stage title row) ───── */
.cn-hist-head{ display:flex; align-items:baseline; justify-content:space-between; }
.cn-hist-head .l{ font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-24); color:var(--cn-t-60); }
.cn-hist-head .r{ font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-14); color:var(--cn-t-60); }
.cn-hist-head .r b{ color:var(--cn-lime); font-weight:inherit; }
.cn-hist-bars{ display:flex; align-items:flex-end; height:var(--cn-hist-h); margin-top:9px; }
.cn-hist-bars span{ flex:1; margin:0 0.5px; border-radius:1px 1px 0 0; background:var(--cn-t-42); }
.cn-hist-zonestrip{ display:flex; margin-top:6px; }
.cn-hist-zonestrip span{ flex:1; height:5px; display:block; }

/* ── tick strip (one primitive, two configurations, index-positioned §5.9) ── */
.cn-ticks{ display:flex; align-items:flex-end; }
/* :not(.cn-marker) — the slider's marker is a <span> living inside this same container, so this
   base rule was giving it flex:1 and margin:0 1.5px. The margin is the one that shows: it pushed
   the 2px stem 1.5px right of the value it points at, on every slider on every page. Same trap as
   the .cn-ticks--slider span rules below; excluded in both places. */
.cn-ticks span:not(.cn-marker){ flex:1; margin:0 1.5px; border-radius:1px; background:var(--cn-t-16); }
/* ── the ruler (site-standard slider, ported from Print Room) ──────────────────────────────
   al ruled Print Room's slider the better control and made it the standard everywhere. This is
   that look ported onto Chain's OWN markup and wiring — deliberately CSS-only, changing not a
   line of JS, so every behaviour the Chain slider already has survives intact: stepper nudges,
   the elastic drag curve, detents every 25, click-to-type, the colour ramps (Kelvin/LGG/film
   couplings), master-dial tracking, isDragging's proxy-render path, and pushUndo once per drag.
   Swapping in Print's own component wholesale would have cost all of that.

   Four things make Print's read as an instrument rather than a bar chart:
     1 · HAIRLINES, not blocks. Each tick is 1px of ink with air around it. Drawn as a centred
         gradient inside the existing flex cell rather than by narrowing the cell, because the
         marker is positioned at (i + 0.5) * 100% / N — the cell centre. Narrowing the cells
         would desynchronise the marker from the ticks it points at.
     2 · A BASELINE. A rule under the ticks. Forty-odd hairlines with nothing beneath them read
         as a barcode; one line under them and the same hairlines read as a graduated scale.
     3 · MAJOR GRADUATIONS every 6th, taller and brighter, so the eye can count without counting.
     4 · A PENNANT on a glowing stem, in amber, so the current value is findable at a glance in a
         column of identical hairlines. */
/* margin-top clears the pennant (7px) that sits above the track, plus a little air, so the
   marker never crowds the value box / steppers on the row above. */
/* 0 · THE RULER READS 0 / 25 / 50 / 75 / 100.  al: "sliders are still not in the middle.
   0 25 50 75 100 simples."
   The marker was already centred; the ruler was not. .cn-ticks is a flex row of N equal cells with
   the hairline drawn down the CENTRE of each cell, so hairline i sits at (i+0.5)/N — which can
   never be 0% or 100%, and at the old N=34 could never be 50% either (it wants index 16.5). The
   marker pointed at a middle the track had no mark for.
   This stretches the strip by exactly half a cell at each end and pulls it back left by the same
   amount, so hairline i lands on i/(N-1): tick 0 on 0%, tick 48 on 100%, tick 24 on exactly 50%.
   Nothing but transparent cell padding overflows — the hairlines are what move.
   Keep the two 48s in step with CHAIN_SLIDER_TICKS (49) in studio.html: they are N-1. */
.cn-ticks--slider{ position:relative; height:30px; margin-top:14px;
  width:calc(100% * 49 / 48); margin-left:calc(-50% / 48); }
/* :not(.cn-marker) on EVERY tick rule below. The marker is itself a <span> inside the strip, so
   these tick rules were matching it and winning on specificity (0-1-1 beats .cn-marker's 0-1-0).
   Three consequences, all live until now and all contributing to "the middle is not the middle":
     · margin:0 killed .cn-marker's own margin-left:-1px, so the 2px stem hung off the RIGHT of its
       left edge instead of straddling it — every marker in the app sat 1px right of its true
       position, permanently. Measured: marker centre 265.500px vs centre tick 264.535px.
     · height:14px overrode the stem's top:0/bottom:1px, so it never spanned the track.
     · the hairline gradient painted over the stem's amber.
   Measured after: marker centre 264.504px against an expected 264.504px. */
.cn-ticks--slider span:not(.cn-marker){
  --tick: var(--cn-t-24, rgba(255,255,255,.24));
  height:14px; margin:0; border-radius:0;
  background:linear-gradient(to right,
    transparent calc(50% - .5px), var(--tick) calc(50% - .5px),
    var(--tick) calc(50% + .5px), transparent calc(50% + .5px));
  transition:height var(--cn-t-state, .16s) var(--cn-ease, ease), background .16s var(--cn-ease, ease); }
/* Majors every 12th tick — children 1/13/25/37/49, i.e. indices 0/12/24/36/48 — which after the
   stretch above are exactly 0%, 25%, 50%, 75%, 100%. This was 6n+1 at N=34, giving 0/6/12/18/24/30:
   six majors that stopped short of both ends and straddled the middle. */
.cn-ticks--slider span:not(.cn-marker):nth-child(12n+1){ --tick: var(--cn-t-46, rgba(255,255,255,.46)); height:21px }
.cn-ticks--slider span:not(.cn-marker).on{ --tick: var(--cn-t-78, rgba(255,255,255,.78)); height:17px }
.cn-ticks--slider span:not(.cn-marker):nth-child(12n+1).on{ --tick: var(--cn-t-90, rgba(255,255,255,.9)); height:23px }
/* THE HOME GRADUATION — the tick the value is measured FROM.
   This used to be :nth-child(25), the geometric centre, on the reasoning that "the centre tick is
   the one the eye checks a symmetric slider against". True — but only 95 of the app's 277 sliders
   ARE symmetric. On the other 182 the tallest, brightest mark on the ruler pointed at a value that
   means nothing: measured, "Vignette midpoint" (0..100) drew the identical 26px centre graduation
   as "Exposure" (±5 EV), which is a large part of why every slider read the same as every other.
   `home` is emitted by chainTickStripHTML at the control's own default (zeroAt), which is exactly
   where the `on` fill already grows from, so the ruler now states its own polarity: a bipolar
   control is tall in the middle and fills outward, a unipolar one is tall at its end and fills
   across. Nothing here is hard-coded to an index — retune a range in the manifest and the
   graduation follows it. */
.cn-ticks--slider span:not(.cn-marker).home{ --tick: var(--cn-t-60, rgba(255,255,255,.6)); height:25px }
.cn-ticks--slider span:not(.cn-marker).home.on{ --tick: var(--cn-t-90, rgba(255,255,255,.9)); height:26px }
.cn-ticks--slider.off span:not(.cn-marker){ --tick: var(--cn-t-12) }
/* 2 · the baseline. Brightens while the ruler is in use, same as Print's.
   Inset by half a cell each side so it spans the real 0%..100% of the row, not the stretched box:
   half a cell of a 49-cell strip is 100%/98 of that strip's own width. */
.cn-ticks--slider::after{ content:''; position:absolute; left:calc(100% / 98); right:calc(100% / 98);
  bottom:0; height:1px;
  background:var(--cn-t-14, rgba(255,255,255,.14)); pointer-events:none;
  transition:background var(--cn-t-state, .16s) var(--cn-ease, ease); }
.cn-ticks--slider:hover::after{ background:var(--cn-t-30, rgba(255,255,255,.3)); }
.cn-ticks--slider:hover span:not(.cn-marker){ height:16px }
.cn-ticks--slider:hover span:not(.cn-marker):nth-child(12n+1){ height:23px }
.cn-ticks--slider:hover span:not(.cn-marker).home{ height:27px }
/* Full opacity on the hue/ramp variant: the colour now arrives as a 1px hairline rather than a
   solid block, and a hairline dimmed to .85 reads as muddy rather than subtle. The thinness is
   what makes it a hint of colour — it doesn't need dimming on top. */
.cn-ticks--hue span:not(.cn-marker){ opacity:1 }
.cn-hue-dot{ display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:2px;
             flex:none; box-shadow:inset 0 0 0 1px rgba(0,0,0,.15); }

/* pointer-events:auto (not none) — the marker sits above the track's own box (top:-10px), so a
   pointerdown aimed right at the visible triangle lands outside .cn-ticks--slider's rect and,
   with events disabled here, had nowhere delegated to bubble from: wireChainSliderDrag's
   pointerdown listener (studio.html) never saw it. The marker is a child of the [data-cn-slider]
   track (see chainSliderHTML), so re-enabling events on it still resolves correctly through
   e.target.closest('[data-cn-slider]') — grabbing the marker now works exactly like grabbing
   anywhere else on the track. */
/* 4 · the marker: a 2px stem carrying a pennant, both amber, both glowing — Print Room's own
   treatment. The bare lime triangle it replaces floated above the ruler with nothing tying it to
   a graduation, so at a glance you could see roughly where the value was but not which tick it
   actually sat on. The stem is what makes it readable; the glow is what makes it findable. */
/* top:0 — NOT a negative offset. The stem spans the track's own height and the pennant sits in
   the strip's margin-top above it, so the marker's total footprint is (track height + 7px) and
   nothing reaches into the value/stepper row above. An earlier top:-9px stacked the pennant's
   own 7px on top of a 9px overhang and bled ~16px into the row above it. */
.cn-marker{ position:absolute; top:0; bottom:1px; width:2px; margin-left:-1px;
            background:var(--cn-amber); border-radius:1px;
            box-shadow:0 0 14px 2px color-mix(in srgb, var(--cn-amber) 50%, transparent);
            pointer-events:auto; cursor:ew-resize; }
.cn-marker i{ display:block; width:0; height:0; position:absolute; left:50%; margin-left:-5px;
              bottom:100%;
              border-left:5px solid transparent; border-right:5px solid transparent;
              border-bottom:0; border-top:7px solid var(--cn-amber);
              filter:drop-shadow(0 0 6px color-mix(in srgb, var(--cn-amber) 55%, transparent)); }

/* ── stage rail (58px, 5-up vertical — replaces Bench's 4-up horizontal tiles) ── */
.cn-stage{ flex:1; min-height:0; border-radius:var(--cn-r-15); cursor:pointer;
           display:flex; flex-direction:column; align-items:center; justify-content:space-between;
           padding:11px 0 12px; border:1px solid var(--cn-line);
           font-family:inherit; transition:background var(--cn-t-state) var(--cn-ease),
           border-color var(--cn-t-state) var(--cn-ease); }
/* Every tile's own background IS --wash (see the .cn-stage rule below), climbing from a near-
   neutral 10% tint to a near-solid 54% amber across the five tiles — but this text colour was a
   flat low-opacity white, so it read fine on tile 01's dark wash and nearly vanished on tile 05's
   bright one. Blending toward the same dark ink the fully-selected tile already uses, scaled by
   that tile's own --wash strength, keeps contrast roughly constant down the whole rail. */
/* --stage-ink is picked per tile in chainRailHTML by MEASURED contrast against that tile's own
   finished background, and every one of the three rows now uses it at full strength.
   What it replaces was a blend between the dark ink and a low-alpha white, weighted by the tile's
   wash alpha — which meant the text tracked toward mid-grey at exactly the point the background
   was also mid-brown. Measured against the composited backgrounds, all five tiles failed WCAG AA
   and tile 04 came out at 1.12:1 — text and background the same luminance, i.e. invisible. That
   is what al was seeing: "you cannot see the titles and you cannot see the icons". */
/* LEFTPANEL.md §2 table, applied exactly: index 9px/.14em/.75, count 8px/.10em/.8. */
.cn-stage-num{ font-size:9px; letter-spacing:var(--cn-ls-14);
                color:var(--stage-ink, var(--cn-t-40)); opacity:.75; }
.cn-stage-title{ writing-mode:vertical-rl; transform:rotate(180deg);
                  font-size:var(--cn-fs-105); letter-spacing:0.26em;
                  color:var(--stage-ink, var(--cn-t-70)); }
.cn-stage-count{ font-size:8px; letter-spacing:var(--cn-ls-10);
                  color:var(--stage-ink, var(--cn-t-42)); opacity:.8; }
/* The icon is nested inside .cn-stage-title and inherits currentColor via stroke, so it moves
   with the ink automatically — it was invisible for the same reason the numbers were. */
.cn-stage-title svg{ stroke:currentColor; }
/* Rail tile name is always visible (not a rollover-only reveal). LEFTPANEL.md §2 table pins
   this back down to 6px/height:8px — the rail reverted to 58px (was a 78px interim widening,
   also reverted), so the label goes back to the original hover-design's own box constraints,
   just permanently shown instead of fading in. Fixed width (not auto) so a long name like
   "OUTPUT" ellipsizes instead of stretching the 58px tile — the spec's own "nowrap; ellipsis"
   only means something against a real width cap. */
.cn-stage-mid{ display:flex; flex-direction:column; align-items:center; gap:2px; }
.cn-stage-label{ display:block; width:44px; height:8px; line-height:8px; overflow:hidden;
                  font-size:6px; letter-spacing:.06em; text-transform:uppercase; text-align:center;
                  white-space:nowrap; text-overflow:ellipsis; color:inherit; opacity:.85; }
/* --wash (always on, every tile — see chainRailHTML) is the grey→amber progression down the
   rail; --fill (selected tile only) is a near-opaque amber block, not just a highlighted edge.
   Both are set inline per-tile off the same stageIndex/4 mix chainModuleTint uses, so a card's
   own module colour and its rail tile read as the same position in the same gradient. */
.cn-stage{ background:var(--wash, var(--cn-surf)); border-color:var(--tint, var(--cn-line)); }
/* Selected is no longer "the only bright tile" — every tile now sits on the real ramp, so the
   distinction is carried by the extra step to the undimmed colour plus a ring in the tile's own
   tint, rather than by being the one tile that isn't mud. */
.cn-stage[aria-selected="true"]{ background:var(--fill); border-color:transparent;
                                 box-shadow:0 0 0 1px var(--tint), 0 2px 10px -4px rgba(0,0,0,.55); }

/* ── stage title row (replaces Bench's group-header row) ─────────────────── */
.cn-stagehead{ flex:none; display:flex; align-items:center; gap:10px; padding:0 4px; }
.cn-stagehead .t{ font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-24); color:var(--cn-ink); }
.cn-stagehead .sub{ font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-10); color:var(--cn-t-50); }
.cn-stagehead .rule{ flex:1; height:1px; background:var(--cn-line); }
.cn-stagehead button{ background:none; border:0; padding:0; cursor:pointer; font-family:inherit;
                      font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-14); color:var(--cn-t-50);
                      display:inline-flex; align-items:center; justify-content:center; }
.cn-stagehead button:hover{ color:var(--cn-ink); }
.cn-stagehead button[aria-pressed="true"]{ color:var(--cn-ink); }

/* ── module list + module card ────────────────────────────────────────── */
.cn-modlist{ flex:1; min-height:0; overflow-y:auto; display:flex; flex-direction:column;
             gap:var(--cn-mod-gap); padding-right:3px;
             scrollbar-width:thin; scrollbar-gutter:stable;
             scrollbar-color:var(--cn-line-btn) transparent; }
.cn-modlist::-webkit-scrollbar{ width:6px }
.cn-modlist::-webkit-scrollbar-thumb{ background:var(--cn-line-btn); border-radius:3px }
.cn-modlist::-webkit-scrollbar-track{ background:transparent }

/* overflow:CLIP, not hidden — and the difference is the whole sticky header below.
   overflow:hidden makes the card a scroll container, which becomes the nearest scrollport for any
   position:sticky descendant; the head would then be sticky against a box that never scrolls, i.e.
   inert. overflow:clip clips identically (the edge bar and the wash still respect the 15px radius)
   without establishing a scrollport, so .cn-mod-head sticks against .cn-modlist where it belongs. */
.cn-mod{ position:relative; flex:none; border-radius:var(--cn-r-15); overflow:clip;
         background:var(--cn-surf-off); border:1px solid var(--cn-line-off);
         box-shadow:inset 0 0 0 999px var(--tint-wash-off, transparent);
         transition:box-shadow var(--cn-t-state) var(--cn-ease); }
.cn-mod.on          { background:var(--cn-surf); border-color:var(--cn-line);
                       box-shadow:inset 0 0 0 999px var(--tint-wash-on, transparent); }
.cn-mod.on.open     { border-color:transparent; }
.cn-mod-edge{ position:absolute; left:0; top:0; bottom:0; width:4px; display:block;
              background:var(--tint, var(--cn-line-off)); opacity:.55; transition:opacity var(--cn-t-state) var(--cn-ease); }
/* --tint (and the two wash alphas above) are set inline per-card by chainModCardHTML
   (BUILD-PLAN-CHAIN.md §4.2's mix() formula, position 0-22 in the 01-23 chain). Previously this
   grey→amber drift only ever showed as a 2-3px edge bar on .on modules — most modules start OFF,
   so scrolling the list read as flat grey. Now every card carries a low-alpha background wash of
   its own tint (--tint-wash-off) regardless of state, the edge bar is always tinted (just dimmer
   until switched on), and the step number leans toward its tint even when off — so the gradient
   is legible module by module without competing with the module's own text. */
.cn-mod.on .cn-mod-edge{ opacity:1; }
/* STICKY. Measured before this: scrolled 900px into an open module, nothing on screen named the
   module being edited — the head had simply left the viewport and the rulers below it are, by
   design, indistinguishable. It now rides the top of the list for as long as its own card is in
   view, so the module name, its number, its tint and its edit count are never more than a glance
   away. backdrop-filter rather than an opaque fill because the ground here is the shell's radial
   gradient, not a flat colour — a fixed hex would band against it at some scroll positions. */
.cn-mod-head{ position:sticky; top:0; z-index:3;
              display:flex; align-items:center; gap:11px; padding:10px 12px 10px 14px;
              background:var(--cn-surf-off);
              box-shadow:inset 0 0 0 999px var(--tint-wash-off, transparent);
              -webkit-backdrop-filter:blur(16px) saturate(1.15);
              backdrop-filter:blur(16px) saturate(1.15); }
.cn-mod.on .cn-mod-head{ background:var(--cn-surf);
              box-shadow:inset 0 0 0 999px var(--tint-wash-on, transparent); }
/* Only an open card needs the rule: it is the line the content scrolls under. */
.cn-mod.open > .cn-mod-head{ border-bottom:1px solid var(--cn-line-div); }
/* Above the head's background so the tint bar is never painted over while stuck. */
.cn-mod-edge{ z-index:4; }
.cn-mod-step{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-10); width:16px; color:var(--tint, var(--cn-t-22)); opacity:.75; }
/* The edit count — a module's own "n frames worked", carried in the head so a COLLAPSED card
   still says whether there is anything of yours inside. This is the signal that makes the list
   scannable: without it, finding your own work meant opening 28 modules in turn. */
.cn-mod-edits{ flex:none; min-width:16px; padding:1px 5px; border-radius:9px;
               font-size:var(--cn-fs-9); font-variant-numeric:tabular-nums; text-align:center;
               letter-spacing:var(--cn-ls-06); line-height:1.5;
               color:var(--cn-amber); background:color-mix(in srgb, var(--cn-amber) 16%, transparent);
               box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cn-amber) 34%, transparent); }
/* Chain shell handover §1: replaces .cn-mod-edits in the row (kept above, unused, in case a
   denser numeric badge is wanted back later) — a plain dot instead of a numeric badge, freeing
   row width for the module name. The exact count is still on the title attribute (a real
   tooltip, not lost, just not spent on always-visible width). */
.cn-mod-dirty{ flex:none; width:7px; height:7px; border-radius:50%;
               background:var(--cn-amber); box-shadow:0 0 6px 1px color-mix(in srgb, var(--cn-amber) 55%, transparent); }
.cn-mod.on .cn-mod-step{ color:var(--tint, var(--cn-grad-b)); opacity:1; }
/* Same flush-icon defect as .card-head .name, in the other component. Every .cn-mod-title in
   Mono carries an inline style="display:flex;..." with no gap, so its 21px glyph sat hard against
   the first letter of the title on all thirteen module cards. The earlier fix only covered the
   Print Room's .card-head .name and .panel-category-label; this is the same bug in the component
   Mono actually uses, and fixing it here covers every room at once. */
.cn-mod-title{ flex:1; min-width:0; font-size:var(--cn-fs-13); color:var(--cn-t-45); cursor:pointer;
               align-items:center; gap:9px;
               background:none; border:0; text-align:left; font-family:inherit; }
.cn-mod-title > svg{ flex:none; }
.cn-mod.on .cn-mod-title{ color:var(--cn-ink); }
.cn-mod-state{ font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-12); color:var(--cn-t-50); }
.cn-mod.on .cn-mod-state{ color:var(--cn-t-60); }
.cn-mod-body{ padding:0 12px 13px 14px; }

/* ── hero block (new — leads every open module's content) ────────────────── */
.cn-hero{ border-radius:var(--cn-r-14); padding:13px 14px 12px;
          background:var(--cn-surf); border:1px solid var(--cn-line-2); }
.cn-hero-kicker{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-24); color:var(--cn-t-62); }
.cn-hero-val{ display:flex; align-items:baseline; gap:8px; margin-top:6px; }
.cn-hero-val b{ font-size:var(--cn-fs-30); font-weight:var(--cn-w-thin); letter-spacing:var(--cn-ls-n35);
                line-height:1.05; color:var(--cn-ink); }
.cn-hero-val span{ font-size:var(--cn-fs-12); color:var(--cn-t-72); }
.cn-hero-cap{ font-size:var(--cn-fs-11); line-height:1.35; margin-top:7px; color:var(--cn-t-60); }

/* ── section header ────────────────────────────────────────────────────── */
.cn-sec{ margin-top:13px; }
.cn-sec-head{ display:flex; align-items:center; gap:9px; }
.cn-sec-head span{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-24); color:var(--cn-t-55); }
.cn-sec-head .rule{ flex:1; height:1px; background:var(--cn-line-div); }

/* ── per-module reset icon (item 15) ──────────────────────────────────── */
.cn-mod-reset{ display:flex; align-items:center; justify-content:center; cursor:pointer;
               background:none; border:0; padding:3px; margin-left:2px; flex:none;
               border-radius:50%; transition:background-color var(--cn-t-state) var(--cn-ease),
               transform 0.25s var(--cn-ease); }
.cn-mod-reset:hover{ background:var(--cn-surf-track); transform:rotate(-40deg); }
.cn-mod-reset svg{ width:12px; height:12px; fill:none; stroke:var(--cn-t-70); stroke-width:1.4;
                    stroke-linecap:round; stroke-linejoin:round; }

/* ── Mask-routing step 3 (§3.1/§5.1/§5.2/§5.4) ────────────────────────────────────────────────
   7-hue fixed palette, one CSS custom property per hue, mirrored verbatim from studio.html's
   MASK_COLOR_PALETTE JS array (search that name) — the two must stay in lockstep since the JS
   array is what actually assigns colorIndex -> hex for JS-driven bits (dot swatches written via
   inline style), while these vars let pure-CSS chrome (the rail) reference the same values by
   var(--cn-mask-N) without round-tripping through an inline style for every property. Chosen to
   avoid pure red AND the CLIP indicator's own two colours (buildFullClipOverlay in studio.html:
   highlight blowout rgb(255,20,60), shadow crush rgb(20,140,255)) so a mask rail/dot can never be
   misread as a clipping warning. */
:root{
  --cn-mask-0: #35C9A5; /* teal */
  --cn-mask-1: #9B7BF0; /* violet */
  --cn-mask-2: #E0B93D; /* gold */
  --cn-mask-3: #E866B0; /* magenta */
  --cn-mask-4: #4FB6E8; /* sky */
  --cn-mask-5: #8CC152; /* sage green */
  --cn-mask-6: #6C7FE0; /* indigo */
}

/* Track selector — segmented control, sits directly under the tool name (§5.1). Built from the
   same flex-row-of-buttons idiom .cn-chip already uses (chip strip ~L660), not a new control
   family. `global` is always first and un-removable (no close affordance rendered on it, ever). */
/* The bar the selector and the mask-strength slider share. Recessed and ruled off top and bottom
   so it reads as a band the card is currently WORKING IN, not as another row of options — the old
   version floated bare in the gap under the head, in the same pill idiom as the option chips inside
   the body, so nothing said "everything below this belongs to Mask 01". */
.cn-trackbar{ display:flex; align-items:center; gap:14px; padding:7px 12px 7px 14px;
              background:var(--cn-well); border-top:1px solid var(--cn-line-div);
              border-bottom:1px solid var(--cn-line-div); }
.cn-track-selector{ display:flex; align-items:center; gap:4px; flex-wrap:wrap; flex:1; min-width:0; }
.cn-track-seg{ display:flex; align-items:center; gap:5px; padding:3px 9px; border-radius:var(--cn-r-8, 6px);
                background:transparent; border:1px solid transparent; cursor:pointer;
                font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-10); color:var(--cn-t-55);
                transition:background-color var(--cn-t-state) var(--cn-ease), border-color var(--cn-t-state) var(--cn-ease); }
.cn-track-seg:hover{ background:var(--cn-surf-btn); color:var(--cn-t-72); }
/* The selected segment is the only lit thing in the bar, and it is lit in the MASK'S OWN colour —
   the same colour as the 3px rail down the card edge and the glyph in the head, so the three
   agree at a glance about which mask you are editing. */
.cn-track-seg[aria-selected="true"]{ background:var(--cn-surf); color:var(--cn-ink);
                border-color:color-mix(in srgb, var(--seg-color, var(--cn-line)) 55%, transparent); }
.cn-track-seg .dot{ width:6px; height:6px; border-radius:50%; flex:none; background:var(--seg-color, var(--cn-t-45)); }
.cn-track-seg .dot.filled{ box-shadow:0 0 0 2px color-mix(in srgb, var(--seg-color, var(--cn-t-45)) 35%, transparent); }
.cn-track-seg.overflow-trigger{ color:var(--cn-t-55); }

/* Tool-panel affordances when a maskable stage has 1+ routed masks (§5.2). Rail sits INSIDE
   .cn-mod-edge's 4px band, thinner (3px) and only present while a mask track is active — no rail
   at all on `global`, per spec ("neutral/no rail on global"). */
.cn-mask-rail{ position:absolute; left:0; top:0; bottom:0; width:3px; z-index:1;
               background:var(--mask-color); }
.cn-mask-glyph{ display:inline-flex; align-items:center; justify-content:center; flex:none;
                width:14px; height:14px; opacity:.9; }
.cn-mask-glyph svg{ width:12px; height:12px; fill:none; stroke:var(--mask-color); stroke-width:1.6;
                     stroke-linecap:round; stroke-linejoin:round; }

/* MASK STRENGTH readout, panel foot (§5.2) — reads/writes the SAME mask.strength the mask tool
   itself owns (studio.html's maskRegistry, step 4's future plug point), never a per-tool copy. */
.cn-mask-strength{ display:flex; align-items:center; gap:8px; flex:0 1 168px; min-width:118px; }
.cn-mask-strength .lbl{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-24); color:var(--cn-t-45); flex:none; }
.cn-mask-strength input[type="range"]{ flex:1; min-width:44px; accent-color:var(--mask-color); }
.cn-mask-strength .val{ font-size:var(--cn-fs-9); color:var(--mask-color); min-width:30px; text-align:right; flex:none; }
/* Narrow sidebar: the label is the first thing worth dropping — the slider is beside the segment
   that names the mask, and its thumb colour already matches. */
@media (max-width: 1180px){ .cn-mask-strength .lbl{ display:none; } }

/* Signed value display for a mask track's own numerics (§5.4) — global stays unsigned. Applied
   via a `.signed` class on whatever element already renders a control's numeric readout. */
.cn-signed-val::before{ content:attr(data-sign); }

/* ── chevron ───────────────────────────────────────────────────────────── */
.cn-chev{ display:flex; align-items:center; cursor:pointer; background:none; border:0; padding:0;
          transition:transform var(--cn-t-state) var(--cn-ease); }
.cn-mod.open .cn-chev{ transform:rotate(180deg); }
.cn-chev svg{ width:13px; height:13px; fill:none; stroke:var(--cn-t-70); stroke-width:1.3;
              stroke-linecap:round; stroke-linejoin:round; }

/* ── module toggle switch ─────────────────────────────────────────────── */
/* NO TRANSITION ON THE SWITCH. al: "They are animating but not changing... just fix it, remove the
   animation just make it work."
   The knob used to slide and the track used to cross-fade. When a press failed to commit (see the
   one-shot suppression in wireChainDelegation), the animation still ran on the way in and snapped
   back — so the control looked alive while the module stayed off, which is worse than a dead
   button: it actively told you it had worked. A switch is a state readout, not a performance. It
   now moves only when the state moves, instantly, so what you see IS what happened. */
.cn-switch{ width:33px; height:18px; border-radius:var(--cn-r-pill); flex:none;
            padding:2px; box-sizing:border-box; display:block; position:relative;
            background:var(--cn-surf-track); border:0; cursor:pointer; }
.cn-switch i{ position:absolute; top:2px; left:2px;
              width:14px; height:14px; border-radius:50%; display:block;
              background:var(--cn-t-50); }
.cn-switch[aria-checked="true"]{ background:var(--tint, var(--cn-grad-b)); }
.cn-switch[aria-checked="true"] i{ transform:translateX(15px); background:#FFF7EF; }

/* ── section label: small-caps sub-header + hairline divider above a logical group of tool
   rows in a dense multi-group module (Lenses' 7 groups, etc.) — opted into per-control via
   manifest.js's `sectionLabel`, see chainToolRowHTML's caller in studio.html. First one in a
   module (right under the widget/preset controls) gets no extra top gap beyond normal flow;
   later ones get a bit more breathing room than a plain .cn-tool so the new group reads as a
   fresh section, not just another row. */
.cn-section-label{ margin-top:20px; padding-top:12px; border-top:1px solid var(--cn-line-div);
                    font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-20); text-transform:uppercase;
                    color:var(--cn-t-55); }
.cn-section-label:first-child{ margin-top:11px; padding-top:0; border-top:0; }

/* ── collapsible group: a genuinely hide/show sub-section (not just a divider like
   .cn-section-label above), for a module whose secondary controls should stay out of the way
   until asked for — e.g. Facelift's Eyes & Lips beneath its primary Selection/Strength/range
   controls. Collapsed by default (chainGroupOpen, studio.html), toggled via the header button;
   chevron rotates open like .cn-chev already does for the module card itself. */
.cn-collapsible{ margin-top:20px; padding-top:12px; border-top:1px solid var(--cn-line-div); }
.cn-collapsible-head{ display:flex; align-items:center; justify-content:space-between; width:100%;
                       background:none; border:0; padding:0; cursor:pointer; font-family:inherit;
                       font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-20); text-transform:uppercase;
                       color:var(--cn-t-55); }
.cn-collapsible-head:hover{ color:var(--cn-t-90); }
.cn-collapsible-chev{ width:14px; height:14px; flex:none; stroke:currentColor; fill:none; stroke-width:1.8;
                       stroke-linecap:round; stroke-linejoin:round; transition:transform var(--cn-t-state) var(--cn-ease); }
.cn-collapsible.open .cn-collapsible-chev{ transform:rotate(180deg); }
.cn-collapsible-body{ display:none; }
.cn-collapsible.open .cn-collapsible-body{ display:block; }

/* ── tool row: 7 control kinds ─────────────────────────────────────────── */
.cn-tool{ margin-top:11px; }
.cn-tool-head{ display:flex; align-items:center; gap:9px; }
.cn-tool-name{ display:flex; align-items:center; gap:7px; flex:1; min-width:0;
               font-size:var(--cn-fs-12); cursor:pointer; color:var(--cn-t-90);
               background:none; border:0; text-align:left; font-family:inherit; }
.cn-tool-dot{ width:7px; height:7px; border-radius:50%; flex:none; display:block; }
/* Boxed value readout — Print Room's own treatment, and the reason its rows read as instrument
   fields rather than loose text. tabular-nums so the digits don't jitter as the value changes
   during a drag. The box is always present (not only while editing) so the control advertises
   that it IS typeable; .cn-tool-val-input below inherits the same metrics, so swapping the span
   for the real <input> on click causes no layout shift. */
.cn-tool-val{ font-size:var(--cn-fs-16); font-weight:var(--cn-w-thin);
              letter-spacing:var(--cn-ls-n2); color:var(--cn-ink); cursor:text;
              font-variant-numeric:tabular-nums; text-align:right; min-width:52px;
              padding:2px 7px; border-radius:var(--cn-r-8, 6px);
              border:1px solid var(--cn-t-16, rgba(255,255,255,.16));
              background:var(--cn-t-6, rgba(255,255,255,.06));
              transition:border-color var(--cn-t-state, .16s) var(--cn-ease, ease); }
.cn-tool-val:hover{ border-color:var(--cn-t-30, rgba(255,255,255,.3)); }
/* Metrics deliberately mirror .cn-tool-val above so click-to-type swaps in place with no shift. */
.cn-tool-val-input{ font:inherit; font-size:var(--cn-fs-16); font-weight:var(--cn-w-thin);
              letter-spacing:var(--cn-ls-n2); color:var(--cn-ink); background:var(--cn-t-6, rgba(255,255,255,.06));
              border:1px solid var(--cn-amber); border-radius:var(--cn-r-8, 6px); width:52px;
              font-variant-numeric:tabular-nums; text-align:right;
              padding:2px 7px; -moz-appearance:textfield; }
.cn-tool-val-input::-webkit-outer-spin-button,.cn-tool-val-input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
/* Was --cn-t-50 at 9.5px: measured 4.0:1 against the panel ground, under AA's 4.5:1 for text this
   small. --cn-t-65 measures 5.6:1 and still reads as the quiet half of the value pair. */
.cn-tool-unit{ font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-10); width:26px; color:var(--cn-t-65, rgba(238,242,241,.65)); }

/* ── THE EDITED ROW ────────────────────────────────────────────────────────
   The one signal a column of identical rulers was missing: which of these have I moved?
   Answering it is what turns 30 rows into "the four I touched, and 26 I didn't", and it is the
   real content of "hard to know where I am" — position in a list matters far less than knowing
   which parts of the list are yours.

   Deliberately quiet, and deliberately NOT the marker: the marker is already amber on all 277
   rows, so pushing more amber there says nothing. The signal is a small amber pip in the left
   margin — the same mark the filmstrip puts on a worked frame — plus the label lifting to full
   ink. Both are paint only; nothing here changes the row's box, so a module never reflows as you
   work down it. */
.cn-tool{ position:relative; }
.cn-tool::before{ content:''; position:absolute; left:-9px; top:6px; width:3px; height:3px;
  border-radius:50%; background:var(--cn-amber); opacity:0; transform:scale(.4);
  transition:opacity var(--cn-t-state, .16s) var(--cn-ease, ease),
             transform var(--cn-t-state, .16s) var(--cn-ease, ease); pointer-events:none; }
.cn-tool.is-set::before{ opacity:1; transform:scale(1); }
.cn-tool.is-set .cn-tool-name{ color:var(--cn-ink); }
.cn-tool.is-set .cn-tool-val{ border-color:color-mix(in srgb, var(--cn-amber) 42%, transparent); }

/* Per-row reset. Panel mode's knobs have had one of these since they shipped (.pn-reset); the
   ruler rows never did, so the only way back to a default was the whole-module reset — all or
   nothing on a 30-parameter module. Held at width 0 rather than display:none when there is
   nothing to undo, so the row's rhythm is identical on a touched and an untouched row and the
   ten of them don't shout at once. */
/* margin-left:-9px cancels .cn-tool-head's own 9px flex gap while the button is collapsed. A
   zero-WIDTH flex item still earns a full gap on each side, so without this every untouched row
   carried 9px of dead space between the unit and the first stepper — measured: the unit ended at
   357px and the stepper started at 375px. It returns to 0 when the row is edited, which is the
   one moment the button actually needs the room. */
.cn-ctlreset{ width:0; height:20px; padding:0; margin:0 0 0 -9px; border:0; background:none; overflow:hidden;
  display:flex; align-items:center; justify-content:center; flex:none; cursor:pointer; opacity:0;
  color:var(--cn-t-55); pointer-events:none;
  transition:width var(--cn-t-state, .16s) var(--cn-ease, ease),
             margin-left var(--cn-t-state, .16s) var(--cn-ease, ease),
             opacity var(--cn-t-state, .16s) var(--cn-ease, ease), color .14s; }
.cn-ctlreset svg{ width:12px; height:12px; flex:none; fill:none; stroke:currentColor;
  stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.cn-tool.is-set .cn-ctlreset{ width:20px; margin-left:0; opacity:.6; pointer-events:auto; }
.cn-tool.is-set .cn-ctlreset:hover{ opacity:1; color:var(--cn-amber); }
.cn-ctlreset:focus-visible{ width:20px; margin-left:0; opacity:1; outline:2px solid var(--cn-amber); outline-offset:1px;
  border-radius:50%; pointer-events:auto; }
@media (prefers-reduced-motion: reduce){
  .cn-tool::before, .cn-ctlreset{ transition:none; }
}

/* 23px was under the 24px minimum target — measured, not estimated. */
.cn-step{ width:24px; height:24px; border-radius:var(--cn-r-8);
          display:flex; align-items:center; justify-content:center;
          font-family:inherit; font-size:var(--cn-fs-13); font-weight:var(--cn-w-light);
          background:var(--cn-surf-btn); border:1px solid var(--cn-line-btn);
          color:var(--cn-ink); cursor:pointer; }
.cn-step:hover{ background:var(--cn-hov-20); }
.cn-step[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-step[disabled]:hover{ background:var(--cn-surf-btn); }

/* isChips */
.cn-tool-label{ font-size:var(--cn-fs-115); color:var(--cn-t-75); }
.cn-chips{ display:flex; gap:5px; margin-top:8px; flex-wrap:wrap; }
.cn-chip{ font-family:inherit; font-size:var(--cn-fs-10); letter-spacing:var(--cn-ls-06);
          padding:7px 12px; border-radius:var(--cn-r-pill); cursor:pointer;
          background:transparent; border:1px solid var(--cn-line-chip); color:var(--cn-t-60); }
.cn-chip[aria-selected="true"]{ background:var(--cn-ink); border-color:transparent; color:var(--cn-ink-on-white); }
.cn-chip[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
/* accent chips — a selection that identifies WHAT you are editing rather than which view you
   are in. White (the default selected fill) reads as a tab; amber reads as "this is live and it
   is yours". Opt-in per control via `accent: true` in the manifest, so it stays rare enough to
   keep meaning something. First user of it: Facelift's face picker (al: "when the face button is
   selected ie face 1 it should turn orange"). */
.cn-chip.is-accent[aria-selected="true"]{ background:var(--cn-amber); border-color:transparent;
                                          color:var(--cn-ink-on-amber); box-shadow:var(--cn-amber-glow); }
.cn-chip.is-accent[aria-selected="true"]:hover:not([disabled]){ background:var(--cn-amber-hi); }
.cn-chip-tick{ vertical-align:-1px; margin-left:2px; }

/* isPicker (new) */
.cn-picker{ display:flex; align-items:center; gap:10px; height:40px; padding:0 6px 0 13px;
            border-radius:var(--cn-r-13); background:rgba(238,242,241,0.045); border:1px solid var(--cn-line); }
.cn-picker-label{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-20); color:var(--cn-t-50); }
.cn-picker-val{ font-size:var(--cn-fs-12); color:var(--cn-ink); max-width:168px; overflow:hidden;
                text-overflow:ellipsis; white-space:nowrap; flex:1; text-align:center; }
.cn-picker-arrow{ width:22px; height:22px; border-radius:var(--cn-r-7); cursor:pointer;
                  display:flex; align-items:center; justify-content:center; color:var(--cn-t-60);
                  background:none; border:0; }
.cn-picker-arrow:hover{ background:var(--cn-hov-12); }
.cn-picker-arrow[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-picker-arrow[disabled]:hover{ background:none; }
.cn-picker-arrow svg{ width:12px; height:12px; fill:none; stroke:currentColor; stroke-width:1.4;
                       stroke-linecap:round; stroke-linejoin:round; }
.cn-picker-val-btn{ font:inherit; font-size:var(--cn-fs-12); color:var(--cn-ink); max-width:168px; overflow:hidden;
                text-overflow:ellipsis; white-space:nowrap; flex:1; text-align:center; background:none; border:0;
                padding:2px 4px; border-radius:var(--cn-r-7); cursor:pointer; }
.cn-picker-val-btn:hover{ background:var(--cn-hov-12); }
.cn-picker-val-btn[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-picker-val-btn[disabled]:hover{ background:none; }
/* Dropdown menu opened by clicking a picker's value text — jumps directly to any option instead
   of stepping one-at-a-time via the arrows. Styled to match .scope-menu (same z-index tier, same
   floating-card look) but under the cn- prefix and using Chain's own design tokens. */
.cn-picker-menu{ position:fixed; z-index:1000; background:color-mix(in srgb, var(--cn-well) 92%, white 8%); border:1px solid var(--cn-line);
                  border-radius:var(--cn-r-12); padding:5px; min-width:180px; max-height:min(320px, 60vh);
                  overflow-y:auto; box-shadow:0 10px 30px rgba(0,0,0,.55); }
.cn-picker-menu-item{ display:flex; align-items:center; gap:8px; width:100%; padding:7px 9px;
                       border:0; border-radius:var(--cn-r-7); background:transparent; color:var(--cn-t-60);
                       font-family:inherit; font-size:var(--cn-fs-12); cursor:pointer; text-align:left; }
.cn-picker-menu-item:hover, .cn-picker-menu-item.cn-highlighted{ background:var(--cn-hov-12); color:var(--cn-ink); }
.cn-picker-menu-item[aria-selected="true"]{ color:var(--cn-ink); font-weight:var(--cn-w-med); }
.cn-picker-menu-item[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-picker-menu-item[disabled]:hover, .cn-picker-menu-item[disabled].cn-highlighted{ background:transparent; color:var(--cn-t-60); }

/* isCheck (new) */
.cn-checkrow{ width:100%; display:flex; align-items:flex-start; gap:10px; cursor:pointer; background:none; border:0;
              padding:0; text-align:left; font-family:inherit; }
.cn-checkbox{ width:15px; height:15px; border-radius:var(--cn-r-5); flex:none; margin-top:1px;
              display:flex; align-items:center; justify-content:center;
              background:transparent; border:1px solid var(--cn-line-check); }
.cn-checkbox[aria-checked="true"]{ background:var(--cn-amber); border-color:transparent; }
.cn-checkbox svg{ width:11px; height:11px; fill:none; stroke:var(--cn-ink-on-amber); stroke-width:2.4;
                   stroke-linecap:round; stroke-linejoin:round; }
.cn-checklabel{ flex:1; font-size:var(--cn-fs-115); line-height:1.35; color:var(--cn-t-90); }
.cn-checkrow[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }

/* isColor (new) */
.cn-swatchrow{ display:flex; gap:6px; margin-top:8px; }
.cn-swatch{ flex:1; height:26px; border-radius:var(--cn-r-9); cursor:pointer; display:block;
            border:1px solid var(--cn-line); }
.cn-swatch[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }

/* isAction (new) */
/* §4.4 specs this as "a full-width button" — but a <button> with display:flex still shrink-
   wraps to its content width even though flex blockifies its outer display to block (form
   controls don't stretch to fill a block container the way a div does). Missing width:100% left
   it a small, off-proportion box floating in an otherwise full-width control column — the
   "oversized loose box" read was actually the opposite bug (undersized, not full-width) reading
   badly against the module's other full-width rows. */
.cn-action{ width:100%; min-height:38px; padding:0 12px; border-radius:var(--cn-r-12); display:flex; align-items:center;
            justify-content:center; cursor:pointer; font-family:inherit; font-size:var(--cn-fs-12);
            box-sizing:border-box; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
            background:var(--cn-surf-btn); border:1px solid var(--cn-line-btn); color:var(--cn-ink); }
.cn-action:hover{ filter:brightness(1.12); }
.cn-action.primary{ background:var(--cn-amber); border-color:transparent; color:var(--cn-ink-on-amber); }
/* secondary — de-emphasized action (Clear selection, Clear detected blemishes): ghost, smaller,
   quieter than the default .cn-action so a tier with a mix of primary/secondary actions reads
   with a clear hierarchy instead of every row looking equally loud (al: "load of buttons"). */
.cn-action.secondary{ background:transparent; border-color:var(--cn-line-2); color:var(--cn-t-60);
                       min-height:32px; font-size:var(--cn-fs-11); font-weight:var(--cn-w-thin); }
.cn-action.secondary:hover{ filter:none; border-color:var(--cn-line-btn); color:var(--cn-ink); }
.cn-action[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
/* A completed one-shot whose RESULT is still live — currently "Take a reading" once a reading
   exists. Amber outline + amber ink rather than .primary's filled amber, so it reads as
   "this has a result" and stays visually distinct from an armed eyedropper waiting for a click. */
.cn-action.done{ border-color:var(--cn-amber); color:var(--cn-amber);
  background:color-mix(in srgb, var(--cn-amber) 12%, transparent); }
/* Icon-only actions are accessories to the control above them, not tool rows of their own.
   .cn-action is width:100% by design, so a bare icon-only button previously stretched the full
   panel width and stacked one-per-line (al: "this layout is 0/5 stars"). chainControlsHTML now
   coalesces consecutive icon-only actions into this cluster: compact squares, right-aligned to
   the same edge as every value readout, tucked up against the row they belong to. */
/* al: "the buttons are on top of each other" — the two Rotate 90 icons sat ON the Angle
   ruler. The -2px pull was fine next to ordinary rows, but .cn-ticks--slider is
   position:relative with its marker overflowing its 30px box, so a negative margin put the
   32px buttons straight through it. Positive clearance instead, and the cluster no longer
   overlaps whatever precedes it. */
.cn-iconcluster{ display:flex; justify-content:flex-end; align-items:center; gap:6px; margin-top:10px; }
/* A text action and its icon-only companion sharing one row as equals — both halves the same
   width, the same height, and the same vertical rhythm as any other .cn-tool row. */
.cn-action-split{ display:flex; align-items:stretch; gap:8px; margin-top:11px; }
/* Explicit halves rather than `flex:1 1 0`: the two children have different intrinsic content
   (a full-width text button vs a 17px glyph), and basis-0 growth was still resolving ~18px apart.
   calc(50% - half the gap) is exact and can't drift. */
.cn-action-split > *{ flex:0 0 calc(50% - 4px); min-width:0; }
.cn-action-split > .cn-tool{ margin-top:0; }
.cn-action-split > .cn-action.icon-only{ width:auto; min-height:38px;
  padding:0 !important; justify-content:center; border-radius:var(--cn-r-12); }
.cn-action-split > .cn-action.icon-only svg{ width:17px; height:17px; }
.cn-iconcluster .cn-action.icon-only{ width:32px; min-width:32px; flex:0 0 auto; min-height:32px;
  padding:0; justify-content:center; border-radius:var(--cn-r-8, 8px); }
.cn-iconcluster .cn-action.icon-only svg{ width:16px; height:16px; }

/* isNote (new) */
.cn-note{ font-size:var(--cn-fs-105); line-height:1.45; color:var(--cn-t-50); }
.cn-text-input{ width:100%; box-sizing:border-box; margin-top:8px; padding:9px 11px;
                 font-family:inherit; font-size:var(--cn-fs-12); color:var(--cn-ink);
                 background:var(--cn-surf); border:1px solid var(--cn-line-2); border-radius:var(--cn-r-10, 8px); }
.cn-text-input:focus{ outline:none; border-color:var(--cn-amber, var(--cn-grad-b)); }
.cn-text-input[disabled]{ opacity:var(--cn-disabled-alpha); }

/* ── widgets (Steps 7-9: brush / zones / vector / mesh / tiles) ─────────── */
.cn-widget{ }

/* brush — Clone & Heal's falloff-preview + a compact tier-tab row (reuses the Tool chip's own
   real click wiring via data-cn-chip, not a second control surface) */
.cn-brush-tabs{ display:flex; gap:5px; margin-top:8px; }
.cn-brush-tab{ flex:1; font-family:inherit; font-size:var(--cn-fs-10); letter-spacing:var(--cn-ls-06);
               padding:6px 0; border-radius:var(--cn-r-9); cursor:pointer; text-align:center;
               background:transparent; border:1px solid var(--cn-line-chip); color:var(--cn-t-60); }
/* The active tab was `.active`, which the theme never styles — console.css:478 lists
   `.cn-brush-tab.on` alongside the other chips, so the app's amber live state was dead code here
   and this inverted cream block painted instead. The markup emits `on` now and this rule is
   retired; do not reintroduce a second vocabulary for "this one is selected". */
/* Geometry lives in console.css now — see the .cn-brush-preview rule there. This was a 96px
   fixed-size svg with flex:none inside a full-width row, i.e. a picture with 240px of dead panel
   beside it, and gap:14px that never applied because the row has one child. */
.cn-brush-readout span{ display:block; font-size:var(--cn-fs-21); font-weight:var(--cn-w-thin); color:var(--cn-ink); }
.cn-brush-readout i{ font-style:normal; font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-14); color:var(--cn-t-50); }

/* masklist — real stageParams.masks.masks[] list, one row per mask (select-on-click, delete
   button per row). Mirrors the pre-Chain masks card's .mask-row idiom (dot/name/meta) in cn-*
   dress rather than a new visual language. */
.cn-mask-empty{ font-size:var(--cn-fs-105); line-height:1.45; color:var(--cn-t-50); margin-top:9px; }
.cn-mask-list{ display:flex; flex-direction:column; gap:4px; margin-top:9px; }
.cn-mask-row{ display:flex; flex-wrap:wrap; align-items:center; gap:9px; width:100%; padding:8px 9px;
              border-radius:var(--cn-r-9); border:1px solid transparent; cursor:pointer;
              background:var(--cn-surf-btn); }
.cn-mask-row:hover{ filter:brightness(1.1); }
.cn-mask-row.sel{ border-color:var(--cn-amber); background:color-mix(in srgb, var(--cn-amber) 10%, var(--cn-surf-btn)); }
.cn-mask-dot{ width:7px; height:7px; border-radius:50%; flex:none; background:var(--cn-amber); }
.cn-mask-row.off .cn-mask-dot{ background:var(--cn-t-30); }
.cn-mask-row.off .cn-mask-name{ opacity:.5; }
.cn-mask-name{ flex:1; min-width:0; font-size:var(--cn-fs-12); color:var(--cn-ink); overflow:hidden;
               text-overflow:ellipsis; white-space:nowrap; }
.cn-mask-meta{ font-size:var(--cn-fs-105); color:var(--cn-t-50); }
.cn-mask-del{ flex:none; width:22px; height:22px; border-radius:50%; border:none; background:transparent;
              color:var(--cn-t-50); font-size:var(--cn-fs-11); cursor:pointer; display:flex;
              align-items:center; justify-content:center; }
.cn-mask-del:hover{ background:var(--cn-line-btn); color:var(--cn-ink); }
/* al's "Show mask" toggle — per-row eye button, next to delete, that flips the mask's real
   outlineVisible field (drawMaskOverlays/getMaskHandleAt gate on it). Amber when the outline
   IS visible so the row's "on" state reads at a glance, same accent drawMaskOverlays itself
   now uses for the on-canvas line. */
.cn-mask-show{ flex:none; width:22px; height:22px; border-radius:50%; border:none; background:transparent;
               color:var(--cn-t-50); cursor:pointer; display:flex; align-items:center; justify-content:center; }
.cn-mask-show svg{ width:14px; height:14px; }
.cn-mask-show:hover{ background:var(--cn-line-btn); color:var(--cn-ink); }
.cn-mask-show.on{ color:var(--cn-amber); }
/* Mask-routing step 4 (§4.1/§4.2/§4.3) — SEND TO trigger, routed-tool chips per mask row,
   inline non-modal un-route confirm, and its Undo toast. Same cn-* dress as the rest of the
   Masks row (surf-btn buttons, --cn-line borders) — no new visual language introduced. */
.cn-mask-sendto{ flex:none; height:22px; padding:0 8px; border-radius:var(--cn-r-9); border:1px solid var(--cn-line);
                  background:transparent; color:var(--cn-t-50); font-size:var(--cn-fs-105); letter-spacing:.02em;
                  cursor:pointer; }
.cn-mask-sendto:hover{ background:var(--cn-line-btn); color:var(--cn-ink); }
.cn-mask-chips{ flex-basis:100%; display:flex; flex-wrap:wrap; gap:6px; margin-top:2px; }
.cn-mask-chip{ display:flex; align-items:center; gap:5px; height:20px; padding:0 4px 0 8px;
               border-radius:999px; font-size:var(--cn-fs-105); color:var(--cn-ink);
               background:color-mix(in srgb, var(--mask-color) 22%, var(--cn-surf-btn));
               border:1px solid color-mix(in srgb, var(--mask-color) 55%, var(--cn-line)); cursor:pointer; }
.cn-mask-chip button{ flex:none; width:14px; height:14px; border-radius:50%; border:none; background:transparent;
               color:var(--cn-t-50); font-size:9px; cursor:pointer; display:flex; align-items:center; justify-content:center; }
.cn-mask-chip button:hover{ background:var(--cn-line-btn); color:var(--cn-ink); }
.cn-mask-unroute-confirm{ flex-basis:100%; display:flex; align-items:center; flex-wrap:wrap; gap:8px;
               margin-top:4px; padding:7px 9px; border-radius:var(--cn-r-9); border:1px solid var(--cn-amber);
               background:color-mix(in srgb, var(--cn-amber) 12%, var(--cn-surf-btn)); font-size:var(--cn-fs-105); color:var(--cn-ink); }
.cn-mask-unroute-confirm button{ height:22px; padding:0 10px; border-radius:var(--cn-r-9); border:1px solid var(--cn-line);
               background:var(--cn-surf-btn); color:var(--cn-ink); font-size:var(--cn-fs-105); cursor:pointer; }
.cn-mask-unroute-confirm button[data-cn-unroute-confirm]{ border-color:var(--cn-amber); color:var(--cn-amber); }
.cn-mask-unroute-confirm button:hover{ filter:brightness(1.15); }
.cn-mask-undo-toast{ position:fixed; left:50%; bottom:22px; transform:translate(-50%, 12px);
               display:flex; align-items:center; gap:12px; padding:10px 14px; border-radius:var(--cn-r-9);
               background:color-mix(in srgb, var(--cn-well) 92%, black 8%); border:1px solid var(--cn-line);
               color:var(--cn-ink); font-size:var(--cn-fs-105); z-index:1200; opacity:0; pointer-events:none;
               transition:opacity .16s ease, transform .16s ease; }
.cn-mask-undo-toast.show{ opacity:1; transform:translate(-50%, 0); pointer-events:auto; }
.cn-mask-undo-toast button{ height:24px; padding:0 12px; border-radius:var(--cn-r-9); border:1px solid var(--cn-amber);
               background:transparent; color:var(--cn-amber); font-size:var(--cn-fs-105); cursor:pointer; }
.cn-mask-undo-toast button:hover{ background:color-mix(in srgb, var(--cn-amber) 14%, transparent); }

/* "Detected in this photo" — scanForMasks() candidates (Face/Person/Background today) for the
   selected AI mask. Same button/pill language as the rest of the masklist widget, sized to fit
   a small thumbnail rather than reusing .cn-mask-chip (which is a routing pill, not a picker). */
.cn-mask-detect{ margin-top:2px; }
.cn-mask-detect-status{ font-size:var(--cn-fs-105); color:var(--cn-t-50); }
.cn-mask-detect-row{ display:flex; flex-wrap:wrap; gap:8px; }
.cn-mask-detect-chip{ display:flex; align-items:center; gap:7px; height:34px; padding:0 12px 0 4px;
               border-radius:999px; border:1px solid var(--cn-line); background:var(--cn-surf-btn);
               color:var(--cn-ink); font-size:var(--cn-fs-105); cursor:pointer; }
.cn-mask-detect-chip:hover{ border-color:var(--cn-amber); }
.cn-mask-detect-chip img{ width:26px; height:26px; border-radius:50%; object-fit:cover; flex:none; }
.cn-mask-detect-swatch{ width:26px; height:26px; border-radius:50%; flex:none;
               background:color-mix(in srgb, var(--cn-amber) 35%, var(--cn-surf-btn)); }
/* al: "refined and refined and refined" — a box-based candidate is usable the instant it
   appears, this is just an honest "still sharpening" signal while the background SAM pass
   hasn't landed for THIS one yet. Pulses rather than spins: this chip is clickable right now,
   unlike the busy-toast spinner elsewhere, which gates a control until it stops. */
.cn-mask-detect-chip.refining{ opacity:.72; }
.cn-mask-detect-chip.refining::after{ content:''; width:6px; height:6px; border-radius:50%;
               background:var(--cn-amber); flex:none; margin-left:-2px;
               animation:cnmaskrefine 1.3s ease-in-out infinite; }
@keyframes cnmaskrefine{ 0%,100%{ opacity:.25; } 50%{ opacity:1; } }
.cn-mask-detect-more{ display:block; width:100%; margin-top:8px; padding:9px 12px;
               border-radius:var(--cn-r-9); border:1px dashed var(--cn-line); background:transparent;
               color:var(--cn-t-50); font-size:var(--cn-fs-105); text-align:left; cursor:pointer; }
.cn-mask-detect-more:hover{ border-color:var(--cn-amber); color:var(--cn-ink); }

/* zones — real per-zone coverage histogram (lastZoneCoverage) + the 11-tile roman-numeral bar,
   both reading the same live zoneSelected state the pre-Chain zone panel already maintains */
.cn-zone-hist{ display:flex; align-items:flex-end; gap:2px; height:40px; margin-top:9px; }
.cn-zone-hist span{ flex:1; min-height:2px; border-radius:1px 1px 0 0; }
.cn-zone-bar{ display:flex; gap:2px; margin-top:6px; }
.cn-zone-tile{ flex:1; height:28px; border-radius:var(--cn-r-7); border:1px solid transparent;
               font-family:inherit; font-size:var(--cn-fs-9); font-weight:var(--cn-w-med);
               cursor:pointer; opacity:.55; }
.cn-zone-tile.active{ opacity:1; border-color:var(--cn-ink); box-shadow:0 0 0 1px var(--cn-ink); }
.cn-zone-tile:hover{ opacity:.85; }

/* vector — hosts the app's real facelift-vectorscope canvas (drawFaceliftVectorscope draws into
   every canvas matching [data-role="facelift-vectorscope"], this one included) */
.cn-vectorscope-wrap{ margin-top:9px; border-radius:var(--cn-r-12); overflow:hidden;
                       background:#05100c; border:1px solid var(--cn-line); display:flex;
                       align-items:center; justify-content:center; }
.cn-vectorscope{ display:block; width:100%; height:auto; aspect-ratio:1/1; }

/* skinline — Skin Line, a second block inside the vector widget (see docs/skinline-brief.md).
   Reading text sits ABOVE the plot (light-meter framing: the number is the primary output, the
   plot is secondary visual confirmation) — hosts drawFaceliftSkinLine()'s real canvas. */
.cn-skinline-reading{ margin-top:9px; font-size:var(--cn-fs-16); font-weight:var(--cn-w-thin);
                       color:var(--cn-ink); font-variant-numeric:tabular-nums; }
.cn-skinline-wrap{ margin-top:6px; border-radius:var(--cn-r-12); overflow:hidden;
                    background:#05100c; border:1px solid var(--cn-line); display:flex;
                    align-items:center; justify-content:center; }
.cn-skinline{ display:block; width:100%; height:auto; aspect-ratio:280/110; }

/* mesh — lens-distortion grid preview, real distortNormalized() math over the module's own k/p
   fields (RP2 cropStraighten's removal-direction k1-k3/p1-p2, or LK3 lenses' additive distK1/K2) */
.cn-mesh-grid{ margin-top:9px; border-radius:var(--cn-r-12); padding:8px;
               background:var(--cn-surf-off); border:1px solid var(--cn-line); }
/* al: widget was rendering at full card width — far larger than the grid preview needs to read
   at a glance. Shrunk to ~1/4 its previous on-screen size (roughly half the linear width,
   centered) rather than the full card. */
.cn-mesh-grid svg{ display:block; width:50%; height:auto; aspect-ratio:1/1; margin:0 auto; }

/* tiles — Dirt's real photographed-overlay picker, thumbnails from the same shuffled asset list
   (getShuffledDirtOverlays/shuffleOrder) the pre-Chain panel already loads */
.cn-tiles-grid{ display:grid; grid-template-columns:repeat(5,1fr); gap:6px; margin-top:9px; }
/* --cn-surf-off is rgba(214,231,250,0.022) — the cool LIFT blue, which is the colour of a RAISED
   face in this system. A tray cell is a recessed bay and takes --co-well. The two token families
   are easy to confuse because their digits are transposed (214,231,250 lift vs 239,231,214 ink). */
.cn-dirt-tile{ position:relative; aspect-ratio:1/1; border-radius:var(--cn-r-9); overflow:hidden;
               cursor:pointer; padding:0; background:var(--co-well); border:1px solid var(--co-edge-2); }
.cn-dirt-tile img{ width:100%; height:100%; object-fit:cover; display:block; }
.cn-dirt-tile.active{ border-color:var(--cn-amber); box-shadow:0 0 0 1px var(--cn-amber); }

/* curve — the real 9-point-per-channel point-curve editor (Capture One-grade drag/add/remove),
   re-skinned from the pre-Chain Adjustments card's own SVG (studio.html:5261-5276) with cn-
   tokens; wireChainCurveDrag() in studio.html owns the pointer drag math */
.cn-curve-wrap{ margin-top:9px; border-radius:var(--cn-r-12); padding:10px;
                background:var(--cn-surf-off); border:1px solid var(--cn-line); }
.cn-curve-svg{ width:100%; height:auto; aspect-ratio:1/1; display:block; touch-action:none; overflow:visible; }
.cn-curve-svg .cn-curve-grid{ stroke:var(--cn-line); stroke-width:1; }
.cn-curve-svg .cn-curve-identity{ stroke:var(--cn-t-30); stroke-width:1; stroke-dasharray:3,3; fill:none; }
.cn-curve-svg .cn-curve-path{ stroke:var(--cn-amber); stroke-width:2; fill:none; }
.cn-curve-svg .cn-curve-point{ fill:var(--cn-well); stroke:var(--cn-amber); stroke-width:2; cursor:ns-resize; }
.cn-curve-svg .cn-curve-point:hover{ fill:var(--cn-amber); }
.cn-curve-svg .cn-curve-endpoint{ cursor:move; fill:var(--cn-amber); fill-opacity:.55; }
.cn-curve-svg .cn-curve-endpoint:hover{ fill-opacity:1; }
.cn-curve-svg.cn-curve-channel-red .cn-curve-path,
.cn-curve-svg.cn-curve-channel-red .cn-curve-point{ stroke:#e5544c; }
.cn-curve-svg.cn-curve-channel-red .cn-curve-point:hover{ fill:#e5544c; }
.cn-curve-svg.cn-curve-channel-green .cn-curve-path,
.cn-curve-svg.cn-curve-channel-green .cn-curve-point{ stroke:#4fb35c; }
.cn-curve-svg.cn-curve-channel-green .cn-curve-point:hover{ fill:#4fb35c; }
.cn-curve-svg.cn-curve-channel-blue .cn-curve-path,
.cn-curve-svg.cn-curve-channel-blue .cn-curve-point{ stroke:#4c8ce5; }
.cn-curve-svg.cn-curve-channel-blue .cn-curve-point:hover{ fill:#4c8ce5; }
.cn-curve-hint{ font-size:var(--cn-fs-9); line-height:1.45; color:var(--cn-t-50); margin-top:8px; }

/* filmcurve — Film Emulation's real characteristic curve (buildFilmToneCurveSVG + real per-
   channel measured-stock LUT samples via filmChannelDataFor), a live non-draggable preview that
   moves with the module's own Toe/Shoulder/Exposure bias sliders */
.cn-filmcurve-wrap{ margin-top:9px; border-radius:var(--cn-r-12); padding:8px 8px 4px;
                     background:var(--cn-surf-off); border:1px solid var(--cn-line); }
.cn-filmcurve-wrap svg{ display:block; width:100%; height:auto; }

/* filmcurve dataset credit — spektrafilm (Andrea Volpato, CC BY-SA 4.0) is what the Tier A
   measured-stock curves above are digitised from; a quiet text link back to /film for the
   full attribution + licence, only shown when the measured (real datasheet) engine is active. */
.cn-film-credit-row{ margin-top:6px; text-align:right; }
.cn-film-credit{ font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-10); color:var(--cn-t-40);
                  text-decoration:none; border-bottom:1px dashed var(--cn-t-30); transition:color .15s,border-color .15s; }
.cn-film-credit:hover{ color:var(--cn-t-70); border-color:var(--cn-t-55); }

/* wheels — GR4 Lift/Gamma/Gain + GR5 HSL & Split Toning drag wheels (decision #13: real formula,
   not a cosmetic dot — see wheelDragToChannelDelta() in studio.html). Ring uses a conic-gradient
   full hue sweep matching the design's own drawn wheel; the puck sits at the live position and
   is repositioned inline during drag by chainRedrawWheelPuck(), full re-render on drag-end. */
.cn-wheel-row{ display:flex; gap:14px; margin-top:9px; justify-content:space-between; }
.cn-wheel-item{ flex:1; display:flex; flex-direction:column; align-items:center; gap:6px; min-width:0; }
/* al (10): "lift gamma feels a lot slicker with a softer look to the wheels ... all colour wheels
   should look like this."
   The hue ring is still a conic gradient because hue IS angular, but a trackball's SATURATION is
   radial — full at the rim, none at the centre — and the old wheel expressed none of that: it was
   a hard conic disc with an opaque well punched out of the middle at inset 18%, so it read as a
   flat colour ring with a hole rather than as a ball you push a colour out of. A radial wash over
   the conic gives the real two-axis reading, and the hard-edged well becomes a soft centre that
   fades into it. Applies to every wheel — LGG, the split-toning pair, Colour Zones. */
.cn-wheel{ position:relative; width:100%; aspect-ratio:1/1; border-radius:50%; cursor:crosshair;
           touch-action:none;
           background:
             radial-gradient(circle at 50% 50%,
               rgba(16,20,26,.97) 0%, rgba(16,20,26,.92) 17%,
               rgba(16,20,26,.55) 38%, rgba(16,20,26,.16) 58%, rgba(16,20,26,0) 74%),
             /* from 0deg, NOT 90. The wheels write their hue as
                  (atan2(dy,dx)*180/PI + 450) % 360
                which puts hue 0 at TWELVE o'clock. `from 90deg` rotates the ramp a quarter turn
                clockwise, putting hue 0 at THREE — so every puck sat 90 degrees away from the
                colour it was standing on. Dragging a trackball straight up pushed RED into the
                photograph while the wheel under the puck was painted VIOLET, and the same quarter
                turn was wrong on the split-toning pair and Colour Zones, since they share this
                rule. Tabulated at all four quadrants before and after. */
             conic-gradient(from 0deg,
               /* The same eight hues HSL_BANDS uses, at the same muted 62%/52% the dominance
                  wedges are drawn at, at their real band centres — so "red" is one colour across
                  this module instead of two. These stops used to be Apple's system palette
                  (#ff3b30, #ff9500, #34c759, #007aff...), which is why the split-toning pair read
                  as a phone colour picker sitting inside a darkroom console: bright, cool-biased,
                  and from a different design system altogether. */
               hsl(0,62%,52%)   0deg,   hsl(30,62%,52%)  30deg,  hsl(60,62%,52%)  60deg,
               hsl(120,62%,52%) 120deg, hsl(180,62%,52%) 180deg, hsl(240,62%,52%) 240deg,
               hsl(275,62%,52%) 275deg, hsl(315,62%,52%) 315deg, hsl(360,62%,52%) 360deg);
           box-shadow:inset 0 0 0 1px rgba(239,231,214,.10), 0 6px 18px -10px rgba(0,0,0,.9); }
/* The master RING is still the outer annulus and still takes its own drag — it just no longer
   paints an opaque well over the middle of the wheel. */
.cn-wheel-ring{ position:absolute; inset:18%; border-radius:50%; background:transparent;
                 box-shadow:inset 0 0 0 1px rgba(239,231,214,.06); pointer-events:none; }
.cn-wheel-center{ position:absolute; left:50%; top:50%; width:3px; height:3px; margin:-1.5px 0 0 -1.5px;
                   border-radius:50%; background:rgba(239,231,214,.30); pointer-events:none; }
.cn-wheel-puck{ position:absolute; width:13px; height:13px; margin:-6.5px 0 0 -6.5px;
                border-radius:50%; background:#FFF7EF; border:0;
                box-shadow:0 0 0 1.5px rgba(11,15,20,.55), 0 2px 7px -1px rgba(0,0,0,.75);
                pointer-events:none; }
.cn-wheel-foot{ display:flex; align-items:center; gap:6px; }
.cn-wheel-label{ font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-14); color:var(--cn-t-55); }
.cn-wheel-reset{ font-family:inherit; font-size:var(--cn-fs-9); line-height:1; padding:2px 5px;
                  border-radius:var(--cn-r-7); cursor:pointer; background:transparent;
                  border:1px solid var(--cn-line-chip); color:var(--cn-t-55); }
.cn-wheel-reset:hover{ color:var(--cn-ink); border-color:var(--cn-line-check); }

/* Light Zones' sun-position pad reuses the .cn-wheel structure HSL/LGG's own wheels use (same
   drag/reset/redraw plumbing), but their rainbow hue ring is meaningless here -- nothing about
   this control picks a colour. It gets its own look instead: a warm-to-dark radial glow, bright
   and centred like a sun seen from directly underneath, fading toward the rim the way real
   grazing/horizon light does -- the same "distance from centre = height" mapping the drag maths
   already uses, so the dial finally looks like what it's actually controlling. The inner ring is
   made transparent (base .cn-wheel-ring is an opaque well covering all but the outer rim) so the
   glow reads across the whole disc, not just a thin border.  al: "design not fixed" -- this is
   the fix: not just new interaction, a visual that actually reads as light position. */
.cn-wheel--sun{
  background: radial-gradient(circle at 50% 50%,
    #fff3d6 0%, #f3c26a 16%, #d98a3f 34%, #7a5a6a 56%, #2c2f4a 78%, #12131f 100%);
}
.cn-wheel--sun .cn-wheel-ring{
  background: transparent;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.10), inset 0 0 22px rgba(0,0,0,0.35);
}
.cn-wheel--sun .cn-wheel-center{ background: rgba(255,255,255,0.4); }
.cn-wheel--sun .cn-wheel-puck{
  background: #fff6df; border-color: #f0b955;
  box-shadow: 0 0 10px 3px rgba(240,185,85,0.6), 0 1px 4px rgba(0,0,0,0.55);
}

/* ── the dial (104px) ──────────────────────────────────────────────────── */
/* Item 4: collapsible wrap around the dial card — header row (chevron) always visible, the real
   .cn-dialcard beneath it collapses away via display:none to reclaim vertical space. Same pattern
   as .cn-histostrip below and .cn-collapsible above (shared .cn-collapsible-chev rotate rule). */
.cn-dialwrap{ flex:none; display:flex; flex-direction:column; gap:8px; }
.cn-dial-collapsehead{ flex:none; display:flex; align-items:center; justify-content:space-between;
              width:100%; padding:2px 2px; background:none; border:0; cursor:pointer;
              font-family:inherit; font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-20);
              color:var(--cn-t-50); }
.cn-dial-collapsehead:hover{ color:var(--cn-t-90); }
.cn-dial-chev{ width:14px; height:14px; flex:none; stroke:currentColor; fill:none; stroke-width:1.8;
              transition:transform .18s var(--cn-ease); transform:rotate(180deg); }
.cn-dialwrap.collapsed .cn-dial-chev{ transform:rotate(0deg); }
.cn-dialwrap.collapsed .cn-dialcard{ display:none; }
.cn-dialcard{ flex:none; display:flex; align-items:center; gap:14px;
              border-radius:var(--cn-r-16); padding:12px 14px;
              background:var(--cn-surf); border:1px solid var(--cn-line); }
.cn-dial{ position:relative; width:var(--cn-dial); height:var(--cn-dial); flex:none; }
.cn-dial svg{ width:100%; height:100%; }
.cn-dial:not(.dead) svg{ cursor:ns-resize; touch-action:none; } /* Item 3: knob is now a real vertical drag surface, not just decorative */
.cn-dial-read{ position:absolute; left:0; right:0; top:38px; text-align:center; pointer-events:none; }
.cn-dial-read b{ display:block; font-size:var(--cn-fs-21); font-weight:var(--cn-w-thin);
                 letter-spacing:var(--cn-ls-n3); line-height:1; color:var(--cn-ink-on-knob); }
.cn-dial-read i{ display:block; margin-top:4px; font-style:normal;
                 font-size:var(--cn-fs-7); letter-spacing:var(--cn-ls-22); color:rgba(15,26,28,0.55); }
.cn-dial-panel{ flex:1; min-width:0; }
.cn-dial-eyebrow{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-20); color:var(--cn-t-50); }
.cn-dial-label{ margin-top:6px; font-size:var(--cn-fs-12); line-height:1.3; color:var(--cn-t-92); }
.cn-dial-btns{ display:flex; gap:7px; margin-top:10px; }
.cn-dial-btns button{ flex:1; height:30px; border-radius:var(--cn-r-10);
                      display:flex; align-items:center; justify-content:center;
                      font-family:inherit; font-size:var(--cn-fs-15); font-weight:var(--cn-w-light);
                      background:var(--cn-surf-btn); border:1px solid var(--cn-line-btn);
                      color:var(--cn-ink); cursor:pointer; }
.cn-dial-btns button:hover{ background:var(--cn-hov-20); }
.cn-dial-btns button[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-dial line.needle{ transition:transform var(--cn-t-needle) var(--cn-ease); }
.cn-dial.dead .cn-dial-read b{ color:var(--cn-t-50); }
.cn-dial.dead line.needle{ opacity:.35; }

/* ── persistent histogram + zone-ramp strip (BUILD-PLAN-CHAIN.md §4.3 item 1) ───────────── */
.cn-histostrip{ flex:none; border-radius:var(--cn-r-16); padding:16px 14px 11px;
                background:var(--cn-surf); border:1px solid var(--cn-line); }
/* Item 4: collapsed state cuts vertical padding to the header's own and hides the body — same
   header-only-remains idea as .cn-dialwrap.collapsed above, just applied to a single element
   (the strip's own head IS the toggle button here, no separate wrap needed). */
.cn-histostrip.collapsed{ padding-bottom:16px; }
.cn-histostrip.collapsed .cn-histostrip-body{ display:none; }
.cn-histostrip-head{ display:flex; align-items:center; justify-content:space-between; width:100%;
                background:none; border:0; padding:0; cursor:pointer; font-family:inherit; }
.cn-histostrip-head:hover .cn-histostrip-label{ color:var(--cn-t-90); }
.cn-histostrip-label{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-20); color:var(--cn-t-50); }
.cn-histostrip-clip{ font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-14); color:var(--cn-amber); margin-left:auto; margin-right:10px; }
.cn-histostrip-chev{ width:14px; height:14px; flex:none; stroke:var(--cn-t-50); fill:none; stroke-width:1.8;
                transition:transform .18s var(--cn-ease); transform:rotate(180deg); }
.cn-histostrip.collapsed .cn-histostrip-chev{ transform:rotate(0deg); }
.cn-histostrip-canvas{ display:block; width:100%; height:44px; margin-top:10px; }
.cn-histostrip-ramp{ display:flex; gap:1px; margin-top:8px; height:6px; border-radius:3px; overflow:hidden; }
.cn-histostrip-ramp span{ flex:1; display:block; }

/* ── viewer toolbar parts ──────────────────────────────────────────────── */
/* The state lives in `color`, and the glyph follows it with stroke:currentColor — the same shape
   .cn-seg and .cn-nav already use, and the reason this now matches the icon system rather than
   fighting it.
   It was `stroke:var(--cn-t-75)` with no color at all, and that silently lost: the blanket
   `.cn-shell svg{ stroke:currentcolor }` further down this file has the SAME specificity (0,1,1)
   and comes later, so it won the cascade — and with no color anywhere on the button, currentColor
   resolved to the initial black. The inactive tools were painting black-on-black. That is the
   other half of al's "you cannot see the icons": the rail tiles and these. Setting color here
   fixes it at the source instead of escalating specificity in a rule that would lose again. */
.cn-cursor{ width:28px; height:28px; border-radius:var(--cn-r-9);
            display:flex; align-items:center; justify-content:center;
            background:transparent; border:0; cursor:pointer; color:var(--cn-t-75); }
.cn-cursor svg{ width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:1.3;
                stroke-linecap:round; stroke-linejoin:round; }
.cn-cursor:not([aria-pressed="true"]):hover{ background:var(--cn-hov-14); color:var(--cn-ink); }
/* al: the active tool "renders as a solid orange blob against outline icons". It did — a 90%
   amber fill with the glyph knocked out in dark ink, which is a different visual language from
   every other icon in this toolbar (1.3px amber-on-transparent outlines). The icon system's own
   selected treatment is the one .cn-nav already uses for the current page: keep the outline, put
   the amber in the STROKE, and carry the state on a faint amber tint plus a real border. The tool
   still reads as unambiguously on, without becoming the loudest object on the screen. */
.cn-cursor[aria-pressed="true"]{ background:color-mix(in srgb, var(--cn-amber) 16%, transparent);
                                 box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cn-amber) 55%, transparent);
                                 color:var(--cn-amber); }
.cn-cursor[aria-pressed="true"]:hover{ background:color-mix(in srgb, var(--cn-amber) 24%, transparent); }
.cn-cursor[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-cursor[disabled]:hover{ background:transparent; }

.cn-vrule-sm{ width:1px; height:20px; background:var(--cn-line-2); flex:none; }
.cn-cursor-label{ font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-16); color:var(--cn-t-60); }

.cn-zoombtn{ width:26px; height:26px; border-radius:var(--cn-r-8);
             display:flex; align-items:center; justify-content:center;
             background:transparent; border:0; cursor:pointer;
             font-family:inherit; font-size:14px; font-weight:var(--cn-w-light); color:var(--cn-t-80); }
.cn-zoombtn:hover{ background:var(--cn-hov-14); }
.cn-zoombtn[disabled]{ opacity:var(--cn-disabled-alpha); cursor:default; }
.cn-zoombtn[disabled]:hover{ background:transparent; }
.cn-zoomread{ font-size:var(--cn-fs-105); letter-spacing:var(--cn-ls-10); width:44px; text-align:center; color:var(--cn-ink); }

.cn-ghost{ font-family:inherit; font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-14);
           display:flex; align-items:center; justify-content:center;
           width:30px; height:30px; padding:0; border-radius:var(--cn-r-pill); margin-left:4px; cursor:pointer;
           background:var(--cn-surf-btn); border:1px solid var(--cn-line-btn); color:var(--cn-t-85); }
.cn-ghost:hover{ background:var(--cn-hov-18); }
.cn-ghost svg{ width:15px; height:15px; fill:none; stroke:currentColor; stroke-linecap:round; stroke-linejoin:round; }

.cn-seg{ font-family:inherit; font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-14);
         display:flex; align-items:center; justify-content:center;
         width:30px; height:30px; padding:0; border-radius:var(--cn-r-pill); border:0; cursor:pointer;
         background:var(--cn-surf-btn); color:var(--cn-t-80); }
.cn-seg svg{ width:15px; height:15px; fill:none; stroke:currentColor; stroke-linecap:round; stroke-linejoin:round; }
.cn-seg + .cn-seg{ margin-left:4px; }
.cn-seg[aria-pressed="true"]{ background:var(--cn-ink); color:var(--cn-ink-on-white); }
/* Panel Mode's knob. It sits with the .cn-seg group but is a mode, not one of the before/after
   pair, so it takes the amber "this is engaged" treatment the viewer tools use rather than the
   inverted white the segmented pair uses for selection. */
.cn-panelbtn{ margin-left:8px; }
.cn-panelbtn[aria-pressed="true"]{ background:color-mix(in srgb, var(--cn-amber) 16%, transparent);
                                   box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cn-amber) 55%, transparent);
                                   color:var(--cn-amber); }
.cn-panelbtn svg{ width:16px; height:16px; }

/* ── viewer frame + corner brackets ───────────────────────────────────── */
.cn-frame{ position:relative; flex:1; min-height:0; border-radius:var(--cn-r-18);
           overflow:hidden; background:var(--cn-well); border:1px solid var(--cn-line); }
.cn-frame > canvas, .cn-frame > .cn-canvaswrap, .cn-frame > .canvas-wrap{ position:absolute; inset:0;
                                                width:100%; height:100%; }
.cn-frame > .session-restore-banner{ position:absolute; top:10px; left:10px; right:10px; z-index:5; margin:0; }

/* ── filmstrip (new) ───────────────────────────────────────────────────── */
.cn-strip{ flex:none; border-radius:var(--cn-r-14); padding:9px 12px 10px;
           background:var(--cn-surf); border:1px solid var(--cn-line); }
.cn-strip-head{ display:flex; align-items:center; gap:10px; }
.cn-strip-head .l{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-22); color:var(--cn-t-50); }
.cn-strip-count{ display:flex; align-items:center; gap:6px; font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-14); color:var(--cn-amber); }
.cn-strip-count i{ width:5px; height:5px; border-radius:50%; display:block; background:var(--cn-amber); }
.cn-strip-head .rule{ flex:1; height:1px; background:var(--cn-line-div); }
.cn-strip-clear{ font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-14); color:var(--cn-t-50); cursor:pointer; background:none; border:0; }
.cn-strip-clear:hover{ color:var(--cn-ink); }
.cn-strip-toggle{ display:flex; align-items:center; gap:7px; height:22px; padding:0 10px; border-radius:var(--cn-r-pill);
                   cursor:pointer; font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-14);
                   background:var(--cn-hov-08); border:1px solid var(--cn-line-btn); color:var(--cn-t-75); }
.cn-strip-toggle:hover{ background:var(--cn-hov-16); }
.cn-strip-toggle svg{ width:11px; height:11px; fill:none; stroke:currentColor; stroke-width:1.5; }
.cn-strip-row{ display:flex; gap:6px; margin-top:9px; overflow-x:auto; padding-bottom:2px; }
.cn-strip-thumb{ position:relative; flex:none; width:74px; border-radius:var(--cn-r-10); padding:4px 4px 3px;
                  cursor:pointer; background:var(--cn-surf); border:1px solid var(--cn-line); }
.cn-strip-thumb.active{ border-color:var(--cn-amber); }
.cn-strip-img{ position:relative; height:44px; border-radius:7px; overflow:hidden; background:#0A1213; }
.cn-strip-img img, .cn-strip-img canvas{ width:100%; height:100%; object-fit:cover; display:block; }
.cn-strip-meta{ display:flex; align-items:center; gap:5px; margin-top:4px; }
.cn-strip-dot{ width:4px; height:4px; border-radius:50%; flex:none; display:block; background:var(--cn-t-40); }
.cn-strip-dot.worked{ background:var(--cn-amber); }
.cn-strip-label{ flex:1; font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-10); color:var(--cn-t-75);
                  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cn-strip-edits{ font-size:8px; letter-spacing:var(--cn-ls-06); color:var(--cn-t-40); }

/* ── chain pills bar (new — replaces Bench's plain bottom strip) ──────────── */
.cn-chainbar{ flex:none; height:40px; display:flex; align-items:center; gap:8px; padding:0 12px;
              border-radius:var(--cn-r-14); background:var(--cn-surf); border:1px solid var(--cn-line);
              font-size:var(--cn-fs-85); letter-spacing:var(--cn-ls-22); color:var(--cn-t-50); }
.cn-chainpill{ display:flex; align-items:center; gap:7px; height:24px; padding:0 11px; border-radius:var(--cn-r-pill);
               cursor:pointer; font-size:var(--cn-fs-9); letter-spacing:var(--cn-ls-16);
               background:var(--cn-hov-08); border:1px solid var(--cn-line-btn); color:var(--cn-t-60); }
.cn-chainpill i{ width:4px; height:4px; border-radius:50%; display:block; background:var(--cn-t-40); }
.cn-chainpill.active{ color:var(--cn-ink); border-color:var(--cn-line-2); }
.cn-chainpill.active i{ background:var(--cn-lime); }
.cn-chainbar .sp{ flex:1; }
.cn-chaincount{ font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-14); color:var(--cn-t-60); }

/* ── EXIF strip + rating dots ──────────────────────────────────────────── */
.cn-exif{ flex:none; height:var(--cn-exif-h); display:flex; align-items:center; gap:18px;
          padding:0 14px; border-radius:var(--cn-r-14);
          background:var(--cn-surf); border:1px solid var(--cn-line);
          font-size:var(--cn-fs-95); letter-spacing:var(--cn-ls-14); color:var(--cn-t-60); }
.cn-exif b{ color:var(--cn-ink); font-weight:inherit; }
.cn-exif .file{ color:var(--cn-ink); letter-spacing:var(--cn-ls-10); }
.cn-exif .sp{ flex:1; }
.cn-rating{ display:flex; gap:5px; }
.cn-rating i{ width:5px; height:5px; border-radius:50%; display:block; background:var(--cn-t-22); }
.cn-rating i.on{ background:var(--cn-amber); }

/* ── Filmstrip: collapsible strip of the session's gallery thumbnails under the viewer.
   Worked (edited) frames border amber (--cn-amber), untouched ones stay a neutral grey line. */
.cn-filmstrip-row{ flex:none; display:flex; align-items:stretch; gap:8px; margin-top:8px; }
.cn-filmstrip{ flex:1 1 auto; min-width:0; display:none; gap:9px; padding:2px 2px 0; overflow-x:auto; }
.cn-filmstrip.on{ display:flex; }
.cn-filmstrip.collapsed{ display:none!important; }
/* al: "this layout is a mess — make symmetrical". Was align-self:center with two auto-sized
   buttons, which floated the pair in the middle of the row instead of squaring them off against
   the thumbnail strip they sit next to. Stretched to match the strip's own height, each button an
   equal half via flex:1, so the two-button column reads as one deliberate block, top edge to top
   edge and bottom edge to bottom edge with the thumbnails beside it. */
.cn-filmstrip-actions{ flex:none; display:none; flex-direction:column; gap:6px; align-self:stretch; width:74px; }
.cn-filmstrip-row:has(.cn-filmstrip.on) .cn-filmstrip-actions{ display:flex; }
.cn-filmstrip-edited,.cn-filmstrip-clear{ flex:1; display:flex; align-items:center; justify-content:center;
  font-size:11px; letter-spacing:.02em; color:var(--cn-t-60, #9a9a9a); background:var(--cn-well);
  border:1px solid var(--cn-t-30); border-radius:var(--cn-r-8, 8px); cursor:pointer; transition:color .12s, border-color .12s, background .12s; }
.cn-filmstrip-edited:hover,.cn-filmstrip-clear:hover{ color:var(--cn-fg, #eee); border-color:var(--cn-t-60, #9a9a9a); }
.cn-filmstrip-edited[aria-pressed="true"]{ color:var(--cn-amber); border-color:var(--cn-amber); background:color-mix(in srgb, var(--cn-amber) 12%, var(--cn-well)); }
/* The cell's WIDTH is written inline by js/gallery/filmstrip.js as strip-height x the photo's
   own aspect ratio — nothing here may set one. It was a fixed 64px box with object-fit:cover,
   which cropped every frame that was not 16:11 (and in the Print Room, a 44px square, where a
   3:2 landscape lost a third of its width and a portrait lost more than half). A strip is a row
   of one height; the honest cell is whatever width that height implies. */
/* ── STATE LANGUAGE ────────────────────────────────────────────────────
   Three states used to be one colour. `--cn-lime` is remapped to `--cn-amber` at the top of
   this file (the lime kill), so .active (--cn-lime), .selected (--cn-lime) and .worked
   (--cn-amber) all resolved to #E8863A: clicking a frame and having edited a frame looked
   identical, and in the Print Room every click just turned something orange.

   They are not the same KIND of fact, so they no longer share a channel:

     BORDER  = where you are.        Neutral bright ink. Never amber.
     TINT+✓  = what you have gathered (multi-select). Sits under the border so a frame that is
               both active and selected still reads as active.
     EDGE BAR = what is true of the photo itself — amber for edited, red for rejected. Its own
               channel entirely, so an edited frame is legible whether or not it is selected.

   That means selection and content state can be read at the same time, which is the whole
   point: "the one I'm on", "the ones I've gathered", "the ones I've worked" are three
   different questions. */
.cn-filmstrip-thumb{ position:relative; flex-shrink:0; border-radius:var(--cn-r-10, 10px);
  overflow:hidden; cursor:pointer; background:var(--cn-well);
  border:2px solid transparent; box-shadow:inset 0 0 0 1px var(--cn-line-off);
  transition:border-color .12s, transform .12s, box-shadow .12s, opacity .12s; }
.cn-filmstrip-thumb:hover{ transform:translateY(-2px); box-shadow:inset 0 0 0 1px var(--cn-line-chip); }

/* where you are */
.cn-filmstrip-thumb.active{ border-color:var(--cn-ink); box-shadow:inset 0 0 0 1px rgba(7,13,14,0.55); }

/* what you have gathered — under the border, so active+selected still reads as active */
.cn-filmstrip-thumb.selected::after{ content:''; position:absolute; inset:0; pointer-events:none;
  background:color-mix(in srgb, var(--cn-ink) 20%, transparent); }
.cn-filmstrip-check{ position:absolute; right:3px; bottom:3px; width:14px; height:14px; display:none;
  align-items:center; justify-content:center; border-radius:50%; z-index:2;
  background:var(--cn-ink); color:var(--cn-ink-on-white); font-size:10px; line-height:1; }
.cn-filmstrip-thumb.selected .cn-filmstrip-check{ display:flex; }

/* what is true of the photo — its own channel down the leading edge */
.cn-filmstrip-thumb.worked::before{ content:''; position:absolute; left:0; top:0; bottom:0; width:4px;
  z-index:2; background:var(--cn-amber); pointer-events:none; }
.cn-filmstrip-thumb.rejected::before{ content:''; position:absolute; left:0; top:0; bottom:0; width:4px;
  z-index:2; background:#C0453F; pointer-events:none; }
/* height:100% not a hardcoded 44 — the cell owns the geometry now, and the image fills it
   exactly because the cell was built from this image's ratio. */
.cn-filmstrip-thumb img{ display:block; width:100%; height:100%; object-fit:cover; }
.cn-filmstrip-noimg{ display:block; width:100%; height:100%; background:var(--cn-well); }

/* Rating, settable from the strip itself. It was a read-only readout, which made the strip a
   navigator; a filmstrip you cannot rate from is the thing that makes it feel basic next to a
   real browser. Earned pips show always; all five appear on hover so there is something to aim
   at, and clicking one sets the rating without leaving the frame you are editing. Pips rather
   than star glyphs because a 56px cell has no room for glyphs, on their own scrim so they read
   over a blown highlight as well as over shadow. */
.cn-filmstrip-rating{ position:absolute; left:3px; bottom:3px; display:flex; gap:2px; z-index:3;
  padding:3px; border-radius:var(--cn-r-pill); background:rgba(7,13,14,0.62); }
.cn-filmstrip-rating b{ display:block; width:5px; height:5px; padding:0; border:0; cursor:pointer;
  border-radius:50%; background:var(--cn-t-30); transition:background-color .1s, transform .1s; }
.cn-filmstrip-rating b.on{ background:var(--cn-amber); }
.cn-filmstrip-rating b:hover{ transform:scale(1.5); background:var(--cn-amber-hi); }
/* Unearned pips stay invisible until the cell is hovered, so a quiet strip stays quiet. */
.cn-filmstrip-rating b:not(.on){ opacity:0; }
.cn-filmstrip-thumb:hover .cn-filmstrip-rating b:not(.on){ opacity:1; }
.cn-filmstrip-rating:empty{ display:none; }

/* Rejected frames stay in the strip — you may still want to look at one — but they read as out
   of the running at a glance, the same dim-and-desaturate the Gallery wall uses. The red edge
   bar above carries the flag itself. */
.cn-filmstrip-thumb.rejected{ opacity:0.4; }
.cn-filmstrip-thumb.rejected img{ filter:saturate(0.25); }
.cn-filmstrip-thumb.rejected:hover{ opacity:0.85; }
.cn-filmstrip-remove{ position:absolute; top:2px; right:2px; width:16px; height:16px; padding:0;
  display:flex; align-items:center; justify-content:center; border-radius:50%; cursor:pointer;
  font-family:inherit; font-size:12px; line-height:1;
  background:rgba(7,13,14,0.72); border:1px solid var(--cn-line-chip); color:var(--cn-t-85);
  opacity:0; transition:opacity .12s, background-color .12s; }
.cn-filmstrip-thumb:hover .cn-filmstrip-remove{ opacity:1; }
.cn-filmstrip-remove:hover{ background:#7A2A28; color:var(--cn-ink); }

/* Free-colour picker (Sky Recovery's Horizon/Zenith). These fields are any colour, not a fixed
   palette, so they get a real <input type="color"> rather than the swatch row — see
   chainToolRowHTML's kind==='color' branch for the bug this replaced. */
.cn-colorval{ margin-left:auto; font-size:var(--cn-fs-105); letter-spacing:var(--cn-ls-05);
              color:var(--cn-t-55); font-variant-numeric:tabular-nums; }
/* input[type=color].cn-colorinput, not .cn-colorinput alone. studio.html carries a legacy
   global `input[type=color]{width:100%;height:32px}` from the old full-width panel layout, and an
   element+attribute selector (0,1,1) outranks a bare class (0,1,0) — so every Chain colour picker
   was being stretched to the full panel width regardless of what this rule said. Measured: all 8
   pickers across Sky, Cinema Looks, Lenses, Diffusion and Dirt rendered 335x32 instead of 34x26.
   Qualifying the selector (0,2,1) wins outright rather than depending on stylesheet order, and
   leaves the legacy rule intact for the legacy panel markup that still uses it. */
input[type=color].cn-colorinput{ width:34px; height:26px; flex:0 0 auto; margin-left:8px; padding:0; cursor:pointer;
                background:none; border:1px solid var(--cn-line-2); border-radius:var(--cn-r-8, 8px); }
.cn-colorinput::-webkit-color-swatch-wrapper{ padding:2px; }
.cn-colorinput::-webkit-color-swatch{ border:none; border-radius:5px; }
.cn-colorinput:disabled{ opacity:.35; cursor:default; }

/* Action-button progress: the wheel sits inside the button on the same line as its own progress
   text (see chainToolRowHTML's action branch), with the settled result as a quiet line below. */
.cn-action.busy{ opacity:1; cursor:progress; }
.cn-action.busy, .cn-action:disabled.busy{ color:var(--cn-t-85, #ddd); }
.cn-action .cn-processing-spinner{ margin-right:8px; vertical-align:-2px; }
.cn-action-status{ margin-top:6px; font-size:var(--cn-fs-105); letter-spacing:var(--cn-ls-05);
                   color:var(--cn-t-55); line-height:1.45; }

/* Fixer progress row — sits where the duplicate tier chips used to, reports the live job. */
.cn-progressrow{ display:flex; align-items:center; gap:9px; min-height:34px;
                 font-size:var(--cn-fs-105); letter-spacing:var(--cn-ls-05); color:var(--cn-t-55); }
.cn-progressrow.busy{ color:var(--cn-t-85, #ddd); }

/* Grading wheels — the outer annulus is the MASTER ring, the inner disc is the trackball. The
   ring is drawn as a distinct band so the two halves of the control are legible before you touch
   it; without that, "double-click resets only the half you hit" is a rule nobody can see. */
.cn-wheel-row-4{ gap:10px; }
.cn-wheel-row-4 .cn-wheel-item{ min-width:0; }
.cn-wheel[data-cn-wheel-hasring]{ position:relative; }
.cn-wheel[data-cn-wheel-hasring]::after{
  content:''; position:absolute; inset:0; border-radius:50%; pointer-events:none;
  border:2px solid color-mix(in srgb, var(--cn-amber) 42%, transparent);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.55);
}

/* Report popover — dropped from the cn-bar's REPORT button (between COMPARE and SCOPES).
   Same floating-card tier as .cn-picker-menu (position:fixed, z-index:1000, --cn-well card),
   just wider and with a textarea instead of a list, since this holds an editable caption rather
   than a set of options. */
.cn-report-popover{ position:fixed; top:calc(var(--cn-bar-h) + 8px); right:16px; z-index:1000;
  width:min(380px, calc(100vw - 32px)); background:color-mix(in srgb, var(--cn-well) 92%, white 8%);
  border:1px solid var(--cn-line); border-radius:var(--cn-r-12); padding:14px;
  box-shadow:0 10px 30px rgba(0,0,0,.55); color:var(--cn-ink); font-family:inherit; }
.cn-report-popover[hidden]{ display:none; }
.cn-report-popover .bulk-panel-head{ font-size:var(--cn-fs-11, 11px); color:var(--cn-t-60); line-height:1.6; }

/* Download-status wheel — see js/models/registry.js / initModelsWheel() in studio.html. */
.cn-models-wheel-seg{ transition:opacity .3s ease; }
.cn-models-wheel-seg.loading{ animation:cnmodelspulse 1.4s ease-in-out infinite; }
@keyframes cnmodelspulse{ 0%,100%{ opacity:.4; } 50%{ opacity:1; } }
.cn-models-btn.active{ background:var(--cn-line-btn); }
.cn-models-list{ display:flex; flex-direction:column; gap:1px; margin-top:10px; max-height:min(50vh, 420px);
  overflow-y:auto; border-radius:var(--cn-r-9); overflow-x:hidden; }
.cn-models-row{ display:flex; align-items:center; gap:9px; padding:9px 4px; background:transparent; border-bottom:1px solid var(--cn-line); }
.cn-models-row:last-child{ border-bottom:none; }
.cn-models-dot{ width:8px; height:8px; border-radius:50%; flex:none; }
.cn-models-row-text{ flex:1; min-width:0; }
/* 500 — see console.css's note on .cn-inst-action.primary: there is no Urbanist 600 to load, so
   600 is synthesised into a faux-bold rather than rendered. */
.cn-models-row-label{ font-size:var(--cn-fs-12,12px); font-weight:500; color:var(--cn-ink); }
.cn-models-row-label i{ font-style:normal; font-weight:500; color:var(--cn-t-50); }
.cn-models-row-tool{ font-size:var(--cn-fs-105,10.5px); color:var(--cn-t-50); margin-top:1px; }
.cn-models-row-status{ font-size:var(--cn-fs-105,10.5px); color:var(--cn-t-50); flex:none; text-align:right; white-space:nowrap; }
.cn-models-row-status.status-ready{ color:var(--cn-ink); font-variant-numeric:tabular-nums; }
.cn-models-row-status.status-loading{ color:var(--cn-amber); }
.cn-wheel[data-cn-wheel-hasring]:hover::after{ border-color:color-mix(in srgb, var(--cn-amber) 78%, transparent); }

/* icon suite — one weight knob for every glyph (docs/design/handover/ICON-SUITE.md), and the
   row-height rules that keep the label->glyph swap geometry-neutral (a glyph sets its flex row
   height; the label it replaces only occupied its line box).
   Glyphs are STROKED, not filled: SVG defaults fill to black, so without this every icon not
   already covered by a component-scoped rule (cn-cmdbtn svg, cn-nav a svg, etc.) renders as a
   solid black shape. Scoped to the svg element, never `svg *`, so paths carrying their own
   fill="currentColor" stroke="none" still paint. */
.cn-shell svg, .panel svg, .work svg{
  fill:none; stroke:currentColor; stroke-width:var(--ic-w, 2);
  stroke-linecap:round; stroke-linejoin:round;
}
.cn-cmdbtn.icon-only svg{ width:19px; height:19px; }
.cn-mod-title svg, .card-head .name svg{ width:21px; height:17px; display:block; flex:none; }
.panel-category-label svg, .cn-section-label svg{ width:13px; height:13px; display:block; flex:none; }

/* Ticker titles: the tool's name sits next to its glyph at all times — module titles, print/mono
   card heads, category headers (the rows with real width to spend; the 24-32px command-bar/nav/
   rail squares have no room for this and keep title/aria-label only). */
.cn-ticker{ display:inline-block; overflow:hidden; max-width:260px; vertical-align:middle;
            opacity:1; }
.cn-ticker-track{ display:inline-block; white-space:nowrap; }

/* SHEET CELLS (Print Room). The cell is a sheet of paper and the photograph is matted inside it at
   the printable area, so the image is positioned absolutely from the inset the room measured off
   the real sheet geometry rather than filling the cell. A faint edge separates paper from the dark
   strip behind it — without it a white sheet on a dark background reads as a blown-out thumbnail. */
.cn-filmstrip-thumb img.on-sheet{ position:absolute; width:auto; height:auto; }
.cn-filmstrip-thumb:has(img.on-sheet){ box-shadow:inset 0 0 0 1px rgba(7,13,14,0.28); }

/* Loupe pick/reject: lit when the state is on, so a full-screen cull shows its own decision back.
   Pick borrows the gallery's picked green, reject its red, matching the grid badges so the same
   decision reads the same in both views. */
.cn-gal-zoombtn.cn-gal-pickbtn.on{ color:#2fbf6d; border-color:rgba(47,191,109,0.55); background:rgba(47,191,109,0.14); }
.cn-gal-zoombtn.cn-gal-rejectbtn.on{ color:#e5534b; border-color:rgba(229,83,75,0.55); background:rgba(229,83,75,0.14); }
