/* ── Lighting Lab — NR brand: black canvas, blue leads, orange punctuates ── */
:root {
  --nr-black: #090909;
  --nr-blue: #1A6EE8;
  --bright-blue: #4A93F0;
  --pale-blue: #A9CBF5;
  --deep-blue: #1257C4;
  --nr-orange: #FF6D00;
  --amber: #FFAB00;
  --gray: #C8C8C8;
  --card: #0D0D0D;
  --line: #1c1c1e;
  --sidebar-w: 316px;

  /* ── the one brand decision this pass did NOT make ──────────────────────
     NR Blue as TYPE measures 4.21:1 on #090909 and 4.11:1 on the #0D0D0D
     sidebar card. WCAG AA wants 4.5:1 for text this size, so the four blue
     labels in this file — .brand-sub (8.5px/600), .panel-title (10.5px/700),
     .bp-title (9.5px/700) and .vf-mirror-title (9.5px/700) — are just under.
     Nothing else is: white on NR Blue is 4.73:1 and passes, NR Orange is
     7.05:1 and passes, and every blue BORDER, FILL and RING is a
     non-text-contrast case at 3:1 and passes comfortably.

     A brand colour is the owner's call, so this token ships set to the brand
     blue EXACTLY — it is an alias, it changes not one pixel, and the app looks
     today what it looked yesterday. What it buys is that the decision, if it
     is ever made, is this one line and not a hunt through four rules. The
     measured candidates are in the launch report; the two that need no new
     colour are `var(--bright-blue)` (#4A93F0, already in this palette and
     already what about/about.css uses for exactly this reason — 6.37:1 on
     black, 6.22:1 on the card) and #2877E9, the same hue and saturation as
     NR Blue lifted 3% in lightness, which measures 4.65:1 and 4.54:1. */
  /* #4A93F0, not NR Blue: the label blue on the black panels measured 4.1:1
     against #0D0D0D, under WCAG AA's 4.5:1 for small text. */
  --blue-label: var(--bright-blue);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
::selection { background: var(--nr-orange); color: #090909; }

html, body {
  height: 100%;
  background: var(--nr-black);
  color: var(--gray);
  /* Montserrat now arrives non-render-blocking (see index.html), so this stack
     is what people actually read for the first few hundred milliseconds — and
     what everything reflows FROM when the swap happens. The fallbacks are the
     widest-metric system faces available per platform, chosen to keep that
     reflow small rather than to look identical. */
  font-family: 'Montserrat', -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  overflow: hidden;
}

button { font-family: inherit; cursor: pointer; }

/* Skip link — the same shape and the same words-per-line as about/about.css's,
   copied rather than shared for the reason that file gives at the top. Parked
   off-screen with `left`, not with display:none or visibility:hidden, because
   both of those take it out of the tab order and there is then nothing to
   focus. #sidebar-content carries tabindex="-1" so the jump actually lands. */
.skip {
  position: absolute; left: -9999px; top: 0;
  display: inline-flex; align-items: center; min-height: 44px;
  background: var(--nr-blue); color: #fff;
  padding: 0 18px; z-index: 100;
  font-weight: 700; font-size: 12px; letter-spacing: 0.16em;
  text-decoration: none;
}
.skip:focus { left: 8px; top: 8px; }
#sidebar-content:focus { outline: none; }
#sidebar-content:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: -2px; }

/* ── top bar ─────────────────────────────────────────────────────────────── */
#topbar {
  height: 58px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 18px;
  border-bottom: 1px solid var(--line);
  background: var(--nr-black);
}
.brand { display: flex; align-items: center; gap: 13px; }
.nr-mark {
  position: relative;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
}
.nr-text {
  font-weight: 800; font-size: 15px; color: #fff; letter-spacing: 0.02em;
}
.bars { position: absolute; right: 1px; bottom: 4px; display: flex; gap: 2px; }
.bars i { display: block; width: 2px; background: var(--nr-blue); }
.bars i:nth-child(1) { height: 6px; }
.bars i:nth-child(2) { height: 9px; }
.bars i:nth-child(3) { height: 12px; }
.bracket { position: absolute; width: 10px; height: 10px; border-color: var(--nr-orange); border-style: solid; }
.bracket.tl { top: 0; left: 0; border-width: 2px 0 0 2px; }
.bracket.br { bottom: 0; right: 0; border-width: 0 2px 2px 0; }
.brand-name { color: #fff; font-weight: 800; font-size: 15px; letter-spacing: 0.24em; }
/* The wordmark ships in two lengths (see index.html and the ≤820px block). The
   full one is what everything above phone width shows; the short mark is out of
   the way until the topbar runs out of room for the long one. */
.bn-short { display: none; }
.brand-sub { color: var(--blue-label); font-weight: 600; font-size: 8.5px; letter-spacing: 0.28em; margin-top: 2px; }

.topbar-right { display: flex; align-items: center; gap: 16px; }
.live-tag {
  font-size: 10px; font-weight: 700; letter-spacing: 0.2em;
  color: var(--amber);
  padding: 4px 10px;
  border: 1px solid #3a2c08;
  border-radius: 3px;
  background: #14100a;
}

/* ── focus-frame: the camera-viewfinder motif — orange corner brackets ──────
   This class used to be `position: relative` and nothing else. On the three
   topbar buttons that was invisible, because those carry four literal <span
   class="fb"> brackets in the markup and the relative positioning is what
   those absolutely-positioned spans resolve against. But ui.js also puts
   `focus-frame` on controls with no <span>s at all — the A/B slots, the two
   A/B minis, "+ ADD LIGHT", "APPLY TO SCENE" and the selected backdrop and
   floor thumbs — where it promised a motif and drew nothing.

   So the class now DOES what it is named for: two corner brackets, drawn on
   :focus-visible with the element's own pseudo-elements. Top-left and
   bottom-right only, which is not a shortcut around ::before/::after being
   two — it is the same two-corner crop mark the NR wordmark uses (.bracket.tl
   / .bracket.br) and the exposure-lock frame it is quoting.

   The outline rules below still apply to everything they applied to before;
   this is added to them, not instead of them, so a control that already had a
   ring keeps it and gains the brackets. `outline-offset: 4px` clears the
   brackets, which sit 3px out. */
.focus-frame { position: relative; }
.focus-frame:focus-visible {
  outline: 2px solid var(--nr-orange);
  outline-offset: 4px;
}
.focus-frame:focus-visible::before,
.focus-frame:focus-visible::after {
  content: '';
  position: absolute;
  width: 8px; height: 8px;
  border-color: var(--nr-orange);
  border-style: solid;
  pointer-events: none;
}
.focus-frame:focus-visible::before { top: -3px; left: -3px; border-width: 2px 0 0 2px; }
.focus-frame:focus-visible::after { bottom: -3px; right: -3px; border-width: 0 2px 2px 0; }
/* The three topbar buttons ship their brackets as real <span class="fb">
   elements and animate them outward on hover. Two sets of brackets on one
   button is a smear, so the markup ones win there. */
.btn-primary.focus-frame:focus-visible::before,
.btn-primary.focus-frame:focus-visible::after,
.btn-ghost.focus-frame:focus-visible::before,
.btn-ghost.focus-frame:focus-visible::after { content: none; }
.btn-primary.focus-frame:focus-visible .fb,
.btn-ghost.focus-frame:focus-visible .fb { opacity: 1; }
/* The selected backdrop/floor thumb clips its canvas to a 4px radius, which
   would also clip brackets sitting 3px outside it. Let them out for the
   duration of the focus; the canvas fills the box exactly, so all that is
   un-clipped is four rounded corners. */
.backdrop-thumb.focus-frame:focus-visible { overflow: visible; }
.btn-primary {
  position: relative;
  background: var(--nr-blue);
  color: #fff;
  border: none;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.18em;
  padding: 10px 20px;
  transition: background 0.15s;
}
.btn-primary:hover { background: var(--bright-blue); }
.btn-primary:active { background: var(--deep-blue); }
/* EXPORT PDF now renders five setup views before it writes the document, which
   is long enough for silence to read as a broken button. Same two signals the
   model downloader gives: a count in the label (main.js) and a bar under it. */
   The id carries it because .btn-primary.focus-frame:focus-visible::after sets
   content:none, and a bar that vanishes the moment the button takes focus is
   worse than no bar. */
.btn-primary.is-busy { background: var(--deep-blue); cursor: progress; }
#btn-export.is-busy::after {
  content: '';
  position: absolute; left: 0; bottom: 0; height: 2px;
  width: var(--p, 0%);
  background: var(--nr-orange);
  transition: width 0.18s linear;
}
.fb {
  position: absolute; width: 7px; height: 7px;
  border-color: var(--nr-orange); border-style: solid;
  transition: all 0.15s;
}
.fb.tl { top: -4px; left: -4px; border-width: 2px 0 0 2px; }
.fb.tr { top: -4px; right: -4px; border-width: 2px 2px 0 0; }
.fb.bl { bottom: -4px; left: -4px; border-width: 0 0 2px 2px; }
.fb.br { bottom: -4px; right: -4px; border-width: 0 2px 2px 0; }
/* secondary topbar action — outlined, so EXPORT PDF stays the primary one */
.btn-ghost {
  position: relative;
  background: transparent;
  color: var(--pale-blue);
  border: 1px solid #24476f;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.18em;
  padding: 9px 16px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.btn-ghost:hover { background: #10233a; color: #fff; border-color: var(--nr-blue); }
.btn-ghost.copied { border-color: var(--nr-orange); color: var(--nr-orange); }
.btn-ghost .fb { opacity: 0; }
.btn-ghost:hover .fb { opacity: 1; }

/* toast — sits above the viewport, never blocks the sidebar controls */
.share-toast {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translate(-50%, 14px);
  max-width: min(560px, calc(100vw - 40px));
  background: #101014;
  border: 1px solid var(--line);
  border-left: 3px solid var(--nr-blue);
  color: var(--gray);
  font-size: 12px;
  line-height: 1.45;
  letter-spacing: 0.02em;
  padding: 11px 16px;
  border-radius: 3px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s, transform 0.22s;
  z-index: 60;
}
.share-toast.show { opacity: 1; transform: translate(-50%, 0); }
.share-toast.warn { border-left-color: var(--amber); }

.btn-primary:hover .fb.tl { top: -6px; left: -6px; }
.btn-primary:hover .fb.tr { top: -6px; right: -6px; }
.btn-primary:hover .fb.bl { bottom: -6px; left: -6px; }
.btn-primary:hover .fb.br { bottom: -6px; right: -6px; }

/* ── layout ──────────────────────────────────────────────────────────────── */
#layout { display: flex; height: calc(100% - 58px); }

#sidebar {
  width: var(--sidebar-w);
  min-width: var(--sidebar-w);
  background: var(--card);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
#sidebar-content { flex: 1; }
#sidebar::-webkit-scrollbar { width: 8px; }
#sidebar::-webkit-scrollbar-thumb { background: #222; border-radius: 4px; }

.sidebar-foot {
  padding: 12px 16px;
  border-top: 1px solid var(--line);
}
.foot-line { font-size: 9px; letter-spacing: 0.12em; color: #808690; font-weight: 500; }

/* panels */
.panel { border-bottom: 1px solid var(--line); }
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 14px 16px 8px;
}
.panel-title {
  color: var(--blue-label);
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.26em;
}
.panel-sub { font-size: 9.5px; color: #808690; font-weight: 600; letter-spacing: 0.12em; }
.panel-body { padding: 4px 16px 16px; }

/* ── A/B rig slots ─────────────────────────────────────────────────────────
   Two stored rigs, sitting above the light cards. Deliberately quieter than a
   light card: no fill until a slot holds something, and the only strong colour
   in the block is the orange rail on the slot that is currently on screen. It
   has to be findable without competing with the thing it is a control for. */
.ab-row { margin: 2px 0 10px; }
.ab-slot {
  display: flex; align-items: stretch; gap: 4px;
  margin-bottom: 4px;
}
.ab-main {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 9px;
  background: #0f0f11;
  border: 1px solid #1d1d20;
  border-left: 3px solid #1d1d20;
  border-radius: 4px;
  padding: 6px 9px;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.ab-main:hover { background: #141416; border-color: #2c2c31; }
.ab-slot.filled .ab-main { border-color: #24242a; }
.ab-slot.live .ab-main { border-left-color: var(--nr-orange); background: #131215; }
.ab-key {
  flex-shrink: 0;
  width: 18px; text-align: center;
  font-size: 13px; font-weight: 700; letter-spacing: 0.04em;
  color: #808690;
}
.ab-slot.filled .ab-key { color: var(--bright-blue); }
.ab-slot.live .ab-key { color: var(--nr-orange); }
.ab-text { min-width: 0; flex: 1; display: block; }
.ab-count {
  display: block;
  font-size: 9px; font-weight: 700; letter-spacing: 0.16em;
  color: #8A9099;
}
.ab-slot.filled .ab-count { color: #c8ccd2; }
.ab-sum {
  display: block;
  font-size: 9.5px; color: #808690; margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ab-mini {
  flex-shrink: 0;
  width: 28px;
  background: #131315;
  border: 1px solid #1d1d20;
  border-radius: 4px;
  color: #8A9099;
  font-size: 12px; line-height: 1;
  cursor: pointer;
}
.ab-mini:hover { color: var(--bright-blue); border-color: var(--nr-blue); }
.ab-mini.ab-clear:hover { color: #e06060; border-color: #7a2020; }
.ab-mini:disabled { opacity: 0.3; cursor: default; }
.ab-mini:disabled:hover { color: #8A9099; border-color: #1d1d20; }
.ab-diff {
  font-size: 9.5px; line-height: 1.45;
  color: var(--pale-blue);
  background: #10161f;
  border: 1px solid #1a2c48;
  border-radius: 4px;
  padding: 6px 9px;
  margin-top: 6px;
}
.ab-diff.same, .ab-diff.hint { color: #8A9099; background: transparent; border-color: transparent; padding: 2px 2px 0; }

/* light cards */
.light-card {
  background: #101011;
  border: 1px solid #1d1d20;
  border-radius: 5px;
  margin-bottom: 8px;
  overflow: hidden;
  transition: border-color 0.15s;
}
.light-card.active { border-color: var(--nr-blue); }
.light-card.disabled { opacity: 0.55; }
.light-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  user-select: none;
}
.light-head:hover { background: #141416; }
.light-power {
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 1px solid #333;
  background: #1a1a1c;
  color: #8A9099;
  font-size: 12px;
  line-height: 1;
  flex-shrink: 0;
}
.light-power.on { color: var(--amber); border-color: #4a3a10; background: #191307; }
.light-info { flex: 1; min-width: 0; }
.light-role { color: #fff; font-weight: 700; font-size: 12px; letter-spacing: 0.08em; }
.light-summary {
  font-size: 9.5px; color: #8A9099; margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gel-dot {
  width: 11px; height: 11px; border-radius: 50%;
  flex-shrink: 0; border: 1px solid #333;
}
.gel-dot.none { background: transparent !important; border-style: dashed; }
.chev { color: #808690; font-size: 10px; flex-shrink: 0; }
.light-controls { padding: 4px 12px 12px; border-top: 1px solid #1a1a1d; }

/* control rows */
.ctl-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 10px;
}
.ctl-row.col { flex-direction: column; align-items: stretch; gap: 5px; }
.ctl-label {
  font-size: 9.5px; font-weight: 600; letter-spacing: 0.16em;
  color: #8a9099; text-transform: uppercase;
  flex-shrink: 0;
}
.ctl-select, .add-light-select {
  background: #17171a;
  color: var(--gray);
  border: 1px solid #26262a;
  border-radius: 4px;
  font-family: inherit;
  font-size: 11px;
  padding: 6px 8px;
  flex: 1;
  min-width: 0;
  max-width: 190px;
}
.ctl-select:focus { outline: none; border-color: var(--nr-blue); }
.mod-note {
  font-size: 9.5px; color: #8A9099; margin-top: 6px; line-height: 1.45;
  padding-left: 8px; border-left: 2px solid var(--deep-blue);
}
.mod-note.warn { color: #a8845a; border-left-color: var(--nr-orange); }
.ctl-hint { font-size: 8.5px; color: #808690; letter-spacing: 0.04em; }

/* ── model download line (ui.js SUBJECT panel, main.js showModelProgress) ──
   Two states in one element: a size before the click, a percentage and a bar
   during the fetch. Empty when every body is already cached, and :empty is
   what stops it reserving a blank row. */
.dl-note {
  font-size: 9px; color: #808690; letter-spacing: 0.04em; line-height: 1.5;
  margin-top: 6px;
}
.dl-note:empty { display: none; }
.dl-note.dl-live { color: #A9CBF5; }
.dl-text { display: block; }
.dl-bar {
  display: block; height: 3px; margin-top: 5px;
  background: #1d1d20; border-radius: 2px; overflow: hidden;
}
.dl-bar i {
  display: block; height: 100%;
  background: var(--nr-blue);
  transition: width 0.15s linear;
}
.phys-readout {
  margin-top: 10px;
  font-size: 10px; font-weight: 600;
  color: var(--pale-blue);
  background: #10161f;
  border: 1px solid #1a2c48;
  border-radius: 4px;
  padding: 6px 9px;
}

/* sliders */
.slider-top { display: flex; justify-content: space-between; align-items: baseline; }
.slider-val { font-size: 10.5px; font-weight: 700; color: var(--bright-blue); letter-spacing: 0.04em; }
.ctl-slider {
  -webkit-appearance: none;
  width: 100%; height: 3px;
  background: #26262a;
  border-radius: 2px;
  outline: none;
}
.ctl-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--nr-blue);
  border: 2px solid #0D0D0D;
  cursor: grab;
  transition: background 0.1s;
}
.ctl-slider::-webkit-slider-thumb:hover { background: var(--bright-blue); }
/* `outline: none` above kills the UA focus ring on every slider in the app,
   which left keyboard users with no idea which one they were on. Put a real
   one back on :focus-visible only, so a mouse drag stays clean. Orange to
   match #link-about and the about page's global rule. */
.ctl-slider:focus-visible,
.vf-dial input[type=range]:focus-visible {
  outline: 2px solid var(--nr-orange);
  outline-offset: 4px;
}
/* Same gap on the two controls that replace their ring with a border tint —
   a 1px border colour change is not a visible focus indicator on a dark UI. */
.ctl-select:focus-visible,
.loc-input:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 2px; }
/* .btn and .view-chip carry no focus style of their own and sit on classes
   that reset the UA ring; give them the same one. */
.btn:focus-visible,
.view-chip:focus-visible,
.vf-crop-chip:focus-visible,
.grid-chip:focus-visible,
.seg-btn:focus-visible,
.gel-swatch:focus-visible,
.backdrop-thumb:focus-visible,
.light-power:focus-visible,
.light-solo:focus-visible,
.panel-head:focus-visible,
.light-head:focus-visible, .light-info:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 2px; }
.ctl-slider::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--nr-blue); border: none; cursor: grab;
}

/* gels */
.gel-swatches { display: flex; flex-wrap: wrap; gap: 5px; justify-content: flex-end; }
.gel-swatch {
  width: 17px; height: 17px;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
}
.gel-swatch.none { border-color: #3a3a3e; background: repeating-linear-gradient(45deg, #222 0 3px, #2e2e32 3px 6px) !important; }
.gel-swatch.sel { border-color: var(--nr-orange); transform: scale(1.15); }

/* honeycomb grids */
.grid-chips { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
.grid-chip {
  font: 600 9.5px/1 'Montserrat', sans-serif;
  letter-spacing: .06em;
  padding: 5px 7px;
  min-width: 26px;
  color: #8b8b93;
  background: #17171a;
  border: 1px solid #2a2a2e;
  border-radius: 3px;
  cursor: pointer;
}
.grid-chip:hover { color: #d6d6da; border-color: #3d3d44; }
.grid-chip.sel {
  color: #fff;
  background: var(--nr-blue);
  border-color: var(--nr-blue);
}
.grid-na {
  flex: 1;
  text-align: right;
  font: 500 9.5px/1.4 'Montserrat', sans-serif;
  color: #8A9099;
  font-style: italic;
}

/* ── segmented ────────────────────────────────────────────────────────────
   A GRID of equal columns, not a wrapping flex row.

   The wrapping row this replaces was added when Uniform grew to four buttons
   ("None Baseball Football Basketball" = 271 px against a 172–218 px control
   column) and the last one hung off the edge. flex-wrap stopped the spill but
   produced the shape the owner reported: three buttons at their natural widths
   and a fourth alone on a second line, hugging the left. Nothing about that
   reads as a decision.

   So the column count is a DECISION, made per control in ui.js:segColumns()
   from the option count and the longest label, and the same at every width —
   a control must not change shape between the desktop sidebar and the phone
   drawer, or "Football" moves under the thumb that learned where it was. Two
   rules keep every row flush:

     • columns are `minmax(min-content, 1fr)` — equal, filling the full width,
       and never narrower than the longest word in the control, so nothing is
       ever clipped or ellipsised;
     • when the item count is not a multiple of the column count, ui.js makes
       the LAST button span the leftover columns, so the final row ends flush
       with the ones above it instead of trailing an orphan.

   Controls with more than two options are given their own line by ui.js
   (`.ctl-row.col`) so those columns are measured against the full panel width
   rather than what is left beside a label. Two-option toggles stay beside
   their label, where they have always fitted.                              */
.segmented {
  display: grid;
  grid-template-columns: repeat(var(--seg-cols, 2), minmax(min-content, 1fr));
  background: #17171a; border-radius: 4px; padding: 2px; gap: 2px;
}
.seg-btn {
  background: transparent;
  border: none;
  color: #8a9099;
  font-size: 10px; font-weight: 600; letter-spacing: 0.06em;
  /* 8px, not 11: the padding is what four wordy options overflow on, and it is
     the cheapest 24 px in the control. Equal columns already give every button
     more air than its text needs. */
  padding: 6px 8px;
  border-radius: 3px;
  min-width: 0;
}
.seg-btn.sel { background: var(--nr-blue); color: #fff; }

/* per-member body chips (mixed group shots)
   Always filled, never "unselected": every chip states who that person IS.
   Colour separates the two bodies at a glance across a five-person row; the
   letter carries the same information for anyone the colours don't reach. */
.body-chip { color: #fff; min-width: 30px; }
.body-chip.is-m { background: var(--nr-blue); }
.body-chip.is-f { background: #b0559a; }

/* uniform colourway chips */
/* One row of ~8 complete schemes replaced the old six rows of twelve swatches
   (72 boxes for one decision). Each chip is a two-tone split — jersey base
   over jersey trim, painted inline as a linear-gradient — which is enough to
   tell "Red & Gold" from "Maroon & White" at a glance; the full six-colour
   scheme lives in KIT_COLORWAYS. Chips are bigger than the old 12px swatches
   because eight per row can afford it and a two-tone split needs the room. */
.kit-colorways {
  display: grid; grid-template-columns: repeat(8, 1fr);
  gap: 4px; flex: 1; min-width: 0;
}
.kit-cw {
  padding: 0; aspect-ratio: 1; min-width: 0;
  border: 1px solid #26262a; border-radius: 3px;
  line-height: 0; cursor: pointer;
}
/* The selected ring is OUTSIDE the chip, not a border on it — a border would
   eat into the chip and change the colours you are trying to judge. Orange
   because two palette entries are NR Blue and Carolina, and a blue ring on a
   blue chip is invisible. */
.kit-cw.sel {
  border-color: var(--nr-orange);
  box-shadow: 0 0 0 2px var(--nr-orange), 0 0 0 3px #0c0c0e;
}
.kit-cw:focus-visible { outline: 2px solid var(--pale-blue); outline-offset: 2px; }
/* MORE COLOURS: the other kits' schemes, under the same chip grid, behind a
   toggle in the START FROM A SETUP row's clothes. */
.kit-more-toggle { margin-top: 6px; }
.kit-colorways-more { margin: 4px 0 10px; }

/* team logo: thumbnail + upload/remove, one column under the picker. The
   thumbnail sits on a checkerboard-free dark tile at a fixed height so a wide
   wordmark and a square crest both read at the same size the crest slot gives
   them (letterboxed, never stretched — the same rule uniforms.js paints by). */
.kit-logo { display: flex; flex-direction: column; gap: 6px; }
.kit-logo-thumb {
  width: 100%; height: 56px; object-fit: contain;
  background: #0c0c0e; border: 1px solid #26262a; border-radius: 3px; padding: 4px;
}

/* backdrop grid */
.backdrop-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
.backdrop-thumb {
  padding: 0; border: 2px solid #26262a; border-radius: 4px;
  background: none; overflow: hidden; aspect-ratio: 1; line-height: 0;
}
.backdrop-thumb.sel { border-color: var(--nr-orange); }
.backdrop-cv { width: 100%; height: 100%; object-fit: cover; }

/* dims */
.dim-inputs { display: flex; align-items: center; gap: 6px; }
.dim-label { font-size: 9px; color: #808690; font-weight: 700; }
.dim-input {
  width: 48px;
  background: #17171a; color: var(--gray);
  border: 1px solid #26262a; border-radius: 4px;
  font-family: inherit; font-size: 11px; padding: 5px 6px;
}

/* generic buttons */
.btn {
  display: block; width: 100%;
  margin-top: 12px;
  background: #17171a;
  border: 1px solid #26262a;
  color: #8a9099;
  font-size: 10px; font-weight: 700; letter-spacing: 0.18em;
  padding: 9px;
  border-radius: 4px;
  /* Named properties rather than `all`: `all` includes outline-width, which
     made the :focus-visible ring animate up from 0 and, measured in Chrome,
     never actually reach 2px. Everything that was being transitioned before
     still is. */
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.btn:hover { border-color: var(--nr-blue); color: var(--bright-blue); }
.btn-add { color: var(--bright-blue); border-color: #1a2c48; }
.btn-remove:hover { border-color: #7a2020; color: #e06060; }
.add-light-wrap { display: flex; gap: 8px; margin-top: 4px; }
.add-light-wrap .btn { margin-top: 0; flex: 1; }
.add-light-wrap .add-light-select { max-width: 90px; }

/* ── viewport ────────────────────────────────────────────────────────────── */
#viewport-wrap { position: relative; flex: 1; min-width: 0; background: #050506; }
#viewport { width: 100%; height: 100%; display: block; touch-action: none; }
#viewport-hint {
  position: absolute; top: 44px; left: 50%; transform: translateX(-50%);
  font-size: 8.5px; letter-spacing: 0.14em; color: #808690; font-weight: 600;
  pointer-events: none;
  text-transform: uppercase;
  white-space: nowrap;
}

/* view chips */
#view-chips {
  position: absolute;
  top: 10px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 4px;
  background: rgba(13, 13, 13, 0.85);
  border: 1px solid #222;
  border-radius: 5px;
  padding: 3px;
  backdrop-filter: blur(4px);
}
.view-chip {
  background: transparent;
  border: none;
  color: #8a9099;
  font-family: inherit;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.14em;
  padding: 5px 10px;
  border-radius: 3px;
  /* Named, not `all` — see the note on .btn: `all` swallows the focus ring. */
  transition: background 0.12s, color 0.12s;
}
.view-chip:hover { color: var(--bright-blue); }
.view-chip.sel { background: var(--nr-blue); color: #fff; }
/* undo / redo, in the same bar: same size as a chip, arrows instead of words */
.bar-sep { width: 1px; align-self: stretch; margin: 2px 3px; background: #2a2d33; }
.bar-btn {
  background: transparent; border: none; color: #8a9099;
  font-family: inherit; font-size: 14px; line-height: 1;
  padding: 3px 8px; border-radius: 3px; cursor: pointer;
  transition: color 0.12s;
}
.bar-btn:hover:not(:disabled) { color: var(--bright-blue); }
.bar-btn:disabled { color: #3a3d43; cursor: default; }
.bar-btn:focus-visible { outline: 2px solid var(--pale-blue); outline-offset: 2px; }

/* ── the setup-views download, on the end of the same row ──────────────────
   Deliberately a different shape from the four chips beside it. Those are a
   radio group — one of them is always `.sel` and pressing one moves the
   camera. This writes a file, so it is outlined rather than filled, it never
   takes the selected state, and a hairline separates it from the group.
   Sized to the chips at every width (44px minimum on phones, in the
   responsive block below), because it sits in the same row and a shorter
   button in a row of tall ones reads as broken rather than as different. */
.view-sep {
  width: 1px; align-self: stretch;
  margin: 2px 3px;
  background: #2c2f34;
}
.view-export {
  position: relative;
  overflow: hidden;
  background: transparent;
  border: 1px solid var(--nr-blue);
  color: var(--bright-blue);
  font-family: inherit;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.14em;
  padding: 4px 9px;
  border-radius: 3px;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.view-export:hover { background: var(--nr-blue); color: #fff; }
.view-export:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 2px; }
/* Busy: the count goes in the label (main.js), the bar fills underneath — the
   same two pieces of information the model downloader shows, at chip size.
   Five full renders is long enough that a button which only greys out reads
   as a page that has stopped responding. */
.view-export.is-busy {
  border-color: var(--nr-orange);
  color: var(--nr-orange);
  background: transparent;
  cursor: progress;
}
.view-export.is-busy::after {
  content: '';
  position: absolute; left: 0; bottom: 0; height: 2px;
  width: var(--p, 0%);
  background: var(--nr-orange);
  transition: width 0.18s linear;
}

/* WebGL2 unavailable — the only state in which the lab shows text instead of
   a render. Deliberately styled like the rest of the app rather than like a
   browser error page: a visitor who lands here should still see whose tool
   this is. */
.gl-unsupported {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: center;
  gap: 16px; padding: 8vh 12vw;
  background: var(--nr-black);
  border-left: 2px solid var(--nr-orange);
}
.gl-unsupported .glu-title {
  font-size: 15px; font-weight: 800; letter-spacing: 0.16em;
  color: #fff;
}
.gl-unsupported .glu-body {
  font-size: 13px; line-height: 1.65; max-width: 52ch; color: #8b8f95;
}

/* The topbar survives that state, so its two actions have to LOOK as dead as
   they are — a full-strength EXPORT PDF button beside a panel saying nothing
   can be rendered is a lie you can click. Only this path ever disables them. */
.btn-primary:disabled, .btn-ghost:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  filter: grayscale(1);
}
.btn-primary:disabled:hover, .btn-ghost:disabled:hover { opacity: 0.35; }

/* vignette — subtle photographic edge falloff */
#vignette {
  position: absolute; inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 130% 115% at 50% 44%, transparent 62%, rgba(0,0,0,0.32) 100%);
}

/* collapsible sections */
.panel-head.collapsible { cursor: pointer; user-select: none; }
.panel-head.collapsible:hover .panel-title { color: var(--bright-blue); }
.panel-chev { display: inline-block; width: 13px; color: #808690; font-size: 9px; }

/* solo */
.light-solo {
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 1px solid #333;
  background: #1a1a1c;
  color: #8A9099;
  font-size: 10px; font-weight: 800;
  line-height: 1;
  flex-shrink: 0;
}
.light-solo.on { color: #090909; background: var(--nr-orange); border-color: var(--nr-orange); }
.light-card.solo { border-color: var(--nr-orange); }
.light-card.muted { opacity: 0.45; }

.stops-chip { color: var(--pale-blue); font-weight: 700; }
.btn-row { display: flex; gap: 8px; }
.btn-row .btn { flex: 1; }
.btn.disabled { opacity: 0.4; cursor: default; }

/* blueprint panel */
#bp-panel {
  position: absolute;
  right: 16px; bottom: 16px;
  width: 356px;
  background: rgba(13, 13, 13, 0.94);
  border: 1px solid #222;
  border-radius: 6px;
  overflow: hidden;
  backdrop-filter: blur(4px);
}
.bp-head {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 8px 11px 6px;
  border-bottom: 1px solid #1c1c1e;
}
.bp-title { color: var(--blue-label); font-size: 9.5px; font-weight: 700; letter-spacing: 0.24em; }
.bp-sub { font-size: 8px; color: #808690; font-weight: 500; letter-spacing: 0.06em; }
#blueprint { width: 354px; height: 330px; display: block; touch-action: none; cursor: crosshair; }

/* presets */
/* ── START FROM A SETUP (top of LIGHTS) ──────────────────────────────────
   The preset names as wrap-around chips: same gate, same PRO tag, no
   descriptions (those live in the PRESETS panel). Quiet outline so the light
   cards below stay the loudest thing in the panel. */
.start-row { margin-bottom: 10px; }
.start-label { font-size: 9px; color: #808690; font-weight: 700; letter-spacing: 0.16em; margin-bottom: 6px; }
/* The label is the collapse toggle: a bare button in the label's own type. */
.start-toggle { display: block; width: 100%; text-align: left; background: none; border: 0; padding: 4px 0; cursor: pointer; }
.start-toggle:hover { color: #fff; }
.start-toggle .panel-chev { display: inline-block; width: 12px; color: var(--nr-blue); }
.start-toggle:focus-visible { outline: 2px solid #FF6D00; outline-offset: 2px; }
.start-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.start-chip {
  position: relative;
  background: transparent; color: #c9d1d9;
  border: 1px solid #262a33; border-radius: 2px;
  font-weight: 600; font-size: 9.5px; letter-spacing: 0.04em;
  padding: 6px 9px; cursor: pointer; line-height: 1;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.start-chip:hover { border-color: var(--nr-blue); color: #fff; background: #12141a; }
.start-chip.pro-gated { color: #8A9099; border-style: dashed; }
.start-chip.pro-gated:hover { border-color: rgba(255, 109, 0, 0.5); }
.start-chip .pro-tag { margin-left: 5px; vertical-align: 0; }
@media (max-width: 640px) {
  .start-chip { padding: 10px 11px; font-size: 11px; }   /* 44px-class tap targets */
}


/* sun */
.sun-box {
  margin-top: 12px;
  border: 1px solid #3a2c08;
  background: #14100a;
  border-radius: 5px;
  padding: 4px 11px 12px;
}
.sun-title {
  color: var(--amber);
  font-size: 10px; font-weight: 700; letter-spacing: 0.22em;
  margin-top: 8px;
}

/* shoot planner */
.planner-box {
  margin-top: 12px;
  border: 1px solid #1a2c48;
  background: #0d1220;
  border-radius: 5px;
  padding: 10px 11px 12px;
}
.planner-title {
  color: var(--bright-blue);
  font-size: 10px; font-weight: 700; letter-spacing: 0.22em;
  margin-bottom: 4px;
}
.loc-wrap { position: relative; margin-top: 8px; }
.loc-input {
  width: 100%;
  background: #17171a; color: var(--gray);
  border: 1px solid #26262a; border-radius: 4px;
  font-family: inherit; font-size: 11.5px;
  padding: 8px 9px;
}
.loc-input:focus { outline: none; border-color: var(--nr-blue); }
.suggest-list {
  position: absolute; top: 100%; left: 0; right: 0;
  z-index: 30;
  background: #131318;
  border: 1px solid #26314a;
  border-radius: 0 0 5px 5px;
  overflow: hidden;
  box-shadow: 0 8px 22px rgba(0,0,0,0.55);
}
.suggest-row {
  display: block; width: 100%;
  text-align: left;
  background: transparent; border: none;
  color: var(--gray);
  font-family: inherit; font-size: 11px;
  padding: 7px 10px;
  cursor: pointer;
}
.suggest-row b { color: #fff; font-weight: 600; }
.suggest-row span { color: #8A9099; font-size: 9.5px; margin-left: 4px; }
.suggest-row:hover { background: #1a2334; }
.planner-row { display: flex; gap: 10px; margin-top: 10px; }
.planner-cell { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.planner-date { width: 100%; color-scheme: dark; }
.planner-face { max-width: none; }
.cond-card {
  margin-top: 10px;
  background: #10161f;
  border: 1px solid #1a2c48;
  border-radius: 5px;
  padding: 10px 11px;
}
.cond-head { color: #fff; font-weight: 700; font-size: 12.5px; letter-spacing: 0.06em; }
.cond-desc { color: #8a94a4; font-size: 9.5px; line-height: 1.45; margin-top: 3px; }
.cond-line { color: var(--pale-blue); font-size: 10px; font-weight: 600; margin-top: 6px; }
.cond-warn {
  color: var(--amber); font-size: 9px; line-height: 1.4; margin-top: 7px;
  padding-left: 7px; border-left: 2px solid #4a3a10;
}

@media (max-width: 1100px) {
  #bp-panel { width: 280px; }
  #blueprint { width: 278px; height: 258px; }
}
@media (max-width: 900px) {
  :root { --sidebar-w: 270px; }
  #bp-panel { width: 210px; }
  #blueprint { width: 208px; height: 196px; }
}

/* viewfinder panel — mirror of the blueprint, bottom-left */
#vf-panel {
  position: absolute;
  left: 16px; bottom: 16px;
  width: 336px;
  border: 1px solid #222;
  border-radius: 6px;
  overflow: hidden;
  /* NO backdrop-filter here — the screen area is a transparent window onto
     the live canvas render; a filter would gaussian-blur the viewfinder */
}
.vf-head {
  background: rgba(13, 13, 13, 0.94);
  gap: 8px;
}
.vf-cam-note {
  font-size: 9px; letter-spacing: 0.08em; color: #808690;
  text-transform: uppercase; margin-left: auto;
}
.vf-collapse {
  background: none; border: none; color: #9a9ca3; cursor: pointer;
  font-size: 11px; padding: 0 2px; margin-left: auto;
}
.vf-collapse:hover { color: #fff; }
#vf-screen {
  position: relative;
  width: 100%; height: 224px;
  /* transparent — the 3D viewfinder render shows through from the canvas */
  border-top: 1px solid #1c1c1f;
  border-bottom: 1px solid #1c1c1f;
}
#vf-crops {
  position: absolute; top: 6px; right: 6px; display: flex; gap: 4px; z-index: 2;
}
.vf-crop-chip {
  background: rgba(9, 9, 9, 0.72);
  border: 1px solid #2a2a2e; border-radius: 4px;
  color: #b9bbc2; font-size: 9px; font-weight: 700; letter-spacing: 0.1em;
  padding: 3px 7px; cursor: pointer;
}
.vf-crop-chip.sel { color: #fff; border-color: var(--nr-blue); background: rgba(26, 110, 232, 0.28); }
#vf-sync {
  display: none;
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  background: rgba(255, 109, 0, 0.82);
  color: #090909; font-size: 9.5px; font-weight: 800; letter-spacing: 0.1em;
  text-align: center; padding: 3px 0;
}
#vf-dials { background: rgba(13, 13, 13, 0.94); padding: 8px 11px 10px; }
.vf-dial { display: flex; align-items: center; gap: 9px; margin-top: 6px; }
.vf-dial:first-child { margin-top: 0; }
.vf-dial-label {
  width: 26px; font-size: 10px; font-weight: 800; letter-spacing: 0.06em; color: #8a8c93;
}
/* The three dials used to be bare <input type=range> and drew the browser's
   default control: a white pill track and a chrome thumb, on a #0d0d0d
   instrument panel. Every other slider in the app is already restyled — this
   just gives the dials the same track, thumb and hover that .ctl-slider has,
   rather than a second look for the same widget. Deliberately NOT merged into
   the .ctl-slider selector: these live at a different size inside a panel that
   floats over the render, and the phone CAMERA rows are real .ctl-sliders. */
.vf-dial input[type=range] {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 3px;
  background: #2c2c31;
  border-radius: 2px;
  outline: none;
  margin: 0;
}
.vf-dial input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--nr-blue);
  border: 2px solid #0d0d0d;
  cursor: grab;
  transition: background 0.1s;
}
.vf-dial input[type=range]::-webkit-slider-thumb:hover { background: var(--bright-blue); }
.vf-dial input[type=range]::-moz-range-track {
  height: 3px; background: #2c2c31; border-radius: 2px; border: none;
}
.vf-dial input[type=range]::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--nr-blue); border: none; cursor: grab;
}
.vf-dial-value {
  width: 52px; text-align: right; font-size: 11px; font-weight: 700; color: #e8e9ec;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 1100px) {
  #vf-panel { width: 262px; }
  #vf-screen { height: 174px; }
}
@media (max-width: 900px) {
  #vf-panel { width: 208px; }
  #vf-screen { height: 138px; }
}

/* ── CAMERA rows in the sidebar (ui.js:buildExposureRows) ──────────────────
   The phone's exposure control. Hidden by default and shown only where
   #vf-panel is not — see the two `display: block` rules further down, which
   are deliberately paired with the two `#vf-panel { display: none }` rules so
   the app can never be in a state with no exposure control at all.

   Styled as a bordered block rather than three loose rows because it is a
   different instrument from the scene settings around it: these three numbers
   are the camera, and everything else in SCENE is the room. */
.vf-mirror {
  display: none;
  border: 1px solid #23232a;
  border-radius: 5px;
  padding: 8px 11px 11px;
  margin: 2px 0 4px;
  background: #0c0c0e;
}
.vf-mirror-title {
  color: var(--blue-label);
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.24em;
}
.vf-mirror .ctl-row { margin-top: 8px; }
.vf-mirror .mod-note { display: none; }

/* footer version tag */
.foot-version { color: #808690; font-size: 9px; letter-spacing: 0.06em; }

/* ─────────────────────────────────────────────────────────────────────────────
   PHONE / SMALL TABLET — the sidebar stops being a column
   ─────────────────────────────────────────────────────────────────────────────
   Above 820px nothing below this line applies and the desktop layout is byte
   for byte what it was: a fixed sidebar beside a flex viewport.

   Below it the whole app becomes ONE vertical scroller (#layout), stacked:

       ┌───────────────┐  topbar (58px, never wraps)
       ├───────────────┤
       │               │  #viewport-wrap — full width, height = screen − dock
       │   3D VIEW     │  − peek. The canvas is still the wrap's ONLY in-flow
       │               │  child at 100%×100%, which is load-bearing: main.js
       ├───────────────┤  sizes the drawing buffer from wrap.clientWidth/Height,
       │  BLUEPRINT    │  so any in-flow sibling here would desync the backing
       ├───────────────┤  store from the CSS box and squash the render.
       │  LIGHTS   ▾   │  ← peek: the drawer handle you drag up
       └───────────────┘

   The blueprint therefore does NOT go in flow. It stays absolutely positioned
   inside the wrap but is pushed to `top: 100%` — just past the canvas — and the
   wrap reserves exactly its height with margin-bottom. Margins do not count
   toward clientHeight, so the canvas keeps matching its buffer while the panel
   lands in a gap of its own making, docked below the 3D view instead of on top
   of the subject.

   Scrolling down from the peek slides the controls up over the viewport: a
   drawer, made of nothing but document flow. It needs no toggle button, which
   is the point — index.html and ui.js are owned by someone else this pass.

   ── WHY THE QUERY HAS TWO ARMS ────────────────────────────────────────────
   The second arm is the landscape-phone band. It used to be a separate query
   further down that changed the LAYOUT only, which left an 821-950px hole:
   a phone held sideways got the phone layout and desktop-sized controls.
   iPhone 14/15/16 Pro Max is 852-956 CSS px in landscape, so the hole was
   the mainstream device, not a corner.

   Measured at 844x390 before the change: .ctl-slider 189x3, .gel-swatch
   17x17, .grid-chip 32x22, .light-solo 24x24, EXPORT PDF 130x32 — 26 of 31
   interactive elements under 44px in at least one dimension.

   The arms are a union, not a widening: at 821-950px in PORTRAIT (a narrow
   desktop window, a tablet) nothing here applies and the desktop layout is
   still byte for byte what it was. The landscape arm's own query, further
   down, still comes AFTER this block and still overrides every layout rule
   in it (#layout, #viewport-wrap, #sidebar, #bp-panel, #vf-panel,
   #viewport-hint), so what the landscape band picks up from here is exactly
   the touch sizing and the one-line topbar, which is what it wanted. */
@media (max-width: 820px),
       (max-width: 950px) and (max-height: 540px) and (orientation: landscape) {
  /* Height of the docked blueprint. The plan is drawn at min(w,h)/extent, so on
     a wider screen a short dock just means a smaller plan floating in empty
     canvas — a tablet gets a deeper one. */
  :root { --bp-dock: 196px; }

  /* ── topbar: one line, always, at any width down to 320 ── */
  #topbar {
    padding: 0 10px;
    gap: 8px;
    overflow: hidden;          /* the brand truncates before the page scrolls */
  }
  .brand { gap: 8px; min-width: 0; }
  .brand-text { min-width: 0; overflow: hidden; }
  .brand-sub { display: none; }
  .brand-name { font-size: 11px; letter-spacing: 0.08em; white-space: nowrap; }
  /* ── the wordmark DEGRADES here, it does not get sawn off ────────────────
     The topbar's right-hand block (caret + COPY LINK + EXPORT PDF) is fixed
     width and the brand gets what is left. Measured in the fallback stack:
     the h1 wants 89px and the box it is given is 19px at 320, 59 at 360, 74 at
     375 and 89 at 390 — so the full wordmark rendered as "LIG" and
     "LIGHTING LA", with `overflow: hidden` on .brand-text doing the sawing.
     Montserrat is wider than the fallback, so even 390's exact fit is not one.

     A truncated product name reads as a broken page, so it is not shown
     truncated at all. Below 460px — the point at which there is a third more
     room than the long wordmark asks for, which is the headroom the webfont
     needs — the mark shortens to LAB, which is a mark rather than a fragment.
     Below 360px not even that fits beside the two buttons, and the NR bracket
     device (which is the logo, brackets and all) carries the brand on its own.

     Both tiers keep the <h1> text in the accessible tree — the long form is
     clipped, never `display: none`, and the short form is aria-hidden — so the
     heading is still "LIGHTING LAB" to a screen reader at 320px. */
  .bn-full {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .bn-short { display: inline; }
  .nr-mark { width: 32px; height: 32px; flex-shrink: 0; }
  .nr-mark img { width: 32px; height: 32px; }
  .topbar-right { gap: 8px; flex-shrink: 0; }
  .live-tag { font-size: 9px; padding: 4px 7px; letter-spacing: 0.12em; }
  /* 44px tall, not 32: these are the two actions you cannot reach any other way */
  .btn-primary, .btn-ghost {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px;
    padding: 0 12px;
    font-size: 9.5px;
    letter-spacing: 0.06em;
    white-space: nowrap;
  }
  .btn-ghost .fb { opacity: 1; }   /* corner brackets are not hover-only here */

  /* ── the column ── */
  #layout {
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior-y: contain;
    -webkit-overflow-scrolling: touch;
  }
  #viewport-wrap {
    order: 1;
    flex: 0 0 auto;
    /* blueprint dock + the 44px of sidebar that peeks below it as a handle */
    height: calc(100% - var(--bp-dock) - 44px);
    margin-bottom: var(--bp-dock);
  }
  #sidebar {
    order: 2;
    width: 100%;
    min-width: 0;
    flex: 0 0 auto;
    min-height: 100%;
    overflow: visible;         /* #layout owns the scrolling now */
    border-right: none;
    border-top: 1px solid var(--line);
  }

  /* ── blueprint: docked under the 3D view, full width, never over the subject ── */
  #bp-panel {
    position: absolute;
    top: 100%; right: auto; bottom: auto; left: 0;
    width: 100%; height: var(--bp-dock);
    display: flex; flex-direction: column;
    border-radius: 0;
    border-width: 1px 0 0 0;
    background: #0d0d0d;
    backdrop-filter: none;
  }
  .bp-head { padding: 9px 14px 7px; }
  .bp-title { font-size: 10px; }
  #blueprint { width: 100%; height: auto; flex: 1 1 auto; min-height: 0; }

  /* ── viewport furniture ── */
  #view-chips { top: 8px; padding: 2px; }
  .view-chip {
    display: inline-flex; align-items: center; justify-content: center;
    /* min-width as well as min-height: L / R / TOP are one and two characters,
       so padding alone left them 37.5px wide — tall enough and too narrow. */
    min-width: 44px; min-height: 44px; padding: 0 15px; font-size: 10px;
  }
  /* The sheet button is in that row and is a real target too. 12px of side
     padding rather than 15: "PNG" is three characters and the row has to stay
     inside a 320px viewport — measured, FRONT + L + R + TOP + separator + this
     comes to 274px there. */
  .view-export {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; padding: 0 12px; font-size: 10px;
  }
  /* The desktop hint names a mouse wheel and six keyboard shortcuts, none of
     which exist here. Same element, honest text, no DOM edit. */
  /* ── ONE LINE, at every phone width ─────────────────────────────────────
     This is a caption floating on the render with no plate behind it, so a
     second line is not a second line — it is a word painted on a moving 3D
     image in grey. Measured, the old string wanted 329px: at 320 the box is
     94% = 301px, so it wrapped and left "BELOW" sitting on the subject's head;
     at 360-390 the box is 330px and it fitted by 1.3px in the fallback stack,
     which Montserrat would have taken back.

     Fixed by making it FIT rather than by letting it wrap: one clause fewer
     (tapping a light is discoverable, "controls below" is the one thing a
     phone user cannot see for themselves and has to be told), and the tracking
     comes down from 0.14em to 0.08em — it is 8.5px type, and 1.19px of letter
     spacing was costing 63px of the line on its own. That is 209px of the
     301px available at 320: 30% of headroom for a wider webfont. `nowrap` is
     the guarantee that it can never wrap again, and the box shrinks to the
     text so a centred nowrap line cannot overflow to one side. */
  #viewport-hint {
    top: 64px;
    font-size: 0;
    white-space: nowrap;
    width: auto;
    max-width: 96%;
    text-align: center;
  }
  #viewport-hint::after {
    content: 'drag orbit · pinch zoom · controls below';
    font-size: 8.5px;
    letter-spacing: 0.08em;
  }

  /* ── viewfinder: keep it on a tablet, where there is room beside the render ── */
  #vf-panel { left: 10px; bottom: 10px; width: 246px; }
  #vf-screen { height: 158px; }
  .vf-collapse {
    min-width: 44px; min-height: 44px;
    margin: -12px -10px -12px auto;
    font-size: 13px;
  }
  /* Same trick as .ctl-slider below: now that the dial has appearance:none, a
     32px height would paint a 32px slab of track. Keep the hairline as a
     painted background and let the ELEMENT be the tall thing. */
  .vf-dial input[type=range] {
    height: 44px;
    background: linear-gradient(#2c2c31, #2c2c31) center / 100% 3px no-repeat;
    border-radius: 0;
  }
  .vf-dial input[type=range]::-webkit-slider-thumb { width: 20px; height: 20px; }
  .vf-dial input[type=range]::-moz-range-thumb { width: 20px; height: 20px; }
  .vf-dial input[type=range]::-moz-range-track { background: none; }

  /* ── touch-sized controls (nothing below 44px you have to hit) ── */
  .panel-head { padding: 16px 16px 12px; min-height: 52px; align-items: center; }
  .panel-title { font-size: 11.5px; }
  .panel-body { padding: 4px 16px 18px; }
  /* A/B: three targets in a row, all of which have to clear 44px. The row is
     full width, so the two 44px minis still leave ~180px of summary. */
  .ab-main { min-height: 52px; padding: 8px 10px; }
  .ab-mini { min-width: 44px; min-height: 52px; font-size: 15px; }
  .ab-count { font-size: 10px; }
  .ab-key { font-size: 15px; width: 20px; }
  .ab-diff { font-size: 10.5px; }
  .light-head { padding: 13px 12px; min-height: 64px; }
  .light-power, .light-solo { width: 44px; height: 44px; font-size: 15px; }
  .light-role { font-size: 13px; }
  /* ── the two summary lines wrap here instead of ellipsising ──────────────
     Both are `white-space: nowrap; text-overflow: ellipsis` on the desktop,
     which is right in a 316px sidebar with nothing else on the line. In the
     phone layouts the line has lost 88px to two 44px buttons before it starts,
     and in the 268px landscape sidebar what survived was measured at 31px of a
     127px string ("400Ws · 1/2 · Sof…") and 66px of a 115px one. An ellipsis
     that eats three quarters of the sentence is not a summary, it is a hint
     that there was one. Two lines cost 13px of card height and say the whole
     thing; .light-head has 64px of min-height to absorb it. */
  .light-summary, .ab-sum {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.35;
  }
  .ab-sum { font-size: 10.5px; }
  .light-summary { font-size: 10.5px; }
  .chev { font-size: 13px; padding: 6px; }
  /* A 3px-tall range input is a 3px-tall target. Keep the hairline track as a
     painted background and give the element a real 26px of height to grab. */
  .ctl-slider {
    height: 44px;
    background: linear-gradient(#26262a, #26262a) center / 100% 3px no-repeat;
    border-radius: 0;
  }
  .ctl-slider::-webkit-slider-thumb { width: 22px; height: 22px; }
  .ctl-slider::-moz-range-thumb { width: 22px; height: 22px; }
  .ctl-select, .add-light-select {
    min-height: 44px; font-size: 12.5px; padding: 8px 10px; max-width: 210px;
  }
  .add-light-wrap .add-light-select { max-width: 104px; }
  .dim-input { min-height: 44px; width: 60px; font-size: 12.5px; }
  .loc-input { min-height: 44px; font-size: 12.5px; }
  .planner-date { min-height: 44px; }
  .suggest-row { min-height: 44px; padding: 12px 10px; font-size: 12px; }
  .btn { min-height: 44px; padding: 13px 9px; font-size: 10.5px; }
  .seg-btn, .grid-chip, .vf-crop-chip {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 0 14px;
  }
  /* 8px of side padding, matching the desktop rule and for the same reason:
     the 44px min-height above is what makes these a legal tap target, and the
     horizontal padding is free to go once the columns are equal and full
     width. The 14px above still applies to .grid-chip and .vf-crop-chip,
     which are content-sized chips rather than grid columns. */
  /* min-width, because a two-option segmented is CONTENT sized. It sits in a
     plain .ctl-row beside its label rather than on its own line, so its grid
     is as wide as its widest word plus padding and no wider — "Show | Hide"
     measured 43.7px per button at every width from 320 to 932, which is 44
     minus a rounding error and still a fail. The columns are
     minmax(min-content, 1fr), so a floor on the button is a floor on the
     column; five of them still fit the 268px landscape sidebar (5x44 = 220 in
     232 of grid). */
  .seg-btn { font-size: 11px; padding: 0 8px; min-width: 44px; }
  .grid-chip { min-width: 46px; font-size: 10.5px; }
  /* A 30px dot is the right SIZE for a gel and the wrong TARGET for a thumb.
     The circle stays 30px; an invisible ::after grows the hit area to a full
     46×46 square. `inset` resolves against the PADDING box, so -10px on a
     2px-bordered 30px dot reaches 46px of border box — measured, not assumed
     (test/mobile-touch.test.mjs probes it with elementFromPoint). It is square
     rather than round because a circle's hit area shrinks off-axis, and the
     16px gap is what keeps neighbouring targets from overlapping: 30 + 16 = 46
     = the tile pitch exactly. */
  .gel-swatches { gap: 16px; }
  .gel-swatch { width: 30px; height: 30px; position: relative; }
  .gel-swatch::after { content: ''; position: absolute; inset: -10px; }
  .ctl-label { font-size: 10.5px; }
  .ctl-row { margin-top: 12px; }
  .backdrop-grid { grid-template-columns: repeat(5, 1fr); gap: 8px; }
  .kit-colorways { grid-template-columns: repeat(6, 1fr); gap: 6px; }
  .sidebar-foot { padding: 16px; }
}

/* ── the wordmark's two thresholds ─────────────────────────────────────────
   Above 460px the topbar has room for the long wordmark again (a landscape
   phone at 844 or 932 is here too, which is why this is not bounded above).
   Restated rather than scoped, because the block above has already swapped the
   two spans and this is what swaps them back. */
@media (min-width: 460px) {
  .bn-full {
    position: static; width: auto; height: auto;
    margin: 0; overflow: visible; clip-path: none;
  }
  .bn-short { display: none; }
}
/* …and below 360 the short mark does not fit either: the NR bracket device is
   the whole lockup. The <h1> keeps its text for a screen reader — clipped, not
   removed, so the document still has a heading. */
@media (max-width: 359px) {
  .bn-short {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .brand { gap: 0; }
}

/* Phones proper. The viewfinder is a 208px instrument panel that floats ON the
   render; at 390px wide it would cover a third of the only view of the subject,
   and the blueprint — which you actually drag — has already claimed the dock.
   Hidden, not shrunk: vfDescriptor() measures its screen element and returns
   null at zero width, so the engine simply skips the pass. */
/* Small tablets: the render is already wide enough that a 196px dock wastes
   half the plan's canvas, and the viewfinder's chips need finger-sized hits. */
@media (min-width: 641px) and (max-width: 820px) {
  :root { --bp-dock: 292px; }
  .vf-crop-chip { font-size: 10px; }
}

@media (max-width: 640px) {
  #vf-panel { display: none; }
  /* …so the sidebar grows the exposure controls instead. These two lines are
     a pair: whatever hides the viewfinder must show the CAMERA rows. */
  .vf-mirror { display: block; }
  #light-count { display: none; }
  .backdrop-grid { grid-template-columns: repeat(4, 1fr); }
}

/* Landscape phones: 390px of height cannot afford a stacked column — a dock
   plus a peek would leave ~100px of 3D view. Go back to side-by-side with a
   narrow sidebar and drop both floating instrument panels instead. Bounded by
   max-height so no desktop window ever reaches it at 920px tall. */
@media (max-width: 950px) and (max-height: 540px) and (orientation: landscape) {
  #layout { flex-direction: row; overflow: hidden; }
  #viewport-wrap { order: 0; height: 100%; margin-bottom: 0; flex: 1 1 auto; }
  #sidebar {
    order: 0;
    /* 268, not 248. The widest thing in this column is a five-option segmented
       control (Group size, 1-5), and its columns are the only target here whose
       width is set by division rather than by a min-width. At 248 the sum was
       248 − 32 sidebar padding − 4 .segmented padding − 8 of gaps = 204/5 =
       40.8px: tall enough at 44 and 3px too narrow. 268 makes it 44.8. The
       twenty pixels come out of a 576px render at 844 wide, which is the
       cheaper of the two things to spend them on. */
    width: 268px; min-width: 268px;
    min-height: 0;
    flex: 0 0 auto;
    overflow-y: auto;
    border-right: 1px solid var(--line);
    border-top: none;
  }
  #bp-panel, #vf-panel, #viewport-hint { display: none; }
  .vf-mirror { display: block; }   /* the pair — see the ≤640px block above */

  /* ── the narrow sidebar's own column arithmetic ─────────────────────────
     Everything above inherits the phone touch sizing from the two-arm query,
     but two GRIDS are sized in columns rather than pixels, and a column count
     that gives a 44px cell across a full-width drawer gives a 31px one here.
     268 − 32 of panel padding = 236px of content:
       backdrop / floor  repeat(5) gap 8 → (236−32)/5 = 40.8   FAIL
                         repeat(4) gap 8 → (236−24)/4 = 53.0   ok
       colourways        repeat(6) gap 6 → (236−30)/6 = 34.3   FAIL
                         repeat(4) gap 6 → (236−18)/4 = 54.5   ok
     Both are square (aspect-ratio: 1), so the column width IS the target. */
  .backdrop-grid { grid-template-columns: repeat(4, 1fr); }
  .kit-colorways { grid-template-columns: repeat(4, 1fr); }
  /* Room W/D: label + two 60px boxes + two unit letters do not fit on one
     236px line, and flex would shrink the boxes below a thumb. Stack it. */
  .ctl-row.room-dims { flex-direction: column; align-items: stretch; gap: 6px; }
  .ctl-row.room-dims .dim-inputs { justify-content: flex-start; }

  /* The light card header, on two rows.
     Once the power and solo buttons are 44px each, a 268px card leaves the
     name and summary about 90px of line — which turns "400Ws · 1/2 · Softbox ·
     6.0'" into four lines beside two round buttons. Wrapping the info block
     onto its own row gives it the full 236px and two lines, and costs nothing:
     the row it leaves behind is exactly the two buttons and the gel dot, which
     is the row a thumb aims at anyway. `order` rather than DOM position so the
     tab order and the reading order stay power, solo, name, summary. */
  .light-head { flex-wrap: wrap; row-gap: 4px; }
  .light-info { flex: 1 0 100%; order: 3; }
  .light-head .gel-dot { margin-left: auto; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   PREFERS-REDUCED-MOTION
   ─────────────────────────────────────────────────────────────────────────────
   Nothing here loops or auto-plays — the 3D view only redraws when something
   changes, which is motion the user asked for and is the content itself, so it
   is deliberately left alone. What IS unrequested is the chrome: a dozen
   `transition: all` rules on buttons, chips and swatches, plus the toast's
   slide. Those are decoration, and on a machine whose owner has asked for less
   motion they should simply not animate.

   Two side effects worth knowing about, both wanted:
     • the focus ring stops fading in over 120-150 ms and appears at once,
       which is strictly better for the people most likely to have this on;
     • `transition: none` also removes the colour cross-fades, so hover and
       selected states snap. That is the point.
   ───────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* ─────────────────────────────────────────────────────────────────────────────
   PRINT
   ─────────────────────────────────────────────────────────────────────────────
   What this replaced: one blank page. Measured before the change (see
   test/fx/print-before-app.pdf): with the browser default of backgrounds off,
   #090909 prints as white, so "EXPORT PDF" printed white-on-white; the WebGL
   canvas printed as an empty rectangle, because a canvas's drawing buffer is
   not part of the print snapshot unless it is preserved and repainted; and the
   316px sidebar sat off the right edge of a portrait sheet.

   The fix is not to print the app better. The 3D view IS the content and it
   does not exist on paper — but the app already writes a real document, and it
   is one button away. So: hide the interface, print the instruction.

   Everything here is inside @media print, so not one byte of it can reach the
   screen. #print-note is display:none at every screen width (below), which is
   why it can sit in the markup unconditionally.
   ───────────────────────────────────────────────────────────────────────── */
#print-note { display: none; }

@media print {
  /* `overflow: hidden` on html/body is what confines the app to one viewport
     on screen. On paper it is what guarantees exactly one page. */
  html, body {
    height: auto;
    overflow: visible;
    background: #fff;
    color: #111;
    font-size: 11pt;
  }
  /* The whole interface. Listed rather than done with a blanket
     `body > * { display: none }` so that anything added to the page later
     fails loudly (prints) instead of silently (vanishes). */
  #topbar, #layout, .share-toast, .skip { display: none !important; }

  #print-note {
    display: block;
    max-width: 6.2in;
    padding: 0;
    /* Print colours are stated in full rather than inherited: a UA that does
       honour backgrounds must not put light grey body text on white. */
    color: #111;
  }
  .pn-brand {
    font-size: 9pt; font-weight: 800; letter-spacing: 0.24em;
    color: #111; margin-bottom: 22pt;
  }
  .pn-title {
    font-size: 19pt; font-weight: 800; line-height: 1.25;
    color: #000; margin-bottom: 14pt;
    /* No orphaned heading: a title must not be the last thing on a sheet. */
    break-after: avoid; page-break-after: avoid;
  }
  .pn-body {
    font-size: 11pt; line-height: 1.55; color: #111;
    margin-bottom: 10pt; max-width: 62ch;
    orphans: 3; widows: 3;
  }
  .pn-body strong { font-weight: 800; }
  .pn-link {
    font-size: 9.5pt; letter-spacing: 0.04em; color: #333;
    margin-top: 4pt;
    /* A share URL is one unbroken 300-character token. Without this it runs
       off the right edge of the sheet and the tail is simply not printed. */
    overflow-wrap: anywhere;
    word-break: break-all;
  }
  #print-share:empty { display: none; }
  @page { margin: 0.85in; }
}

/* ═══ v2.0 — Free/Pro (js/gate.js) ═══════════════════════════════════════════
   One rule the whole tier system hangs on: Pro features stay VISIBLE, wearing
   one small tag, and open the upgrade modal when clicked. Nothing is hidden,
   nothing is clamped, and nothing here touches a rendered pixel of the scene.
   NR brand: #090909 canvas, blue leads, orange punctuates — the tag is the
   one place orange is spent, so it reads as the same accent everywhere. */

/* ── the PRO tag ── */
.pro-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 4px 1px;
  font-size: 7.5px; font-weight: 800; line-height: 1;
  letter-spacing: 0.14em;
  color: var(--nr-orange);
  background: #090909;
  border: 1px solid rgba(255, 109, 0, 0.75);
  border-radius: 2px;
  vertical-align: 1px;
  pointer-events: none;   /* the tag never steals the click from its control */
}
/* On the blue EXPORT PDF button the dark pill keeps the orange legible. */
#btn-export .pro-tag { margin-left: 7px; vertical-align: 0; }

/* ── gated controls keep their shape; the ring says why they will not fire ── */
.gel-swatch.pro-gated {
  opacity: 0.5;
  border-style: dashed;
  border-color: rgba(255, 109, 0, 0.55);
}
.gel-swatch.sel { opacity: 1; }   /* a pro gel that ARRIVED in the state still reads selected */
.grid-chip.pro-gated {
  color: #9A9EA8;   /* 6.1:1 on the chip; #6f6f77 measured 3.6:1 */
  border-style: dashed;
  border-color: rgba(255, 109, 0, 0.45);
}
.backdrop-thumb { position: relative; }
.backdrop-thumb.pro-gated::after {
  content: 'PRO';
  position: absolute; right: 3px; bottom: 3px;
  padding: 2px 3px 1px;
  font-size: 7px; font-weight: 800; line-height: 1; letter-spacing: 0.12em;
  color: var(--nr-orange);
  background: rgba(9, 9, 9, 0.85);
  border: 1px solid rgba(255, 109, 0, 0.75);
  border-radius: 2px;
}
.seg-btn.pro-gated { color: #9A9EA8; }
.seg-btn.pro-gated .pro-tag { margin-left: 5px; }
.planner-gated .planner-gated-btn { margin-top: 8px; }

/* ── the modal (upgrade / sign-in / account share one overlay) ── */
#nr-modal-overlay {
  position: fixed; inset: 0;
  display: none;
  align-items: center; justify-content: center;
  background: rgba(4, 5, 8, 0.74);
  z-index: 300;
  padding: 16px;
}
#nr-modal-overlay.open { display: flex; }
.nr-modal {
  background: #0D0D0D;
  border: 1px solid #23232a;
  border-top: 2px solid var(--nr-blue);
  border-radius: 6px;
  width: min(430px, 100%);
  max-height: calc(100vh - 32px);
  overflow: auto;
  padding: 20px 20px 16px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.65);
}
.up-context {
  font-size: 10px; font-weight: 600; letter-spacing: 0.05em;
  color: var(--nr-orange);
  margin-bottom: 8px;
}
.up-headline {
  color: #fff;
  font-size: 16.5px; font-weight: 800; letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 0 0 12px;
}
.up-features {
  list-style: none;
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px 14px;
  margin: 0 0 16px; padding: 0;
}
.up-features li {
  position: relative;
  padding-left: 13px;
  font-size: 10.5px; line-height: 1.35; color: #c8c8c8;
}
.up-features li::before {
  content: '';
  position: absolute; left: 0; top: 4.5px;
  width: 5px; height: 5px;
  background: var(--nr-blue);
}
.up-prices { display: flex; gap: 8px; margin-bottom: 6px; }
.up-price {
  flex: 1;
  display: flex; flex-direction: column; gap: 3px;
  align-items: center; justify-content: center;
  min-height: 48px; padding: 8px 4px;
  background: #101014;
  border: 1px solid #26262c; border-radius: 4px;
  color: #c8c8c8;
  transition: border-color 0.15s, background 0.15s;
}
.up-price:hover { border-color: var(--nr-blue); }
.up-price.sel { border-color: var(--nr-blue); background: #0f1a2c; color: #fff; }
.up-price:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 2px; }
.up-price-amt { font-size: 12.5px; font-weight: 800; letter-spacing: 0.02em; }
.up-price-sub {
  font-size: 8px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: #8A9099; text-align: center;
}
.up-price.sel .up-price-sub { color: var(--pale-blue); }
.up-status { min-height: 15px; margin: 2px 0 8px; font-size: 10px; line-height: 1.4; color: #8A9099; }
.up-status.warn { color: var(--amber); }
.up-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.up-actions .up-go { flex: 1.4; min-height: 44px; min-width: 160px; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; box-sizing: border-box; }
.up-actions .up-later { flex: 1; min-height: 44px; margin-top: 0; width: auto; display: inline-flex; align-items: center; justify-content: center; }
.up-go:focus-visible, .up-later:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 2px; }
/* Account dialog: MANAGE ACCOUNT on its own full-width row, the two
   secondary actions beneath it. */
.account-modal .up-actions .up-go { flex: 1 1 100%; order: -1; white-space: nowrap; }
.account-modal .up-actions .up-later { flex: 1 1 0; }
.up-signin-note { margin-top: 12px; font-size: 9.5px; color: #808690; }

/* sign-in */
.si-form { display: flex; flex-direction: column; gap: 9px; }
.si-input {
  min-height: 44px;
  background: #101014;
  border: 1px solid #26262c; border-radius: 4px;
  color: #fff;
  font-family: inherit; font-size: 12px;
  padding: 10px 12px;
}
.si-input::placeholder { color: #6f757e; }
.si-input:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 1px; }

@media print {
  #nr-modal-overlay { display: none !important; }
}

/* ═════════════════════════════════════════════════════════════════════════
   v2.0 — THE SETUP LIBRARY + CLOUD SYNC (js/library.js)
   Local saves for everyone; cloud rows for Pro. Same NR palette: blue leads,
   orange punctuates, nothing shouts. The panel frame itself is the standard
   .panel that section() builds — only the body's contents live here. */

/* ── save bar ── */
.lib-save-row { display: flex; gap: 6px; margin-bottom: 10px; }
.lib-input {
  flex: 1; min-width: 0;
  min-height: 34px;
  background: #101014;
  border: 1px solid #26262c; border-radius: 4px;
  color: #fff;
  font-family: inherit; font-size: 11px;
  padding: 6px 9px;
}
.lib-input::placeholder { color: #6f757e; }
.lib-input:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 1px; }
.lib-btn {
  min-height: 34px; padding: 0 10px;
  background: #101014;
  border: 1px solid var(--nr-blue); border-radius: 4px;
  color: var(--pale-blue);
  font-family: inherit; font-size: 9.5px; font-weight: 800; letter-spacing: 0.1em;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.lib-btn:hover { background: var(--nr-blue); color: #fff; }
.lib-btn:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 2px; }

/* ── the list ── */
.lib-list { display: flex; flex-direction: column; gap: 5px; }
.lib-row {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px;
  background: #101014;
  border: 1px solid #1d1d23; border-radius: 4px;
}
.lib-row.active { border-color: var(--nr-blue); }
.lib-row.cloud .lib-meta::before {
  content: '';
  display: inline-block; width: 6px; height: 6px; margin-right: 5px;
  border-radius: 50%;
  background: var(--nr-blue);
  vertical-align: 1px;
}
.lib-info { flex: 1; min-width: 0; }
.lib-name {
  font-size: 11px; font-weight: 600; color: #e8e8e8;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lib-meta { margin-top: 2px; font-size: 8.5px; letter-spacing: 0.04em; color: #8A9099; }
.lib-actions { display: flex; gap: 4px; flex-shrink: 0; }
.lib-act {
  min-height: 26px; padding: 0 6px;
  background: transparent;
  border: 1px solid #26262c; border-radius: 3px;
  color: #8A9099;
  font-family: inherit; font-size: 8px; font-weight: 800; letter-spacing: 0.08em;
  transition: color 0.15s, border-color 0.15s;
}
.lib-act:hover { color: #fff; border-color: var(--nr-blue); }
.lib-act:focus-visible { outline: 2px solid var(--nr-orange); outline-offset: 1px; }
.lib-act-del:hover { border-color: var(--amber); color: var(--amber); }
.lib-empty, .lib-hint {
  font-size: 9.5px; line-height: 1.5; color: #808690;
  padding: 4px 1px 2px;
}
.lib-hint { margin-bottom: 8px; }
.lib-rename { width: 100%; }

/* ── sync indicator (topbar) ──
   One quiet word. Free users never see it except the transient
   "Saved on this device" after an explicit local save. */
#sync-indicator {
  display: none;
  align-items: center;
  min-height: 24px; padding: 0 8px;
  font-size: 9px; font-weight: 700; letter-spacing: 0.08em;
  color: #8A9099;
  white-space: nowrap;
}
#sync-indicator.show { display: inline-flex; }
#sync-indicator.sync-saved, #sync-indicator.sync-local { color: #8A9099; }
#sync-indicator.sync-saving, #sync-indicator.sync-syncing { color: var(--pale-blue); }
#sync-indicator.sync-offline, #sync-indicator.sync-error { color: var(--amber); }
@media (max-width: 640px) {
  #sync-indicator { display: none !important; }   /* the topbar budget is spent */
}

/* ── library modals (conflict / import / delete) — same family as #nr-modal-overlay ── */
#lib-modal-overlay {
  position: fixed; inset: 0;
  display: none;
  align-items: center; justify-content: center;
  background: rgba(4, 5, 8, 0.74);
  z-index: 300;
  padding: 16px;
}
#lib-modal-overlay.open { display: flex; }
.lib-modal-body {
  font-size: 11px; line-height: 1.55; color: #c8c8c8;
  margin: 0 0 14px;
}

@media print {
  #lib-modal-overlay, #sync-indicator { display: none !important; }
}
