/* ─────────────────────────────────────────────────────────────
   RESET & TOKENS
   ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --bg:           #0d0c18;
  --panel:        #13111f;
  --card:         #17152a;
  --card-hover:   #1c1a32;
  --border:       #1e1c35;
  --border-mid:   #2a2848;
  --border-focus: #4a4878;

  --teal:         #4ecdc4;
  --teal-dim:     rgba(78, 205, 196, 0.12);
  --teal-glow:    rgba(78, 205, 196, 0.25);
  --purple:       #8b7ec8;
  --purple-dim:   rgba(139, 126, 200, 0.15);
  --success:      #38d9a9;
  --success-dim:  rgba(56, 217, 169, 0.12);
  --warning:      #f59e0b;
  --warning-dim:  rgba(245, 158, 11, 0.12);
  --danger:       #f56565;
  --danger-dim:   rgba(245, 101, 101, 0.10);

  --text:         #e4e0f4;
  --text-sec:     #9590b8;
  /* #4e4a70 → #827da9. The old value measured 2.25:1 on --panel and carried
     hints, panel titles and inactive nav labels — body copy at a contrast
     WCAG AA fails outright. This clears 4.5:1 on --panel and --card while
     staying visibly below --text-sec, so the three-step hierarchy holds. */
  --text-muted:   #827da9;

  /* Border-radius scale (per design system) */
  --radius-sm:       6px;   /* icon buttons */
  --radius-btn:      8px;   /* regular buttons */
  --radius-input:   10px;   /* inputs / selects / textareas */
  --radius-card:    12px;   /* cards / panels */
  --radius:         10px;   /* legacy alias */
  --radius-lg:      16px;

  /* 8px spacing scale */
  --space-1:   4px;
  --space-2:   8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;

  /* Typography scale */
  --fs-section:   12px;   /* uppercase section titles */
  --fs-label:     11px;   /* field labels */
  --fs-input:     13px;   /* input text + button text */
  --fs-body:      13px;
  --fs-display:   22px;   /* selected joint name + similar */
  --lh-tight:     1.25;
  --lh-body:      1.55;
  --letter-section: 0.07em;

  /* Heights (8px grid) */
  --h-input:      36px;
  --h-btn:        36px;
  --h-btn-sm:     28px;

  --topbar-h:     52px;
  /* 52 → 68: the status bar carries two lines per cell now. Every overlay
     derives its bottom edge from this token, so they all follow. */
  --bottom-h:     68px;
  /* 280 → 240 on both rails hands 80px back to the viewport, which is the
     product. */
  --left-w:       240px;
  --right-w:      240px;

  /* ── OVERLAY LAYOUT CONTRACT ──────────────────────────────────────
     Every element floating over the 3D canvas — the gizmo panel, the
     recent-pose strip, the canvas toolbar, the orbit hint — positions
     itself from these three values and nothing else. Before this, each
     one hard-coded its own offsets against the viewport edges, which is
     why the gizmo and the pose strip could end up on top of each other
     with no breakpoint able to fix it: there was nothing to arbitrate.

     --canvas-left is the x where usable canvas starts. It equals the
     left rail's width normally, and 0 in the compact band where that
     rail becomes an overlay drawer. Override these, not the consumers. */
  --overlay-gap:  18px;
  --gizmo-w:      380px;
  /* Gutter a rail keeps when collapsed — just wide enough for the icon tab
     that brings it back. Never zero, or there would be nothing to click. */
  --rail-stub:    40px;
  /* The always-visible workspace navigator down the far left. */
  --rail-w:       72px;
  /* The docked SELECTED JOINT + BODY SHAPE column between the viewport and
     the joint selector. */
  --mid-w:        300px;
  /* Was the width of the floating canvas toolbar, which the camera bar had to
     clear. That column is gone — absorbed into #workspace-rail, which sits
     outside the canvas — so there is nothing left inside the viewport to
     avoid. Zero rather than deleted, so the camera bar's centring formula
     reads unchanged and can be re-armed if anything ever floats there again. */
  --toolbar-w:    0px;
  --canvas-left:  var(--left-w);
  --canvas-right: calc(var(--right-w) + var(--prail-w));
  /* Width of the right-hand panel rail (#panel-rail). */
  --prail-w:      72px;

  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
}

/* ── COLLAPSE WIDTHS ───────────────────────────────────────────────
   Collapsing a rail redefines its width, and everything downstream —
   the rail itself, the canvas edges, the overlays that key off them —
   follows from that one substitution. No consumer needs to know a rail
   can collapse.

   These must live on <body>, not :root. A `--canvas-left: var(--left-w)`
   declared on :root resolves against :root's own --left-w and inherits
   the finished value, so overriding --left-w further down would change
   nothing. Declared here, both resolve against the same element and the
   collapse classes below take effect. Keep this block above the narrow
   band's `body` rule so that one wins on equal specificity. */
body {
  /* The rail is permanent chrome, so it is always part of the left edge. */
  --canvas-left:  calc(var(--rail-w) + var(--left-w));
  /* Two columns sit right of the canvas now: the mid column (SELECTED JOINT
     + BODY SHAPE) and the joint selector rail. */
  --canvas-right: calc(var(--prail-w) + var(--right-w) + var(--mid-w) + var(--overlay-gap));
}

/* Zeroing both is what makes #rail-stub's position deterministic: it is
   anchored at `left: var(--left-w)`, so leaving the panel open would park the
   button at x=240 — floating in the canvas instead of at the screen edge.
   responsiveShell couples these two in JS; this is the same rule in CSS, so
   the layout cannot disagree with the behaviour. */
body.rail-collapsed  { --rail-w:  0px; --left-w: 0px; }
/* Left goes to zero, not to a stub: the workspace rail is its affordance, and
   its icons already name what is behind the panel. Defined here with the
   others — a second, conflicting declaration further down was winning only
   because it came later. */
body.left-collapsed  { --left-w:  0px; }
body.right-collapsed { --right-w: 0px; }   /* reopened from #panel-rail */
body.mid-collapsed   { --mid-w:   0px; }  /* docks into the Joint Selector */

body {
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
}

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

/* Announced, never drawn. For state carried by a glyph or a colour, where a
   screen reader would otherwise get "○" or nothing at all. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
img { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

/* ─────────────────────────────────────────────────────────────
   3D CANVAS (always in background)
   ───────────────────────────────────────────────────────────── */
#canvas-container {
  position: fixed;
  inset: 0;
  z-index: 0;
}

#canvas-container > canvas:not(#pose-canvas) {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

#pose-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 2;
}

/* SELECTED JOINT — docked top half of the mid column */
#mini-gizmo-panel {
  position: fixed;
  /* controls.js reveals the editor chrome with `display: flex` on every
     element in its list, and this panel is now on that list. Without an
     explicit direction the children laid out in a ROW — which is why the
     header, the joint name and GIZMO DISPLAY ended up side by side with a
     horizontal scrollbar. Stated here so it survives however it is shown. */
  flex-direction: column;
  /* Nothing inside may push the header out of view at any width. */
  overflow-x: hidden;
  /* Docked, not floating. It is the top half of the mid column; #mid-panel
     is the bottom half. The two share --mid-w and the same right edge, so
     they read as one column even though they are separate elements — which
     is what lets SELECTED JOINT stay 120 lines deep inside #canvas-container
     instead of being cut and pasted across the document. */
  top: calc(var(--topbar-h) + var(--overlay-gap));
  right: calc(var(--prail-w) + var(--right-w) + var(--overlay-gap));
  width: var(--mid-w);
  /* SELECTED JOINT is the whole mid column now — BODY SHAPE moved under the
     body map in the right rail — so it gets the full usable height. */
  bottom: calc(var(--bottom-h) + var(--overlay-gap));
  max-height: calc(calc(100vh / var(--ui-scale, 1)) - var(--topbar-h) - var(--bottom-h)
                   - var(--overlay-gap) * 2);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 16px;
  background: rgba(13, 12, 24, 0.92);
  border: 1px solid #2a2a40;
  border-radius: 10px;
  z-index: 30;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
  pointer-events: auto;
}

/* Gizmo tutorial modal overlay */
#gizmo-tutorial {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  /* Above the camera bar (200) as well as the rails — it is a modal. */
  z-index: 300;
  display: flex !important;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
#gizmo-tutorial[hidden],
#gizmo-tutorial.is-hidden {
  display: none !important;
}
#gizmo-tutorial .tutorial-content {
  background: #14132a;
  border: 1px solid #2a2a40;
  border-radius: 12px;
  padding: 24px;
  max-width: 480px;
  width: 100%;
  max-height: calc(90vh / var(--ui-scale, 1));
  overflow-y: auto;
  box-shadow: 0 16px 60px rgba(0, 0, 0, 0.6);
}
#gizmo-tutorial .tutorial-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid #2a2a40;
}
#gizmo-tutorial .tutorial-step {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin-bottom: 14px;
}
#gizmo-tutorial .tut-num {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: linear-gradient(135deg, #8b7ec8, #4ecdc4);
  color: #14132a;
  font-weight: 700;
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
}
#gizmo-tutorial .tut-title {
  font-size: 14px;
  font-weight: 600;
  color: #e0e0e8;
  margin-bottom: 3px;
}
#gizmo-tutorial .tut-text {
  font-size: 12px;
  color: #a0a0b8;
  line-height: 1.5;
}
#btn-gizmo-help:hover {
  background: rgba(139, 126, 200, 0.2) !important;
  color: #e0e0e8 !important;
  border-color: #8b7ec8 !important;
}

/* ─────────────────────────────────────────────────────────────
   SEGMENTED CONTROL — .seg (track) + .seg-opt (option)

   One pattern, written once: a groove holding N options where
   exactly one is filled. Before this, .ref-gender-btn and the
   .rot-pad-snap variant of .snap-seg-btn each rebuilt the same
   reset — transparent ground, no border, inherited font, the
   same tint on hover — differing only in accent.

   .seg-opt is the reset and the states; the track is the groove.
   Options keep their feature class (.ref-gender-btn,
   .snap-seg-btn) because JS selects on it and the phone/coarse
   tiers size through it — those rules are untouched. The feature
   class now carries only what is unique to that instance.

   POSITION IS LOAD-BEARING: this block must stay ahead of every
   instance that overrides it. .rot-pad-snap sits ~200 lines below
   and is a single class, so with the base further down the base
   won on source order and quietly reset the compact track's
   padding and radius. Base first, instances after.

   Sizing deliberately lives on the instance, not here — no height,
   no line-height, no font-weight. A blanket metric on a shared
   control is how the 48px touch floor once flattened the
   considered 36px camera pills (see the touch block near the
   phone tier); this base does not repeat that.
   ───────────────────────────────────────────────────────────── */
/* The track is a groove cut into the panel, not another raised card: it sits
   BELOW the panel ground so the one filled cell reads as a pill resting in
   it. When the track was lighter than the panel, five options and one
   selection all floated at the same height and the control read as five
   loose buttons. */
.seg {
  display: flex;
  gap: 2px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 3px;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

.seg-opt {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  border-radius: 4px;
  color: var(--text-sec);
  font-family: inherit;
  font-size: var(--fs-label);
  /* One weight for every cell, selected or not. These tracks size to their
     labels, so bolding the selected one reflows the row on each click. */
  font-weight: 600;
  letter-spacing: 0.01em;
  text-align: center;
  /* Labels here are single words ("Neutral", "45°"); letting one wrap
     would grow the track rather than the label. */
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}

/* Hover skips the selected cell — tinting an already-filled segment
   only muddies which one is on. */
.seg-opt:hover:not(:disabled):not(.is-active) {
  background: var(--card-hover);
  color: var(--text);
}

/* Inset, because the track clips: an outset ring on the first or last
   option would be cropped by the groove's edge. Neither migrated
   control had a focus ring at all before this. */
.seg-opt:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: -2px;
}

.seg-opt:disabled { opacity: 0.45; cursor: not-allowed; }

/* Selected: a filled cell lifted out of the groove. The ground carries the
   state, so it survives without relying on hue alone, and the offset shadow
   is what makes it read as raised rather than merely coloured.

   One selected treatment for every segmented control in the app. The model
   selector used to get a purple tint-and-ring instead, which was quiet
   enough that the control looked like it had no selection at all. */
.seg-opt.is-active {
  background: var(--teal);
  color: #0d0c18;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* ─────────────────────────────────────────────────────────────
   TOGGLES — one checkbox, one switch, split by what they mean

   There were four checkbox implementations in this file: .ws-check at
   16px with a drawn box, .draw-check at 14px native, .mirror-toggle at
   13px native, .settings-check at 16px native — three of them relying on
   accent-color, in two different accents. Same control, four answers.

   The split below is by meaning, not by panel:

     CHECKBOX  something is shown        Grid, Floor, Shadows, guides
     SWITCH    a mode stays on and       Limits, Mirror L↔R
               changes what the tool
               does to your next edit

   Limits silently constrains every rotation you make; it looked exactly
   like "show the grid". A switch reads as machinery left running.

   --check-accent lets the Draw workspace keep its purple without
   forking the component.
   ───────────────────────────────────────────────────────────── */
.ws-check     input[type="checkbox"],
.draw-check   input[type="checkbox"],
.settings-check input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin: 0;
  flex: none;
  position: relative;
  border: 1px solid var(--border-focus);
  border-radius: 4px;
  background: var(--bg);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.45);
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.ws-check     input[type="checkbox"]:checked,
.draw-check   input[type="checkbox"]:checked,
.settings-check input[type="checkbox"]:checked {
  background: var(--check-accent, var(--teal));
  border-color: var(--check-accent, var(--teal));
  box-shadow: none;
}
/* Drawn, not a glyph — a font tick lands differently on every platform. */
.ws-check     input[type="checkbox"]:checked::after,
.draw-check   input[type="checkbox"]:checked::after,
.settings-check input[type="checkbox"]:checked::after {
  content: '';
  position: absolute;
  left: 5px;
  top: 1.5px;
  width: 4px;
  height: 8px;
  border: solid #0d0c18;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.ws-check     input[type="checkbox"]:focus-visible,
.draw-check   input[type="checkbox"]:focus-visible,
/* A checkbox that cannot be ticked has to look like one. Paper grain is
   disabled while the viewport is on the studio background — there is nothing
   for the grain to sit on — and without this it read as simply 'off'. */
.settings-check input[type="checkbox"]:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.settings-check input[type="checkbox"]:disabled + span { color: var(--text-muted); }

.settings-check input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--check-accent, var(--teal));
  outline-offset: 2px;
}

/* Every toggle row is the target, not the 16px box inside it. 24px is the
   WCAG 2.2 AA floor for a pointer target (2.5.8). */
.ws-check,
.draw-check,
.settings-check,
.mirror-toggle { min-height: 24px; }

/* ── Switch ─────────────────────────────────────────────────────────
   Same box model as the checkbox — off is a recessed groove, on is the
   accent — so the two read as one family with two jobs. */
.pf-switch {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 26px;
  height: 15px;
  margin: 0;
  border-radius: 999px;
  border: 1px solid var(--border-focus);
  background: var(--bg);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.45);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.pf-switch::after {
  content: '';
  position: absolute;
  top: 1px;
  left: 1px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--text-sec);
  transition: transform 0.18s cubic-bezier(0.22, 1, 0.36, 1), background 0.15s ease;
}
.pf-switch:checked {
  background: var(--teal);
  border-color: var(--teal);
  box-shadow: none;
}
.pf-switch:checked::after {
  transform: translateX(11px);
  background: #0d0c18;
}
.pf-switch:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* ── Hit expansion ───────────────────────────────────────────────────
   Some controls are deliberately small: the snap stepper rides in a
   crowded header row, and the gizmo's collapse/help sit in a 22px title
   strip. Making them visually bigger would wreck the rows they live in,
   so the TARGET grows instead of the control — a centred, transparent
   ::after that costs no layout because it is absolutely positioned.

   This is why the rule is "never shrink the target", not "never draw a
   small control". Only elements that can carry a pseudo-element qualify;
   inputs and selects are replaced elements and need real height. */
.rot-pad-snap .seg-opt,
.gizmo-collapse,
.gizmo-head-actions button,
.sec-toggle,
.rail-collapse,
.draw-mini-btn {
  position: relative;
}
.rot-pad-snap .seg-opt::after,
.gizmo-collapse::after,
.gizmo-head-actions button::after,
.sec-toggle::after,
.rail-collapse::after,
.draw-mini-btn::after,
.pf-switch::before {          /* ::after is the switch's knob */
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100%, 24px);
  height: max(100%, 24px);
  transform: translate(-50%, -50%);
}

/* ─────────────────────────────────────────────────────────────
   WIZARD SCREEN
   ───────────────────────────────────────────────────────────── */
#wizard-screen {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow-y: auto;
  padding: 40px 20px;
}

/* ── Step 1: Upload ─────────────── */
.wizard-logo {
  text-align: center;
  font-size: 28px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -0.5px;
  margin-bottom: 6px;
}

/* ── Step 2: Detecting ──────────── */
.detecting-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
  display: flex;
  gap: 20px;
  align-items: flex-start;
}

@keyframes pulse-bar {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.65; }
}

/* Keypoints pills in detection step */
.kp-section { margin-top: 16px; }

/* ── Step 3: Result ─────────────── */
.step-section-label {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 14px;
}

/* ── Step 4: Model Config ───────── */
.config-layout {
  display: flex;
  gap: 24px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
  align-items: flex-start;
}

/* Floating Camera presets — viewport control, NOT page navigation.
   Positioned over the model area with clear separation from the header
   above (24px gap). Segmented control style: single rounded pill. */
#cam-preset-bar {
  position: fixed;
  /* Default: below regular topbar OR below admin Pose Editor 60px header */
  top: 84px;                  /* 60px (any header) + 24px breathing room */
  /* Anchored to the middle of the window and nothing else. Centring it on
     the canvas area made it slide sideways every time a rail collapsed or
     expanded — but this bar is a fixed landmark you aim at, so it should
     stay put while the panels around it move. */
  left: 50%;
  transform: translateX(-50%);
  /* The widest it can be while still clearing the vertical toolbar. Past
     that it wraps to a second row instead of sliding under the toolbar,
     which is what the original viewport-centred version got wrong.
     Doubled because centring makes the constraint symmetric: half of any
     overflow lands on each side of centre. */
  max-width: calc(calc(100vw / var(--ui-scale, 1))
                  - (var(--canvas-left) + var(--toolbar-w) + 10px) * 2
                  - var(--overlay-gap) * 2);
  flex-wrap: wrap;
  justify-content: center;
  display: flex;
  align-items: center;
  /* 1px, not 2 — the presets are one control, and the padding inside each
     label already supplies the reading gap. The separation that matters in
     this row is the rule before Free, which is why it gets 9px. */
  gap: 1px;
  padding: 4px;
  background: rgba(20, 19, 42, 0.85);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-input);
  z-index: 200;
  /* Enough lift to separate the bar from a light patch of model, no more.
     This floats a few millimetres, not a few centimetres. */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.30);
  font-family: inherit;
}
.cam-preset-label {
  display: none;   /* Hidden — buttons read clearly enough without a label */
}

/* The presets are a segmented control, so the resting state carries no
   chrome at all: five labels, evenly weighted, and the only filled cell
   is the view you are actually looking through. Boxes around all five
   would make the row compete with the model behind it. */
.cam-preset-btn {
  height: var(--h-btn-sm);
  padding: 0 10px;
  background: transparent;
  color: var(--text-sec);
  border: 1px solid transparent;   /* reserved, so :active never shifts */
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
  letter-spacing: 0.01em;
}
.cam-preset-btn:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.05);
}

/* Teal marks state and only state — the same tint-plus-border treatment
   .ws-btn and .ws-mode use for "the one you are in". A solid teal fill
   was the old behaviour; at this size it read as a primary action sitting
   on top of the model rather than as a selected segment. */
.cam-preset-btn.is-active {
  color: var(--teal);
  background: var(--teal-dim);
  /* Held back from full --teal. The label already turned teal, so the
     edge only has to close the shape; at full strength it outlined the
     word instead, which is a ring, not a pill. */
  border-color: rgba(78, 205, 196, 0.55);
}

/* Free is a MODE, not a destination: every other button jumps the camera
   somewhere and hands control back, this one changes what dragging does.
   The hairline rule and the heavier label carry that on their own — an
   outline at rest would put a second box in a row whose whole logic is
   that exactly one thing is boxed. */
.cam-preset-free {
  position: relative;
  margin-left: 9px;
  padding: 0 11px;
  font-weight: 600;
}
.cam-preset-free::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 50%;
  transform: translateY(-50%);
  width: 1px;
  height: 58%;              /* a hairline, not a wall — scales per tier */
  background: var(--border-focus);
}

/* Reset is the one control here you press by accident and regret, so it
   stays quiet: icon only, narrower than a label, and dimmed to the floor
   that still clears 3:1 against the bar. No divider — one rule in a bar
   this small is a separator, two is a fence. */
.cam-preset-reset {
  margin-left: 1px;
  padding: 0 8px;
  color: #7b7699;
  font-size: 13px;
  line-height: 1;
}
.cam-preset-reset:hover { color: var(--text); }

/* Rotation snap — see .seg / .seg-opt. The boxed-at-rest treatment that
   used to live here never rendered: every .snap-seg-btn in the document
   sits inside .rot-pad-snap, which overrode the ground, border and radius
   on all four of them. The class is kept — JS selects on it and the
   coarse-pointer tier sizes through it — but its styling is the shared one.
   Metrics below are the compact instance only. */

/* Joint rotation pad — X/Y/Z numeric inputs with +/- buttons */
.rot-pad {
  margin-top: 10px;
  padding: 12px 12px 10px;
  background: #1a1a2e;
  border: 1px solid #2a2a40;
  border-radius: 7px;
}
.rot-pad-title {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #8a8aa3;
  font-family: inherit;
}
.rot-pad-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}
/* The label and its segmented control are ONE flex item.
   They used to be two siblings in a flex-wrap header, with margin-left:auto
   on the label. When the header wrapped, the label was flung to the right
   end of the PREVIOUS row and read as a label for the Limits checkbox next
   to it, while the buttons it names sat unlabelled on the row below. The
   auto margin belongs to the pair, not to the word. */
.rot-pad-snap-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}
.rot-pad-snap-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #8a8aa3;
}
/* Compact track: this one rides in the rotation pad's header row next to
   the Limits toggle, so it runs tighter than the default .seg and its
   options size to their labels ("Free", "5°", "45°") rather than sharing
   the width equally. */
.rot-pad-snap {
  /* Tighter than the default track, and keyed to the rot-pad shell it sits
     in (#1a1a2e ground, #2a2a40 edge) rather than to the panel tokens. */
  background: #14132a;
  border-color: #2a2a40;
  border-radius: 4px;
  padding: 2px;
}
.rot-pad-snap .seg-opt {
  flex: 0 0 auto;          /* sizes to "Free" / "45°", not equal thirds */
  padding: 3px 7px;
  font-size: 10px;
  font-weight: 500;
  border-radius: 3px;
}
/* Resting ink only. Unqualified, this beats .seg-opt.is-active on source
   order and paints the selected label grey on its own teal fill. */
.rot-pad-snap .seg-opt:not(.is-active) { color: #8a8aa3; }

/* Mirror L↔R checkbox in the rotation pad header */
.mirror-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  user-select: none;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-sec, #8a8aa3);
  padding: 3px 6px;
  border-radius: 4px;
  transition: color 0.12s, background 0.12s;
}
.mirror-toggle:hover { color: var(--text, #fff); background: rgba(255,255,255,0.04); }
/* Mirror L↔R and Limits are modes, not view toggles — see .pf-switch. */
.mirror-toggle:has(input:checked) { color: var(--teal, #4ecdc4); }

/* "L. knee won't bend further (155°)" — which limit just stopped a drag, or
   what switching Limits on did. Warning tint, not error: the joint did what
   the switch asked. jointLimits.js shows it and hides it after a few seconds. */
.rot-pad-limit-note {
  margin: -2px 0 8px;
  padding: 5px 8px;
  border-radius: 4px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--warning);
  background: var(--warning-dim);
}
.rot-pad-limit-note[hidden] { display: none; }

/* Mirror mode selector — Symmetric vs Same direction */
.mirror-mode-select {
  background: var(--card, #1a1a2e);
  color: var(--text-sec, #8a8aa3);
  border: 1px solid var(--border, #2a2a40);
  border-radius: 4px;
  /* A select is a replaced element, so the target has to be real height. */
  min-height: 24px;
  padding: 2px 4px;
  font-size: 10px;
  font-family: inherit;
  cursor: pointer;
  margin-left: -2px;
}
.mirror-mode-select:hover { color: var(--text, #fff); border-color: var(--text-sec, #8a8aa3); }

/* Joint nav + action buttons (Reset / Deselect / Prev / Next) */
.joint-actions {
  display: grid;
  grid-template-columns: 34px 1fr 1fr 34px;
  gap: 6px;
  margin-top: 10px;
}
.joint-nav-btn,
.joint-action-btn {
  height: 32px;
  padding: 0 10px;
  background: #1a1a2e;
  color: #cfd0e8;
  border: 1px solid #2a2a40;
  border-radius: 5px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  transition: all 0.12s;
}
.joint-nav-btn { padding: 0; font-size: 16px; }
.joint-nav-btn:hover,
.joint-action-btn:hover { border-color: #4ecdc4; color: #fff; }
.joint-action-btn:disabled { opacity: 0.4; cursor: not-allowed; border-color: #2a2a40; color: #6b6885; }
.rot-pad-row {
  display: grid;
  grid-template-columns: 20px 32px 1fr 32px;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}
.rot-pad-row:last-child { margin-bottom: 0; }
.rot-pad-axis {
  font-family: 'JetBrains Mono', Consolas, monospace;
  font-weight: 700;
  font-size: 13px;
  text-align: center;
}
.rot-pad-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  background: #14132a;
  color: #cfd0e8;
  border: 1px solid #2a2a40;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
  font-weight: 600;
  font-family: inherit;
  line-height: 1;
  transition: all 0.1s;
}
.rot-pad-btn:hover { background: #34325a; color: #fff; border-color: #4ecdc4; }
.rot-pad-btn:active { transform: scale(0.93); }
.rot-pad-input {
  width: 100%;
  height: 32px;
  padding: 0 8px;
  background: #14132a;
  color: #e0e0e8;
  border: 1px solid #2a2a40;
  border-radius: 5px;
  font-size: 14px;
  font-weight: 500;
  font-family: 'JetBrains Mono', Consolas, monospace;
  text-align: center;
  outline: none;
  box-sizing: border-box;
}
.rot-pad-input:focus { border-color: #4ecdc4; box-shadow: 0 0 0 2px rgba(78,205,196,0.18); }
.rot-pad-input::-webkit-inner-spin-button { display: none; }
.rot-pad-input::-webkit-outer-spin-button { display: none; }
.rot-pad-input { -moz-appearance: textfield; }
.rot-pad-input:disabled { opacity: 0.4; cursor: not-allowed; }

/* Recent pose strip — bottom-left of canvas */
#recent-pose-strip {
  position: absolute;
  /* Was `left: 14px`, which drew the strip on top of the left rail (it wins
     the z-index at 90 vs 20). It belongs inside the canvas area. */
  left: calc(var(--canvas-left) + var(--overlay-gap));
  bottom: 64px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  background: rgba(20, 19, 42, 0.85);
  backdrop-filter: blur(8px);
  border: 1px solid #2a2845;
  border-radius: 8px;
  z-index: 90;
  /* Fill the canvas when nothing else is over it… */
  max-width: calc(calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right) - var(--overlay-gap) * 2);
}

/* The gizmo no longer floats over the canvas — it is a docked column, and
   --canvas-right already accounts for it. One claimant fewer. */
.rps-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.7px;
  color: #6b6885;
  text-transform: uppercase;
}
.rps-row {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  max-width: 100%;
}
.rps-row::-webkit-scrollbar { height: 3px; }
.rps-row::-webkit-scrollbar-thumb { background: #34325a; border-radius: 2px; }
.rps-empty {
  font-size: 11px;
  color: #6b6885;
  padding: 8px 4px;
}
.rps-card {
  width: 80px;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid #2a2845;
  border-radius: 6px;
  cursor: pointer;
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: inherit;
  transition: all 0.12s;
}
.rps-card:hover {
  border-color: #4ecdc4;
  transform: translateY(-1px);
}
.rps-card img {
  width: 80px;
  height: 80px;
  object-fit: cover;
  display: block;
}
.rps-no-thumb {
  width: 80px;
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #0e0d20;
  color: #34325a;
  font-size: 22px;
}
.rps-name {
  font-size: 9px;
  color: #cfd0e8;
  padding: 3px 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
}

/* Styled prompt modal (replaces window.prompt / window.alert) */
.pm-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.55);
  z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  animation: pm-fade 0.12s ease;
}
@keyframes pm-fade { from { opacity: 0; } to { opacity: 1; } }
.pm-modal {
  background: #14132a;
  color: #e0e0e0;
  border: 1px solid #2a2845;
  border-radius: 10px;
  padding: 22px 24px;
  width: min(420px, calc(92vw / var(--ui-scale, 1)));
  /* Was `Inter, sans-serif` — but Inter is never loaded (no @font-face, no
     font link), so this modal alone fell back to Arial while the rest of the
     app used the system stack from :root. `inherit` matches every other
     component here. */
  font-family: inherit;
  box-shadow: 0 14px 40px rgba(0,0,0,0.55);
}
.pm-title    { font-size: 17px; font-weight: 600; color: #fff; margin-bottom: 6px; }
.pm-message  { font-size: 13px; color: #8e8baf; margin-bottom: 14px; line-height: 1.5; }
.pm-input {
  width: 100%;
  padding: 10px 12px;
  background: #0e0d20;
  color: #fff;
  border: 1px solid #2a2845;
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
  outline: none;
  box-sizing: border-box;
  margin-bottom: 18px;
}
.pm-input:focus { border-color: #4ecdc4; box-shadow: 0 0 0 2px rgba(78,205,196,0.18); }
.pm-actions  { display: flex; gap: 8px; justify-content: flex-end; }
/* PromptModal.choose — one button per option, full width, equal size. */
.pm-choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: 8px; margin: 4px 0 14px; }
.pm-choice  { background: #2a2845; color: #e0e0e8; padding: 12px 10px; font-weight: 600; border: 1px solid transparent;
              display: flex; flex-direction: column; align-items: center; gap: 8px; }
.pm-choice-icon { width: 30px; height: 40px; overflow: visible; color: #a99cf0; }
.pm-choice-icon circle,
.pm-choice-icon path:first-of-type { fill: currentColor; stroke: none; }
.pm-choice-icon path + path { fill: none; stroke: currentColor; stroke-linecap: round; }
.pm-choice.is-default .pm-choice-icon,
.pm-choice:hover .pm-choice-icon { color: #4ecdc4; }
.pm-choice:hover { background: #34325a; }
.pm-choice.is-default { border-color: #4ecdc4; color: #4ecdc4; }
.pm-btn      { padding: 8px 18px; border: none; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; font-family: inherit; transition: all 0.12s; }
.pm-btn-cancel  { background: #2a2845; color: #cfd0e8; }
.pm-btn-cancel:hover  { background: #34325a; }
.pm-btn-confirm { background: #4ecdc4; color: #0d0c18; }
.pm-btn-confirm:hover { filter: brightness(1.1); }
.pm-btn-danger  { background: #e85a5a; color: #fff; }
.pm-btn-danger:hover  { background: #ff6b6b; }

/* Body Type pill row in config step — matches option-card style exactly */
.bodytype-pill-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  margin-bottom: 20px;
  width: 100%;
}

.bodytype-pill.active {
  border-color: var(--teal);
  background: var(--teal-dim);
  color: var(--teal);
}

/* ─────────────────────────────────────────────────────────────
   BUTTONS

   .btn-primary / .btn-secondary / .btn-sm used to be declared twice:
   once here and once in "4. Buttons — unified sizing" further down.
   The later block set the same properties, so it won every one of
   them on source order and this copy rendered nothing — except for
   one leak: its :hover set `opacity`, which the later :hover does
   not, so primary buttons were dimming to 88% AND brightening 8% at
   the same time, from two rules written years apart.

   The unified block below is now the only definition. Anything that
   belongs to all buttons goes there, not here.
   ───────────────────────────────────────────────────────────── */

/* The drop zone's call to action. It is the one thing to press in that
   card, so it takes the raised-neutral rung rather than sitting at the
   same weight as "Open full Gallery" underneath it. */
.btn-browse-sm {
  background: var(--border-mid);
  color: var(--text);
  border: 1px solid var(--border-focus);
  border-radius: var(--radius-sm);
  padding: 7px 16px;
  font-size: 12px;
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  transition: background 0.15s, border-color 0.15s;
}

.btn-browse-sm:hover { background: var(--border-focus); border-color: #5b5890; }

/* Browse / Use camera sit side by side in the empty upload zone, and
   Replace / Camera do the same under a loaded photo. Wrapping instead of
   shrinking: the rail is narrow and a squeezed two-word button reads as
   broken, while a second row reads as a second option. */
.editor-upload-actions,
.ref-photo-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
}

.ref-photo-actions {
  margin-top: 8px;
}

/* ─────────────────────────────────────────────────────────────
   SIDE VIEW — the optional second photo.
   js/ui/controls.js initSideView() · js/detect/sideViewDisambiguator.js

   Set apart from the reference card above it by a rule and a recessed
   ground, because it is a different KIND of thing: the card is the photo
   the pose came from, this is a second opinion about it. Styled quieter
   than the primary upload throughout — it is genuinely optional, and an
   equally loud block would read as a required second step.
   ───────────────────────────────────────────────────────────── */
.ref-sideview {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.ref-sideview-head {
  display: flex;
  align-items: center;
  gap: 6px;
}

.ref-sideview-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-sec);
}

.ref-optional {
  padding: 0 5px;
  border-radius: 3px;
  background: var(--border);
  color: var(--text-muted);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.ref-sideview-hint {
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* Small and wide: this is a reminder of which photo was used, not something
   to inspect. The pose itself is the result, and it is already on screen. */
.ref-sideview-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 72px;
  margin-top: 8px;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.ref-sideview-thumb img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.ref-sideview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.ref-sideview-actions > .btn-sm {
  flex: 1 1 0;
  min-width: 92px;
}

.ref-sideview-actions [hidden] { display: none; }

/* The verdict. Four states, and the colour is the summary:
     is-fixed  something was actually corrected — the interesting one
     is-ok     quiet confirmation, deliberately understated
     is-warn   the photo was refused; the front photo still stands
     is-note   nothing conclusive, which is not a failure either       */
.ref-sideview-verdict {
  margin-top: 8px;
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card);
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-sec);
}

.ref-sideview-verdict.is-fixed {
  border-color: var(--teal);
  background: var(--teal-dim);
  color: var(--teal);
}

.ref-sideview-verdict.is-ok {
  border-color: var(--success);
  background: var(--success-dim);
  color: var(--success);
}

.ref-sideview-verdict.is-warn {
  border-color: var(--warning);
  background: var(--warning-dim);
  color: var(--warning);
}

.ref-sideview-verdict[hidden] { display: none; }

/* The buttons split the row evenly when both are present, and the survivor
   fills it when the camera one is hidden - so a device with no camera gets
   exactly the full-width control this panel had before. */
.ref-photo-actions > .btn-sm {
  flex: 1 1 0;
  min-width: 96px;
}

.btn-camera-sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.btn-camera-sm[hidden] { display: none; }

/* -------------------------------------------------------------
   CAMERA CAPTURE OVERLAY
   ------------------------------------------------------------- */
.pf-camera-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(6, 5, 14, 0.82);
  backdrop-filter: blur(3px);
}

.pf-camera-box {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(760px, 100%);
  max-height: 100%;
  padding: 14px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
}

.pf-camera-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.pf-camera-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.pf-camera-x {
  background: none;
  border: 0;
  color: var(--text-sec);
  font-size: 22px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

.pf-camera-x:hover { color: var(--text); }

/* The stage keeps a 16:9 box whatever the camera reports, so the dialog does
   not jump size between starting the stream and the first frame arriving. */
.pf-camera-stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  min-height: 200px;
  overflow: hidden;
  background: #000;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* contain, not cover: cropping a reference photo to fill the box could cut
   off the feet, and framing the whole body is the entire point of the hint
   underneath. */
.pf-camera-video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.pf-camera-msg {
  position: absolute;
  max-width: 42ch;
  padding: 0 16px;
  text-align: center;
  font-size: 12px;
  line-height: 1.6;
  color: var(--danger);
}

.pf-camera-hint {
  font-size: 11px;
  color: var(--text-sec);
  text-align: center;
}

.pf-camera-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

.pf-camera-actions [data-act="shoot"] { min-width: 110px; }

.pf-camera-actions [data-act="shoot"]:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.pf-camera-actions [hidden] { display: none; }

/* On a phone the dialog is the screen: give the preview the room and let the
   controls sit under it at full width. */
@media (max-width: 640px) {
  .pf-camera-overlay { padding: 0; }

  .pf-camera-box {
    width: 100%;
    height: 100%;
    border: 0;
    border-radius: 0;
  }

  .pf-camera-stage {
    flex: 1;
    aspect-ratio: auto;
  }

  .pf-camera-actions > * { flex: 1 1 0; }
}

/* ─────────────────────────────────────────────────────────────
   EDITOR TOPBAR
   ───────────────────────────────────────────────────────────── */
#topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--topbar-h);
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  padding: 0 20px;
  gap: 0;
  /* Below #admin-panel / #settings-panel (100), above the editor rails.
     Was briefly raised to 60 to escape the upload wizard's full-screen
     overlay; the wizard is gone, so the original value is correct again.
     Must sit above the side rails (20/21) and the mid panel (30): the account
     menu drops out of the bar and is trapped in this stacking context, so a
     lower value painted the joint selector over it. */
  z-index: 40;
}

/* The wordmark is a <button> so it is reachable by keyboard and announced as
   the control it is — it reloads the app. The reset below strips the UA button
   look; the styling itself is unchanged from when this was a <span>. */
.brand {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.3px;
  margin-right: 24px;
  white-space: nowrap;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
  transition: opacity 0.15s;
}

.brand:hover  { opacity: 0.7; }
.brand:active { opacity: 0.5; }
.brand:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; border-radius: 4px; }

/* The monogram is the phone tier's fallback and is drawn nowhere else. */
.brand-mark { display: none; }

#editor-nav {
  display: flex;
  gap: 2px;
}

.nav-btn {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
  padding: 5px 16px;
  border-radius: 20px;
  transition: color 0.15s, background 0.15s;
}

.nav-btn:hover { color: var(--text-sec); }

.nav-btn.active {
  color: var(--text);
  background: var(--border-mid);
  font-weight: 600;
}

.user-badge {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 10px;
}

.iso-label {
  font-size: 11px;
  color: var(--text-muted);
  font-weight: 500;
  letter-spacing: 0.5px;
}

.user-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--purple-dim);
  border: 1px solid var(--border-focus);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  color: var(--purple);
}

/* ─────────────────────────────────────────────────────────────
   EDITOR LEFT PANEL
   ───────────────────────────────────────────────────────────── */
#left-panel {
  position: fixed;
  top: var(--topbar-h);
  /* The workspace rail sits to its left and is always visible. */
  left: var(--rail-w);
  bottom: var(--bottom-h);
  width: var(--left-w);
  background: var(--panel);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  z-index: 20;
  display: flex;
  flex-direction: column;
}

#left-panel::-webkit-scrollbar { width: 3px; }
#left-panel::-webkit-scrollbar-thumb { background: var(--border-mid); border-radius: 2px; }

.panel-section {
  padding: 14px 14px 16px;
  border-bottom: 1px solid var(--border);
}

.panel-section:last-child { border-bottom: none; }

.panel-title {
  font-size: 10px;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 12px;
  white-space: nowrap;
}

/* Editor upload zone */
.editor-upload-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  border: 1.5px dashed var(--border-mid);
  border-radius: var(--radius-sm);
  padding: 16px 10px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s;
}

.editor-upload-zone:hover,
.editor-upload-zone.drag-over {
  border-color: var(--teal);
  background: var(--teal-dim);
}

.editor-upload-icon {
  color: var(--text-muted);
  margin-bottom: 1px;
}

.editor-upload-zone:hover .editor-upload-icon { color: var(--teal); }

.editor-upload-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.editor-upload-sub {
  font-size: 11px;
  color: var(--text-muted);
  line-height: 1.4;
}

.format-tags-sm {
  display: flex;
  gap: 5px;
  margin-top: 2px;
}

.fmt-tag-sm {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--border);
  border-radius: 3px;
  padding: 1px 6px;
  letter-spacing: 0.4px;
}

/* Reference image thumbnail */
.ref-thumb-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
}

.ref-thumb-badge {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(13, 12, 24, 0.85);
  font-size: 11px;
  color: var(--text-sec);
  text-align: center;
  padding: 5px 0;
}

/* ── Reference card (post-upload state of the REFERENCE panel) ──
   The container now ADAPTS to the image's natural aspect ratio —
   portrait photos display as portrait (tall + narrow), landscape
   as landscape (short + wide). No fixed height, no forced aspect
   ratio. The image drives the container's height with `height: auto`
   plus a min/max clamp so the sidebar layout stays reasonable.
   `contain` (via the image's intrinsic sizing here — no crop needed
   since the container matches) keeps the whole figure visible. */
.ref-card {
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.ref-card .ref-thumb-wrap {
  aspect-ratio: auto;         /* image sets it */
  height: auto;               /* grows with image */
  min-height: 132px;          /* placeholder height before/without image */
  max-height: 420px;          /* cap so huge portraits don't dominate the sidebar */
  background: #0e0d20;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ref-card .ref-thumb-wrap img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 420px;
  object-fit: contain;
}

.ref-card:hover .ref-thumb-wrap,
.ref-card.drag-over .ref-thumb-wrap {
  border-color: var(--teal);
  box-shadow: 0 0 0 2px var(--teal-dim);
}

.ref-filename {
  margin: 6px 2px 0;
  font-size: 11px;
  color: var(--text-sec);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── REFERENCE: 'detecting' state ─────────────────────────────────────────
   Shown in the rail while MediaPipe runs, so the editor stays live behind
   it instead of being covered by a full-screen step. */
.ref-detect-veil {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: rgba(13, 12, 24, 0.72);
  backdrop-filter: blur(2px);
}
.ref-detect-spinner {
  width: 22px;
  height: 22px;
  border: 2px solid var(--border-mid);
  border-top-color: var(--teal);
  border-radius: 50%;
  animation: ref-detect-spin 0.7s linear infinite;
}
@keyframes ref-detect-spin { to { transform: rotate(360deg); } }

.ref-detect-label {
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text);
}

/* Indeterminate: MediaPipe's detect() is one synchronous call with no
   progress to report, so this shows ACTIVITY, not completion. Animating
   transform (not width) keeps it off the layout path. */
.ref-detect-track {
  position: relative;
  height: 3px;
  margin: 8px 2px 0;
  border-radius: 2px;
  background: var(--border);
  overflow: hidden;
}
.ref-detect-fill {
  position: absolute;
  inset: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--teal), transparent);
  transform: translateX(-100%);
  animation: ref-detect-sweep 1.1s ease-in-out infinite;
}
@keyframes ref-detect-sweep {
  to { transform: translateX(100%); }
}

/* Respect users who have asked for less motion — hold a static bar. */
@media (prefers-reduced-motion: reduce) {
  .ref-detect-spinner { animation: none; border-top-color: var(--teal); }
  .ref-detect-fill    { animation: none; transform: none; opacity: 0.5; }
}

/* ── Model gender, shown at the moment it is safe to change ───────────── */
.ref-detect-cancel {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 5px;
  font-size: 11px;
  font-family: inherit;
  color: var(--text-sec);
  background: transparent;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.ref-detect-cancel:hover { color: var(--text); border-color: var(--border-focus); }

.ref-gender { margin-bottom: 10px; }
.set-delete-account { color: var(--danger); }
.set-delete-account:hover { background: rgba(245, 101, 101, 0.1); }
.ref-gender-label {
  display: block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 5px;
}
/* Track, groove and states come from .seg / .seg--purple / .seg-opt in the
   SEGMENTED CONTROL block. Only the instance's own metrics remain. */
.ref-gender-opts { gap: 3px; }
.ref-gender-btn  { padding: 5px 4px; }
.ref-gender-opts.is-locked { opacity: 0.7; }

.ref-manual-note {
  padding: 10px 12px;
  background: var(--card);
  border: 1px dashed var(--border-mid);
  border-radius: var(--radius-sm);
  font-size: 11px;
  color: var(--text-sec);
  text-align: center;
}

/* ── POSE CORRECTIONS: swap toggles ──────────────────────────────────
   These are stateful toggles, not one-shot actions. Previously the JS
   toggled a `.active` class that had no style rule anywhere, so the user
   got no feedback about whether a swap was currently applied. */
.swap-btn-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  margin-bottom: 14px;
}

.swap-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 6px;
  line-height: 1.25;
  white-space: nowrap;
}

.swap-btn .swap-lr {
  font-size: 10px;
  color: var(--text-muted);
  font-weight: 400;
}

.swap-btn.is-active {
  background: var(--teal);
  border-color: var(--teal);
  color: #0e0d20;
  font-weight: 600;
  box-shadow: 0 0 0 2px var(--teal-dim);
}

.swap-btn.is-active .swap-lr {
  color: rgba(14, 13, 32, 0.7);
}

/* ── POSE ADJUSTMENT: posture presets (Stand / Squat / Sit / Kneel) ── */
.posture-preset-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 5px;
  margin-top: 8px;
}

.posture-preset-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 8px 2px;
  font-size: 10px;
  line-height: 1.1;
}

.posture-preset-btn.is-active {
  background: var(--teal);
  border-color: var(--teal);
  color: #0e0d20;
  font-weight: 600;
}

/* Morph target sliders (body shape panel) */
#morph-list {
  display: flex;
  flex-direction: column;
}

.morph-row {
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
}

.morph-row:last-child { border-bottom: none; }

.morph-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 5px;
  gap: 6px;
}

.morph-name {
  font-size: 12px;
  color: var(--teal);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.morph-val {
  font-size: 12px;
  color: var(--teal);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.morph-empty {
  font-size: 12px;
  color: var(--text-muted);
  padding: 8px 0;
  text-align: center;
}

/* ── Sliders — one control for the morph panel and the pose rail ──────
   .shape-slider had no styling at all and was rendering as a stock range
   input, which put Chrome's default blue — a colour that exists nowhere
   else in this app — on three controls in the Pose workspace.

   The track is a groove: recessed, with an inset shadow, so the handle
   reads as sitting in it. The handle keeps its teal (it is the live value,
   which is what teal means here) but trades its old 0 0 5px halo for a real
   offset shadow — a zero-offset colored glow is decoration, not depth.

   No filled "played" portion, deliberately: Body rotation runs −180…180
   with 0 at centre, so a fill growing from the left edge would draw the
   wrong story on the very control the user touches most.

   The input is taller than the 4px track it draws so the hit area is
   pointer-sized; the visible track thickness is unchanged. A range input
   is a replaced element and cannot carry a ::after hit-expander, so the
   height has to be real — 24px is the WCAG 2.2 AA target floor (2.5.8),
   and these were 16px. */
.shape-slider,
.morph-slider,
.draw-row input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 28px;
  background: transparent;
  border: none;
  outline: none;
  cursor: pointer;
  display: block;
  margin: 0;
}

.shape-slider::-webkit-slider-runnable-track,
.morph-slider::-webkit-slider-runnable-track,
.draw-row input[type="range"]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.55);
}
.shape-slider::-moz-range-track,
.morph-slider::-moz-range-track,
.draw-row input[type="range"]::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.55);
}

.shape-slider::-webkit-slider-thumb,
.morph-slider::-webkit-slider-thumb,
.draw-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--slider-accent, var(--teal));
  border: 2px solid var(--panel);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  cursor: pointer;
  margin-top: -6.5px;            /* centres the 17px handle on the 6px track */
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.shape-slider::-moz-range-thumb,
.morph-slider::-moz-range-thumb,
.draw-row input[type="range"]::-moz-range-thumb {
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--slider-accent, var(--teal));
  border: 2px solid var(--panel);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  cursor: pointer;
}

.shape-slider:hover::-webkit-slider-thumb,
.draw-row input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.15); }
.shape-slider:active::-webkit-slider-thumb,
.draw-row input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.15); }

/* The handle is the control, so the ring goes on the handle, not around the
   full-width input where it would box the whole row. */
.shape-slider:focus-visible::-webkit-slider-thumb,
.draw-row input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55), 0 0 0 3px var(--teal-dim);
}
.shape-slider:focus-visible::-moz-range-thumb,
.draw-row input[type="range"]:focus-visible::-moz-range-thumb {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55), 0 0 0 3px var(--teal-dim);
}

/* Character customization — gender + preset buttons (top of panel) */
.morph-section-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-muted, #8e8baf);
  margin: 14px 0 8px;
}
/* (Gender styles removed. Gender is an input to reconstruction, not a
   character-customisation control, so it renders in the REFERENCE rail
   during detection — see .ref-gender-* above — and never in this panel.
   These rules had already been dead since the wizard claimed gender.) */

.morph-presets { margin-bottom: 16px; }
/* Body Type selector — real buttons, not a cramped 5-up segmented strip.
   At sidebar width, 5 equal columns gave each button ~42px, which truncated
   "Athletic"/"Muscular". A 3-column auto-fit grid lets the labels breathe
   and wrap onto a second row instead. */
/* Five options, two rows — too many to fit one track at rail width without
   truncating "Athletic"/"Muscular", so it stays a grid. The tighter gutter
   and the shared recessed ground are what make it read as one field of
   choices rather than five unrelated buttons. */
.morph-bodytype-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
  padding: 4px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}
/* Borderless inside the shared groove — the container draws the edge now,
   so each option no longer carries a box of its own. */
.morph-bodytype-btn {
  padding: 8px 6px;
  min-height: 32px;
  background: transparent;
  color: var(--text-sec);
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease;
}
/* Neutral, so teal keeps meaning "this is the selected one". */
.morph-bodytype-btn:hover:not(.is-active) {
  background: var(--card-hover, #1c1a32);
  color: var(--text);
}
.morph-bodytype-btn:active { transform: scale(0.97); }
/* Kept in step with .seg-opt and .cam-preset-btn even though this control
   is not on the shared track — a selector you can tab into should say so
   the same way everywhere. Offset clears the 6px grid gutter. */
/* Same filled cell as .seg-opt.is-active, lifted out of the same groove —
   the old 0 0 0 2px teal ring was a flat halo, not depth. */
.morph-bodytype-btn.is-active {
  background: var(--teal);
  color: #0d0c18;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}


/* Advanced settings (collapsible wrapper around all category sliders) */
.morph-advanced {
  border-top: 1px solid var(--border);
  margin-top: 8px;
  padding-top: 4px;
}
.morph-advanced-summary {
  cursor: pointer;
  padding: 10px 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-sec, #cfd0e8);
  user-select: none;
  list-style: none;
}
.morph-advanced-summary::-webkit-details-marker { display: none; }
.morph-advanced-summary::before {
  content: '▸';
  display: inline-block;
  width: 14px;
  font-size: 10px;
  color: var(--text-muted, #8e8baf);
  transition: transform 0.15s;
}
.morph-advanced[open] > .morph-advanced-summary::before {
  transform: rotate(90deg);
}
.morph-advanced-body {
  padding-left: 4px;
}

/* Categorized morph panel — search + collapsible sections */
.morph-search {
  width: 100%;
  padding: 7px 10px;
  margin-bottom: 10px;
  background: var(--bg-deep, #0e0d20);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text, #e0e0e0);
  font-size: 12px;
  outline: none;
}
.morph-search:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 2px rgba(0, 229, 255, 0.15);
}

.morph-cat {
  border-bottom: 1px solid var(--border);
  margin: 0;
}
.morph-cat:last-child { border-bottom: none; }

.morph-cat-summary {
  cursor: pointer;
  padding: 9px 4px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-sec, #cfd0e8);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  user-select: none;
  list-style: none;
}
.morph-cat-summary::-webkit-details-marker { display: none; }
.morph-cat-summary::before {
  content: '▸';
  display: inline-block;
  width: 14px;
  font-size: 10px;
  color: var(--text-muted, #8e8baf);
  transition: transform 0.15s;
}
.morph-cat[open] > .morph-cat-summary::before {
  transform: rotate(90deg);
}
.morph-cat-summary:hover { color: var(--teal); }

.morph-cat-count {
  font-size: 10px;
  font-weight: 400;
  /* --text-muted (#4e4a70) measures 2.31:1 on this badge's own #0e0d20
     ground, well under the 4.5:1 floor for 10px text. The count IS the
     payload of a collapsed group -- it is the only thing telling you how
     much is inside -- so it cannot be the dimmest text in the panel.
     --text-sec measures 5.91:1 on the same ground. */
  color: var(--text-sec, #9590b8);
  background: var(--bg-deep, #0e0d20);
  padding: 2px 7px;
  border-radius: 9px;
  letter-spacing: 0;
}

.morph-cat-body {
  padding: 4px 0 8px 14px;
}
.morph-cat-body .morph-row {
  padding: 5px 0;
}

/* ─────────────────────────────────────────────────────────────
   EDITOR RIGHT PANEL
   ───────────────────────────────────────────────────────────── */
#right-panel {
  position: fixed;
  top: var(--topbar-h);
  right: var(--prail-w);
  bottom: var(--bottom-h);
  width: var(--right-w);
  background: var(--panel);
  border-left: 1px solid var(--border);
  overflow-y: auto;
  overflow-x: hidden;        /* never side-scroll */
  z-index: 20;
  padding: 14px 0 0;
  display: flex;
  flex-direction: column;    /* force vertical stacking */
  box-sizing: border-box;
}

/* Every direct child fills the panel width, never overflows */
#right-panel > * {
  width: 100%;
  box-sizing: border-box;
  flex-shrink: 0;
}

#joint-instruction,
#active-joint-bar {
  word-wrap: break-word;
  overflow-wrap: break-word;
}

/* Active joint bar — button row wraps cleanly on narrow panels */
#active-joint-bar > div:last-child {
  flex-wrap: wrap;
}
#active-joint-bar button {
  min-width: 0;              /* allow shrinking */
  white-space: nowrap;
}

#right-panel::-webkit-scrollbar { width: 3px; }
#right-panel::-webkit-scrollbar-thumb { background: var(--border-mid); border-radius: 2px; }

#right-panel .panel-title {
  margin-bottom: 4px;
  padding: 0 14px 10px;
  border-bottom: 1px solid var(--border);
}

/* Joint list */
#joint-list { padding: 4px 0; }

.joint-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--text-sec);
  transition: background 0.15s, color 0.15s;
  cursor: default;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-bottom: 1px solid var(--border);
}
.joint-item > span:last-child {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.joint-item:last-child { border-bottom: none; }

.joint-item.ik-enabled { cursor: pointer; }

.joint-item.ik-enabled:hover {
  background: var(--card);
  color: var(--text);
}

.joint-item.active {
  background: var(--purple-dim);
  color: var(--text);
  border-left: 2px solid var(--purple);
  padding-left: 12px;
}

.joint-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* ── Build Checklist (top of joint list) ── */
.joint-build-checklist {
  padding: 8px 14px 10px;
  border-bottom: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.015);
}
.joint-checklist-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text-dim, #6b6b8a);
  margin-bottom: 6px;
}
.joint-checklist-items {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: center;
}
.joint-checklist-item {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: var(--text-sec);
  cursor: pointer;
  user-select: none;
  padding: 2px 4px;
  border-radius: 3px;
  transition: color 0.15s, background 0.15s;
}
.joint-checklist-item:hover { color: var(--text); background: rgba(255,255,255,0.03); }
.joint-checklist-item input { cursor: pointer; accent-color: var(--teal, #4ecdc4); margin: 0; }
.joint-checklist-item.done { color: var(--text-dim, #6b6b8a); text-decoration: line-through; }
.joint-checklist-reset {
  margin-left: auto;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim, #6b6b8a);
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 11px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.joint-checklist-reset:hover { color: var(--text); border-color: var(--text-sec); }

/* ── Header: Focus filter + Advanced toggle ── */
.joint-list-header {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 14px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
.joint-focus-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.joint-focus-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text-dim, #6b6b8a);
}
.joint-focus-select {
  flex: 1;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 6px;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
}
.joint-focus-select:hover { border-color: var(--text-sec); }
.joint-advanced-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-sec);
  cursor: pointer;
  user-select: none;
}
.joint-advanced-toggle input {
  cursor: pointer;
  accent-color: var(--purple);
}
.joint-advanced-toggle:hover { color: var(--text); }

/* ── Category headers (advanced mode) ── */
.joint-category-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-sec);
  cursor: pointer;
  user-select: none;
  background: rgba(255, 255, 255, 0.02);
  border-bottom: 1px solid var(--border);
  transition: background 0.15s, color 0.15s;
}
.joint-category-header:hover {
  background: var(--card);
  color: var(--text);
}
.joint-chevron {
  width: 10px;
  font-size: 9px;
  color: var(--text-dim, #6b6b8a);
  flex-shrink: 0;
}
.joint-cat-name { flex: 1; }
.joint-cat-count {
  font-size: 10px;
  color: var(--text-dim, #6b6b8a);
  background: var(--card);
  padding: 1px 6px;
  border-radius: 8px;
}

/* Gizmo display / ring orientation — radio groups wearing .seg.

   These were a hand-built copy of the segmented track (flex row, dark
   ground, 3px padding, rounded children) with the metrics inline on every
   label. They now use the real component, so they polish with everything
   else. The radio inputs stay in the markup untouched — controls.js reads
   them by name — but the dot is gone: in a segmented control the filled
   cell already IS the selection, and drawing both said it twice while
   making the row look like a settings list. */
.gizmo-mode-opt {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px 6px;
  /* Owned here, not inline on the label. An inline `color` beats every
     stylesheet rule, so while it was there the :has(input:checked) rule below
     could never darken the text — the selected option rendered light grey on
     teal and its label was unreadable. */
  color: #a0a0b8;
}
.gizmo-mode-opt input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  margin: 0;
}
.gizmo-mode-opt:hover:not(:has(input:checked)) {
  background: var(--card-hover);
  color: var(--text);
}
.gizmo-mode-opt:has(input:checked) {
  background: var(--teal);
  color: #0d0c18;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
/* The label is the control, so focus has to be visible on it rather than on
   the input that is now off-screen. */
.gizmo-mode-opt:has(input:focus-visible) {
  outline: 2px solid var(--teal);
  outline-offset: -2px;
}

/* Group label inside the floating joint panel. Matches .panel-subtitle's
   role one level down, where the panel has its own tighter rhythm. */
.gizmo-group-label {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--letter-section);
  color: var(--text-sec);
  margin-bottom: var(--space-2);
}

/* ─────────────────────────────────────────────────────────────
   CANVAS OVERLAY CONTROLS
   ───────────────────────────────────────────────────────────── */

.canvas-btn {
  width: 32px;
  height: 32px;
  border-radius: 5px;
  background: transparent;
  border: none;
  color: var(--text-sec);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
}

.canvas-btn:hover {
  background: var(--border);
  color: var(--text);
}

.canvas-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.canvas-btn:disabled:hover {
  background: transparent;
  color: var(--text-sec);
}

/* ─────────────────────────────────────────────────────────────
   CANVAS ORBIT HINT
   ───────────────────────────────────────────────────────────── */
#canvas-hint {
  position: fixed;
  bottom: calc(var(--bottom-h) + 16px);
  /* Window-centred and static, like the camera bar: collapsing a panel
     must not slide it sideways. */
  left: 50%;
  transform: translateX(-50%);
  z-index: 15;
  background: rgba(13, 12, 24, 0.75);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border-mid);
  border-radius: 20px;
  padding: 6px 18px;
  font-size: 12px;
  color: var(--text-muted);
  pointer-events: none;
  white-space: nowrap;
}

/* ─────────────────────────────────────────────────────────────
   EDITOR BOTTOM BAR
   ───────────────────────────────────────────────────────────── */
#bottom-bar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  height: var(--bottom-h);
  background: var(--panel);
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  z-index: 20;
}

.status-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-sec);
}

.status-dot-sm {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border-mid);
  flex-shrink: 0;
  transition: background 0.3s;
}

.status-dot-sm.detected {
  background: var(--success);
  box-shadow: 0 0 5px var(--success);
}

.status-dot-sm.active {
  background: var(--teal);
  box-shadow: 0 0 5px var(--teal);
  animation: blink 1.4s ease-in-out infinite;
}

.status-dot-sm.error { background: var(--danger); }

@keyframes blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

.bottom-actions { display: flex; gap: 8px; }

/* ─────────────────────────────────────────────────────────────
   COMPACT BAND — 900px … 1429px

   The band where the old layout failed: at 1024px the canvas was 544px
   and the gizmo 380px, so it covered 70% of the figure. Shrinking the
   gizmo does not rescue that — at 280px it still eats half.

   The fix is not a breakpoint, it is the collapse system. Both rails
   collapse to slivers on demand at any width, and a narrower gizmo here
   means one collapsed rail is enough to clear the model: 300px of gizmo
   against a 784px canvas is 38% instead of 70%.
   ───────────────────────────────────────────────────────────── */
@media (min-width: 900px) and (max-width: 1429px) {
  body { --gizmo-w: 300px; }
}

/* ─────────────────────────────────────────────────────────────
   NARROW SCREENS — collapse, never delete

   Below 900px both rails arrive collapsed (responsiveShell.js), so the
   canvas gets the width while every panel stays one tap from the sliver.
   Expanding a rail here floats it over the canvas instead of squeezing
   it: at 360px an inline 240px rail would leave 120px of model.

   The canvas-edge values stay pinned to the sliver in this band, so the
   gizmo keeps its position while a rail sits open on top of it.
   ───────────────────────────────────────────────────────────── */
@media (max-width: 899px) {
  body {
    --canvas-left:  var(--rail-stub);
    /* Two slivers on the right now: the mid column and the joint selector. */
    --canvas-right: var(--prail-w);
    /* An expanded mid column overlays rather than squeezing. */
    --mid-w: min(272px, calc(calc(100vw / var(--ui-scale, 1)) - var(--rail-stub) * 2 - var(--overlay-gap) * 3));
  }

  /* Expanded columns float above the canvas overlays, and never take the
     whole screen — the other slivers stay reachable. */
  #left-panel,
  #right-panel {
    z-index: 25;
    max-width: calc(calc(100vw / var(--ui-scale, 1)) - var(--rail-stub) * 2);
  }
  #mid-panel,
  #mini-gizmo-panel { z-index: 26; }

  body:not(.left-collapsed)  #left-panel  { box-shadow:  14px 0 40px rgba(0, 0, 0, 0.5); }
  body:not(.right-collapsed) #right-panel { box-shadow: -14px 0 40px rgba(0, 0, 0, 0.5); }

  /* The square gizmo canvas is `width: 100%` with `aspect-ratio: 1`, so the
     panel's width is also its height budget. Trim the padding here; the cap
     on the square itself is below. */
  #mini-gizmo-panel {
    padding: 10px;
    /* Nothing inside may push the header out of view. */
    overflow-x: hidden;
  }

  /* The square gizmo is the panel's whole height budget, so cap it directly
     instead of letting it inherit the panel width. */
  .gizmo-wrap { max-width: 168px; margin: 0 auto 10px; }

  #selected-joint-name { font-size: 13px; margin-bottom: 6px; }
  .rot-pad             { padding: 9px; margin-top: 8px; }
  .rot-pad-header      { gap: 6px; margin-bottom: 6px; }
  .gizmo-mode-opt      { font-size: 10px; padding: 3px 4px; }

  /* A select's intrinsic width comes from its widest option ("Same
     direction"), which is what pushed the panel wider than its own box and
     scrolled the header title out of view. */
  .mirror-mode-select,
  #hand-preset-select {
    min-width: 0;
    max-width: 100%;
    font-size: 11px;
  }
  .rot-pad-header > * { min-width: 0; }

  /* Desktop language ("Scroll to zoom") and it sits exactly where the gizmo
     does — two floating things fighting for the same spot. */
  #canvas-hint { display: none; }

  /* Same conflict, bottom-left. The gizmo is the one you came for. */
  #recent-pose-strip { display: none; }

  /* Compact type in the rails too: "all panels smaller" means the rails, not
     just the floating one. Structural padding is left alone so nothing
     reflows unexpectedly. */
  #left-panel,
  #right-panel { font-size: 12px; }
  .joint-item  { padding: 8px 12px; font-size: 12px; }
  .bm-svg      { max-height: 210px; }
}

/* The middle band keeps a 300px gizmo, so its square needs a cap of its own —
   268px of gizmo plus a rotation pad overruns a 768px-tall laptop. */
@media (min-width: 900px) and (max-width: 1429px) {
  .gizmo-wrap { max-width: 236px; margin-left: auto; margin-right: auto; }
}

@media (max-width: 640px) {
  #canvas-hint {
    left: 50%;
  }
}

/* ─────────────────────────────────────────────────────────────
   ADMIN PANEL
   ───────────────────────────────────────────────────────────── */
#admin-panel {
  position: fixed;
  top: var(--topbar-h);
  left: 0;
  right: 0;
  bottom: 0;
  background: #0f0f1e;
  overflow-y: auto;
  z-index: 100;
  padding: 24px 32px;
}

.admin-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

/* Underline-indicator tab bar (Linear / Vercel style) — no per-tab borders,
   single accent reserved for the active state. */
.admin-tabs { display: flex; gap: 0; }
.admin-tab {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;   /* space reserved → no layout jump */
  color: var(--text-sec);
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  margin-bottom: -1px;            /* underline flushes with the nav's bottom border */
}
.admin-tab:hover { color: var(--text); }
.admin-tab.active {
  color: var(--text);
  font-weight: 600;
  border-bottom-color: var(--purple);
  background: transparent;
}

.admin-actions { display: flex; gap: 8px; }

.admin-body { padding-top: 8px; }
/* ═══════════════════════════════════════════════════════════════════════
   ADMIN SHELL — sidebar navigation (js/admin/index.js)

   Replaces the six-tab header. The tabs were named after the tools that
   built them (Pose Manager, Coverage Tracker, Library Inspector), which is
   an inventory of implementation rather than a description of the job, and
   a flat row cannot express that Review Queue and Coverage are both parts
   of one subject. A sidebar can nest, and it has room for a count against
   the one destination where work accumulates.

   The panel's own padding and scrolling stand down here: the shell owns
   both, because the sidebar has to reach the full height and scroll
   independently of the content beside it.
   ═══════════════════════════════════════════════════════════════════════ */

#admin-panel.has-shell { padding: 0; overflow: hidden; }

.admin-shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  height: 100%;
}

/* ── Sidebar ─────────────────────────────────────────────────────────── */
.admin-sidebar {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  padding: 18px 12px 12px;
  background: var(--panel);
  border-right: 1px solid var(--border);
}

.admin-brand { padding: 0 10px 18px; }
.admin-brand-name {
  display: block;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.3px;
  color: var(--text);
}
.admin-brand-sub {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: 2px;
}

.admin-nav { list-style: none; margin: 0; padding: 0; flex: 1; }
.admin-nav-sub { list-style: none; margin: 2px 0 6px; padding: 0 0 0 26px; }

.admin-nav-link {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: none;
  border-radius: var(--radius-btn);
  color: var(--text-sec);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background 0.13s, color 0.13s;
}
.admin-nav-link:hover { background: var(--card); color: var(--text); }
.admin-nav-link:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

/* The active row is the only filled one on the sidebar, so "where am I"
   never competes with a hover state for the same visual weight. */
.admin-nav-link.is-active {
  background: var(--purple-dim);
  color: var(--text);
  font-weight: 600;
}
.admin-nav-link.is-sub { font-size: 12.5px; padding: 6px 10px; justify-content: space-between; }

/* A group header is a signpost, not a destination — it lands on the first
   child. Dimmer than its children so the row you can act on reads first. */
.admin-nav-link.is-group {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  padding-top: 12px;
}
.admin-nav-link.is-group:hover { background: transparent; color: var(--text-sec); }

.admin-nav-icon {
  width: 16px;
  flex: none;
  text-align: center;
  font-size: 12px;
  opacity: 0.85;
}

/* Only ever rendered with a non-zero count — see AdminUI.reviewBadge. */
.admin-nav-badge {
  flex: none;
  min-width: 18px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--warning);
  color: #171326;
  font-size: 10px;
  font-weight: 700;
  text-align: center;
}

.admin-sidebar-foot {
  border-top: 1px solid var(--border);
  padding-top: 8px;
  margin-top: 8px;
}
.admin-nav-link.is-quiet { color: var(--text-muted); }
.admin-nav-link.is-quiet:hover { color: var(--text); }

/* ── Main column ─────────────────────────────────────────────────────── */
.admin-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.admin-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 26px;
  border-bottom: 1px solid var(--border);
  flex: none;
}
.admin-crumb {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.admin-crumb-sep { color: var(--text-muted); margin: 0 4px; font-weight: 400; }

.admin-shell .admin-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 24px 26px 40px;
}

/* Below ~900px two columns cost the content more than the navigation is
   worth. The sidebar becomes a scrolling strip above the content, keeping
   every destination reachable without a hamburger and a state to manage. */
@media (max-width: 899px) {
  .admin-shell { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
  .admin-sidebar {
    flex-direction: row;
    align-items: center;
    gap: 4px;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 8px 12px;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
  .admin-brand, .admin-sidebar-foot { display: none; }
  .admin-nav { display: flex; gap: 4px; }
  .admin-nav-sub { display: flex; padding: 0; margin: 0; gap: 4px; }
  .admin-nav-link { white-space: nowrap; padding: 7px 12px; }
  .admin-nav-link.is-group { display: none; }
  .admin-shell .admin-body { padding: 18px 16px 32px; }
  .admin-topbar { padding: 12px 16px; }
}

/* ═══ OVERVIEW (js/admin/overview.js) ═══════════════════════════════════ */

.ov-greet { margin-bottom: 22px; }
.ov-title {
  margin: 0;
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--text);
}
.ov-sub { margin: 4px 0 0; font-size: 13px; color: var(--text-sec); }

.ov-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 28px;
}

.ov-attention { list-style: none; margin: 0; padding: 0; }
.ov-att {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--card);
  margin-bottom: 8px;
}
.ov-att-text { flex: 1; font-size: 13px; color: var(--text); }
.ov-att-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-muted);
}
.ov-att--warn .ov-att-dot { background: var(--warning); }
.ov-att--bad  .ov-att-dot { background: var(--danger); }
.ov-att--bad  { border-color: rgba(245, 101, 101, 0.35); }

.ov-clear { font-size: 13px; color: var(--text-sec); margin: 0; max-width: 62ch; }

.ov-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}
.ov-status {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s, background 0.15s;
}
.ov-status:hover { border-color: var(--border-focus); background: var(--card-hover); }
.ov-status-n { font-size: 20px; font-weight: 650; color: var(--text); }
.ov-status-l { font-size: 11px; color: var(--text-sec); }

.ov-note { font-size: 12px; color: var(--text-sec); margin: 12px 0 0; max-width: 70ch; }
.ov-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--teal);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ═══ REVIEW QUEUE (js/admin/reviewQueue.js) ════════════════════════════ */

.rq-head { margin-bottom: 20px; }
.rq-title {
  margin: 0;
  font-size: 19px;
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--text);
}
.rq-sub { margin: 5px 0 0; font-size: 12.5px; color: var(--text-sec); max-width: 72ch; }

.rq-list { display: flex; flex-direction: column; gap: 12px; }

.rq-card {
  display: grid;
  grid-template-columns: 132px minmax(0, 1fr) auto;
  gap: 18px;
  align-items: start;
  padding: 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}
.rq-thumb {
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-btn);
  overflow: hidden;
  background: var(--bg);
}
.rq-thumb img { width: 100%; height: 100%; object-fit: cover; }

.rq-name { margin: 0 0 10px; font-size: 15px; font-weight: 600; color: var(--text); }

.rq-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px 16px;
  margin: 0;
}
.rq-facts dt {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-muted);
}
.rq-facts dd { margin: 2px 0 0; font-size: 12.5px; color: var(--text); }
.rq-ok { color: var(--success); }
.rq-no { color: var(--warning); }
/* The submitter's name. Teal marks it as a person rather than a setting —
   the one fact on this card that is about who is waiting on the answer. */
.rq-from { color: var(--teal); font-weight: 600; }

.rq-desc {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: var(--lh-body);
  color: var(--text-sec);
  max-width: 68ch;
}

.rq-actions { display: flex; flex-direction: column; gap: 6px; min-width: 128px; }

.rq-empty { text-align: center; padding: 48px 20px; }
.rq-empty-mark {
  width: 44px;
  height: 44px;
  margin: 0 auto 14px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--success-dim);
  color: var(--success);
  font-size: 20px;
}
.rq-empty p { margin: 6px auto 16px; }

@media (max-width: 899px) {
  .rq-card { grid-template-columns: 96px minmax(0, 1fr); }
  .rq-actions { grid-column: 1 / -1; flex-direction: row; flex-wrap: wrap; }
}

/* ═══ SYSTEM (js/admin/system.js) ═══════════════════════════════════════ */

.sys-health { list-style: none; margin: 0; padding: 0; }
.sys-row {
  display: grid;
  grid-template-columns: 14px 150px minmax(0, 1fr);
  gap: 4px 12px;
  align-items: baseline;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.sys-row:last-child { border-bottom: none; }
.sys-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-muted);
  align-self: center;
}
.sys-dot--ok      { background: var(--success); }
.sys-dot--warn    { background: var(--warning); }
.sys-dot--bad     { background: var(--danger); }
.sys-dot--unknown { background: var(--text-muted); }
.sys-label { font-size: 12.5px; color: var(--text-sec); }
.sys-value { font-size: 13px; color: var(--text); }
.sys-note {
  grid-column: 3;
  font-size: 11.5px;
  color: var(--text-muted);
  max-width: 72ch;
}

/* Sections whose backend does not exist yet. Deliberately plain — this is a
   specification, and dressing it up as a working screen is how a
   placeholder gets mistaken for a feature. */
.sys-pending { max-width: 62ch; }
.sys-pending-tag {
  display: inline-block;
  margin-bottom: 10px;
  padding: 3px 9px;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-muted);
}
.sys-pending-why,
.sys-pending-note {
  font-size: 13px;
  line-height: var(--lh-body);
  color: var(--text-sec);
}
.sys-pending-note { color: var(--text-muted); font-size: 12px; }
.sys-pending-needs {
  margin: 16px 0;
  padding: 14px 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}
.sys-pending-needs .admin-section-title { margin-bottom: 8px; }
.sys-pending-needs ul { margin: 0; padding-left: 18px; }
.sys-pending-needs li { font-size: 12.5px; color: var(--text-sec); margin-bottom: 5px; }
.sys-pending code {
  font-size: 11.5px;
  color: var(--teal);
  background: var(--bg);
  padding: 1px 5px;
  border-radius: 4px;
}


/* Stat cards */
.admin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
  margin-bottom: 28px;
}
.admin-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px 18px;
}
.admin-card-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-sec);
  margin-bottom: 6px;
}
.admin-card-value {
  font-size: 32px;
  font-weight: 700;
  color: var(--text);
}

/* Category coverage */
.admin-section { margin-bottom: 28px; }
.admin-section-title {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-sec);
  margin-bottom: 12px;
}
.admin-coverage {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
}
.coverage-row {
  display: grid;
  grid-template-columns: 140px 1fr 60px;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
}
.coverage-name { font-size: 13px; color: var(--text); }
.coverage-bar {
  height: 8px;
  background: #1a1a2e;
  border-radius: 4px;
  overflow: hidden;
}
/* Progress fills scale instead of resizing.
   Animating width relayouts the bar on every frame; scaleX is composited.
   The fill is full-width and squashed by transform, and because a 90deg
   gradient spans the element either way the painted result is identical.
   The track's own border-radius + overflow:hidden does the clipping, so the
   fills no longer carry a radius of their own. */
.coverage-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, #4ecdc4, #8b7ec8);
  transform-origin: left center;
  transition: transform 0.3s;
}
.coverage-count {
  font-size: 11px;
  font-family: monospace;
  color: var(--text-sec);
  text-align: right;
}

/* Pose manager toolbar */
.admin-toolbar {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.admin-toolbar input,
.admin-toolbar select {
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 13px;
}
.admin-toolbar input { min-width: 240px; }

/* Pose grid (manager view) */
/* --gal-card is the gallery's card-size slider (gallery/index.js), set on the
   gallery's own #admin-body only — the admin grids never see it and stay at
   260. min() keeps one card from overflowing a window narrower than it. */
.pose-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(var(--gal-card, 260px), 100%), 1fr));
}

.admin-header .gal-size { gap: 4px; color: var(--text-sec); }
.gal-size input[type="range"] {
  width: 128px;
  margin: 0 2px;
  accent-color: var(--teal);
  cursor: pointer;
}
.gal-size-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: inherit;
  cursor: pointer;
  transition: color 0.15s, background-color 0.15s;
}
.gal-size-btn svg { width: 16px; height: 16px; }
.gal-size-btn:hover { color: var(--text); background: var(--card-hover); }
.gal-size-btn:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.pose-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.pose-thumb {
  aspect-ratio: 1;
  background: #0a0a14;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.pose-thumb img { width: 100%; height: 100%; object-fit: cover; }
.pose-thumb .no-thumb {
  color: var(--text-sec);
  font-size: 11px;
  font-style: italic;
}
.pose-meta { padding: 12px; }
.pose-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
}
.pose-tags { display: flex; gap: 4px; margin-bottom: 10px; flex-wrap: wrap; }
.pose-tag {
  font-size: 10px;
  padding: 2px 8px;
  border-radius: 4px;
  background: #1a1a2e;
  border: 1px solid #2a2a40;
  color: var(--text-sec);
}
.pose-row-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.pose-row-actions button {
  height: 32px;
  padding: 0 14px;
  font-size: 12.5px;
}
/* The glyph-only actions (favourite, delete) are square, with the glyph a
   touch larger than the text so it reads at the same weight. */
.pose-row-actions .btn-secondary {
  min-width: 32px;
  padding: 0 9px;
  font-size: 15px;
}

/* Submit carries a WORD, so it opts back out of the glyph metrics above.
   It is a secondary button on purpose: Apply is what people came to the
   card for, and sending a pose to a reviewer is the rarer, heavier act. */
.pose-row-actions .gal-submit {
  padding: 0 12px;
  font-size: 12.5px;
  font-weight: 600;
}
/* Already waiting on somebody. Marked in the warning tone the review queue
   uses for the same state, not in teal — teal here would read as done. */
.pose-row-actions .gal-submit.is-pending {
  border-color: color-mix(in srgb, var(--warning) 55%, var(--border));
  color: var(--warning);
}
.pose-row-actions .gal-submit[disabled] { opacity: 0.55; cursor: default; }

/* Status chips beside the category. Same shape as .pose-tag, tinted by what
   they mean: amber while a person still has to decide, teal once one has said
   yes, red once one has said no. */
.pose-tag--review {
  background: color-mix(in srgb, var(--warning) 16%, transparent);
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
  color: var(--warning);
}
.pose-tag--live {
  background: var(--teal-dim);
  border-color: color-mix(in srgb, var(--teal) 45%, transparent);
  color: var(--teal);
}
.pose-tag--rejected {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  color: var(--danger);
}

/* Modal dialog */
.admin-modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.admin-modal-content {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  width: 440px;
  max-width: calc(90vw / var(--ui-scale, 1));
  color: var(--text);
}
.admin-modal-content h3 {
  margin: 0 0 16px;
  font-size: 16px;
}
.admin-modal-content label {
  display: block;
  margin-bottom: 10px;
  font-size: 12px;
  color: var(--text-sec);
}
.admin-modal-content input,
.admin-modal-content select,
.admin-modal-content textarea {
  width: 100%;
  margin-top: 4px;
  padding: 6px 10px;
  background: #1a1a2e;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  box-sizing: border-box;
}

/* ─────────────────────────────────────────────────────────────
   ROLE SELECTOR MODAL
   ───────────────────────────────────────────────────────────── */
.role-modal {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.85);
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.role-modal-content {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 32px;
  max-width: 720px;
  width: 100%;
  text-align: center;
}
.role-modal-content h2 {
  color: var(--text);
  margin: 0 0 8px;
  font-size: 22px;
}
.role-modal-content p {
  color: var(--text-sec);
  margin-bottom: 24px;
}
.role-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.role-option {
  background: #1a1a2e;
  border: 2px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  cursor: pointer;
  text-align: left;
  transition: all 0.2s;
  color: var(--text);
}
.role-option:hover {
  border-color: var(--purple);
  background: var(--purple-dim);
  transform: translateY(-2px);
}
.role-icon { font-size: 40px; margin-bottom: 10px; }
.role-name { font-size: 18px; font-weight: 600; margin-bottom: 4px; }
.role-desc { font-size: 12px; color: var(--text-sec); margin-bottom: 10px; }
.role-option ul {
  list-style: none;
  padding: 0;
  font-size: 11px;
  color: var(--text-sec);
}
.role-option ul li {
  padding: 2px 0;
}
.role-option ul li::before {
  content: "✓ ";
  color: var(--teal);
  font-weight: bold;
}

/* Settings panel uses admin panel base styles */
#settings-panel {
  position: fixed;
  top: var(--topbar-h);
  left: 0; right: 0; bottom: 0;
  background: var(--bg);
  overflow-y: auto;
  z-index: 100;
  padding: 24px 32px;
}

/* Coverage tracker per-category blocks */
.coverage-cat-block {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin-bottom: 12px;
}
.coverage-cat-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.coverage-cat-meta {
  font-size: 11px;
  color: var(--text-sec);
}
.coverage-empty {
  font-size: 12px;
  color: var(--text-sec);
  font-style: italic;
  padding: 8px 0;
}
.coverage-list {
  list-style: none;
  padding: 0;
}
.coverage-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.coverage-item:last-child { border-bottom: none; }
.coverage-check {
  width: 18px;
  color: var(--text-sec);
  font-family: monospace;
}
.coverage-check.checked { color: var(--purple); }
.coverage-item-name { flex: 1; color: var(--text); }

/* Floating tip shown on first entry to admin Pose Editor */
.admin-editor-tip {
  position: fixed;
  top: calc(var(--topbar-h) + 80px);
  left: 32px;
  background: var(--card);
  border: 1px solid var(--purple);
  border-radius: 10px;
  padding: 14px 18px;
  max-width: 320px;
  font-size: 12px;
  color: var(--text);
  z-index: 200;
  line-height: 1.5;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}

/* ─────────────────────────────────────────────────────────────
   ADMIN POSE EDITOR — left sidebar (templates, metadata, save)
   ───────────────────────────────────────────────────────────── */
#admin-editor-sidebar {
  position: fixed;
  top: calc(var(--topbar-h) + 64px); /* below admin header */
  left: 16px;
  width: 280px;
  max-height: calc(calc(100vh / var(--ui-scale, 1)) - var(--topbar-h) - 80px);
  overflow-y: auto;
  background: rgba(20,18,42,0.95);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  z-index: 115; /* above 3D canvas, below modals */
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
  font-size: 12px;
  color: var(--text);
}
.admin-side-section {
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.admin-side-section:first-child { padding-top: 4px; }
.admin-side-section:last-child  { border-bottom: none; padding-bottom: 4px; }
.admin-side-title {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-sec);
  margin-bottom: 10px;
  font-weight: 600;
}
#admin-editor-sidebar label {
  display: block;
  margin-bottom: 8px;
  font-size: 11px;
  color: var(--text-sec);
}
#admin-editor-sidebar input,
#admin-editor-sidebar select,
#admin-editor-sidebar textarea {
  width: 100%;
  margin-top: 4px;
  padding: 6px 8px;
  background: #1a1a2e;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-size: 12px;
  font-family: inherit;
  box-sizing: border-box;
}
#admin-editor-sidebar textarea { resize: vertical; min-height: 40px; }
.admin-side-hint {
  margin-top: 6px;
  font-size: 11px;
  color: var(--text-sec);
  line-height: 1.5;
}

/* Toast notification */
#admin-toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--purple);
  color: #fff;
  padding: 10px 18px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  z-index: 500;
  opacity: 0;
  transition: opacity 0.25s;
  box-shadow: 0 6px 20px rgba(0,0,0,0.5);
  pointer-events: none;
}

/* Push the admin pose editor sidebar below the fixed admin header */
#admin-panel .admin-header[style*="position: fixed"] ~ #admin-body { padding-top: 70px; }
/* Header is now 56px (single row, no tabs) so sidebar can sit higher */
#admin-editor-sidebar {
  top: calc(var(--topbar-h) + 70px);
  max-height: calc(calc(100vh / var(--ui-scale, 1)) - var(--topbar-h) - 90px);
}

/* Admin sidebar — navigation grid (always visible while in Pose Editor) */
.admin-side-nav-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}
.admin-side-nav-btn {
  background: #1a1a2e;
  border: 1px solid var(--border);
  color: var(--text-sec);
  padding: 6px 8px;
  border-radius: 5px;
  font-size: 11px;
  cursor: pointer;
  text-align: left;
  transition: all 0.15s;
  font-family: inherit;
}
.admin-side-nav-btn:hover {
  background: var(--purple-dim);
  color: var(--text);
  border-color: var(--purple);
}
.admin-side-nav { background: rgba(139, 126, 200, 0.06); margin: -14px -14px 0; padding: 14px; border-radius: 10px 10px 0 0; }

/* Admin Pose Editor — fixed top navigation bar
   Layout matches the Dashboard's .admin-header: label on top row (LEFT),
   tabs + actions on the row below. Bounded height + solid background to
   avoid backdrop-blur artifacts over the WebGL canvas. */
#admin-editor-topnav {
  position: fixed;
  top: 0;                       /* topbar is hidden in admin Pose Editor */
  left: 0;
  right: 0;
  height: 60px;
  padding: 0 16px;
  display: none;                /* shown via JS as flex */
  flex-direction: row;
  align-items: center;
  background: #14132a;          /* solid — no backdrop blur over canvas */
  border-bottom: 1px solid var(--border);
  z-index: 200;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  overflow: hidden;
}
.admin-top-tab {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-sec);
  padding: 6px 12px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
  transition: all 0.15s;
  font-family: inherit;
  white-space: nowrap;
}
.admin-top-tab:hover {
  background: var(--card);
  color: var(--text);
}
.admin-top-tab.active {
  background: var(--purple-dim);
  color: var(--text);
  border-color: var(--purple);
}

/* Old shifted-position rule — superseded by the rule above (was for 48px nav) */

/* Coverage Tracker — legend + clickable checklist items */
.coverage-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 16px;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.coverage-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-sec);
}
.coverage-legend-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.coverage-clickable {
  cursor: pointer;
  /* Slides on transform, not padding. The nudge used to animate padding-left
     10px -> 14px, which reflows the row on every hover; translateX is
     visually identical and stays off the layout path. */
  transition: background 0.15s, transform 0.15s;
  padding-left: 10px !important;
}
.coverage-clickable:hover {
  background: var(--card-hover, #1c1a32);
  transform: translateX(4px);
}

.coverage-status-badge {
  font-size: 10px;
  padding: 2px 8px;
  border-radius: 4px;
  font-weight: 600;
  white-space: nowrap;
}

/* Coverage tracker toolbar — legend left, grouping toggle right */
.coverage-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.coverage-grouping {
  display: flex;
  gap: 4px;
}
.coverage-sub {
  font-size: 10px;
  color: var(--text-sec);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-left: 8px;
  font-weight: 500;
}

/* ─────────────────────────────────────────────────────────────
   COVERAGE TRACKER — VISUAL CARDS
   ───────────────────────────────────────────────────────────── */
.coverage-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
  gap: 12px;
}
.coverage-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  transition: border-color 0.15s;
}
.coverage-card:hover { border-color: var(--purple); }
.coverage-card.expanded {
  grid-column: 1 / -1;   /* expanded card spans full row */
}
.coverage-card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  margin-bottom: 8px;
}
.coverage-card-title {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
  font-size: 14px;
}
.coverage-card-chevron {
  font-size: 10px;
  color: var(--text-sec);
}
.coverage-card-stats { font-size: 12px; color: var(--text-sec); }
.coverage-card-fraction {
  font-family: monospace;
  font-size: 13px;
  color: var(--text);
}
.coverage-card-progress {
  height: 8px;
  background: #1a1a2e;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 6px;
}
.coverage-card-progress-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--teal), var(--purple));
  transform-origin: left center;
  transition: transform 0.3s;
}
.coverage-card-meta {
  font-size: 11px;
  color: var(--text-sec);
  margin-bottom: 10px;
}

/* ─────────────────────────────────────────────────────────────
   DASHBOARD — BIG PROGRESS CARD
   ───────────────────────────────────────────────────────────── */
.big-progress-card {
  background: linear-gradient(135deg, rgba(139,126,200,0.15), rgba(78,205,196,0.15));
  border: 1px solid var(--purple);
  border-radius: 12px;
  padding: 24px;
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 24px;
  align-items: center;
  margin-bottom: 14px;
}
.big-progress-fraction {
  font-size: 48px;
  font-weight: 700;
  color: var(--text);
  line-height: 1;
}
.big-progress-label {
  font-size: 13px;
  color: var(--text-sec);
  margin-top: 6px;
}
.big-progress-bar {
  height: 24px;
  background: rgba(0,0,0,0.3);
  border-radius: 12px;
  overflow: hidden;
}
.big-progress-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--teal), var(--purple));
  transform-origin: left center;
  transition: transform 0.4s;
}

.phase-breakdown {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin-top: 14px;
}
.phase-row {
  display: grid;
  grid-template-columns: 220px 1fr 60px;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
}
.phase-name { font-size: 13px; color: var(--text); }
.phase-bar {
  height: 6px;
  background: #1a1a2e;
  border-radius: 3px;
  overflow: hidden;
}
.phase-bar-fill {
  height: 100%;
  background: var(--teal);
  border-radius: 3px;
}
.phase-count {
  font-family: monospace;
  font-size: 11px;
  color: var(--text-sec);
  text-align: right;
}

/* ─────────────────────────────────────────────────────────────
   DASHBOARD — RECENTLY CREATED
   ───────────────────────────────────────────────────────────── */
.recent-list {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.recent-row {
  display: grid;
  grid-template-columns: 48px 1fr auto auto;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}
.recent-row:last-child { border-bottom: none; }
.recent-thumb {
  width: 48px;
  height: 48px;
  border-radius: 6px;
  overflow: hidden;
  background: #0a0a14;
}
.recent-thumb img { width: 100%; height: 100%; object-fit: cover; }
.recent-thumb .no-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: var(--text-muted);
  font-size: 12px;
}
.recent-name {
  font-size: 13px;
  color: var(--text);
  font-weight: 600;
}
.recent-meta {
  font-size: 11px;
  color: var(--text-sec);
  margin-top: 2px;
}
.recent-date {
  font-size: 11px;
  color: var(--text-sec);
  font-family: monospace;
}

/* ─────────────────────────────────────────────────────────────
   ANALYTICS PAGE
   ───────────────────────────────────────────────────────────── */
.analytics-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.analytics-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
}
.analytics-card-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-sec);
  margin-bottom: 6px;
}
.analytics-card-value {
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 4px;
}
/* "No data yet" is the absence of an answer, so it should not be typeset
   with the same authority as a real pose name. */
.analytics-card-value.is-empty {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-sec);
}
.analytics-card-sub {
  font-size: 11px;
  color: var(--text-sec);
}

/* Pose Manager empty state. Spans the whole grid so it centres against the
   panel rather than the first column, and holds its own exit — the previous
   version was centred prose telling you which button to go and find. */
.pm-empty {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: 56px var(--space-5);
  text-align: center;
}
.pm-empty-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.pm-empty-body {
  max-width: 42ch;
  font-size: 12.5px;
  line-height: var(--lh-body);
  color: var(--text-sec);
}

.analytics-rec-list {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px;
}
.analytics-rec-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  font-size: 13px;
  color: var(--text);
  border-bottom: 1px solid var(--border);
}
.analytics-rec-row:last-child { border-bottom: none; }
/* Matches .scope-rec-n in the Coverage Tracker — both answer "author this
   next", so they count the same way. Replaces a monospaced ☐ that read as a
   tickable box and was not one. */
.analytics-rec-n {
  flex: none;
  width: 14px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ─────────────────────────────────────────────────────────────
   AUTH MODAL (login / register)
   ───────────────────────────────────────────────────────────── */
.auth-modal {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.auth-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 32px;
  width: 100%;
  max-width: 380px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}
.auth-brand {
  /* Solid, like the topbar .brand. It is the same wordmark in both places,
     so it should not read as two different marks. */
  font-size: 22px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -0.5px;
  margin-bottom: 8px;
  text-align: center;
}
.auth-card h2 {
  font-size: 18px;
  color: var(--text);
  margin-bottom: 6px;
  text-align: center;
}
.auth-sub {
  text-align: center;
  font-size: 12px;
  color: var(--text-sec);
  margin-bottom: 18px;
}
.auth-card label {
  display: block;
  font-size: 11px;
  color: var(--text-sec);
  margin-bottom: 12px;
}
.auth-card input {
  width: 100%;
  margin-top: 4px;
  padding: 9px 12px;
  background: #1a1a2e;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
}
.auth-card input:focus {
  outline: none;
  border-color: var(--purple);
}
.auth-error {
  background: rgba(245,101,101,0.1);
  border: 1px solid rgba(245,101,101,0.3);
  color: var(--danger, #f56565);
  font-size: 12px;
  padding: 8px 10px;
  border-radius: 6px;
  margin-bottom: 12px;
}
.auth-card button[type="submit"] {
  width: 100%;
  padding: 10px;
  background: linear-gradient(135deg, var(--purple), var(--teal));
  border: none;
  border-radius: 6px;
  color: #fff;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  margin-top: 4px;
}
.auth-switch {
  margin-top: 16px;
  text-align: center;
  font-size: 12px;
  color: var(--text-sec);
}
.auth-switch a {
  color: var(--purple);
  text-decoration: none;
  font-weight: 600;
}
.auth-switch a:hover { text-decoration: underline; }

/* ── Demo account quick-fill ──────────────────────────────────────
   Two rows above the form that fill it in one click. The credentials
   are printed in demoAccounts.js and visible in the page either way,
   so hiding them behind typing protects nothing and turns a live
   demo into a spelling test. Each row states what the account is
   FOR, because "admin vs user" is the one thing a viewer needs to
   understand before anything else on screen makes sense. */
.auth-demo {
  margin-bottom: var(--space-4, 16px);
  padding-bottom: var(--space-4, 16px);
  border-bottom: 1px solid var(--border);
}
.auth-demo-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-2, 8px);
}
.auth-demo-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  margin-bottom: 6px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-btn, 8px);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.auth-demo-btn:last-child { margin-bottom: 0; }
.auth-demo-btn:hover {
  background: var(--card-hover);
  border-color: var(--border-focus);
}
.auth-demo-avatar {
  flex: none;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  background: var(--border);
  border-radius: 50%;
  font-size: 13px;
}
.auth-demo-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.auth-demo-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}
.auth-demo-blurb {
  font-size: 10.5px;
  color: var(--text-sec);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.auth-demo-use {
  flex: none;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--teal);
}
/* ── Working states: the submit spinner and the post-sign-in cover ── */
.auth-card button[type="submit"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: filter 0.15s, opacity 0.15s;
}
.auth-card button[type="submit"].is-loading {
  cursor: progress;
  opacity: 0.85;
}
.auth-spinner {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  animation: auth-spin 0.7s linear infinite;
  flex: none;
}
@keyframes auth-spin { to { transform: rotate(360deg); } }

.auth-modal { transition: opacity 260ms cubic-bezier(0.16, 1, 0.3, 1); }
.auth-modal.is-leaving { opacity: 0; pointer-events: none; }

.auth-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: min(280px, 80vw);
  margin-bottom: 6vh;
  animation: auth-loading-in 0.25s ease-out both;
}
.auth-loading-ring {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 3px solid var(--border-mid);
  border-top-color: var(--teal);
  border-right-color: var(--purple);
  animation: auth-spin 0.8s linear infinite;
  margin-bottom: 4px;
}
.auth-loading-title {
  font-size: 19px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
}
.auth-loading-track {
  position: relative;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--border-mid);
  overflow: hidden;
}
.auth-loading-step {
  font-size: 12px;
  color: var(--text-sec);
  text-align: center;
  min-height: 1.2em;
}
@keyframes auth-loading-in { from { opacity: 0; transform: translateY(6px); } }

@media (prefers-reduced-motion: reduce) {
  .auth-spinner,
  .auth-loading-ring { animation-duration: 2s; }
  .auth-loading { animation: none; }
}

/* ── Welcome (back from a verified email link) ───────────────────── */
.welcome-card {
  text-align: center;
  padding-top: 36px;
  animation: auth-loading-in 0.3s ease-out both;
}
.welcome-badge {
  width: 72px;
  height: 72px;
  margin: 0 auto 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--success);
  background: var(--success-dim);
  box-shadow: 0 0 0 8px rgba(56, 217, 169, 0.06), 0 0 32px rgba(56, 217, 169, 0.18);
  animation: welcome-pop 0.5s 0.1s cubic-bezier(.2, 1.4, .4, 1) both;
}
.welcome-badge svg { width: 34px; height: 34px; }
.welcome-card .auth-brand {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.welcome-card h2 { font-size: 24px; margin-bottom: 10px; }
.welcome-lead {
  font-size: 15px;
  font-weight: 600;
  color: var(--success);
  margin: 0 0 6px;
}
.welcome-body {
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-sec);
  margin: 0 0 24px;
}
@keyframes welcome-pop { from { opacity: 0; transform: scale(0.6); } }
@media (prefers-reduced-motion: reduce) {
  .welcome-card, .welcome-badge { animation: none; }
}

/* ── "Check your inbox" (after register / unverified sign-in) ──────
   Own classes rather than .btn-primary: that one is the app's toolbar
   button (fixed height, teal fill) and does not belong on the auth card. */
.verify-pending { text-align: center; }
.verify-icon {
  width: 56px;
  height: 56px;
  margin: 4px auto 16px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--teal);
  background: var(--teal-dim);
  box-shadow: 0 0 0 6px rgba(78, 205, 196, 0.05);
}
.verify-icon svg { width: 26px; height: 26px; }
.verify-pending h2 { font-size: 20px; margin-bottom: 8px; }
.verify-lead {
  font-size: 13px;
  color: var(--text-sec);
  margin: 0 0 18px;
}
.verify-body {
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-sec);
  margin: 0 0 12px;
  padding: 12px 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.verify-email {
  color: var(--text);
  font-weight: 600;
  word-break: break-all;
}
.verify-hint {
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-muted);
  margin: 0 0 20px;
}
.verify-status {
  font-size: 12px;
  padding: 8px 10px;
  border-radius: 6px;
  margin: -6px 0 16px;
}
.verify-status--ok {
  background: var(--success-dim);
  border: 1px solid rgba(56, 217, 169, 0.3);
  color: var(--success);
}
.verify-status--error {
  background: var(--danger-dim);
  border: 1px solid rgba(245, 101, 101, 0.3);
  color: var(--danger);
}
.verify-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.verify-btn {
  width: 100%;
  padding: 11px 12px;
  border-radius: 8px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: filter 0.15s, background 0.15s, border-color 0.15s;
}
.verify-btn--primary {
  border: none;
  color: #fff;
  background: linear-gradient(135deg, var(--purple), var(--teal));
}
.verify-btn--primary:hover { filter: brightness(1.08); }
.verify-btn--ghost {
  background: transparent;
  color: var(--text-sec);
  border: 1px solid var(--border-mid);
}
.verify-btn--ghost:hover { color: var(--text); border-color: var(--border-focus); }
.verify-btn:disabled { opacity: 0.55; cursor: wait; }
.verify-btn:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

.auth-note {
  margin-top: var(--space-4, 16px);
  text-align: center;
  font-size: 10.5px;
  color: var(--text-muted);
}

/* ─────────────────────────────────────────────────────────────
   TOPBAR ACCOUNT CHIP
   Identity, not a mode switch. Shows WHO is signed in and their
   role, and is the single home for sign out / view as — the three
   scattered role buttons collapsed into this one menu.
   ───────────────────────────────────────────────────────────── */
.account-chip-wrap { position: relative; margin-left: var(--space-2, 8px); }

.account-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 4px 10px 4px 4px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.account-chip:hover { background: var(--border); border-color: var(--border-mid); }
.account-chip:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

.account-avatar {
  flex: none;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 11px;
  font-weight: 700;
  color: #0d0c18;
  background: var(--purple);
}
/* Teal marks the admin — the same colour the app already uses for
   authoring surfaces, so the chip agrees with the panels it opens. */
.account-avatar[data-role="admin"] { background: var(--teal); }

/* Profile picture over the initial. The initial stays underneath, so an image
   that is still loading — or failed and was removed — just shows the letter.
   Hidden until loaded: no broken-image icon ever flashes in the circle. */
.account-avatar { position: relative; overflow: hidden; }
.account-avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.2s;
}
.account-avatar-img.is-loaded { opacity: 1; }
.account-avatar--xl {
  width: 112px;
  height: 112px;
  font-size: 42px;
  box-shadow: 0 0 0 4px var(--card), 0 0 0 6px var(--border-mid);
}

/* ── Change profile picture dialog ─────────────────────────────── */
.avatar-editor {
  position: fixed;
  inset: 0;
  z-index: 3200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(8, 7, 16, 0.72);
  backdrop-filter: blur(4px);
  animation: auth-loading-in 0.18s ease-out both;
}
.avatar-editor-card {
  position: relative;
  width: 100%;
  max-width: 360px;
  padding: 28px 28px 24px;
  text-align: center;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}
.avatar-editor-card h2 { font-size: 18px; color: var(--text); margin: 0 0 4px; }
.avatar-editor-sub { font-size: 12px; color: var(--text-sec); margin: 0 0 20px; }
.avatar-editor-x {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.avatar-editor-x:hover { color: var(--text); background: var(--border); }
.avatar-drop {
  position: relative;
  display: inline-block;
  margin: 0 auto 14px;
  border-radius: 50%;
  cursor: pointer;
}
.avatar-drop .account-avatar--xl { transition: box-shadow 0.15s, filter 0.15s; }
.avatar-drop:hover .account-avatar--xl { filter: brightness(1.08); }
.avatar-drop.is-over .account-avatar--xl {
  box-shadow: 0 0 0 4px var(--card), 0 0 0 6px var(--teal);
}
.avatar-drop-badge {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: #0d0c18;
  background: var(--teal);
  box-shadow: 0 0 0 3px var(--card);
}
.avatar-drop-badge svg { width: 16px; height: 16px; }
.avatar-editor-hint {
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--text-muted);
  margin: 0 0 18px;
}
.avatar-editor-hint a { color: var(--purple); }
.avatar-editor-status { margin: -6px 0 14px; }
.avatar-editor-actions { display: flex; flex-direction: column; gap: 8px; }
.avatar-editor-actions .verify-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
/* The inline-flex above would otherwise beat the [hidden] attribute, and
   "Save picture" would show before anything was chosen. */
.avatar-editor-actions .verify-btn[hidden] { display: none; }
.avatar-remove { color: var(--danger); }
.avatar-remove:hover { color: var(--danger); border-color: rgba(245, 101, 101, 0.45); }

.account-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.25;
}
.account-name { font-size: 12px; font-weight: 600; color: var(--text); }
.account-role {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-sec);
}

/* Mounted on <body> and placed from the chip by auth.js, so it is not
   trapped under the top bar's stacking context. */
.account-menu {
  position: fixed;
  top: calc(var(--topbar-h) + 6px);
  right: 12px;
  width: 256px;
  max-width: calc(calc(100vw / var(--ui-scale, 1)) - 24px);
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: 14px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.02) inset;
  padding: 6px;
  z-index: 3100;   /* above the topbar (50) and the panels (100) */
  transform-origin: top right;
  animation: account-menu-in 0.14s ease-out;
}
.account-menu[hidden] { display: none; }
@keyframes account-menu-in {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .account-menu { animation: none; } }

/* Header: who is signed in, on a tinted band. */
.account-menu-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  margin-bottom: 6px;
  border-radius: 10px;
  background: linear-gradient(135deg, rgba(139, 126, 200, 0.14), rgba(78, 205, 196, 0.06));
}
.account-avatar--lg {
  width: 40px;
  height: 40px;
  font-size: 16px;
  box-shadow: 0 0 0 3px rgba(139, 126, 200, 0.18);
}
.account-avatar--lg[data-role="admin"] { box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.18); }
.account-menu-who   { min-width: 0; }
.account-menu-name  { font-size: 13.5px; font-weight: 700; color: var(--text); line-height: 1.3;
                      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-menu-email { font-size: 11.5px; color: var(--text-sec); line-height: 1.35;
                      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-menu-tag {
  display: inline-block;
  margin-top: 6px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--purple);
  background: rgba(139, 126, 200, 0.14);
}
.account-menu-tag[data-role="admin"] { color: var(--teal); background: rgba(78, 205, 196, 0.12); }

.account-menu-group { display: flex; flex-direction: column; gap: 2px; }

.account-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 38px;
  padding: 0 10px;
  background: transparent;
  border: none;
  border-radius: 8px;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.account-menu-item svg {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--text-sec);
  transition: color 0.12s ease;
}
.account-menu-item:hover,
.account-menu-item:focus-visible { background: var(--card-hover); }
.account-menu-item:hover svg,
.account-menu-item:focus-visible svg { color: var(--teal); }
.account-menu-item:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

/* The way out, set apart and tinted on hover so it is never a misclick
   away from Settings. */
.account-menu-item--signout:hover,
.account-menu-item--signout:focus-visible { background: rgba(245, 101, 101, 0.1); color: var(--danger); }
.account-menu-item--signout:hover svg,
.account-menu-item--signout:focus-visible svg { color: var(--danger); }

.account-menu-sep { height: 1px; margin: 6px 4px; background: var(--border); }

/* On a phone the name and role do not fit next to the nav; the avatar
   alone still says who you are, and the menu carries the rest. */
@media (max-width: 699px) {
  .account-meta { display: none; }
  .account-chip { padding: 4px; border-color: transparent; }
}

/* ─────────────────────────────────────────────────────────────
   VIEW-AS BANNER
   An admin looking at the user experience is in a state the UI
   otherwise gives no sign of — same reasoning as the Artist Mode
   lock badges. It stays visible in Focus Mode for that reason:
   the one thing worse than chrome you did not want is a mode you
   cannot tell you are in.
   ───────────────────────────────────────────────────────────── */
.view-as-banner {
  position: fixed;
  top: calc(var(--topbar-h, 48px) + 10px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: calc(calc(100vw / var(--ui-scale, 1)) - 24px);
  padding: 8px 10px 8px 14px;
  background: var(--warning-dim);
  border: 1px solid var(--warning);
  border-radius: 999px;
  backdrop-filter: blur(8px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  color: var(--text);
  font-size: 12px;
  z-index: 3050;
}
.view-as-banner strong { color: var(--warning); }
.view-as-banner button {
  flex: none;
  padding: 5px 12px;
  background: var(--warning);
  border: none;
  border-radius: 999px;
  color: #0d0c18;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}
.view-as-banner button:hover { filter: brightness(1.08); }
.view-as-banner button:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

@media (max-width: 699px) {
  .view-as-banner {
    /* The phone tab bar owns the bottom; the topbar owns the top edge.
       Full-width under the topbar is the only strip that is free. */
    left: 12px;
    right: 12px;
    transform: none;
    max-width: none;
    justify-content: space-between;
    font-size: 11px;
  }
}

/* ─────────────────────────────────────────────────────────────
   ADMIN PANEL TITLE — sits ABOVE the tabs (like the PoseForge title)
   ───────────────────────────────────────────────────────────── */
.admin-panel-label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  color: var(--text-muted);
  text-transform: uppercase;
  display: block;
  margin: 0 0 6px -1px;        /* -1px compensates for letter-spacing optical offset */
  text-align: left;
  align-self: flex-start;
}

/* Dashboard / other views — header is a stacked block:
   ROW 1: ADMIN PANEL title (full width)
   ROW 2: [tabs left ............. actions right]                          */
.admin-header {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 16px;
  row-gap: 0;
}
.admin-header .admin-panel-label { grid-column: 1 / -1; }
.admin-header .admin-tabs        { grid-column: 1; display: flex; gap: 0; flex-wrap: wrap; }
.admin-header .admin-actions     { grid-column: 2; display: flex; gap: 8px; align-items: center; }

/* Pose Editor top nav — SAME visual layout as .admin-header (stacked
   title on top, tabs + actions on the row below).  The container itself
   stays fixed-positioned above the 3D canvas via its prior rules. */
/* ── Pose Editor header ──────────────────────────────────────────────
   Slim editor chrome: ~60px tall, single horizontal baseline shared by
   back button, page title, and action buttons. 16px outer padding,
   24px between major button groups. Title gets the primary visual
   weight so it reads as a page anchor.
*/
#admin-editor-topnav {
  display: none;
  flex-direction: row;
  align-items: center;
  padding: 0 16px;
  min-height: 60px;
  height: 60px;
}
#admin-editor-topnav .admin-panel-label { margin-bottom: 0; }
#admin-editor-topnav .admin-topnav-row {
  display: flex;
  align-items: center;
  gap: 24px;
  width: 100%;
  height: 100%;
}
#admin-editor-topnav .admin-topnav-row .admin-tabs {
  flex: 1;
  display: flex;
  gap: 0;
  flex-wrap: wrap;
}
#admin-editor-topnav .admin-topnav-row .admin-actions {
  display: flex;
  gap: 8px;
}

/* ─────────────────────────────────────────────────────────────
   NAV BAR — final polish: action buttons match tab metrics +
   continuous bottom border under the tabs (so the underline
   indicator on the active tab sits on that line cleanly).
   ───────────────────────────────────────────────────────────── */

.admin-header,
#admin-editor-topnav {
  border-bottom: 1px solid var(--border);
}
/* Remove the duplicated padding-bottom — the bottom border now provides the
   separation, and the tab's negative margin-bottom flushes the underline. */
.admin-header { padding-bottom: 0; }

/* Action button vertical metrics — match the tab height so the right side
   doesn't sit visually lower than the tab text. */
.admin-header .admin-actions .btn-sm,
.admin-header .admin-actions .btn-primary,
.admin-header .admin-actions .btn-secondary,
#admin-editor-topnav .admin-actions .btn-sm,
#admin-editor-topnav .admin-actions .btn-primary,
#admin-editor-topnav .admin-actions .btn-secondary {
  height: 30px;
  padding: 0 12px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
}

/* Joint limits toggle — OFF state gets a warning-orange treatment so
   the admin can see at a glance that limits are disabled. */
#admin-topnav-limits.limits-off {
  color: #f59e0b;
  border-color: rgba(245, 158, 11, 0.4);
  background: rgba(245, 158, 11, 0.08);
}
#admin-topnav-limits.limits-off:hover {
  background: rgba(245, 158, 11, 0.16);
}

/* ─────────────────────────────────────────────────────────────
   POSE EDITOR — focused-workspace header (no tabs)
   Single row:  [← Back to Dashboard]  POSE EDITOR  [Reset] [Save]
   ───────────────────────────────────────────────────────────── */

/* Override the column layout used for the previous tabbed header */
.admin-topnav-single {
  display: flex !important;
  flex-direction: row !important;
  align-items: center !important;
  justify-content: space-between;
  width: 100%;
  height: 100%;
}

/* The editor topnav — slim header, single horizontal row */
#admin-editor-topnav:has(.admin-topnav-single) {
  height: 60px;
  padding: 0 16px;
  flex-direction: row !important;
  justify-content: stretch;
}

/* Page title — centered, weighted, the visual anchor of the header */
.admin-panel-label--editor {
  margin: 0 !important;
  text-align: center;
  flex: 1;
  color: var(--text);
  font-size: 17px;
  letter-spacing: 0.02em;
  font-weight: 600;
  text-transform: none;
}

/* Back-to-Dashboard button — subtle, secondary-style */
.btn-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-sec);
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 500;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  font-family: inherit;
}
.btn-back:hover {
  background: var(--card);
  color: var(--text);
  border-color: var(--border-mid);
}
.btn-back-arrow {
  font-size: 15px;
  line-height: 1;
  position: relative;
  top: -1px;
}

/* ═════════════════════════════════════════════════════════════════════
   DESIGN SYSTEM POLISH — applied AFTER legacy styles to override
   inconsistent spacing / radii / typography without renaming classes
   or touching JavaScript. Uses CSS variables from :root.
   ═════════════════════════════════════════════════════════════════════ */

/* ── 1. Panel cards (left + right sidebars in editor) ─────────────── */
#left-panel,
#right-panel {
  padding: var(--space-6) var(--space-5);
  font-size: var(--fs-body);
}
#left-panel .panel-section,
#right-panel .panel-section {
  background: transparent;
  padding: 0;
  margin-bottom: var(--space-6);
  border: none;
}
#left-panel .panel-section:last-child,
#right-panel .panel-section:last-child { margin-bottom: 0; }

/* ── 2. Section titles — uniform uppercase header ─────────────────── */
/* Two label levels, and they have to be told apart at a glance:
   .panel-title names the panel; .panel-subtitle names a group inside it.
   Size, weight and tracking carry the difference — not brightness, because
   both were sitting on --text-muted, which is 2.1:1 on the panel ground.
   A label you have to lean in to read is not a quiet label, it is an
   unreadable one, and it was flattening every group boundary in the rails. */
.panel-title,
.admin-section-title,
.morph-section-title,
.tool-section-label,
.rps-label,
.rot-pad-title,
.rot-pad-snap-label {
  font-size: var(--fs-section);
  font-weight: 700;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-sec);
  margin: 0 0 var(--space-2);
  line-height: 1;
}

/* ── 3. Inputs / selects / textareas — consistent height + radius ─── */
#left-panel input[type="text"],
#left-panel input[type="email"],
#left-panel input[type="search"],
#left-panel input[type="number"],
#left-panel select,
#left-panel textarea,
#right-panel input[type="text"],
#right-panel input[type="number"],
#right-panel select {
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text);
  border-radius: var(--radius-input);
  padding: 0 var(--space-3);
  height: var(--h-input);
  font-size: var(--fs-input);
  font-family: inherit;
  outline: none;
  box-sizing: border-box;
  width: 100%;
  transition: border-color 0.15s, box-shadow 0.15s;
}
#left-panel textarea,
#right-panel textarea {
  min-height: 84px;
  height: auto;
  padding: var(--space-3);
  line-height: var(--lh-body);
  resize: vertical;
}
#left-panel input:focus,
#left-panel select:focus,
#left-panel textarea:focus,
#right-panel input:focus,
#right-panel select:focus,
#right-panel textarea:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 2px var(--teal-dim);
}

/* Label spacing */
.field-label,
.shape-row > span:first-child {
  display: block;
  font-size: var(--fs-label);
  color: var(--text-sec);
  margin-bottom: var(--space-2);
  font-weight: 500;
}

/* ── 4. Buttons — one ladder, unified sizing ──────────────────────────
   Five rungs, each answering "how much does this matter":

     .btn-primary    the one committing action on a screen (teal)
     .btn-emphasis   the leading action inside a panel group — raised
                     neutral, deliberately NOT teal, so a panel can have
                     a lead action without spending the selection colour
     .btn-secondary  ordinary action — outlined, sits back
     .btn-quiet      ghost; present but not asking for attention
     .btn-reset      undo-shaped and slightly dangerous; neutral at rest,
                     names the risk only on hover

   Teal is reserved for selected / active / focused / detected. A
   secondary button turning teal on hover was spending the selection
   colour on "the cursor is here", which is why nothing in the editor
   read as selected.
   ──────────────────────────────────────────────────────────────────── */
.btn-primary,
.btn-emphasis,
.btn-secondary,
.btn-quiet,
.btn-reset {
  height: var(--h-btn);
  padding: 0 var(--space-5);
  border-radius: var(--radius-btn);
  font-size: var(--fs-input);
  font-weight: 600;
  font-family: inherit;
  border: 1px solid transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.06s;
  box-sizing: border-box;
}
.btn-primary {
  background: var(--teal);
  color: #0d0c18;
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-primary:active { transform: scale(0.98); }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }

/* Raised neutral. The offset-plus-blur shadow is what separates it from the
   outlined rung — a flat lighter fill alone reads as "disabled secondary". */
.btn-emphasis {
  background: var(--border-mid);
  color: var(--text);
  border-color: var(--border-focus);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.btn-emphasis:hover { background: var(--border-focus); border-color: #5b5890; }
.btn-emphasis:active { transform: scale(0.98); }
.btn-emphasis:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }

.btn-secondary {
  background: var(--card);
  color: var(--text);
  border-color: var(--border-mid);
}
/* Lifts toward the raised rung on hover instead of turning teal. */
.btn-secondary:hover {
  background: var(--card-hover);
  border-color: var(--border-focus);
  color: var(--text);
}
.btn-secondary:active { transform: scale(0.98); }
.btn-secondary:disabled { opacity: 0.4; cursor: not-allowed; color: var(--text-muted); border-color: var(--border); }

.btn-quiet {
  background: transparent;
  color: var(--text-sec);
  border-color: transparent;
}
.btn-quiet:hover { background: rgba(255, 255, 255, 0.05); color: var(--text); }
.btn-quiet:active { transform: scale(0.98); }
.btn-quiet:disabled { opacity: 0.4; cursor: not-allowed; }

/* Neutral until you reach for it. A reset that is red at rest reads as an
   error state on a panel you have not touched yet. */
.btn-reset {
  background: transparent;
  color: var(--text-sec);
  border-color: var(--border-mid);
}
.btn-reset:hover {
  background: var(--danger-dim, rgba(245, 101, 101, 0.10));
  border-color: rgba(245, 101, 101, 0.45);
  color: #f89a9a;
}
.btn-reset:active { transform: scale(0.98); }
.btn-reset:disabled { opacity: 0.4; cursor: not-allowed; }

.btn-sm {
  height: var(--h-btn-sm);
  padding: 0 var(--space-3);
  font-size: 12px;
}

/* ── Detected-pose readout ────────────────────────────────────────────
   Three facts arrive stacked in one card and used to land at nearly one
   size: what the pose is, which way the figure faces, and how sure the
   system is. The card sets the pose name's size and weight, and
   poseClassifier.js writes the view line at 10px inside it, so the
   hierarchy falls out of the container without touching the renderer. */
#pose-class-label {
  padding: var(--space-3) var(--space-3);
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  font-size: 15px;
  font-weight: 650;
  line-height: 1.25;
  color: var(--text);
}

.ref-detect-meta {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: center;
  margin-top: var(--space-2);
}
/* Confidence is a measurement — tabular, and readable rather than a hint. */
#pose-class-conf {
  margin-left: auto;
  font-size: 10px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-sec);
}

/* ── Keyboard focus, once, for every button ─────────────────────────
   Six classes each carried a byte-identical copy of this ring, and the
   other twelve — including .btn-primary and .btn-secondary, the two
   most-used controls in the app — had none at all and fell back to the
   UA ring. There is no global focus rule in this stylesheet, so a
   keyboard user got a different answer per control.

   Listed explicitly rather than as a bare `button:focus-visible` so
   that controls with a considered ring of their own keep it: .seg-opt
   insets to -2px because its track clips, and .pt-btn to -3px inside
   the phone tab bar. A blanket rule would have flattened both.
   ──────────────────────────────────────────────────────────────────── */
.btn-primary:focus-visible,
.btn-emphasis:focus-visible,
.btn-secondary:focus-visible,
.btn-quiet:focus-visible,
.btn-reset:focus-visible,
.btn-browse-sm:focus-visible,
.btn-back:focus-visible,
.canvas-btn:focus-visible,
.topbar-btn:focus-visible,
.sb-btn:focus-visible,
.gh-btn:focus-visible,
.nav-btn:focus-visible,
.ws-btn:focus-visible,
.pm-btn:focus-visible,
.auth-demo-btn:focus-visible,
.cam-preset-btn:focus-visible,
.morph-bodytype-btn:focus-visible,
.joint-nav-btn:focus-visible,
.joint-action-btn:focus-visible,
.rot-pad-btn:focus-visible,
.draw-btn:focus-visible,
.draw-mini-btn:focus-visible,
.draw-float-btn:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* Icon buttons (canvas toolbar, mini gizmo close, etc.) */
.canvas-btn {
  width: var(--h-btn-sm);
  height: var(--h-btn-sm);
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-sec);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 2px;
  transition: background 0.12s, color 0.12s;
}
.canvas-btn:hover {
  background: var(--card-hover);
  color: var(--text);
}

/* ── 5. Mini-gizmo panel (right side floating) ────────────────────── */
#mini-gizmo-panel {
  padding: var(--space-5);
  border-radius: var(--radius-card);
  border-color: var(--border-mid);
}
/* The answer to "what am I editing?", and the heading of this panel —
   everything below it modifies this one joint. The display size was already
   specified here; an inline font-size:15px on the element had been
   overriding it, which is what left the panel opening with four things at
   roughly equal weight. The inline style is gone, so this rule now lands. */
#selected-joint-name {
  font-size: var(--fs-display);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: var(--text);
  margin-bottom: var(--space-4);
}
/* Nothing selected: the line goes away entirely.
   It is a placeholder, not a name, and #gizmo-empty-state sits directly below
   it saying the same three words at display size -- so the panel printed "No
   Joint Selected" twice, which reads as a rendering fault rather than a state.
   Shrinking it (the previous mitigation here) made the duplicate quieter
   without making it stop being a duplicate. The phone tier already resolves
   this the same way; this is that rule extended to every tier.
   phoneShell.js maintains body.pf-has-joint from this element's own text, on
   every tier, so the state is already available here. */
body:not(.pf-has-joint) #selected-joint-name { display: none; }

/* ── 5b. Empty gizmo state (shown when no joint selected) ────────── */
.gizmo-wrap {
  position: relative;
  margin-bottom: var(--space-4);
}
.gizmo-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-5);
  background: rgba(13, 12, 24, 0.92);
  border: 1px dashed var(--border-mid);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  text-align: center;
  pointer-events: none;
}
.gizmo-empty-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-sec);
  letter-spacing: 0.02em;
}
.gizmo-empty-hint {
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 220px;
}

/* The phone wording of the same hint. Hidden here and swapped in by the
   phone tier at the bottom of this file, so the desktop shell never sees
   it and neither line has to hedge for the other's input device. */
.gizmo-empty-hint--phone { display: none; }

/* ── 6. Rotation pad — already polished but tighten spacing ───────── */
.rot-pad {
  padding: var(--space-4);
  border-radius: var(--radius-card);
  background: var(--card);
  border-color: var(--border-mid);
}
.rot-pad-header { margin-bottom: var(--space-3); }
.rot-pad-row { margin-bottom: var(--space-2); }
.rot-pad-input,
.rot-pad-btn {
  height: var(--h-btn-sm);
  border-radius: var(--radius-sm);
}

/* ── 7. Joint list — wider clickable rows ─────────────────────────── */
#joint-list .joint-item {
  padding: 10px var(--space-4);
  border-radius: var(--radius-sm);
  margin: 2px var(--space-2);
  transition: background 0.12s;
}
#joint-list .joint-item:hover {
  background: var(--card-hover);
}
#joint-list .joint-item.active {
  background: var(--teal-dim);
  color: var(--text);
}
.joint-list-header,
.joint-section-header {
  padding: var(--space-3) var(--space-4) var(--space-2);
  font-size: var(--fs-section);
  font-weight: 700;
  letter-spacing: var(--letter-section);
  color: var(--text-sec);
  text-transform: uppercase;
}

/* ── 8. Canvas-controls floating toolbar — wider, breathing room ──── */

/* ── 9. Bottom bar — match the design system ──────────────────────── */
#bottom-bar {
  padding: 0 var(--space-5);
  background: var(--panel);
  border-top: 1px solid var(--border-mid);
}

/* ── 10. Admin pose editor topnav — match canvas-controls style ───── */
#admin-editor-topnav {
  padding: 0 var(--space-5);
  border-bottom: 1px solid var(--border-mid);
  background: var(--bg);
}

/* ── 11. Pose adjustment sliders — consistent spacing ─────────────── */
.shape-control { margin-bottom: var(--space-4); }
.shape-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: var(--space-2);
  font-size: var(--fs-label);
  gap: var(--space-3);
}
/* The shared .field-label rule gives this span a bottom margin, which is for
   a label stacked above its input — inside this flex row it just pushed the
   name off the value's baseline. */
.shape-row > span:first-child { margin-bottom: 0; }

/* A readout, not a selection. These were teal, which put the accent on three
   numbers that only ever report where the handle already is — and the handle
   is teal too. Tabular figures and a fixed column stop the value shifting
   under the cursor while you drag. */
.shape-val {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  min-width: 46px;
  text-align: right;
  flex-shrink: 0;
}

/* ── 12. Recent Pose Strip — match design system ──────────────────── */
#recent-pose-strip {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-card);
  border-color: var(--border-mid);
  gap: var(--space-2);
}

/* ── 13. Body Shape (right side morph panel) ──────────────────────── */
.morph-cat-summary {
  padding: var(--space-3) var(--space-2);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-sec);
}
/* Body Type buttons are sized in the component rule above (3-col grid,
   9px/6px padding). This section previously shrank them back to 6px/4px,
   which re-cramped the labels — keep only the type-scale alignment here. */
.morph-bodytype-btn { font-size: var(--fs-label, 11.5px); }

/* ── 14. Force consistent shadows on cards/panels ─────────────────── */
.card-shadow,
#mini-gizmo-panel,
.rot-pad,
.admin-card {
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
}

/* ── 15. Vertical rhythm: paragraphs in sidebar ───────────────────── */
#left-panel p,
#right-panel p {
  font-size: 12px;
  color: var(--text-sec);
  line-height: var(--lh-body);
  margin: 0 0 var(--space-3);
}

/* ─────────────────────────────────────────────────────────────
   PANEL SUBHEADING
   Groups controls inside one panel-section so POSE TOOLS can hold
   "Global pose" and "Actions" without splitting into two cards.
   ───────────────────────────────────────────────────────────── */
/* Group label. One step down from .panel-title in size and weight, and it
   opens a group rather than closing one — so the space above it is larger
   than the space below, which is what makes the boundary visible without
   drawing a box around anything. */
.panel-subtitle {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-sec);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: var(--space-5) 0 var(--space-2);
}
/* …except the first one in a panel, which already has the title above it. */
.panel-title + .panel-subtitle { margin-top: 0; }

/* ─────────────────────────────────────────────────────────────
   BODY MAP — visual joint selector (js/bodyMap.js)
   ───────────────────────────────────────────────────────────── */
.body-map-pane {
  padding: 6px 14px 14px;
}

.bm-svg {
  display: block;
  width: 100%;
  /* Was 300px, which left a band of dead space between the figure and its
     caption and made the label look detached from the map. */
  max-height: 250px;
  margin: 0 auto;
  overflow: visible;
}

/* Silhouette underlay — gives the dots a body to sit on without competing
   with them for attention. #23233a sat so close to the panel colour that the
   body barely registered; this reads as a figure while staying quiet. */
.bm-skull,
.bm-torso {
  fill: #2e2e4a;
}

.bm-limbs line {
  stroke: #2e2e4a;
  stroke-width: 10;
  stroke-linecap: round;
}

.bm-links line {
  stroke: #3d3d5c;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.bm-node { cursor: pointer; outline: none; }

/* Invisible, generously sized pointer target — the visible dot is 4px in a
   120-unit viewBox, far too small to hit reliably on a laptop trackpad. */
.bm-hit { fill: transparent; }

.bm-dot {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 0.15s ease;
}

.bm-ring {
  fill: none;
  stroke: var(--teal);
  stroke-width: 1.5;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.bm-node:hover .bm-dot { transform: scale(1.4); }
.bm-node:hover .bm-ring { opacity: 0.45; }

.bm-node:focus-visible .bm-ring {
  opacity: 1;
  stroke: var(--text);
}

/* Selected joint — the halo is the same teal used for the active tab and
   the active camera preset, so "teal = what you are working on" holds. */
.bm-node.is-active .bm-dot { transform: scale(1.5); }
.bm-node.is-active .bm-ring {
  opacity: 1;
  animation: bm-pulse 2s ease-in-out infinite;
}

/* Mirror partner — dashed and dimmer, so it reads as "this moves too"
   rather than "this is also selected". */
.bm-node.is-mirror .bm-ring {
  opacity: 0.6;
  stroke-dasharray: 2.5 2.5;
}

@keyframes bm-pulse {
  0%, 100% { transform: scale(1);    opacity: 1;    }
  50%      { transform: scale(1.18); opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
  .bm-node.is-active .bm-ring { animation: none; }
  .bm-dot, .bm-ring { transition: none; }
}

/* Live label — carries the hovered joint, then falls back to the selected
   one. Replaces a static legend: the name you need is the one under the
   cursor, not a colour key you have to decode. */
/* Both of these sat on --text-muted, which is ~2.1:1 against the rail — the
   caption is the body map's only instruction, so it has to be legible even
   while it stays quiet. */
/* Selected-joint card: name, body region, and prev / next (J, Shift+J). */
.bm-card {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--card);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.bm-card.is-selected {
  border-color: rgba(78, 205, 196, 0.45);
  background: linear-gradient(135deg, rgba(78, 205, 196, 0.08), rgba(139, 126, 200, 0.06));
}
.bm-card-text { flex: 1; min-width: 0; text-align: center; }
.bm-caption {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-sec);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bm-caption.is-named { color: var(--text); }
.bm-region {
  margin-top: 1px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.bm-card.is-selected .bm-region { color: var(--teal); }
.bm-step {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border-mid);
  border-radius: 8px;
  background: transparent;
  color: var(--text-sec);
  cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}
.bm-step svg { width: 15px; height: 15px; }
.bm-step:hover { color: var(--text); background: var(--card-hover); border-color: var(--teal); }
.bm-step:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* R / L at the figure's shoulders — the figure faces you. */
.bm-side {
  font-size: 9px;
  font-weight: 700;
  fill: var(--text-muted);
  letter-spacing: 0.04em;
}

.bm-hint {
  margin-top: 8px;
  text-align: center;
  font-size: 10px;
  line-height: 1.35;
  color: var(--text-sec);
  opacity: 0.85;
}

/* Joint Selector instructions: one line, details on demand. */
.joint-instruction {
  padding: 0 14px 12px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-sec);
  border-bottom: 1px solid var(--border);
}
.ji-lead { margin: 0 0 6px; color: var(--text); font-weight: 500; }
.ji-more summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--teal);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.ji-more summary::-webkit-details-marker { display: none; }
.ji-more summary::before {
  content: '';
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.ji-more[open] summary::before { transform: rotate(45deg); }
.ji-more summary:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; border-radius: 4px; }
.ji-more ul { margin: 8px 0 0; padding-left: 16px; display: grid; gap: 5px; }
.joint-instruction kbd {
  display: inline-block;
  min-width: 16px;
  padding: 1px 5px;
  border: 1px solid var(--border-mid);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--card);
  color: var(--text);
  font: 600 10px/1.4 inherit;
  text-align: center;
}

/* ─────────────────────────────────────────────────────────────
   RAIL COLLAPSE

   Both controls are `position: fixed`, not absolute. The rails scroll
   their own content, and an absolutely positioned child would scroll
   away with it — these have to stay put.
   ───────────────────────────────────────────────────────────── */

/* The inner-edge tab that collapses an expanded rail. Sits on the edge
   rather than in the corner so it never collides with a section's own
   collapse chevron. */
/* Header toggle: a sidebar icon at the right end of the panel's first title
   row. The reopen tab (.rail-stub) takes the same top line and the same
   size, so the control is where you left it in both states. */
:root { --panel-toggle: 28px; --panel-toggle-top: calc(var(--topbar-h) + var(--space-6) - 7px); }

#left-panel > .rail-collapse,
#right-panel > .rail-collapse {
  position: fixed;
  top: var(--panel-toggle-top);
  z-index: 21;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--panel-toggle);
  height: var(--panel-toggle);
  padding: 0;
  background: var(--panel);
  color: var(--text-muted);
  border: 1px solid var(--border-mid);
  border-radius: 7px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

#left-panel > .rail-collapse {
  left: calc(var(--rail-w) + var(--left-w) - var(--space-5) - var(--panel-toggle));
}

#right-panel > .rail-collapse {
  right: var(--space-5);
}


#left-panel > .rail-collapse:hover,
#right-panel > .rail-collapse:hover {
  color: var(--text);
  background: var(--border);
}

#left-panel > .rail-collapse:focus-visible,
#right-panel > .rail-collapse:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* The sliver a collapsed rail leaves behind — and the button back. */
/* A collapsed rail leaves a compact icon tab, not a full-height strip of
   vertical text. The old sliver spent 38px × the whole viewport height to
   say one word, and three of them read as a wall. The tab is ~34px square,
   pinned near the top where the panel's own header was. */
#left-panel > .rail-stub,
#right-panel > .rail-stub {
  position: fixed;
  top: var(--panel-toggle-top);
  z-index: 21;
  display: none;                 /* shown only while that rail is collapsed */
  align-items: center;
  justify-content: center;
  width: var(--panel-toggle);
  height: var(--panel-toggle);
  padding: 0;
  background: var(--panel);
  color: var(--text-sec);
  border: 1px solid var(--border-mid);
  border-radius: 7px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

#left-panel  > .rail-stub { left: calc(var(--rail-w) + 8px); }
#right-panel > .rail-stub { right: 8px; }

/* Scale the glyph with the tab. Set here rather than on each <svg> so the
   tool column's own 16px icons are unaffected — CSS beats the width/height
   presentation attributes. */
#rail-stub svg {
  width: 19px;
  height: 19px;
}
#left-panel  > .rail-stub svg,
#right-panel > .rail-stub svg,
#mid-panel   > .rail-stub svg {
  width: 16px;
  height: 16px;
}

/* The label stays in the DOM for screen readers and the tooltip, but the
   icon carries it visually. */
.rail-stub-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

#left-panel > .rail-stub:hover,
#right-panel > .rail-stub:hover {
  color: var(--text);
  background: var(--border);
}

#left-panel > .rail-stub:focus-visible,
#right-panel > .rail-stub:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: -2px;
}

/* Collapsed state: the tab is all that renders. Note this hides the rail's
   contents, never the rail itself — the gutter and the tab stay, so there is
   always something to click. */
body.left-collapsed  #left-panel  > *:not(.rail-stub),
body.right-collapsed #right-panel > *:not(.rail-stub) {
  display: none !important;
}

body.left-collapsed  #left-panel  > .rail-stub,
body.right-collapsed #right-panel > .rail-stub {
  display: flex;
}

/* The collapsed gutter goes transparent so only the tab is visible — a
   full-height panel-coloured strip is exactly the "vertical" look the tab
   replaces. The width stays, so the canvas edge is still predictable. */
body.left-collapsed  #left-panel,
body.right-collapsed #right-panel {
  overflow: hidden;
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}

/* Same stacking trap as the workspace rail: each panel's own z-index makes a
   context its .rail-stub cannot escape, so at narrow widths the wrapped
   camera bar (200) painted over every collapse tab. Collapsed panels are
   transparent and effectively zero-width, so lifting them is free. */
body.left-collapsed  #left-panel,
body.right-collapsed #right-panel,
body.mid-collapsed   #mid-panel {
  z-index: 210;
}

/* ─────────────────────────────────────────────────────────────
   SECTION COLLAPSE
   Chevrons are injected by responsiveShell.js, so a section added to
   index.html later gets this behaviour without touching either file.
   ───────────────────────────────────────────────────────────── */
.panel-title--collapsible {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  cursor: pointer;
  user-select: none;
}

.sec-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 4px;
  color: var(--text-muted);
  cursor: pointer;
  transition: transform 0.18s ease, color 0.15s ease, background 0.15s ease;
}

.panel-title--collapsible:hover .sec-toggle {
  color: var(--text-sec);
  background: var(--border);
}

.sec-toggle:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 1px;
}

.panel-section.is-collapsed .sec-toggle { transform: rotate(-90deg); }

.panel-section.is-collapsed > *:not(.panel-title) { display: none !important; }

/* The title's bottom margin exists to separate it from a body that is no
   longer there. */
.panel-section.is-collapsed .panel-title { margin-bottom: 0; }

/* ─────────────────────────────────────────────────────────────
   GIZMO PANEL COLLAPSE
   Collapses to the header strip. The joint name survives, so you never
   lose track of what is selected while clearing the view of the model.
   ───────────────────────────────────────────────────────────── */
.gizmo-head-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.gizmo-collapse {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: transparent;
  border: 1px solid #2a2a40;
  border-radius: 50%;
  color: #8a8aa3;
  cursor: pointer;
  transition: transform 0.18s ease, color 0.15s ease;
}

.gizmo-collapse:hover { color: var(--text); }

.gizmo-collapse:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

#mini-gizmo-panel.is-collapsed .gizmo-collapse { transform: rotate(180deg); }

#mini-gizmo-panel.is-collapsed > *:not(:first-child):not(#selected-joint-name) {
  display: none !important;
}

#mini-gizmo-panel.is-collapsed #selected-joint-name { margin-bottom: 0; }

/* ─────────────────────────────────────────────────────────────
   TOUCH INPUT
   Keyed to the pointer, not the width — a touchscreen laptop at
   1440px needs these as much as a tablet does.
   ───────────────────────────────────────────────────────────── */
@media (pointer: coarse) {
  :root           { --rail-stub: 46px; }
  .canvas-btn     { width: 44px; height: 44px; }
  .joint-item     { padding: 13px 14px; }
  .snap-seg-btn   { min-height: 34px; }
  .rot-pad-btn    { min-height: 38px; }
  .sec-toggle     { width: 32px; height: 32px; }
  .gizmo-collapse { width: 30px; height: 30px; }
  .bm-hit         { r: 11; }    /* larger joint hit area on the body map */

  :root { --panel-toggle: 34px; }
}

@media (prefers-reduced-motion: reduce) {
  .sec-toggle,
  .gizmo-collapse,
  #left-panel > .rail-stub,
  #right-panel > .rail-stub,
  #left-panel > .rail-collapse,
  #right-panel > .rail-collapse { transition: none; }
}

/* ─────────────────────────────────────────────────────────────
   TOPBAR ACTIONS — Undo / Redo
   ───────────────────────────────────────────────────────────── */
#topbar-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 4px;
}

.topbar-btn {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 32px;
  padding: 6px 12px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 7px;
  color: var(--text-sec);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

.topbar-btn:hover:not(:disabled) { color: var(--text); background: var(--border); }
.topbar-btn:disabled { opacity: 0.38; cursor: not-allowed; }

/* ─────────────────────────────────────────────────────────────
   CANVAS TOOL COLUMN — labeled groups
   Icon over label, boxed, grouped under a caption. The old strip
   was five unlabeled icons; nothing said what any of them did.
   ───────────────────────────────────────────────────────────── */





/* Teal marks state — the active navigation mode is state. */


/* ─────────────────────────────────────────────────────────────
   DEBUG TOOL BUTTONS
   diagPanel.js, poseDebugPanel.js, retrievalInspector.js and
   debugRestPose.js each append a bare <button> straight into this
   column — the four unlabeled icons that were sitting under POSE.

   They are instrumentation, so they follow the same single switch as
   the reference-card diagnostics: off for users, on for admins,
   forceable with localStorage.setItem('poseforge.diagnostics', '1').

   Matching on :not(.tool-btn) means any future debug module is covered
   without editing this rule or that module.
   ───────────────────────────────────────────────────────────── */
/* !important is load-bearing here: all four modules set `display:flex` via
   btn.style.cssText, and an inline declaration beats any stylesheet rule
   without it. That is why hiding them silently failed the first time. */

/* ─────────────────────────────────────────────────────────────
   STATUS BAR
   Four readout cells on the left, camera actions on the right.
   ───────────────────────────────────────────────────────────── */
.sb-groups {
  display: flex;
  align-items: center;
  gap: 0;
  min-width: 0;
  flex: 1;
  overflow: hidden;
}

.sb-cell {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 0 22px;
  border-right: 1px solid var(--border);
}

.sb-cell:first-child { padding-left: 4px; }
.sb-cell:last-child  { border-right: none; }

.sb-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--text-muted);
}

/* Teal marks state: this is the joint you are working on. */
.sb-icon--accent { color: var(--teal); }

.sb-text { min-width: 0; }

.sb-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  line-height: 1.3;
}

.sb-value {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.35;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sb-value.is-empty { color: var(--text-muted); font-weight: 500; }

.sb-sub {
  font-size: 11px;
  color: var(--text-sec);
  line-height: 1.3;
  white-space: nowrap;
}

.sb-conf-row { display: flex; align-items: center; gap: 10px; }

.sb-meter {
  display: block;
  width: 130px;
  height: 5px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}

/* Scaled, not resized. The track above clips with its own border-radius and
   overflow:hidden, so the fill needs no rounding of its own — which is what
   makes scaleX safe here, and keeps the animation off the layout path. */
.sb-meter-fill {
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: var(--text-muted);
  transition: transform 0.25s ease;
}

.sb-meter-fill.is-high { background: var(--teal); }
.sb-meter-fill.is-mid  { background: #f59e0b; }
.sb-meter-fill.is-low  { background: #ef4444; }

.sb-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 0 16px;
  background: #1a1a2e;
  border: 1px solid var(--border-mid);
  border-radius: 8px;
  color: var(--text-sec);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.sb-btn:hover { color: var(--text); border-color: var(--text-muted); }

/* The bar's two resets undo different things and used to be one control
   drawn twice. Weight now matches consequence: clearing every joint
   rotation is not the same size of act as re-framing the camera. */
.sb-btn--reset:hover {
  color: #f89a9a;
  background: var(--danger-dim);
  border-color: rgba(245, 101, 101, 0.45);
}
/* The one constructive act in a bar of undos. Same geometry as its
   neighbours — the accent carries the difference, not the size — but it does
   rest in colour rather than waiting for a hover, because the complaint that
   put it here was that saving could not be found. */
.sb-btn--save {
  color: var(--teal);
  border-color: rgba(78, 205, 196, 0.35);
}
.sb-btn--save:hover {
  color: #0d0c18;
  background: var(--teal);
  border-color: var(--teal);
}

.sb-btn--quiet {
  background: transparent;
  border-color: transparent;
  font-weight: 500;
}
.sb-btn--quiet:hover { background: var(--card); border-color: var(--border-mid); }

/* Narrow screens: keep the selected joint and the actions, drop the other
   readouts rather than letting the cells fight over 400px. */
@media (max-width: 1100px) {
  .sb-cell--conf { display: none; }
}
@media (max-width: 880px) {
  .sb-groups .sb-cell:first-child { display: none; }
}
@media (max-width: 700px) {
  .sb-btn span { display: none; }
  .sb-btn { padding: 0 12px; }
}

/* ─────────────────────────────────────────────────────────────
   WORKSPACE RAIL
   Permanent navigator: "what am I doing?". Replaces the 410px
   floating tool column, which was five unlabelled icons.
   ───────────────────────────────────────────────────────────── */
#workspace-rail {
  position: fixed;
  top: var(--topbar-h);
  left: 0;
  bottom: var(--bottom-h);
  width: var(--rail-w);
  z-index: 21;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 0;
  background: var(--panel);
  border-right: 1px solid var(--border);
  box-sizing: border-box;
  overflow-y: auto;
}

/* Right-hand twin of the workspace rail: one button per panel, click to
   hide, click again to show (responsiveShell.js). */
#panel-rail {
  position: fixed;
  top: var(--topbar-h);
  right: 0;
  bottom: var(--bottom-h);
  width: var(--prail-w);
  z-index: 21;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 0;
  background: var(--panel);
  border-left: 1px solid var(--border);
  box-sizing: border-box;
  overflow-y: auto;
}

.ws-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex-shrink: 0;
  width: 58px;
  padding: 9px 2px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9px;
  color: var(--text-muted);
  font: inherit;
  font-size: 9.5px;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.ws-btn svg { width: 20px; height: 20px; }

.ws-btn:hover { color: var(--text-sec); background: #1a1a2e; }

/* Teal marks state — the workspace you are in. */
.ws-btn.is-active {
  color: var(--teal);
  background: rgba(78, 205, 196, 0.10);
  border-color: var(--teal);
}
/* Panel hidden (clicking the active workspace toggles it): keep the teal
   label so you know which workspace comes back, drop the filled frame so it
   reads as "closed". */
body.left-collapsed:not(.phone-shell) #workspace-rail .ws-btn.is-active {
  background: transparent;
  border-color: transparent;
}


/* ── The Draw entry ───────────────────────────────────────────────────
   Sits at the foot of the rail, under a rule. Deliberately not a .ws-btn:
   those choose which pane the left panel shows and stay lit to mark where
   you are. This one leaves the Editor entirely, so it reads as a door
   rather than a tab — no active state, and a hairline above it saying the
   list of workspaces ended. */
.ws-draw {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex-shrink: 0;
  width: 58px;
  margin-top: 10px;
  padding: 9px 2px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9px;
  color: var(--text-muted);
  font: inherit;
  font-size: 9.5px;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  cursor: pointer;
  position: relative;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.ws-draw::before {
  content: '';
  position: absolute;
  top: -6px;
  left: 8px;
  right: 8px;
  height: 1px;
  background: var(--border-mid);
}
.ws-draw svg { width: 20px; height: 20px; }
.ws-draw:hover {
  color: var(--text);
  background: rgba(107, 98, 200, 0.14);
  border-color: #6b62c8;
}
.ws-draw:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* The way back sits left of the rail title, where a back control belongs. */
.draw-back { margin-right: auto; }
.draw-panel-head { gap: 10px; }

/* ─────────────────────────────────────────────────────────────
   WORKSPACE PANES
   One at a time. The panes are the existing .panel-section
   elements — nothing was re-parented, so every ID other modules
   write into still resolves.
   ───────────────────────────────────────────────────────────── */
#left-panel > [data-workspace] { display: none; }
#left-panel > [data-workspace].is-active { display: block; }

/* The rail is the persistent affordance for the left side now, so a
   collapsed panel goes to zero rather than leaving a tab: the rail icons
   already say what is behind it, and clicking one reopens the panel. */
/* The header toggle design gives the left panel its own reopen tab again —
   except when the whole rail is folded, where #rail-stub reopens both and
   the two would sit on top of each other. */
body.rail-collapsed #left-panel > .rail-stub { display: none !important; }

/* ── Rail collapse ─────────────────────────────────────────────────
   The tab sits at the left panel's right edge rather than at x:0, so it
   never covers the panel it reopens — no coupling needed between the two. */
#workspace-rail > .rail-collapse {
  position: fixed;
  bottom: calc(var(--bottom-h) + var(--overlay-gap));
  left: calc(var(--rail-w) - 34px);
  z-index: 22;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: #1a1a2e;
  color: var(--text-muted);
  border: 1px solid var(--border-mid);
  border-radius: 7px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

#workspace-rail > .rail-collapse:hover { color: var(--text); background: var(--border); }
#workspace-rail > .rail-collapse:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* Lives outside #workspace-rail on purpose. It is position:fixed, so its
   parent contributes nothing to layout — but nesting it inside the rail meant
   the rail's own z-index, width, overflow and display could all trap or clip
   it. As a child of <body> it answers to nothing but this rule, and its
   z-index is real rather than confined to an ancestor's stacking context. */
#rail-stub {
  position: fixed;
  top: calc(var(--topbar-h) + var(--overlay-gap));
  left: var(--left-w);
  /* 400, not 210: the collapsed rails also sit at 210, and #left-panel comes
     after this button in the document — an equal z-index means the later
     element wins, so the panel was painting over the control that reopens it.
     Nothing else in the editor goes above 210 short of the modals. */
  z-index: 400;
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  background: var(--panel);
  color: var(--text-sec);
  border: 1px solid var(--border-mid);
  border-left: none;
  border-radius: 0 9px 9px 0;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

#rail-stub:hover { color: var(--text); background: var(--border); }
#rail-stub:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

body.rail-collapsed #workspace-rail {
  width: 0;
  padding: 0;
  border: none;
  background: transparent;
  overflow: visible;
  /* Above #cam-preset-bar (200). A collapsed rail is a zero-width transparent
     box, so lifting it costs nothing — and the stub inside cannot lift itself:
     the rail's own z-index makes a stacking context that traps every child
     below 200 no matter what value they carry. That is what made this button
     unclickable once the camera bar started wrapping at narrow widths. */
  z-index: 210;
}
body.rail-collapsed #workspace-rail > * { display: none !important; }
body.rail-collapsed #rail-stub { display: flex; }

/* Navigation: left-drag mode picker */
.ws-modes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-top: 8px;
}

.ws-mode {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 9px 2px;
  background: #1a1a2e;
  border: 1px solid var(--border-mid);
  border-radius: 8px;
  color: var(--text-sec);
  font: inherit;
  font-size: 9.5px;
  font-weight: 600;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.ws-mode svg { width: 16px; height: 16px; }
.ws-mode:hover { color: var(--text); border-color: var(--text-muted); }

.ws-mode.is-active {
  color: var(--teal);
  border-color: var(--teal);
  background: rgba(78, 205, 196, 0.10);
}

.ws-mode:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* View / IK checkbox rows */
/* The whole row is the control. It was already a <label>, so the row was
   clickable — but nothing said so, and a 15px box was the only thing that
   looked interactive. The row now lights up as one object on hover, which
   is also what makes the setting and its name read as a single thing. */
.ws-check {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 32px;
  margin: 0 calc(var(--space-2) * -1);
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--text-sec);
  font-size: 12.5px;
  cursor: pointer;
  user-select: none;
  transition: background 0.12s ease, color 0.12s ease;
}

.ws-check:hover { color: var(--text); background: var(--card); }
.ws-check:has(input:checked) { color: var(--text); }

/* Box, tick and focus ring come from the shared toggle component. */

.ws-empty {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-muted);
  margin: 6px 0 0;
}

/* Detection readout — the explanation under the pose name.
   poseClassifier.js wraps orientation / coverage / result source /
   reconstruction in .detection-details; the "Detection details" toggle in the
   View workspace flips the body class. The pose name, the confidence line,
   the Unknown-Pose action and the candidate list sit outside the wrapper and
   are never hidden — they are the answer and the actions, not the reasoning. */
body.hide-detection-details .detection-details { display: none; }

/* Developer tools host — the inspector modules append their buttons here. */
#debug-tools-wrap { display: none; }
body.diagnostics-on #debug-tools-wrap { display: block; }

#debug-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

/* Rule-of-thirds guide over the canvas. Pointer-events off so it never
   intercepts an orbit drag. */
body.show-thirds::after {
  content: '';
  position: fixed;
  top: var(--topbar-h);
  bottom: var(--bottom-h);
  left: var(--canvas-left);
  right: var(--canvas-right);
  z-index: 14;
  pointer-events: none;
  background:
    linear-gradient(to right,  transparent calc(33.333% - 1px), rgba(255,255,255,0.13) calc(33.333% - 1px), rgba(255,255,255,0.13) 33.333%, transparent 33.333%,
                               transparent calc(66.666% - 1px), rgba(255,255,255,0.13) calc(66.666% - 1px), rgba(255,255,255,0.13) 66.666%, transparent 66.666%),
    linear-gradient(to bottom, transparent calc(33.333% - 1px), rgba(255,255,255,0.13) calc(33.333% - 1px), rgba(255,255,255,0.13) 33.333%, transparent 33.333%,
                               transparent calc(66.666% - 1px), rgba(255,255,255,0.13) calc(66.666% - 1px), rgba(255,255,255,0.13) 66.666%, transparent 66.666%);
}

/* ─────────────────────────────────────────────────────────────
   SHORT LANDSCAPE — a phone held sideways

   Every other breakpoint in this file keys off width, which is the
   wrong axis here: a phone in landscape is roughly 844 × 390, and the
   scarce dimension is HEIGHT. Unpatched, the topbar and status bar
   alone eat 120px of a 390px screen.

   This is the query that separates "works on a tablet" from "works on
   a phone held sideways".
   ───────────────────────────────────────────────────────────── */
@media (orientation: landscape) and (max-height: 560px) {
  :root {
    --topbar-h:    44px;
    --bottom-h:    46px;
    --overlay-gap: 10px;
  }

  /* One line per status cell instead of two — the sub-line is the first
     thing worth losing when vertical space is the constraint. */
  .sb-cell   { padding: 0 14px; gap: 8px; }
  .sb-sub    { display: none; }
  .sb-icon   { width: 18px; height: 18px; }
  .sb-meter  { width: 84px; }
  .sb-btn    { min-height: 32px; padding: 0 12px; }

  #topbar { padding-top: 0; padding-bottom: 0; }

  /* The gizmo's square canvas is its height budget, and here there is
     almost none — shrink it hard and let the numeric pad carry the work. */
  .gizmo-wrap { max-width: 108px; }
  #mini-gizmo-panel { max-height: calc(calc(100vh / var(--ui-scale, 1)) - var(--topbar-h) - var(--bottom-h) - 16px); }

  /* Workspace rail: icons only, no captions. */
  .ws-btn      { width: 44px; padding: 7px 2px; }
  .ws-btn span { display: none; }
  :root        { --rail-w: 54px; }

  /* Two floating hints over a 390px-tall canvas is one too many. */
  #canvas-hint       { display: none; }
  #recent-pose-strip { display: none; }
}

/* ─────────────────────────────────────────────────────────────
   CAMERA BAR — compact below 900px

   At full size the seven presets need ~393px, which is wider than the
   bar is allowed to be on a phone, so it wrapped to two rows and became
   a tall block sitting over the model. Trimming the padding and type
   brings it to ~295px, which fits on one row down to 360px.

   `top` also moves off its hardcoded 84px here. That number exists to
   clear the admin editor's 60px header — on the editor surface the
   topbar is 52px, so 84px was leaving a 32px hole for no reason.
   ───────────────────────────────────────────────────────────── */
@media (max-width: 899px) {
  #cam-preset-bar {
    top: calc(var(--topbar-h) + 10px);
    gap: 1px;
    padding: 3px;
    border-radius: 8px;
  }

  .cam-preset-btn {
    height: 24px;
    padding: 0 8px;
    font-size: 11px;
  }

  /* The rule keeps its breathing room proportionally — shrinking the gap
     with everything else would let Free rejoin the group it is separate
     from. */
  .cam-preset-free { margin-left: 8px; padding: 0 9px; }
  .cam-preset-free::before { left: -4px; }

  .cam-preset-reset {
    padding: 0 7px;
    font-size: 12px;
  }
}

/* Shorter still when the screen has no height to spare. */
@media (orientation: landscape) and (max-height: 560px) {
  #cam-preset-bar { top: calc(var(--topbar-h) + 6px); padding: 2px; }
  .cam-preset-btn { height: 22px; padding: 0 7px; font-size: 10.5px; }
  .cam-preset-free { margin-left: 7px; padding: 0 8px; }
  .cam-preset-reset { padding: 0 6px; font-size: 11px; }
}

/* Settings checkbox row */
.settings-check {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 32px;
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}

.settings-check input {
  cursor: pointer;
}

body.diagnostics-on #debug-tools > button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  min-height: 30px;
  margin-top: 4px;
  background: transparent;
  border: 1px dashed var(--border-mid);
  border-radius: 6px;
  color: var(--text-muted);
  cursor: pointer;
}

/* ─────────────────────────────────────────────────────────────
   MID PANEL — bottom half of the docked mid column (BODY SHAPE)
   ───────────────────────────────────────────────────────────── */
/* #mid-panel holds no content of its own any more — BODY SHAPE moved to the
   right rail. It survives as the host for the mid column's collapse controls,
   which are fixed-positioned children, so it needs no box of its own. Without
   it the column could be collapsed by the responsive ladder with no sliver to
   bring it back. */
#mid-panel {
  position: fixed;
  top: var(--topbar-h);
  right: calc(var(--prail-w) + var(--right-w) + var(--overlay-gap));
  width: 0;
  height: 0;
  padding: 0;
  border: none;
  background: transparent;
  box-shadow: none;
  overflow: visible;
  z-index: 30;
}

/* Collapse: the whole column goes, gizmo included, and the sliver speaks for
   both halves. */
/* The column's hide toggle lives in the Selected Joint header, first of its
   actions (index.html, .gizmo-panel-toggle). Same icon as the side panels. */
.gizmo-panel-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border-mid);
  border-radius: 6px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.gizmo-panel-toggle svg { width: 14px; height: 14px; }
.gizmo-panel-toggle:hover { color: var(--text); background: var(--border); }
.gizmo-panel-toggle:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* The phone tier has no mid column to hide — the panel lives in a sheet. */
body.phone-shell .gizmo-panel-toggle { display: none; }

/* Reopen tab: the top-right corner the header actions sat in. */
#mid-panel > .rail-stub {
  position: fixed;
  top: calc(var(--topbar-h) + var(--overlay-gap) + 8px);
  right: calc(var(--prail-w) + var(--right-w) + var(--overlay-gap));
  z-index: 31;
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--panel-toggle);
  height: var(--panel-toggle);
  padding: 0;
  background: var(--panel);
  color: var(--text-sec);
  border: 1px solid var(--border-mid);
  border-radius: 7px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

#mid-panel > .rail-stub:hover { color: var(--text); background: var(--border); }
#mid-panel > .rail-stub:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

/* Hidden from #panel-rail: the column goes, and the canvas gets its width. */
body.mid-collapsed { --mid-w: 0px; }
body.mid-collapsed #mini-gizmo-panel,
body.mid-collapsed #mid-panel > * { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════
   CREATE POSE — "Start from a pose" picker
   (js/createPose.js, #create-from-pose in the left panel)

   Lives in a 240px rail, so the grid is two columns of ~96px. Thumbnails
   are square because the library renders them square; letting them set
   their own height made the accordion jump as each category opened.
   ═══════════════════════════════════════════════════════════════════════ */
.create-from-pose { margin-top: 14px; }

/* "or" rule between the drop zone and the library route. Neither is the
   primary path — they are two equal ways in — so this separates rather
   than subordinates. */
.create-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 12px;
  color: var(--text-muted);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.create-or::before,
.create-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

.create-pose-cats { display: flex; flex-direction: column; gap: 4px; }

/* No radius/overflow here — the summary and grid draw their own joined
   shape, and clipping at this level would round off the seam between them. */
.cp-cat { min-width: 0; }

.cp-cat-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 9px;
  cursor: pointer;
  list-style: none;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 11px;
  color: var(--text);
  user-select: none;
  transition: background 0.15s, border-color 0.15s;
}
.cp-cat-summary::-webkit-details-marker { display: none; }
.cp-cat-summary:hover { background: var(--card-hover); border-color: var(--border-mid); }

/* The open category has to visibly own the thumbnails under it. Without an
   accent the summary rows are identical and the grid reads as floating
   between two unrelated headings rather than belonging to one. The square
   bottom edge joins it to its own grid. */
.cp-cat[open] .cp-cat-summary {
  background: var(--card-hover);
  border-color: var(--border-focus);
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
.cp-cat[open] .cp-cat-name { color: var(--teal); }

.cp-cat-name { font-weight: 600; }

.cp-cat-count {
  font-size: 10px;
  color: var(--text-sec);
  background: var(--panel);
  border-radius: 999px;
  padding: 1px 7px;
  flex: none;
}

/* Boxed to the open summary above it, so the grouping is unambiguous. */
.cp-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--border-focus);
  border-top: none;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

.cp-tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  min-width: 0;                     /* lets the name ellipsis inside a grid cell */
}

.cp-tile-thumb {
  position: relative;
  display: block;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  transition: border-color 0.15s, transform 0.15s;
}
.cp-tile-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* No thumbnail is not a broken pose — name the pose instead of framing a gap. */
.cp-tile-noimg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  font-size: 9px;
  line-height: 1.25;
  text-align: center;
  color: var(--text-muted);
}

.cp-tile:hover .cp-tile-thumb,
.cp-tile:focus-visible .cp-tile-thumb {
  border-color: var(--teal);
  transform: translateY(-1px);
}
.cp-tile:focus-visible { outline: none; }
.cp-tile:focus-visible .cp-tile-thumb { box-shadow: 0 0 0 2px var(--teal-glow); }

.cp-tile-name {
  font-size: 10px;
  line-height: 1.3;
  color: var(--text-sec);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cp-tile:hover .cp-tile-name { color: var(--text); }

/* Applied confirmation. The pose lands on the model instantly, but the model
   may be off-screen or mid-orbit, so the tile acknowledges the click too. */
.cp-tile.is-applied .cp-tile-thumb {
  border-color: var(--success);
  box-shadow: 0 0 0 2px var(--success-dim);
}

.cp-empty {
  margin: 0;
  padding: 12px 10px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-sec);
  background: var(--card);
  border: 1px dashed var(--border-mid);
  border-radius: var(--radius-sm);
}

.create-gallery-link { width: 100%; margin-top: 10px; }

/* ═══════════════════════════════════════════════════════════════════════
   ARTIST MODE (js/artistMode.js)
   ═══════════════════════════════════════════════════════════════════════ */

/* FOCUS MODE — strip the chrome, keep the figure.

   Works by zeroing the layout variables rather than hiding panels one by
   one. Every canvas edge and centred overlay is derived from these four, so
   one substitution moves the whole layout and nothing downstream needs to
   know Focus Mode exists. Same mechanism as the collapse classes above. */
body.pf-focus {
  --prail-w: 0px;
  --rail-w:  0px;
  --left-w:  0px;
  --right-w: 0px;
  --mid-w:   0px;
}
/* Editor Focus keeps the Joint Selector and Selected Joint (artistMode.js
   shows both on entry). The bars are gone, so the panels take their height. */
body.pf-focus:not(.draw-mode):not(.phone-shell) {
  --right-w:  240px;
  --mid-w:    300px;
  --topbar-h: 0px;
  --bottom-h: 0px;
}
body.pf-focus #topbar,
body.pf-focus #workspace-rail,
body.pf-focus #left-panel,
body.pf-focus #mid-panel,
body.pf-focus.phone-shell #right-panel,
body.pf-focus.draw-mode #right-panel,
body.pf-focus #bottom-bar,
body.pf-focus #rail-stub,
body.pf-focus #panel-rail,
body.pf-focus .panel-edge-toggle,
/* Floating overlays. These are not in any panel, so zeroing the layout
   variables above leaves them exactly where they were — they have to be
   named. Each one was visibly still painted over the figure in testing. */
body.pf-focus #cam-preset-bar,
body.pf-focus.phone-shell #mini-gizmo-panel,
body.pf-focus.draw-mode #mini-gizmo-panel,
body.pf-focus #recent-pose-strip {
  display: none;
}

/* The canvas hint is a first-run affordance; in Focus Mode the badge already
   says what is going on and a second floating label just adds noise. */
body.pf-focus #canvas-hint { display: none; }

/* POSE LOCK — the cursor is the honest signal that a drag will do nothing.
   Without it the gizmo still looks grabbable and the first thing a locked
   user does is drag it and assume the app is broken. */
body.pose-locked #canvas-container { cursor: not-allowed; }

/* Disabled controls read as "not available here" unless they are visibly
   the same controls, switched off. */
body.pose-locked #bone-rotation-pad { opacity: 0.45; }
body.pose-locked #bone-rotation-pad,
body.pose-locked #slider-yaw,
body.pose-locked #slider-hip-y,
body.pose-locked #slider-model-y { cursor: not-allowed; }

/* BADGE — a lock that gives no sign it is on is indistinguishable from a bug.
   Sits bottom-centre over the canvas, out of the way of both the camera bar
   at the top and the status bar at the bottom. */
.artist-badge {
  position: fixed;
  bottom: calc(var(--bottom-h) + 16px);
  left: calc(var(--canvas-left) + (calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right)) / 2);
  transform: translateX(-50%);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(19, 17, 31, 0.92);
  border: 1px solid var(--warning);
  color: var(--warning);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  pointer-events: none;          /* never blocks a click on the model */
}
/* In Focus Mode the bottom bar is gone, so the badge takes its space. */
body.pf-focus .artist-badge { bottom: 20px; }

/* Fullscreen, offered only while Focus Mode is on. Quieter than the Exit
   chip beside it — outlined rather than filled — because it goes one step
   further into the mode, and the filled control on a badge should be the one
   that gets you back out. */
.artist-badge-fs {
  pointer-events: auto;
  flex: none;
  display: grid;
  place-items: center;
  margin: -4px 0 -4px 4px;
  width: 30px;
  height: 30px;
  padding: 0;
  /* Literal rgba of --warning (#f59e0b), as the badge's own background is:
     these are tints of one token, and the file has no alpha-tint idiom. */
  border: 1px solid rgba(245, 158, 11, 0.45);
  border-radius: 999px;
  background: transparent;
  color: var(--warning);
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s, border-color 0.15s;
}
.artist-badge-fs svg { width: 15px; height: 15px; display: block; }
.artist-badge-fs:hover  { background: rgba(245, 158, 11, 0.18); }
.artist-badge-fs:active { background: rgba(245, 158, 11, 0.30); }
.artist-badge-fs:focus-visible { outline: 2px solid var(--warning); outline-offset: 2px; }

/* The way out, for devices with no Esc key (js/ui/artistMode.js decides when
   to build it). The badge is inert over the canvas by design, so the live
   part has to re-arm itself. Sized for a thumb, not a cursor: this is the
   only control on the screen when it exists, and missing it means being
   stuck in a mode with no visible controls at all. */
.artist-badge-exit {
  pointer-events: auto;
  margin: -4px -8px -4px 2px;   /* eat the pill's padding — target, not gap */
  padding: 8px 14px;
  min-height: 32px;
  border: none;
  border-radius: 999px;
  background: var(--warning);
  color: #171326;
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  -webkit-tap-highlight-color: transparent;
}
.artist-badge-exit:active { opacity: 0.75; }
.artist-badge-exit:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

/* ─────────────────────────────────────────────────────────────
   COMPOSITION SUGGESTION  (js/library/compositionSuggestion.js)

   Two states share this card: the Rule 8 composition offer after a
   MEDIUM match, and the "no confident match" candidate list at LOW.
   It sits under the detection readout in the REFERENCE rail because
   it is a statement about the detection.

   Deliberately quiet: the pose is already applied and correct enough
   to keep. This is an offer, not an error, so it uses the teal accent
   rather than warning colour — except the LOW variant, which is a
   genuine "nothing matched" and earns --warning.
   ───────────────────────────────────────────────────────────── */
.composition-suggestion {
  margin-top: var(--space-2);
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  overflow: hidden;
}

.cs-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--teal-dim);
  border-bottom: 1px solid var(--border);
}
.cs-head-warn { background: var(--warning-dim); }

.cs-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--teal);
  flex: none;
}
.cs-dot-warn { background: var(--warning); }

.cs-title {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--teal);
}
.cs-head-warn .cs-title { color: var(--warning); }

.cs-close {
  margin-left: auto;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.cs-close:hover { background: var(--card-hover); color: var(--text); }

.cs-body { padding: var(--space-3); }

.cs-question {
  font-size: var(--fs-body);
  color: var(--text);
  margin-bottom: var(--space-2);
}

.cs-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-btn);
}
.cs-check  { color: var(--success); font-size: 12px; flex: none; }
.cs-donor  { font-size: var(--fs-input); color: var(--text); font-weight: 600;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-score  { margin-left: auto; font-size: var(--fs-label); color: var(--text-sec);
             font-variant-numeric: tabular-nums; flex: none; }

.cs-meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-size: var(--fs-label);
  color: var(--text-sec);
}
.cs-est   { color: var(--teal); font-size: var(--fs-input); font-variant-numeric: tabular-nums; }
.cs-delta { margin-left: auto; color: var(--success); font-variant-numeric: tabular-nums; }

.cs-note {
  margin-top: var(--space-2);
  font-size: var(--fs-label);
  line-height: var(--lh-body);
  color: var(--text-muted);
}

.cs-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.cs-actions > button { flex: 1; }

/* ── LOW-confidence candidate list ── */
.cs-cands { display: flex; flex-direction: column; gap: var(--space-1); margin-top: var(--space-2); }

.cs-cand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-btn);
  color: var(--text);
  font-size: var(--fs-input);
  text-align: left;
  cursor: pointer;
}
.cs-cand:hover { background: var(--card-hover); border-color: var(--border-focus); }

.cs-cand-rank  { color: var(--text-muted); font-size: var(--fs-label);
                 font-variant-numeric: tabular-nums; flex: none; }
.cs-cand-name  { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-cand-score { margin-left: auto; color: var(--text-sec); font-size: var(--fs-label);
                 font-variant-numeric: tabular-nums; flex: none; }

/* The phone shell's own two elements are inert everywhere by default, so a
   600px-wide desktop window — or a phone whose JS has not run yet — never
   shows a tab bar over a docked editor. The tier below turns them on, and
   only inside body.phone-shell. */
#phone-sheet,
#phone-siderail,
#phone-tabbar { display: none; }

/* ═══════════════════════════════════════════════════════════════════════
   PHONE TIER — ≤699px
   (markup: #phone-sheet / #phone-tabbar in index.html, driver:
    js/phoneShell.js, which owns the `phone-shell` class on <body>)

   Every rule below either scopes to `body.phone-shell` or styles a
   descendant of #phone-sheet / #phone-tabbar, which are display:none
   outside it. So this tier cannot fire unless the JS agrees the viewport
   is a phone, and the desktop shell is untouched — not overridden, not
   reached.

   WHY A SECOND SHELL RATHER THAN MORE BREAKPOINTS

   The desktop editor docks four columns around the canvas and hands the
   narrow band a ladder of collapses. That ladder runs out below ~700px:
   at 412px the workspace rail (72) plus two 40px slivers plus their gap
   is 170px of chrome — 41% of the screen — and what it is protecting is
   a 3D viewport, the one thing on this surface worth the pixels. No
   amount of shrinking fixes a layout whose premise is "several panels at
   once"; a phone can hold exactly one.

   So the premise changes. The canvas goes full-bleed and every panel
   becomes a bottom sheet that one thumb raises and drops. The tab bar is
   the navigator, in the thumb zone, where the rail's job moves.

   WHAT IS NOT RE-PARENTED: all of it. #left-panel, #right-panel and
   #mini-gizmo-panel are re-anchored into the sheet by these rules alone.
   Every id the other modules write into resolves exactly as it does on
   desktop, and #workspace-rail keeps driving workspaces.js — it just
   lies down and becomes a chip strip at the sheet's top edge.

   NO `!important` ANYWHERE IN THIS BLOCK, deliberately. index.html's
   switchView() hides the editor by writing inline `display:none` when
   Gallery or Settings opens; an !important show rule here would punch
   the panels back through those surfaces. Specificity does the whole
   job, and phoneShell.js clears the two states that pre-date it (the
   gizmo's initial inline display, the rail-collapse classes) rather
   than out-shouting them.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {

  body.phone-shell {
    /* The topbar absorbs the status-bar inset instead of letting the page
       start under it. Every downstream calc already keys off --topbar-h. */
    --topbar-h: calc(48px + env(safe-area-inset-top, 0px));

    --ph-safe-l:  env(safe-area-inset-left,   0px);
    --ph-safe-r:  env(safe-area-inset-right,  0px);
    --ph-safe-b:  env(safe-area-inset-bottom, 0px);

    --ph-tabbar-h: 58px;
    --ph-ribbon-h: 44px;
    --ph-handle-h: 32px;
    --ph-strip-h:  56px;

    /* Everything that floats above the thumb zone measures from here. */
    --ph-chrome-b: calc(var(--ph-tabbar-h) + var(--ph-ribbon-h) + var(--ph-safe-b));

    /* Peek stops short of half the screen on purpose: this is a pose
       editor, so the figure has to stay visible while you work a joint.
       That is also why there is no scrim — the model is not "behind" the
       sheet, it is the thing the sheet is for. The min() keeps the sheet
       clear of the topbar on short screens. */
    /* THE VIEWPORT FLOOR.
       Every sheet height is expressed as "how much canvas is left", not "how
       tall is the sheet", so the figure can never be squeezed out by a panel
       that happens to have a lot of rows. --ph-canvas-min is the guarantee:
       whatever the state, at least this much viewport survives. */
    /* One horizontal inset for everything in the sheet. */
    --ph-gutter: 16px;

    --ph-canvas-min: calc(32vh / var(--ui-scale, 1));
    --ph-sheet-max: calc(calc(100vh / var(--ui-scale, 1)) - var(--topbar-h) - var(--ph-chrome-b) - 12px);

    /* peek — the default, and the only state the app opens in. Kept inside
       the "UI never covers more than 35-40%" budget: everything past this is
       something the user asked for by dragging the handle. */
    --ph-sheet-h: min(calc(36vh / var(--ui-scale, 1)), var(--ph-sheet-max));
    --ph-sheet-y: calc(100vh / var(--ui-scale, 1));

    /* No rails inline any more, so the canvas is the viewport. Overlay
       consumers (#canvas-hint, the camera bar's centring) read these. */
    --rail-w: 0px;  --left-w: 0px;  --right-w: 0px;  --mid-w: 0px;  --prail-w: 0px;
    --canvas-left:  0px;
    --canvas-right: 0px;
    --bottom-h: var(--ph-chrome-b);
  }

  /* half — the working height for list-shaped panels. */
  body.phone-shell[data-sheet-size="half"] {
    --ph-sheet-h: min(calc(52vh / var(--ui-scale, 1)), var(--ph-sheet-max));
  }

  /* full — for long forms. Capped by the canvas floor, not by a flat 88vh:
     the old value left a sliver of viewport, which on a 915px screen meant
     the figure was gone. The floor has to be low enough that full is
     actually taller than half — a first attempt at 60vh made 'full' SHORTER
     than 'half', so the handle appeared to shrink the sheet. */
  body.phone-shell[data-sheet-size="full"] {
    --ph-sheet-h: min(calc(calc(100vh / var(--ui-scale, 1)) - var(--ph-canvas-min)), var(--ph-sheet-max));
  }

  body.phone-shell.sheet-open { --ph-sheet-y: 0px; }

  /* ── Chrome that has no job here ─────────────────────────────────────
     The tab bar is the affordance for all four panels now, so the rail
     stubs and chevrons would be a second, quieter way to do the same
     thing — and every one of them costs canvas. #mid-panel goes entirely:
     it holds no content, only the collapse controls for a column that
     does not exist on a phone. */
  body.phone-shell #mid-panel,
  body.phone-shell #rail-stub,
  body.phone-shell #panel-rail,
  body.phone-shell #canvas-hint,
  body.phone-shell #recent-pose-strip,
  body.phone-shell #workspace-rail > .rail-collapse,
  body.phone-shell #left-panel  > .rail-collapse,
  body.phone-shell #left-panel  > .rail-stub,
  body.phone-shell #right-panel > .rail-collapse,
  body.phone-shell #right-panel > .rail-stub { display: none; }

  /* ── Topbar ──────────────────────────────────────────────────────── */
  body.phone-shell #topbar {
    padding: env(safe-area-inset-top, 0px)
             calc(12px + var(--ph-safe-r)) 0 calc(12px + var(--ph-safe-l));
  }
  /* The wordmark stays, but it has to earn its width.

     This rule used to be `display: none`, on the arithmetic that at 412px
     padding 24 + brand 97 + actions 130 left 161px for a nav needing 228
     (Editor 58 + Admin Panel 96 + Settings 70 + gaps). #editor-nav is an
     overflow-x:auto strip, so the shortfall did not overflow visibly -- it
     silently CLIPPED Settings, hiding a destination behind a scroll with
     no affordance saying it was there. Correct diagnosis, wrong lever:
     dropping the wordmark left the phone with no statement of what the
     app IS anywhere on the surface. The name appeared once, on the boot
     splash, and was gone by the time anyone could read it.

     So the width comes out of the three places that were paying above the
     rate instead:
        brand   16px -> 14px, and a 24px -> 8px gutter
        nav-btn 12px ->  9px side padding, on every tab
        actions 40px -> 36px wide, 10px -> 8px padding
     All the figures below are measured, not estimated -- Chrome against
     this stylesheet, so they carry the real system-font metrics.

        padding  24      brand+gutter  73.4      undo/redo  76
        nav: 178 (Editor/Gallery/Settings) | 155 (Admin Panel/Settings)
             209 (the widest row the app can build: an admin who has
                  switched the Editor tab back on in Settings)

     Worst case is therefore 24 + 73.4 + 209 + 76 = 382.4, which clears
     390px -- iPhone 12 through 16, and most Androids -- with ~8px to
     spare, and the common 178px row by ~39px. Below 385 the monogram
     takes over; see the two nested tiers.

     SINCE MEASURED: the action buttons are 40px rather than 36 (thumb
     targets -- see the PHONE TOP BAR block at the end of this file), so
     the actions cost 8px more and that ~8px of headroom is spent. The
     widest row an admin can build therefore reaches the nav's scroll
     strip a little sooner than it did; the common rows (114px for
     Editor/Gallery, 91px for Admin Panel, both measured) still fit at
     360px with room over. Scrolling with visible edges is the designed
     fallback, not the failure this budget exists to prevent. */
  body.phone-shell .brand {
    flex: 0 0 auto;
    font-size: 14px;
    margin-right: 8px;
  }
  body.phone-shell .nav-btn { padding: 6px 9px; font-size: 12.5px; }

  /* 440px and under (was 384px; the design typefaces run wider) -- iPhone SE/8 at 375, the 360px Androids, and
     everything narrower. The wordmark's 382.4px worst case no longer
     fits, and a destination outranks a logo: the nav is how you get
     somewhere, the mark only says where you already are. So the mark
     compresses rather than the nav, and the name survives as its
     initials instead of vanishing. The word itself is still on the boot
     splash and the sign-in card. */
  @media (max-width: 440px) {
    body.phone-shell .brand-word { display: none; }
    body.phone-shell .brand-mark { display: inline; }
    body.phone-shell .brand {
      font-size: 15px;
      letter-spacing: 0;
      margin-right: 8px;
    }
  }

  /* The last rung. With the monogram the widest row still wants
     24 + 25 + 209 + 76 = 334px, so at 320 -- an SE-generation phone, or
     anyone at 200% zoom -- even the initials cost a destination. Here the
     mark goes, and the original rule's reasoning holds unchanged: the tab
     bar and the nav both say where you are; a logo does not, and it is
     the only thing in this row that is not somewhere to go.

     Below ~300px the widest row overflows on its own, with or without a
     mark. That predates this rule and there is no width left to give it;
     the scroll edges on #editor-nav are what make it survivable. */
  @media (max-width: 344px) {
    body.phone-shell .brand { display: none; }
  }

  /* Undo and Redo keep their icons and lose their words. They are the two
     controls whose meaning a glyph actually carries. */
  body.phone-shell #topbar-actions .topbar-btn span { display: none; }
  body.phone-shell #topbar-actions .topbar-btn {
    min-width: 36px;
    padding: 0 8px;
    justify-content: center;
  }

  /* ── Camera pill ─────────────────────────────────────────────────────
     Anchored to both edges and scrolled rather than centred and wrapped.
     A second row of presets is a block sitting on the model; a row that
     runs off the edge is a row you flick. Wrapping is what the ≤899px
     compaction was still doing at 412px, because its width budget came
     out at 276px against ~295px of buttons.

     The presets sit centred in that full-width bar. `safe` is the whole
     point of the declaration: plain `center` on a scroll container puts
     the overflow on BOTH sides once the row is too wide, and the part
     that spills past the start edge cannot be scrolled back to — Front
     would be unreachable on the narrowest phones. `safe center` centres
     while it fits and falls back to flex-start the moment it does not.
     The bare `center` above it is the fallback for engines that don't
     parse the keyword (Safari before 15.4); they centre without the
     guard, which is the ordinary flex behaviour and still correct at
     every width where the row fits. */
  body.phone-shell #cam-preset-bar {
    top: calc(var(--topbar-h) + 10px);
    /* Sized by its contents and centred, NOT pinned to both edges. Edge
       anchoring made the bar a full-width band with the presets adrift in
       the middle of it — chrome that looked like a header instead of a
       control, and 300px of tinted glass laid over the model for nothing.
       The clamp keeps it inside the safe area, so it only reaches the
       edges on a screen narrow enough to actually need them. */
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    width: max-content;
    max-width: calc(calc(100vw / var(--ui-scale, 1)) - var(--ph-safe-l) - var(--ph-safe-r) - 20px);
    flex-wrap: nowrap;
    justify-content: center;
    justify-content: safe center;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    gap: 1px;
    padding: 4px;
    border-radius: var(--radius-card);
  }
  body.phone-shell #cam-preset-bar::-webkit-scrollbar { display: none; }

  /* 36px of pill inside a 44px bar: the touch target is the row height,
     which is what a thumb actually hits. The old 46px min-width padded
     every label out to the same slab; the height is what the thumb needs,
     so the width goes back to following the word. */
  body.phone-shell .cam-preset-btn {
    flex: none;
    height: 36px;
    min-width: 0;
    padding: 0 11px;
    font-size: 12.5px;
  }
  body.phone-shell .cam-preset-free { margin-left: 9px; padding: 0 11px; }
  body.phone-shell .cam-preset-reset {
    flex: none;
    height: 36px;
    min-width: 0;
    padding: 0 10px;
    font-size: 14px;
  }

  /* ── Status ribbon ───────────────────────────────────────────────────
     One line, above the tab bar. Of the four readout cells only detection
     survives — Pose and Confidence already drop at 880/1100, and Selected
     Joint has two louder homes on a phone (the Rotate sheet names it, the
     model highlights it). The two camera actions stay, as icons. */
  body.phone-shell #bottom-bar {
    top: auto;
    bottom: calc(var(--ph-tabbar-h) + var(--ph-safe-b));
    height: var(--ph-ribbon-h);
    gap: 10px;
    padding: 0 calc(12px + var(--ph-safe-r)) 0 calc(12px + var(--ph-safe-l));
    background: rgba(15, 14, 26, 0.92);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--border);
    z-index: 220;
  }

  body.phone-shell .sb-groups .sb-cell { display: none; }
  body.phone-shell .sb-groups .sb-cell:first-child {
    display: flex;
    padding: 0;
    gap: 8px;
    border-right: none;
  }
  body.phone-shell .sb-sub  { display: none; }
  body.phone-shell .sb-text { min-width: 0; overflow: hidden; }
  body.phone-shell .sb-value {
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  body.phone-shell .bottom-actions { flex: none; gap: 6px; }
  body.phone-shell .sb-btn {
    position: relative;
    width: 36px;
    min-height: 36px;
    padding: 0;
    justify-content: center;
  }
  body.phone-shell .sb-btn span { display: none; }
  /* Visually 36px, 44px to a thumb, and the extra area stays inside the
     ribbon so it never overlaps the tab bar below. */
  body.phone-shell .sb-btn::before {
    content: '';
    position: absolute;
    inset: -4px;
  }

  /* ── Tab bar ─────────────────────────────────────────────────────────
     The four destinations are the three panels the desktop docks around
     the canvas, plus a second door to the left panel. Create and Tools
     both open it — "add a reference photo" is where a phone session
     starts, and one level of burial costs more than the tab does. Which
     one lights up follows the active workspace, so the pair never reads
     as two tabs doing the same thing. */
  body.phone-shell #phone-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 230;
    display: flex;
    height: calc(var(--ph-tabbar-h) + var(--ph-safe-b));
    padding: 0 var(--ph-safe-r) var(--ph-safe-b) var(--ph-safe-l);
    background: rgba(15, 14, 26, 0.96);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-top: 1px solid var(--border);
  }

  .pt-btn {
    position: relative;
    flex: 1;
    min-width: 0;
    height: var(--ph-tabbar-h);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    background: none;
    border: none;
    color: var(--text-muted);
    font: inherit;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    transition: color 0.16s ease;
    -webkit-tap-highlight-color: transparent;
  }

  .pt-btn svg { width: 21px; height: 21px; }
  .pt-btn.is-active        { color: var(--teal); }
  .pt-btn:active           { color: var(--text-sec); }
  .pt-btn.is-active:active { color: var(--teal); }
  .pt-btn:focus-visible    { outline: 2px solid var(--teal); outline-offset: -3px; }

  /* The one authored moment in this tier is the sheet's rise; the tab
     indicator is its echo, on the same curve, so the two read as one
     gesture rather than two effects. */
  .pt-btn::after {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    width: 24px;
    height: 2.5px;
    border-radius: 0 0 3px 3px;
    background: var(--teal);
    transform: translateX(-50%) scaleX(0);
    transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .pt-btn.is-active::after { transform: translateX(-50%) scaleX(1); }

  /* ── Side rail: Create and Draw ──────────────────────────────────────
     The two ways a session starts, in the canvas's top-left corner: the
     figure is framed centred, so this is the one corner it never stands in,
     and nothing else on this tier floats there. Under the sheet in z-order —
     a full-height sheet on a short phone reaches this corner, and then the
     sheet is what is being worked in. The lit state is the sheet strip's
     chip style, so Create reads as the same control it was in the sheet. */
  body.phone-shell #phone-siderail {
    position: fixed;
    top: calc(var(--topbar-h) + 12px);
    left: calc(10px + var(--ph-safe-l));
    z-index: 205;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 4px;
    background: rgba(15, 14, 26, 0.92);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: 0 8px 24px -10px rgba(0, 0, 0, 0.55);
  }

  .psr-btn {
    width: 52px;
    height: 52px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 0;
    background: none;
    border: 1px solid transparent;
    border-radius: 12px;
    color: var(--text-sec);
    font: inherit;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.01em;
    transition: color 0.16s ease, background-color 0.16s ease, border-color 0.16s ease;
    -webkit-tap-highlight-color: transparent;
  }
  .psr-btn svg { width: 20px; height: 20px; }
  .psr-btn:active { background: var(--card-hover); }
  .psr-btn.is-active {
    color: var(--teal);
    background: var(--teal-dim);
    border-color: var(--teal);
  }
  .psr-btn:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

  /* Focus Mode puts the chrome away; this is chrome. A see-through sheet at
     full height would show the rail through its own first rows. */
  body.phone-shell.pf-focus #phone-siderail { display: none; }
  body.phone-shell.sheet-seethru.sheet-open[data-sheet-size="full"] #phone-siderail { visibility: hidden; }

  /* ── Sheet ───────────────────────────────────────────────────────────
     ONE moving element. phoneShell.js parents the panels into this frame
     while the phone tier is up, so the surface and its contents are a
     single box that transforms once — there is no second element whose
     position has to be kept in step, and therefore no way for the sheet
     to come apart from what is inside it.

     The panels go back to their original parents on the way out, so the
     desktop shell gets the document it started with. */
  body.phone-shell #phone-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--ph-chrome-b);
    height: var(--ph-sheet-h);
    z-index: 210;
    display: flex;
    flex-direction: column;
    /* The handle and the close button are absolute over this. */
    padding-top: var(--ph-handle-h);
    background: rgba(19, 17, 31, 0.94);
    backdrop-filter: blur(20px) saturate(1.25);
    -webkit-backdrop-filter: blur(20px) saturate(1.25);
    border-top: 1px solid var(--border-mid);
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -14px 44px -10px rgba(0, 0, 0, 0.66);
  }

  body.phone-shell #phone-sheet {
    transform: translateY(var(--ph-sheet-y));
    transition: transform 0.34s cubic-bezier(0.22, 1, 0.36, 1);
  }

  /* Under the thumb the sheet has to track the finger exactly; a 340ms
     ease between every pointermove is lag, not motion. phoneShell.js
     writes --ph-sheet-y inline for the duration of the drag. */
  body.phone-shell.sheet-dragging #phone-sheet { transition: none; }

  /* Closed, the sheet is off the screen AND out of the tab order — a
     control you cannot see must not be one you can tab into.
     `visibility` is delayed by the length of the slide rather than
     switched with the class, because flipping it on the same frame would
     blank the sheet before it has travelled anywhere. phoneShell.js keeps
     data-sheet pointing at the last panel for the same reason: the sheet
     leaves with its contents, not as an empty box. */
  body.phone-shell:not(.sheet-open) #phone-sheet {
    visibility: hidden;
    transition: transform 0.34s cubic-bezier(0.22, 1, 0.36, 1),
                visibility 0s linear 0.34s;
  }

  #phone-sheet-handle {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: var(--ph-handle-h);
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    /* The drag is driven in JS, so the browser must not claim the gesture. */
    touch-action: none;
    -webkit-tap-highlight-color: transparent;
  }
  #phone-sheet-handle:focus-visible { outline: 2px solid var(--teal); outline-offset: -4px; }

  .ph-grip {
    width: 38px;
    height: 4px;
    border-radius: 2px;
    background: var(--border-focus);
    transition: background 0.16s ease;
  }
  #phone-sheet-handle:active .ph-grip { background: var(--text-sec); }

  #phone-sheet-close {
    position: absolute;
    top: 0;
    right: 4px;
    width: 40px;
    height: var(--ph-handle-h);
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--text-muted);
    -webkit-tap-highlight-color: transparent;
  }
  #phone-sheet-close:active        { color: var(--text); }
  #phone-sheet-close:focus-visible { outline: 2px solid var(--teal); outline-offset: -4px; }

  /* ── Panels, as flow children of the sheet ───────────────────────────
     Docked geometry off, flex on: they are ordinary boxes inside the
     frame now, so the strip takes its 56px and whichever panel is up
     takes the rest. No heights are computed against --ph-sheet-h any
     more — dragging the sheet taller just gives the panel more room. */
  body.phone-shell #left-panel,
  body.phone-shell #right-panel,
  body.phone-shell #mini-gizmo-panel {
    position: static;
    inset: auto;
    width: auto;
    /* CLEARS THE DESKTOP RESERVATION.
       The <=899px shell caps these at calc(100vw - var(--rail-stub) * 2) to
       leave room for the two rail collapse stubs that flank a docked panel.
       Those stubs do not exist here — the panel is a flow child of the sheet
       — so at 412px the cap silently made every panel 320px and, being a
       block, it sat flush left with 92px of dead space on the right. Setting
       width:auto was not enough; max-width outranks it. */
    max-width: none;
    height: auto;
    max-height: none;
    flex: 1 1 auto;
    min-height: 0;                 /* lets it scroll instead of pushing out */
    /* One gutter, owned here, so every panel is inset by the same amount.
       Sections used to carry their own 16px, the joint panel's children
       carried none and the gizmo panel carried 14px — three different
       margins depending on which tab you were on. */
    padding: 0 calc(var(--ph-gutter) + var(--ph-safe-r))
             16px calc(var(--ph-gutter) + var(--ph-safe-l));
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  body.phone-shell #mini-gizmo-panel { padding-top: 4px; }

  /* One panel at a time — the sheet is the only slot there is. */
  body.phone-shell #left-panel,
  body.phone-shell #right-panel,
  body.phone-shell #mini-gizmo-panel,
  body.phone-shell #workspace-rail { display: none; }

  body.phone-shell[data-sheet="create"] #left-panel,
  body.phone-shell[data-sheet="tools"]  #left-panel,
  body.phone-shell[data-sheet="tools"]  #workspace-rail { display: flex; }

  /* Create and Draw have their own doors on the canvas (#phone-siderail), so
     the strip drops them — and the Create sheet drops the strip, which then
     had nothing left to switch to, and gives its 56px to the panel. */
  body.phone-shell #workspace-rail .ws-btn[data-ws="reference"],
  body.phone-shell #workspace-rail .ws-draw { display: none; }
  body.phone-shell[data-sheet="joint"]  #right-panel      { display: flex; }
  body.phone-shell[data-sheet="rotate"] #mini-gizmo-panel { display: flex; }

  /* ── Workspace chips ─────────────────────────────────────────────────
     The rail, lying down. workspaces.js is untouched: it still queries
     #workspace-rail .ws-btn and still owns which pane is active, so the
     five workspaces stay one group with one behaviour on both shells. */
  body.phone-shell #workspace-rail {
    position: static;
    inset: auto;
    width: auto;
    height: var(--ph-strip-h);
    flex: none;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    /* Same gutter as the panel below it, so the first chip lines up with the
       content rather than sitting 2px proud of it. The strip still bleeds off
       the right edge — that overflow is the affordance that says it scrolls. */
    padding: 0 calc(var(--ph-gutter) + var(--ph-safe-r))
             0 calc(var(--ph-gutter) + var(--ph-safe-l));
    background: transparent;
    border-right: none;
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  body.phone-shell #workspace-rail::-webkit-scrollbar { display: none; }

  body.phone-shell .ws-btn {
    flex-direction: row;
    width: auto;
    min-height: 48px;
    padding: 0 14px;
    gap: 8px;
    border-radius: 999px;
    background: var(--card);
    border-color: var(--border);
    font-size: 12.5px;
    white-space: nowrap;
  }
  body.phone-shell .ws-btn svg  { width: 17px; height: 17px; }
  /* Beats the landscape-short rule that hides these on a sideways phone:
     a chip strip has room for words, and five unlabelled pills is the
     unlabelled tool column this rail was built to replace. */
  body.phone-shell .ws-btn span { display: inline; }
  body.phone-shell .ws-btn.is-active {
    background: rgba(78, 205, 196, 0.12);
    border-color: var(--teal);
  }

  /* Panel sections stop being cards inside a rail and become the sheet's
     own content. */
  /* The panel owns the horizontal gutter now; a section that added its own
     would inset content twice and make the sheet look padded but narrow. */
  body.phone-shell #left-panel  .panel-section,
  body.phone-shell #right-panel .panel-section { padding: 14px 0 18px; }
  body.phone-shell #right-panel { padding-top: 4px; }
  body.phone-shell #left-panel,
  body.phone-shell #right-panel { font-size: 13px; }

  /* Touch targets inside the sheet. The desktop rail packs these tighter
     because a mouse is precise; a thumb is not. */
  /* 48px is the floor, not 40: these are the two things you hit most on this
     tier and both were under it. Set here rather than in a later override so
     there is one number per control. */
  body.phone-shell .joint-item { min-height: 48px; padding: 12px 14px; font-size: 13px;
                                 display: flex; align-items: center; }

  /* The gizmo's square is the sheet's height budget, same as it is the
     panel's on desktop. */
  /* NO CAP HERE.
     .gizmo-wrap exists to keep the gizmo CANVAS square, and the empty state
     is a sibling inside it. On the phone tier the canvas is never shown (the
     gizmo is on the figure), so the cap sized nothing and instead squeezed
     the "no joint selected" copy into a 190px column that wrapped one word
     per line, on top of the rotation controls. */
  body.phone-shell .gizmo-wrap { max-width: none; margin: 0 0 12px; }

  /* ── Rotate, with nothing selected ───────────────────────────────────
     The Rotate tab is always tappable, and #mini-gizmo-panel already owns
     an empty state that controls.js raises and lowers (#gizmo-empty-state).
     It is reused verbatim; only the hint line swaps, because the desktop
     one points right at a rail this shell does not have. */
  body.phone-shell .gizmo-empty-hint          { display: none; }
  body.phone-shell .gizmo-empty-hint--phone   { display: block; }
  body.phone-shell .gizmo-empty               { padding: 40px 0 34px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   SIDEWAYS PHONE — ≤699px wide, held landscape

   A bottom sheet is the wrong shape here and no amount of tuning fixes
   it. On a 667×375 screen the topbar and the bottom chrome already take
   ~126px; a sheet deep enough to hold the joint list would leave the
   model about 50px, which is not a pose editor, it is a scroll view with
   a picture behind it.

   So the sheet turns ninety degrees with the phone. It comes in from the
   left against a vertical tab rail, and it spends the axis this screen
   actually has. The tab bar goes with it — 68px of width costs 10% here,
   where 58px of height would have cost 15% of the scarce axis.

   Only the geometry changes. Same four destinations, same panels, same
   class contract, so phoneShell.js needs to know nothing about this.
   The one behaviour that goes is the drag-to-resize: a fixed-width side
   sheet has no second height to drag to, so the handle is hidden and the
   close button carries the whole job.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  body.phone-shell {
    --topbar-h:     calc(42px + env(safe-area-inset-top, 0px));
    --ph-tabbar-w:  68px;
    --ph-ribbon-h:  36px;
    --ph-strip-h:   46px;
    --ph-head-h:    34px;                    /* the close-button row */
    --ph-sheet-w:   min(calc(54vw / var(--ui-scale, 1)), 320px);

    /* Nothing sits below the ribbon any more — the tab bar moved to the
       side — so the bottom chrome is the ribbon and the home indicator. */
    --ph-chrome-b:  calc(var(--ph-ribbon-h) + var(--ph-safe-b));
    --bottom-h:     var(--ph-chrome-b);

    /* Off-screen is to the left now, past the rail it hides behind. */
    --ph-sheet-x: calc(-1 * (var(--ph-tabbar-w) + var(--ph-sheet-w)
                             + var(--ph-safe-l)));
  }
  body.phone-shell.sheet-open { --ph-sheet-x: 0px; }

  /* ── Tab rail ────────────────────────────────────────────────────── */
  body.phone-shell #phone-tabbar {
    top: var(--topbar-h);
    bottom: 0;
    left: 0;
    right: auto;
    width: calc(var(--ph-tabbar-w) + var(--ph-safe-l));
    height: auto;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    padding: 8px 0 calc(8px + var(--ph-safe-b)) var(--ph-safe-l);
    border-top: none;
    border-right: 1px solid var(--border);
  }

  .pt-btn {
    flex: none;
    width: 100%;
    height: auto;
    min-height: 54px;
    font-size: 10px;
    gap: 3px;
  }
  .pt-btn svg { width: 19px; height: 19px; }

  /* The indicator turns with the rail: a bar down the outer edge. */
  .pt-btn::after {
    top: 50%;
    left: 0;
    width: 2.5px;
    height: 22px;
    border-radius: 0 3px 3px 0;
    transform: translateY(-50%) scaleY(0);
  }
  .pt-btn.is-active::after { transform: translateY(-50%) scaleY(1); }

  /* The side rail clears the tab rail, and rides the sheet's edge when it
     slides in over this corner, on the sheet's own curve — so Create stays
     lit beside the panel it opened and Draw stays one tap away. */
  body.phone-shell #phone-siderail {
    top: calc(var(--topbar-h) + 10px);
    left: calc(var(--ph-tabbar-w) + var(--ph-safe-l) + 10px);
    transition: transform 0.34s cubic-bezier(0.22, 1, 0.36, 1);
  }
  body.phone-shell.sheet-open #phone-siderail { transform: translateX(var(--ph-sheet-w)); }

  /* ── Sheet, on the X axis ────────────────────────────────────────── */
  /* The portrait tier's :not(.sheet-open) rule sets visibility and the
     delayed transition, never a transform, so this one declaration is
     enough to turn the whole slide through ninety degrees. The panels
     ride along as flow children — nothing else has to be told. */
  body.phone-shell #phone-sheet {
    transform: translateX(var(--ph-sheet-x));
  }

  body.phone-shell #phone-sheet {
    top: var(--topbar-h);
    bottom: var(--ph-chrome-b);
    left: calc(var(--ph-tabbar-w) + var(--ph-safe-l));
    right: auto;
    width: var(--ph-sheet-w);
    height: auto;
    border-top: none;
    border-right: 1px solid var(--border-mid);
    border-radius: 0 18px 18px 0;
    box-shadow: 14px 0 44px -10px rgba(0, 0, 0, 0.66);
  }

  /* No second height to drag to, so the grip goes and the close button
     carries the whole job — and the space the frame reserves at its top
     is that button's height, not the hidden handle's. */
  body.phone-shell #phone-sheet       { padding-top: var(--ph-head-h); }
  body.phone-shell #phone-sheet-handle { display: none; }
  body.phone-shell #phone-sheet-close  { top: 0; right: 2px; height: var(--ph-head-h); }

  body.phone-shell #workspace-rail { height: var(--ph-strip-h); }
  /* Landscape is shorter, but a thumb is the same size in both orientations,
     so the floor does not move — only the padding tightens. */
  body.phone-shell .ws-btn { min-height: 48px; padding: 0 11px; font-size: 11.5px; }

  /* ── Chrome that has to clear the rail ───────────────────────────── */
  body.phone-shell #bottom-bar {
    bottom: 0;
    height: calc(var(--ph-ribbon-h) + var(--ph-safe-b));
    padding: 0 calc(12px + var(--ph-safe-r)) var(--ph-safe-b)
               calc(12px + var(--ph-tabbar-w) + var(--ph-safe-l));
  }

  /* Centred on the canvas rather than the window: the vertical tab rail
     eats the left edge here, so half its width is added back to the 50%
     anchor to put the bar over the middle of what is actually visible. */
  body.phone-shell #cam-preset-bar {
    left: calc(50% + (var(--ph-tabbar-w) + var(--ph-safe-l)) / 2);
    max-width: calc(calc(100vw / var(--ui-scale, 1)) - var(--ph-tabbar-w) - var(--ph-safe-l)
                    - var(--ph-safe-r) - 20px);
  }
  body.phone-shell .cam-preset-btn,
  body.phone-shell .cam-preset-reset { height: 32px; }

  body.phone-shell .gizmo-wrap { max-width: none; }
}

/* The sheet is a position change, not a decoration; when motion is
   unwanted it still has to arrive, just without the travel. */
@media (prefers-reduced-motion: reduce) {
  /* Specific enough to beat the tier's own :not(.sheet-open) rule, which
     carries the transition shorthand and would otherwise keep the slide
     alive in exactly the state this is meant to switch off. Visibility
     still needs its delay or the sheet would blank a frame early. */
  body.phone-shell #phone-sheet,
  body.phone-shell:not(.sheet-open) #phone-sheet {
    transition: visibility 0s linear 0s;
  }
  body.phone-shell #phone-tabbar,
  body.phone-shell #phone-siderail,
  body.phone-shell .psr-btn,
  body.phone-shell .pt-btn,
  body.phone-shell .pt-btn::after { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   PHONE — posing controls
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {

  /* Desktop plumbing, hidden on the phone tier.
     "Gizmo Display: Body / Panel / Auto" and "Ring Orientation: World /
     Local" are questions about where a gizmo lives and which frame its rings
     use. Both have sensible defaults, neither means anything on a tier with
     no gizmo, and together they push the control you came for two rows
     further from your thumb. Still fully available on desktop. */
  .phone-shell #gizmo-display-row,
  .phone-shell #gizmo-space-row { display: none; }

  /* 32x38 was below the 44px both Apple and Google publish as the minimum,
     and these are the steppers you use when a drag got you close but not
     exact — precisely the moment a missed tap is most annoying. */
  .phone-shell .rot-pad-btn {
    width: 44px;
    height: 44px;
    font-size: 18px;
  }
  .phone-shell .rot-pad-row input { height: 40px; font-size: 15px; }

}

/* ═══════════════════════════════════════════════════════════════════════
   MOVEMENT PAD  (#joint-joystick — js/ui/jointJoystick.js)

   Two swings on a square pad, twist on its own slider below. The labels
   are written by JS from the measured axis roles, so nothing here names an
   axis: the layout only reserves the four edges for whatever words arrive.
   ═══════════════════════════════════════════════════════════════════════ */
#joint-joystick {
  margin-bottom: var(--space-3);
  padding: var(--space-3);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}
#joint-joystick[data-empty] { display: none; }

.jj-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}
.jj-title {
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: var(--letter-section);
  color: var(--text-sec);
}
.jj-recentre {
  background: transparent;
  border: 1px solid var(--border-mid);
  color: var(--text-sec);
  border-radius: var(--radius-sm);
  font-size: 10px;
  padding: 3px 8px;
  cursor: pointer;
}
.jj-recentre:hover { color: var(--text); border-color: var(--border-focus); }

.jj-invert { display: flex; gap: 3px; margin-left: auto; margin-right: var(--space-2); }
.jj-inv {
  width: 24px;
  height: 22px;
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid var(--border-mid);
  color: var(--text-muted);
  border-radius: var(--radius-sm);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.jj-inv:hover { color: var(--text-sec); border-color: var(--border-focus); }
.jj-inv.is-active {
  color: var(--teal);
  border-color: var(--teal);
  background: var(--teal-dim);
}

/* The pad plus its four labels. Labels are positioned rather than in flow so
   the pad stays square and centred no matter how long a word gets
   ("Straighten" is nearly twice "Up"). */
.jj-padwrap {
  position: relative;
  padding: 15px 0;
  display: flex;
  justify-content: center;
}
.jj-label {
  position: absolute;
  font-size: 10px;
  color: var(--text-sec);
  white-space: nowrap;
  pointer-events: none;
}
.jj-label-up    { top: 0;    left: 50%; transform: translateX(-50%); }
.jj-label-down  { bottom: 0; left: 50%; transform: translateX(-50%); }
.jj-label-left  { left: 0;   top: 50%;  transform: translateY(-50%); }
.jj-label-right { right: 0;  top: 50%;  transform: translateY(-50%); }

.jj-pad {
  position: relative;
  width: 118px;
  height: 118px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  cursor: grab;
  touch-action: none;          /* the pad owns the gesture, not the scroller */
}
.jj-pad:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.jj-dragging .jj-pad  { cursor: grabbing; }

.jj-crosshair { position: absolute; background: var(--border); pointer-events: none; }
.jj-crosshair-h { left: 8%; right: 8%; top: 50%;  height: 1px; }
.jj-crosshair-v { top: 8%; bottom: 8%; left: 50%; width: 1px; }

.jj-knob {
  position: absolute;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;     /* centre on the % position JS sets */
  left: 50%;
  top: 50%;
  border-radius: 50%;
  background: var(--teal);
  box-shadow: 0 0 0 4px var(--teal-dim);
  pointer-events: none;
}

/* ── Pelvis: one slider per axis ──────────────────────────────────────
   Same track and knob as the leg-swing slider so the two hip controls
   read as one family, just stacked three deep. */
#jj-axis-row { margin-bottom: var(--space-2); }

.jj-axis {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.jj-axis + .jj-axis { margin-top: 2px; }

.jj-axis-name {
  width: 12px;
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  flex: none;
}
.jj-axis-val {
  width: 40px;
  text-align: right;
  font-size: 11px;
  color: var(--text-sec);
  font-variant-numeric: tabular-nums;
  flex: none;
}

.jj-axis-slider {
  position: relative;
  flex: 1;
  height: 30px;
  cursor: grab;
  touch-action: none;
}
.jj-axis-slider:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.jj-dragging .jj-axis-slider  { cursor: grabbing; }

.jj-axis-track {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 3px;
  margin-top: -1.5px;
  border-radius: 2px;
  background: var(--bg);
  border: 1px solid var(--border-mid);
}
.jj-axis-centre {
  position: absolute;
  left: 50%; top: 50%;
  width: 1px; height: 11px;
  margin: -5.5px 0 0 -0.5px;
  background: var(--border-focus);
}
.jj-axis-knob {
  position: absolute;
  width: 18px; height: 18px;
  margin: -9px 0 0 -9px;
  left: 50%; top: 50%;
  border-radius: 50%;
  background: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-dim);
  pointer-events: none;
}

@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  .phone-shell .jj-axis-slider { height: 44px; }
  .phone-shell .jj-axis-knob   { width: 26px; height: 26px; margin: -13px 0 0 -13px; }
  .phone-shell .jj-axis-val    { font-size: 12px; width: 44px; }
}

.jj-twist-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.jj-twist-label { font-size: 10px; color: var(--text-sec); min-width: 34px; }
.jj-twist {
  position: relative;
  flex: 1;
  height: 30px;
  cursor: grab;
  touch-action: none;
}
.jj-twist:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.jj-dragging .jj-twist  { cursor: grabbing; }
.jj-twist-track {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 3px;
  margin-top: -1.5px;
  border-radius: 2px;
  background: var(--bg);
  border: 1px solid var(--border-mid);
}
.jj-twist-knob {
  position: absolute;
  width: 18px; height: 18px;
  margin: -9px 0 0 -9px;
  left: 50%; top: 50%;
  border-radius: 50%;
  background: var(--purple);
  box-shadow: 0 0 0 3px var(--purple-dim);
  pointer-events: none;
}

.jj-fallback {
  margin-top: var(--space-2);
  font-size: 10px;
  line-height: 1.4;
  color: var(--warning);
}

/* Phone: the pad is the primary control here, so it gets the room the
   desktop spends on a gizmo canvas, and the twist slider grows to a
   comfortable thumb target. */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  .phone-shell .jj-pad     { width: 168px; height: 168px; }
  .phone-shell .jj-knob    { width: 34px; height: 34px; margin: -17px 0 0 -17px; }
  .phone-shell .jj-twist   { height: 44px; }
  .phone-shell .jj-label   { font-size: 11px; }
}

/* body.tp-dragging lived here — the drag-guard for Quick Pose, the removed
   drag-the-limb-to-pose gesture (js/ui/touchPosing.js). Nothing sets the class
   now that dragging on the figure only moves the camera. */

/* One hint per shell — see #joint-instruction in index.html. */
.hint-phone   { display: none; }
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  .phone-shell .hint-desktop { display: none; }
  .phone-shell .hint-phone   { display: inline; }

  /* The nav labels wrap on a 412px screen: "Admin Panel" becomes two lines
     and pushes the top bar to double height, which eats canvas on the one
     surface that has none to spare. Let the row scroll instead of growing. */
  .phone-shell #editor-nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    /* Keeps the scroll strip a real flex item. Without it the nav's
       min-content width wins and the strip is clipped by the actions group
       rather than scrolled, which is how Settings went missing. */
    min-width: 0;

    /* Scroll edges -- not a permanent fade. The two `local` layers are
       painted onto the CONTENT and scroll away with it, so the `scroll`
       edge beneath each one is only uncovered when there is really more
       nav in that direction.

       This is the guard on the width budget that the wordmark now shares
       (see body.phone-shell .brand). That budget is measured, but it is
       measured against a system font whose metrics differ per platform,
       so on some device the row may still come up a few pixels short. The
       failure it must never repeat is the SILENT one: a destination that
       scrolled out of sight with nothing on screen admitting it existed.
       With this, overflow says so, and a flick reaches it.

       The edge is drawn in --purple, not in black. A shadow is the usual
       spelling of this, and it is the wrong one here: the bar is #13111f,
       so black on black is a decoration you cannot see, and an affordance
       nobody notices is the bug it was meant to fix wearing a fix. */
    background:
      /* Covers. Opaque for the full 14px the edge below occupies, and only
         then fading -- a cover that starts fading immediately leaves the
         edge showing through at rest, which would make a row that fits
         look like a row that scrolls. */
      linear-gradient(to right, var(--panel) 0 14px, rgba(19, 17, 31, 0) 22px)
        left  center / 22px 100% no-repeat local,
      linear-gradient(to left,  var(--panel) 0 14px, rgba(19, 17, 31, 0) 22px)
        right center / 22px 100% no-repeat local,
      /* Edges. Uncovered only once the content has scrolled off that side. */
      linear-gradient(to right, var(--purple), rgba(139, 126, 200, 0))
        left  center / 14px 100% no-repeat scroll,
      linear-gradient(to left,  var(--purple), rgba(139, 126, 200, 0))
        right center / 14px 100% no-repeat scroll;
  }
  .phone-shell #editor-nav::-webkit-scrollbar { display: none; }
  .phone-shell #editor-nav .nav-btn {
    white-space: nowrap;
    flex: 0 0 auto;
  }
  .phone-shell #topbar { height: var(--topbar-h); }

  /* Same for the workspace chips inside the sheet — "Pose" was being clipped
     mid-word and IK was off-screen with no sign it existed. */
  .phone-shell #workspace-rail {
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .phone-shell #workspace-rail::-webkit-scrollbar { display: none; }
  .phone-shell #workspace-rail .ws-btn {
    flex: 0 0 auto;
    white-space: nowrap;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   PHONE — mobile-first pass
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {

  /* ── Touch targets ──────────────────────────────────────────────────
     48px is the Material floor and the one Apple's 44pt rounds up to on a
     3x screen. Applied by role rather than per-component so a control
     added later inherits it instead of being missed.

     .cam-preset-btn is the one deliberate exception. It sits in a bar
     whose own height is the target — 36px of pill inside a 44px row, see
     the camera pill block above — so the blanket floor was not adding a
     target, it was overriding a considered one. min-height beats height
     regardless of source order, so listing it here quietly inflated every
     preset to 48px and made the landscape 32px rule a no-op. */
  .phone-shell .btn-secondary,
  .phone-shell .btn-primary,
  .phone-shell .pt-btn,
  .phone-shell .morph-bodytype-btn,
  .phone-shell .ref-gender-btn,
  .phone-shell select,
  .phone-shell input[type="text"],
  .phone-shell input[type="number"] {
    min-height: 48px;
  }
  .phone-shell .rot-pad-btn { width: 48px; height: 48px; font-size: 19px; }
  .phone-shell .rot-pad-row input { height: 48px; font-size: 16px; }

  /* Checkbox rows are targets too — the label is the target, not the 13px
     box, so the whole row has to be tall enough to hit. */
  .phone-shell .ws-check {
    min-height: 48px;
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .phone-shell .ws-check input[type="checkbox"] { width: 20px; height: 20px; }

  /* ── Rotate sheet: only what posing a joint needs ────────────────────
     The gizmo help button and the collapse chevron are desktop panel
     furniture; on a sheet with its own handle and close button they are a
     third and fourth way to do the same thing. */
  .phone-shell #btn-gizmo-help,
  .phone-shell #btn-gizmo-collapse { display: none; }
}

/* Phone-tier-only controls. Desktop must not change, so anything added for
   the phone carries this and is invisible everywhere else. */
.phone-only { display: none; }
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  .phone-shell .phone-only { display: block; }

  /* ── Body map ────────────────────────────────────────────────────────
     Bigger, and given the height to actually scale up — the pane was
     letterboxing a 120x200 drawing into 250px and the figure came out
     under 300px wide. Selection is a filled ring rather than a size
     change, so a selected joint reads at a glance without moving. */
  .phone-shell .body-map-pane { padding: 4px 0 10px; }
  .phone-shell .bm-svg { width: 100%; height: auto; max-height: calc(44vh / var(--ui-scale, 1)); }
  .phone-shell .bm-dot { r: 5; }
  .phone-shell .bm-node.is-active .bm-ring,
  .phone-shell .bm-node[aria-pressed="true"] .bm-ring { opacity: 1; stroke-width: 2.5; }

  /* The List tab is the 48px-compliant way to pick a joint: rows can be as
     tall as they need to be, where the map is bounded by the figure's own
     proportions. Both reach the same selection. */
  .phone-shell #joint-list .joint-item,
  .phone-shell #joint-list button {
    min-height: 48px;
    display: flex;
    align-items: center;
  }
}

@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  /* The status ribbon and the tab bar are fixed above the sheet, so the last
     rows of any panel sat underneath them — the body map's feet and the
     "Start from a pose" heading were both cut off with no sign there was more.
     Pad the scroll area by exactly the chrome that overlaps it. */
  body.phone-shell #left-panel,
  body.phone-shell #right-panel,
  body.phone-shell #mini-gizmo-panel {
    padding-bottom: calc(var(--ph-ribbon-h) + 20px);
  }
}

/* ─────────────────────────────────────────────────────────────
   CONFIDENCE BREAKDOWN  (js/library/confidenceBreakdown.js)

   Per-region scores for the retrieved pose. Sits between the
   detection readout and the composition suggestion, so the panel
   reads: what matched → why → what to do about it.

   Bar tone reuses the decision thresholds (CompositionPolicy), so
   a region the engine would call weak is the one that reads red.
   "Not detected" means unscored, never 0% — occluded is not the same as
   wrong, and a region that was never measured gets no bar at all.
   ───────────────────────────────────────────────────────────── */
.confidence-breakdown {
  margin-top: var(--space-2);
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  overflow: hidden;
}

.cb-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}
.cb-title {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-sec);
}
.cb-overall {
  margin-left: auto;
  font-size: var(--fs-input);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.cb-body { padding: var(--space-3); }

.cb-sep {
  height: 1px;
  background: var(--border);
  margin: var(--space-2) 0;
}

.cb-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 3px 0;
}
.cb-label {
  flex: 0 0 68px;
  font-size: var(--fs-label);
  color: var(--text-sec);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-bar {
  flex: 1;
  height: 5px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
.cb-bar > i {
  display: block;
  height: 100%;
  width: 100%;
  background: currentColor;
  /* Scales rather than resizes. Animating width relayouts the row on every
     frame; scaleX is composited. The track already has border-radius +
     overflow:hidden, so it does the clipping and the fill carries no radius
     of its own — which also keeps the cap from being distorted by the scale.
     Same treatment as the coverage and progress fills. */
  transform-origin: left center;
  transition: transform 220ms ease;
}
.cb-val {
  flex: 0 0 34px;
  text-align: right;
  font-size: var(--fs-label);
  font-variant-numeric: tabular-nums;
  color: var(--text-sec);
}

/* Tone carries on the row so the bar (currentColor) and the number agree. */
.cb-good  { color: var(--teal); }
.cb-ok    { color: var(--warning); }
.cb-weak  { color: var(--danger); }
.cb-good .cb-val, .cb-ok .cb-val, .cb-weak .cb-val { color: inherit; }

/* Unscored rows carry words, not a number, and have no bar. The value cell
   stops being a 34px numeric column and takes the space the track would have
   used — so "Not detected" fits inside the existing panel width instead of
   forcing the rail wider. It still ends on the same right edge as every
   percentage, so the column still scans top-to-bottom.

   The label may ellipsize here (it cannot in a scored row, where the fixed
   68px always fits) — that is the correct thing to give up first if the rail
   is ever narrower than these two pieces of text. */
.cb-unscored { color: var(--text-muted); }
.cb-unscored .cb-label { flex: 0 1 auto; }
.cb-unscored .cb-val {
  /* Grows to hold the right edge, never shrinks: if the rail is too narrow
     for both, the region name ellipsizes (its position in a fixed head-down
     order still identifies it) rather than "Not detected" losing letters. */
  flex: 1 0 auto;
  color: var(--text-muted);
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: normal;   /* tabular figures are for digits */
}

/* The region a suggestion targets, or any region scoring weak. */
.cb-highlight .cb-label { color: var(--text); font-weight: 600; }

.cb-note {
  margin-top: var(--space-2);
  font-size: var(--fs-label);
  line-height: var(--lh-body);
  color: var(--text-muted);
}

/* ═══════════════════════════════════════════════════════════════════════
   PHONE — Rotate has two layouts, never both
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {

  /* Which one is showing is driven by body.pf-has-joint, which phoneShell.js
     already maintains from the selected-joint readout. controls.js still
     owns the empty state's own display (inline none on select), so the two
     agree rather than compete. */

  /* ── Empty state: no joint selected ──────────────────────────────────
     Every rotation control is hidden, not disabled. A disabled Step / X / Y /
     Z / Mirror stack is a wall of dead controls that says "broken" long
     before it says "pick a joint first". */
  body.phone-shell:not(.pf-has-joint) #bone-rotation-pad,
  body.phone-shell:not(.pf-has-joint) .joint-actions,
  body.phone-shell:not(.pf-has-joint) #hand-preset-section,
  body.phone-shell:not(.pf-has-joint) #mini-gizmo-canvas,
  /* The "SELECTED JOINT / No Joint Selected" readout as well. The empty
     state below states it larger, and both at once looked duplicated.
     Display:none does not affect textContent, so phoneShell.js keeps
     reading this node to maintain .pf-has-joint. */
  body.phone-shell:not(.pf-has-joint) #gizmo-head,
  body.phone-shell:not(.pf-has-joint) #selected-joint-name { display: none; }

  body.phone-shell .gizmo-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
  }
  body.phone-shell .gizmo-empty-title {
    font-size: 19px;
    font-weight: 600;
    color: var(--text);
    letter-spacing: -0.2px;
  }
  /* Full sheet width. No max-width, no column: this is one short sentence and
     constraining it is exactly what broke it. */
  body.phone-shell .gizmo-empty-hint--phone {
    font-size: 14px;
    line-height: 1.5;
    width: 100%;
    max-width: none;
    color: var(--text-sec);
    text-wrap: pretty;
  }

  /* ── Editor: joint selected ──────────────────────────────────────────
     The empty state's wrapper collapses so it contributes no margin, and the
     rotation editor gets the whole sheet. */
  body.phone-shell.pf-has-joint .gizmo-wrap { margin: 0; }
}

/* ─────────────────────────────────────────────────────────────
   COVERAGE BY SCOPE  (js/admin/coverageTracker.js)

   Three cards — Full / Upper / Lower. Full defers to the detailed
   POSE_PLAN table below it; the partial scopes score against the
   PART_PLAN archetypes and recommend what to author next.
   ───────────────────────────────────────────────────────────── */
.scope-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-3);
}

.scope-card {
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  padding: var(--space-3);
}

.scope-card-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.scope-card-title {
  font-size: var(--fs-input);
  font-weight: 600;
  color: var(--text);
}
.scope-card-count {
  margin-left: auto;
  font-size: var(--fs-label);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.scope-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
.scope-bar > i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: currentColor;
}
.scope-card-pct {
  margin-top: var(--space-1);
  font-size: var(--fs-label);
  font-variant-numeric: tabular-nums;
}
.scope-good { color: var(--teal); }
.scope-ok   { color: var(--warning); }
.scope-weak { color: var(--danger); }
/* Nothing authored yet — a starting line, not a grade. */
.scope-none { color: var(--text-sec); }
.scope-bar.scope-none > i { background: var(--text-muted); }

.scope-card-note {
  font-size: var(--fs-label);
  line-height: var(--lh-body);
  color: var(--text-muted);
}

.scope-checks {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  gap: 2px;
}
.scope-check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-label);
}
.scope-check-mark { flex: none; width: 12px; text-align: center; }
.scope-check-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An unauthored archetype is the starting state of every library, not a
   failure — the same distinction the confidence panel draws when it refuses
   to print 0% for a region it never measured. Forty-two red crosses told a
   new admin they had forty-two problems on day one.

   Red stays available and now means something: an archetype that WAS
   authored and then failed validation. Nothing emits .is-invalid yet; it is
   here so the next person reaches for it instead of reaching for red again. */
.scope-check.is-done    { color: var(--text-sec); }
.scope-check.is-done .scope-check-mark { color: var(--success); }
.scope-check.is-todo    { color: var(--text-sec); }
.scope-check.is-todo .scope-check-mark { color: var(--text-muted); }
.scope-check.is-invalid { color: var(--text-sec); }
.scope-check.is-invalid .scope-check-mark { color: var(--danger); }

.scope-rec-title {
  margin-top: var(--space-3);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-sec);
}
.scope-recs {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.scope-rec {
  display: grid;
  grid-template-columns: 16px 1fr;
  gap: 2px var(--space-2);
  font-size: var(--fs-label);
}
.scope-rec-n {
  grid-row: span 2;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.scope-rec-name { color: var(--text); font-weight: 600; }
.scope-rec-why  { color: var(--text-muted); line-height: var(--lh-body); }

/* Category grouping inside a scope coverage card. Mirrors the
   Scope → Category → Name chain in the pose editor, so the checklist
   and the authoring form describe the library the same way. */
.scope-cats { margin-top: var(--space-3); display: grid; gap: var(--space-3); }

.scope-cat-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding-bottom: 3px;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-label);
  font-weight: 600;
  color: var(--text-sec);
}
.scope-cat-count {
  margin-left: auto;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}
.scope-cat .scope-checks { margin-top: var(--space-1); }


/* ═══════════════════════════════════════════════════════════════════════
   DRAWING CANVAS swatches  (js/ui/drawingCanvas.js)
   ═══════════════════════════════════════════════════════════════════════ */
.paper-swatches {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
  padding: 0 14px 8px;
}
.paper-sw {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 6px 2px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 9px;
  cursor: pointer;
}
.paper-sw:hover { border-color: var(--border-focus); color: var(--text-sec); }
.paper-sw.is-active {
  border-color: var(--teal);
  color: var(--teal);
  background: var(--teal-dim);
}
/* The chip is the sheet itself, so it carries a border rather than relying
   on contrast against the panel — white paper on a dark panel needs no
   outline, but gray and black do. */
.paper-chip {
  width: 100%;
  height: 18px;
  border-radius: 3px;
  border: 1px solid rgba(255,255,255,0.18);
}
.paper-chip--off {
  background: repeating-linear-gradient(45deg,
    var(--card) 0 4px, var(--border-mid) 4px 8px);
}


/* ── Reference Flip (js/ui/artistMode.js) ────────────────────────────
   Only the canvas mirrors. The overlays that sit on top of it — hints,
   badges, the thirds guide — must stay readable, so they are deliberately
   left out of the transform rather than counter-flipped. */
body.pf-flip #canvas-container > canvas {
  transform: scaleX(-1);
}
/* A mirrored viewport accepts no pointer work: ArtistMode forces both locks
   and controls.js refuses picking, and this closes the last route in. The
   cursor says so rather than leaving you clicking at a dead image. */
body.pf-flip #canvas-container > canvas {
  cursor: default;
}


/* ── Reposition (js/ui/artistMode.js) ────────────────────────────────
   The cursor is the only affordance this mode has: OrbitControls owns
   the gesture, so nothing else on screen changes. */
body.pf-reposition #canvas-container > canvas { cursor: move; }

/* ══════════════════════════════════════════════════════════════════════
   DRAW MODE — js/draw/*
   ══════════════════════════════════════════════════════════════════════

   Draw is a top-level destination that RE-DRESSES the editor surface
   rather than covering it (see switchView in index.html). Gallery and
   Settings can be panels over the canvas; Draw cannot, because it needs
   the same renderer, the same scene and the same loaded model.

   So the layout change is done here, in one place, by folding the
   authoring rails away and handing their width to #draw-panel. Nothing in
   JS writes an inline display for these — one authority per element.     */

:root {
  --draw-w:      268px;   /* right rail — STUDY  */
  --draw-w-left: 252px;   /* left rail  — FIGURE */
}

/* The authoring surface, gone. Not a collapse the user can reverse — in
   Draw mode there is nothing to reopen, because a joint list and a
   rotation pad are instruments for changing a pose that is frozen by
   definition in here. */
body.draw-mode #workspace-rail,
body.draw-mode #rail-stub,
body.draw-mode #panel-rail,
body.draw-mode #left-panel,
body.draw-mode #mid-panel,
body.draw-mode #right-panel,
body.draw-mode #mini-gizmo-panel,
body.draw-mode #recent-pose-strip,
body.draw-mode #canvas-hint {
  display: none !important;
}

/* Overlay geometry follows. Everything centred over the canvas reads its
   edges from these two, so one substitution moves the camera bar, the
   status readouts and the artist badge together. */
body.draw-mode {
  --prail-w: 0px;
  --rail-w:  0px;
  --left-w:  0px;
  --mid-w:   0px;
  --right-w: 0px;
  --canvas-left:  calc(var(--draw-w-left) + var(--overlay-gap));
  --canvas-right: calc(var(--draw-w) + var(--overlay-gap));
}

/* ── Focus Mode in here ──────────────────────────────────────────────
   The generic Focus rules (search pf-focus above) hide the authoring
   chrome by name, and Draw's rails did not exist when that list was
   written — so entering Focus from Draw stripped the topbar and left both
   rails standing, which is the one thing Focus is for.

   Named with the compound selector on purpose: `body.draw-mode` sets
   --canvas-left/--canvas-right further up in this section, and a bare
   `body.pf-focus` override loses to it on source order. The extra class
   wins on specificity instead of depending on where the block sits. */
body.draw-mode.pf-focus #draw-shell,
body.draw-mode.pf-focus #draw-tabbar,
/* The floating stack goes with them. One of its buttons is what turned this
   state on — the badge is what turns it off — and the other is a setting you
   make while framing, not while drawing. */
body.draw-mode.pf-focus #draw-float-stack {
  display: none !important;
}

/* With the rails gone the overlays — badge, gesture HUD, export toast —
   should centre on the whole viewport, not on the gap the rails left. */
body.draw-mode.pf-focus {
  --canvas-left:  0px;
  --canvas-right: 0px;
}

/* Draw mode paints its own thirds into the guide overlay so it obeys the
   guide opacity and mirrors with the canvas. Two grids at once would be a
   bug, so the CSS one stands down while we are in here. */
body.draw-mode.show-thirds::after { display: none; }

/* Pose lock is what entering Draw mode MEANS, so the editor's
   `cursor: not-allowed` over the whole viewport would be a lie here — the
   camera is still yours to orbit, it is only the rig that is frozen. */
body.draw-mode.pose-locked #canvas-container { cursor: default; }

/* ── The guide overlay ───────────────────────────────────────────────
   A second canvas inside #canvas-container, which is what registers it to
   the figure for free in two ways: it inherits the container's geometry,
   and Reference Flip's existing `body.pf-flip #canvas-container > canvas`
   rule mirrors it in lockstep with the WebGL canvas. No second flip is
   declared here — there is one flip in this app and artistMode.js owns it.
   Label text inside the overlay cancels the mirror locally so it stays
   readable (drawGuides.js). */
#draw-guide-layer {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

/* ── Rails ───────────────────────────────────────────────────────────
   Two of them, sharing everything except which edge they hug.

   #draw-shell wraps both. On this tier it is an ordinary block with no box
   of its own — both children are position:fixed, so they anchor to the
   viewport and the wrapper is inert. It exists for the narrower tiers
   below, where it becomes the single scrolling container that stacks them
   without any DOM being re-parented. */
.draw-rail {
  position: fixed;
  top: var(--topbar-h);
  bottom: var(--bottom-h);
  z-index: 20;
  background: var(--panel);
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
}

#draw-panel-left {
  left: 0;
  width: var(--draw-w-left);
  border-right: 1px solid var(--border);
}

#draw-panel {
  right: 0;
  width: var(--draw-w);
  border-left: 1px solid var(--border);
}

.draw-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 14px 10px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--panel);
  z-index: 2;
}

.draw-panel-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  color: var(--text-sec);
}

/* Quiet outline chip — a hairline outline on nothing, for controls that sit
   on top of the artwork and must not compete with it. .draw-mini-btn and the
   gesture HUD's .gh-btn were written separately and arrived at byte-identical
   grounds, borders, ink, size and hover; only their padding and radius ever
   differed. Shared here, specialised below. */
.draw-mini-btn,
.gh-btn {
  background: transparent;
  border: 1px solid var(--border-mid);
  color: var(--text-sec);
  font-size: 11px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.draw-mini-btn:hover,
.gh-btn:hover { color: var(--text); border-color: var(--border-focus); }

.draw-mini-btn { padding: 3px 10px; border-radius: 12px; }   /* pill */

/* ── Sections ─────────────────────────────────────────────────────────
   Collapsible because six groups of controls at full height is more than
   any laptop viewport can spare, and the viewport is the point. */
.draw-section { border-bottom: 1px solid var(--border); }

.draw-section-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 11px 14px;
  background: transparent;
  border: none;
  color: var(--text-sec);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 0.15s;
}
.draw-section-head:hover { color: var(--text); }

.draw-chev { transition: transform 0.18s ease; flex: none; }
.draw-section.is-open .draw-chev { transform: rotate(180deg); }

.draw-section-body { display: none; padding: 2px 14px 14px; }
.draw-section.is-open .draw-section-body { display: block; }

.draw-subtitle {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 14px 0 7px;
}

.draw-note {
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-muted);
  margin: 6px 0 0;
}
.draw-note--alert { color: #e0a06a; }

/* ── Rows ─────────────────────────────────────────────────────────────
   label / control / readout. The readout is a fixed width so a slider does
   not resize itself as the number under it goes from 8 to 100%. */
.draw-row {
  display: grid;
  grid-template-columns: 58px 1fr 42px;
  align-items: center;
  gap: 8px;
  margin-top: 9px;
}
.draw-row > label { font-size: 11px; color: var(--text-sec); }
.draw-row .draw-val {
  font-size: 11px;
  color: var(--text);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* Draw drives the shared slider component; only its accent is local. */
.draw-row { --slider-accent: #6b62c8; }
.draw-row input[type="range"] { width: 100%; }
.draw-row .draw-select--sm,
.draw-row .draw-number { grid-column: 2 / span 2; }

.draw-select, .draw-number {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text);
  font-size: 12px;
  padding: 6px 8px;
  border-radius: 7px;
}
.draw-select { margin-top: 10px; }
.draw-select--sm { margin-top: 0; }
.draw-select:focus, .draw-number:focus {
  outline: none;
  border-color: var(--border-focus);
}

/* ── Checkboxes ───────────────────────────────────────────────────── */
.draw-check {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  color: var(--text-sec);
  padding: 7px 0;
  cursor: pointer;
  transition: color 0.15s;
}
.draw-check:hover { color: var(--text); }
.draw-check { --check-accent: #6b62c8; }   /* Draw keeps its own accent */

/* ── Light ball ───────────────────────────────────────────────────────
   The control and the preview are the same object: it shades as you drag,
   so the key can be aimed without looking away from the figure. */
.draw-ball-wrap {
  display: flex;
  justify-content: center;
  padding: 10px 0 4px;
}
/* The ball is its own WebGL render with a transparent clear, so the well
   behind it is CSS: a dark sky that the sun's glow and the lit sphere read
   against in either theme. */
.draw-ball-wrap { flex-direction: column; align-items: center; gap: 6px; }
#draw-light-ball {
  border-radius: 50%;
  cursor: grab;
  touch-action: none;
  background: radial-gradient(circle at 50% 42%, #1d1b33 0%, #0c0b18 62%, #07060f 100%);
  box-shadow: inset 0 0 0 1px rgba(157, 150, 220, 0.18), 0 2px 14px rgba(0, 0, 0, 0.45);
}
#draw-light-ball.is-dragging { cursor: grabbing; }
.draw-ball-hint {
  margin: 0;
  font-size: 10.5px;
  color: var(--text-sec);
  opacity: 0.8;
  text-align: center;
}

/* Viewport sun (js/draw/lightGizmo.js). */
body.pf-light-gizmo-hover #canvas-container > canvas { cursor: grab; }
body.pf-light-gizmo-drag  #canvas-container > canvas { cursor: grabbing; }
#draw-light-ball:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 3px;
}

/* ── View styles ──────────────────────────────────────────────────── */
.draw-styles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  margin-top: 8px;
}
.draw-style {
  display: flex;
  align-items: center;
  gap: 7px;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text-sec);
  font-size: 11px;
  padding: 8px;
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.draw-style:hover { border-color: var(--border-focus); color: var(--text); }
.draw-style.is-active {
  border-color: #6b62c8;
  color: var(--text);
  background: rgba(107, 98, 200, 0.14);
}
.ds-chip {
  width: 13px; height: 13px;
  border-radius: 3px;
  flex: none;
  border: 1px solid rgba(255, 255, 255, 0.16);
}
/* Each chip previews what the mode does, so the list is readable without
   having to try all four. */
.ds-normal     { background: linear-gradient(135deg, #d8d2ff, #6f68a8); }
.ds-silhouette { background: linear-gradient(135deg, #aaa69e 50%, #08080c 50%); }
.ds-clay       { background: linear-gradient(135deg, #d6d1c8, #8b877e); }
.ds-bands      { background: linear-gradient(135deg, #e6e0d4 33%, #a8a294 33% 66%, #5e5a52 66%); }
/* The net chip is drawn as a net: two crossing line sets over the sheet,
   which is the only chip here that previews a STRUCTURE rather than a tone. */
.ds-mesh {
  background:
    repeating-linear-gradient(0deg,  #cfcadf 0 1px, transparent 1px 4px),
    repeating-linear-gradient(90deg, #cfcadf 0 1px, transparent 1px 4px),
    #26232f;
}

/* The fifth style has no partner on its row, so it takes the whole width
   rather than sitting in a half-empty one. */
.draw-style:last-child:nth-child(odd) { grid-column: 1 / -1; }

/* ── Paper swatches ───────────────────────────────────────────────── */
.draw-papers { display: flex; gap: 6px; }
.draw-paper {
  width: 30px; height: 26px;
  padding: 3px;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 0.15s, transform 0.12s;
}
.draw-paper span { display: block; width: 100%; height: 100%; border-radius: 3px; }
.draw-paper:hover { border-color: var(--border-focus); }
.draw-paper.is-active { border-color: #6b62c8; transform: translateY(-1px); }

/* ── Lenses ───────────────────────────────────────────────────────────
   Millimetres, because focal length is the foreshortening dial and nobody
   has an intuition for a vertical field of view in degrees. */
.draw-lenses {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
}
.draw-lens {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text-sec);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  padding: 7px 0;
  border-radius: 7px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.draw-lens:hover { border-color: var(--border-focus); color: var(--text); }
.draw-lens.is-active {
  border-color: #6b62c8;
  color: var(--text);
  background: rgba(107, 98, 200, 0.14);
}
.draw-lens-unit { font-size: 10px; color: var(--text-muted); flex: none; }

.draw-angles { display: flex; flex-wrap: wrap; gap: 5px; }
.draw-angle {
  flex: 1 0 auto;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text-sec);
  font-size: 11px;
  padding: 6px 10px;
  border-radius: 7px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.draw-angle:hover { border-color: var(--border-focus); color: var(--text); }

/* ── Buttons ──────────────────────────────────────────────────────── */
.draw-btn {
  width: 100%;
  margin-top: 8px;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text-sec);
  font-size: 12px;
  font-weight: 500;
  padding: 9px 12px;
  border-radius: 8px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.draw-btn:hover { border-color: var(--border-focus); color: var(--text); }
.draw-btn--primary {
  background: #6b62c8;
  border-color: #6b62c8;
  color: #fff;
  font-weight: 600;
}
.draw-btn--primary:hover { background: #7a71d8; border-color: #7a71d8; color: #fff; }
.draw-btn--ghost { font-size: 11px; padding: 7px 12px; }

/* Controls that exist only on the phone tier, because the pointer tiers
   carry the same action somewhere the phone has folded away. Hidden here so
   the phone block is the single place that opts each one in, rather than
   every other tier having to remember to opt it out. */
.draw-phone-only { display: none; }

/* ══════════════════════════════════════════════════════════════════════
   GESTURE SESSION HUD — js/draw/gestureSession.js

   Top-centre of the canvas, small, translucent. The figure is the point of
   this screen: a timer that covers it defeats the exercise, and one that
   has to be hunted for defeats the timer.                                */

#gesture-hud {
  position: fixed;
  /* BOTTOM of the canvas, not the top.
     The top centre is already taken: #cam-preset-bar is pinned there at
     84px and wraps to a second row at narrow widths, so a clock placed
     under the topbar landed underneath it and lost its own buttons. The
     bottom is also simply the better place for a drawing timer — it stays
     in peripheral vision instead of sitting over the figure's head, which
     is the part you are looking at. */
  bottom: calc(var(--bottom-h) + 18px);
  left: calc(var(--canvas-left) + (calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right)) / 2);
  transform: translateX(-50%);
  z-index: 30;
  display: none;
  min-width: 300px;
  padding: 0 0 8px;
  border-radius: 12px;
  background: rgba(13, 12, 24, 0.82);
  border: 1px solid var(--border-mid);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
  box-shadow: 0 6px 26px rgba(0, 0, 0, 0.42);
  overflow: hidden;
}
#gesture-hud.is-on { display: block; }

.gh-bar { height: 3px; background: rgba(255, 255, 255, 0.08); }
.gh-fill {
  display: block;
  height: 100%;
  width: 100%;
  background: #6b62c8;
  transform-origin: left center;
  /* scaleX, not width.
     The bar is driven off the 100ms session tick, so it re-animates ten times
     a second for as long as a pose lasts — up to ten minutes. Transitioning
     `width` would put a layout and a paint into every one of those frames,
     and this HUD sits over a WebGL canvas that wants the whole 16ms budget
     for the figure. A transform is composited, so the timer costs the render
     loop nothing.

     The transition duration matches the tick so the bar glides between
     updates instead of stepping. */
  transition: transform 0.1s linear, background 0.25s;
}
#gesture-hud.is-ending .gh-fill { background: #e0703f; }
#gesture-hud.is-paused .gh-fill { background: var(--text-muted); }

.gh-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px 4px;
}
.gh-time {
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  min-width: 46px;
}
#gesture-hud.is-ending .gh-time { color: #ef8b5c; }
#gesture-hud.is-paused .gh-time { color: var(--text-muted); }

.gh-count {
  font-size: 11px;
  color: var(--text-sec);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.gh-name {
  font-size: 11px;
  color: var(--text-muted);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gh-actions { display: flex; gap: 4px; flex: none; }
/* Ground, ink and hover come from the quiet-chip pair in the Draw panel.
   The HUD sits over the figure at a smaller scale, so it runs tighter and
   squarer than the pill. */
.gh-btn { line-height: 1; padding: 5px 8px; border-radius: 6px; }
.gh-btn--end { color: #d98c6a; }

/* Focus mode strips the chrome; the session clock is not chrome, it is the
   exercise, so it stays and only moves down into the reclaimed space. */
body.pf-focus #gesture-hud { bottom: 18px; }

/* The artist badge is centred on the same bottom edge. It is usually absent
   in Draw mode — pose lock is the mode and says nothing — but Reference Flip
   forces camera lock, so a flipped session would stack the two. Lift the
   badge over the clock for the duration rather than letting them collide. */
body.gesture-running .artist-badge {
  bottom: calc(var(--bottom-h) + 74px);
}
body.gesture-running.pf-focus .artist-badge { bottom: 74px; }

/* ══════════════════════════════════════════════════════════════════════
   DRAW MODE — narrow and phone tiers
   ══════════════════════════════════════════════════════════════════════ */

/* Between the phone shell and full width the panel narrows rather than
   turning into a sheet: there is still room for a docked column, and a
   sheet would cost a tap for every light nudge. */
/* Two 250px rails is 500px of chrome, which at 1100px leaves the figure
   less than half the window — so below that the split folds back into one
   column on the right. The shell becomes the scroller and both rails go
   static inside it: same markup, same ids, one stack. */
@media (max-width: 1099px) and (min-width: 700px) {
  :root { --draw-w: 240px; }

  body.draw-mode { --canvas-left: 0px; }

  #draw-shell {
    position: fixed;
    top: var(--topbar-h);
    bottom: var(--bottom-h);
    right: 0;
    width: var(--draw-w);
    z-index: 20;
    background: var(--panel);
    border-left: 1px solid var(--border);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* Written with the ids, not .draw-rail: #draw-panel-left / #draw-panel set
     width and border at id specificity for the two-rail tier, and a class
     selector can never take them back. A rail left at its desktop width
     inside a narrower shell overflows it and clips its own controls. */
  #draw-panel-left,
  #draw-panel {
    position: static;
    width: 100%;
    border: none;
    overflow: visible;
  }
  /* Only the first rail's header should read as the top of the column; the
     second becomes a divider between the two groups. */
  #draw-panel .draw-panel-head { border-top: 1px solid var(--border-mid); }

  .draw-row { grid-template-columns: 52px 1fr 38px; }
}

/* ══════════════════════════════════════════════════════════════════════
   MOTION STUDY — js/draw/motionStudy.js

   The scrubber is the hero. A study is two or three moments and the space
   between them, so the widest, tallest, easiest-to-hit control is the one
   that moves through that space — the keys are markers on its track, not a
   list competing with it for room.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Key track ─────────────────────────────────────────────────────────
   Dots are absolutely placed at their real position along the study, so the
   track and the scrubber below it are two views of one timeline and can
   never drift apart. */
.motion-track {
  position: relative;
  height: 46px;
  /* Inset by half a dot. The end dots are centred on 0% and 100% of the
     track, so without this their outer halves hang past the panel's padding
     and get clipped by its edge. */
  margin: 10px 14px 2px;
}
.motion-track.is-empty { height: 0; margin: 0; }

.mt-line {
  position: absolute;
  left: 0;
  right: 0;
  top: 15px;
  height: 2px;
  border-radius: 1px;
  background: var(--border-mid);
}

.mt-dot {
  position: absolute;
  top: 4px;
  /* The dot is placed by its centre, so the first and last sit exactly on
     the ends of the line rather than half a dot inside them. */
  transform: translateX(-50%);
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--border-focus);
  color: var(--text-sec);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.mt-dot:hover { border-color: #8a80e8; color: var(--text); }
.mt-dot.is-selected {
  border-color: #6b62c8;
  background: #6b62c8;
  color: #fff;
}
/* The key the playhead is nearest. Distinct from selection: one says "this
   is what Replace and Delete act on", the other says "this is what you are
   looking at". */
.mt-dot.is-near { box-shadow: 0 0 0 4px rgba(107, 98, 200, 0.22); }

.mt-n { display: block; line-height: 20px; }
.mt-end {
  position: absolute;
  top: 26px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
}

/* The playhead riding the same track as the dots. */
.mt-head {
  position: absolute;
  top: 2px;
  width: 2px;
  height: 28px;
  margin-left: -1px;
  border-radius: 1px;
  background: #ffd479;
  pointer-events: none;
  box-shadow: 0 0 6px rgba(255, 212, 121, 0.55);
}

/* ══════════════════════════════════════════════════════════════════════
   MOTION STUDY STRIP — the Editor's authoring surface (js/draw/motionStudy.js)

   Floats over the canvas above the bottom bar, centred on the usable canvas
   the way the other canvas-level overlays are. Hidden entirely until the
   study has its first key: `.is-on` is added by paint(), and switchView's
   inline display still governs whether the editor surface is up at all, so
   the two authorities never fight — one decides "is the editor on screen",
   the other "does this study exist yet".
   ══════════════════════════════════════════════════════════════════════ */

#motion-strip {
  display: none;
  position: fixed;
  bottom: calc(var(--bottom-h) + 14px);
  left: calc(var(--canvas-left) + (calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right)) / 2);
  transform: translateX(-50%);
  z-index: 90;
  width: min(600px, calc(calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right) - 32px));
  padding: 10px 14px 12px;
  border-radius: 12px;
  background: rgba(19, 17, 31, 0.93);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--border-mid);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.46);
}
#motion-strip.is-on { display: block; }

/* Collapsed keeps the track and the scrubber — the two things you glance at
   — and folds away the buttons. */
#motion-strip.is-collapsed .ms-body { display: none; }
#motion-strip.is-collapsed { padding-bottom: 8px; }

/* Header. The maker is a floating panel over the canvas with no chrome of
   its own to name it, so it says what it is — and carries the one line of
   state worth reading without looking down at the buttons. */
.ms-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 0 60px 6px 2px;
}
.ms-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  color: var(--text-sec);
}
.ms-hint {
  font-size: 10px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.ms-close {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.ms-close:hover { color: #e08a6a; background: rgba(255, 255, 255, 0.06); }

.ms-collapse {
  position: absolute;
  top: 6px;
  /* Left of the close button, which owns the corner. */
  right: 32px;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.ms-collapse:hover { color: var(--text); background: rgba(255, 255, 255, 0.06); }
#motion-strip.is-collapsed .ms-collapse svg { transform: rotate(180deg); }

/* The strip's own track sits inside a narrower box than the panel's, so it
   gets the same half-a-dot inset to keep the end markers off the edges. */
#motion-strip .motion-track { margin: 4px 14px 2px; height: 42px; }

/* ── Transport row: prev / scrubber / next / % ────────────────────────
   The scrubber takes every pixel the row can spare — it is the control the
   whole feature exists to offer. */
.ms-transport {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 8px;
}
.ms-transport .motion-step { width: 34px; height: 26px; }
.ms-transport .motion-scrub { margin: 0; height: 26px; }
.ms-transport .motion-pct { min-width: 38px; text-align: right; }

/* ── Body rows ───────────────────────────────────────────────────────── */
.ms-body { margin-top: 8px; }
.ms-row {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 7px;
  flex-wrap: wrap;
}
.ms-row--onion { gap: 5px; }

.ms-btn {
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text-sec);
  font-size: 11px;
  padding: 7px 11px;
  border-radius: 7px;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.ms-btn:hover:not(:disabled) { border-color: var(--border-focus); color: var(--text); }
.ms-btn:disabled { opacity: 0.4; cursor: default; }
.ms-btn--primary {
  background: #6b62c8;
  border-color: #6b62c8;
  color: #fff;
  font-weight: 600;
  min-width: 84px;
}
.ms-btn--primary:hover:not(:disabled) { background: #7a71d8; border-color: #7a71d8; }
.ms-btn--warn { color: #d98c6a; }

.ms-select {
  background: var(--bg);
  border: 1px solid var(--border-mid);
  color: var(--text);
  font-size: 11px;
  padding: 6px 7px;
  border-radius: 7px;
}
.ms-select--wide { flex: 1; min-width: 120px; }

.ms-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-sec);
  cursor: pointer;
  white-space: nowrap;
}
.ms-check input { width: 14px; height: 14px; accent-color: #6b62c8; }

.ms-mini { width: 66px; height: 20px; accent-color: #6b62c8; }
.ms-val {
  font-size: 10px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  min-width: 30px;
}
.ms-note { margin: 8px 0 0; font-size: 11px; color: #e0a06a; }

#motion-strip .motion-counter { min-width: 40px; }

/* Focus mode strips the chrome, and the study strip is chrome. */
body.pf-focus #motion-strip { display: none !important; }

/* Narrow desktops: the strip gives up its fixed width before the canvas does. */
@media (max-width: 1099px) {
  #motion-strip { padding: 8px 10px 10px; }
  .ms-btn { padding: 6px 8px; }
  .ms-select--wide { min-width: 90px; }
}

/* Phone: full-bleed above the tab bar, and the library picker goes — adding
   a key from the library is a desktop-scale act of browsing. */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  #motion-strip {
    left: 8px;
    right: 8px;
    width: auto;
    transform: none;
    bottom: calc(var(--ph-tabbar-h, 58px) + var(--ph-safe-b, 0px) + var(--ph-ribbon-h, 44px) + 8px);
  }
  #motion-strip .motion-track { height: 50px; }
  .ms-transport .motion-step { width: 44px; height: 34px; }
  .ms-transport .motion-scrub { height: 34px; }
  .ms-btn { padding: 10px 10px; font-size: 12px; }
  .ms-check input { width: 18px; height: 18px; }
  .ms-select--wide { display: none; }
}

/* ── Onion skin rows ───────────────────────────────────────────────────
   A colour chip on each row, matching the tint that ghost is drawn in —
   cool for the key behind you, warm for the one ahead. Without it the two
   opacity sliders are indistinguishable and you adjust the wrong one. */
.onion-row { position: relative; padding-left: 18px; }
.onion-row::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  border-radius: 3px;
}
.onion-row--prev::before { background: #5a86c9; }
.onion-row--next::before { background: #d08a4a; }

/* ── Stepper ───────────────────────────────────────────────────────── */
.motion-stepper {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 2px 0 8px;
}
.motion-step {
  width: 46px;
  height: 30px;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  border-radius: 8px;
  color: var(--text-sec);
  font-size: 11px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.motion-step:hover:not(:disabled) { border-color: var(--border-focus); color: var(--text); }
.motion-step:disabled { opacity: 0.4; cursor: default; }

/* ── The scrubber ──────────────────────────────────────────────────────
   Deliberately oversized. This is the control the whole feature exists to
   offer, and a 4px hairline track would make finding 37% a fight. */
.motion-scrub {
  width: 100%;
  margin: 2px 0 0;
  height: 30px;
  accent-color: #ffd479;
  cursor: ew-resize;
}
.motion-scrub:disabled { opacity: 0.4; cursor: default; }

.motion-readout {
  display: flex;
  justify-content: center;
  min-height: 15px;
}
.motion-pct {
  font-size: 12px;
  font-weight: 700;
  color: #ffd479;
  font-variant-numeric: tabular-nums;
}

/* ── Transport ─────────────────────────────────────────────────────── */
.motion-transport {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "play count" "easing easing";
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.motion-play { grid-area: play; margin-top: 0; }
.motion-counter {
  grid-area: count;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-sec);
  font-variant-numeric: tabular-nums;
  min-width: 44px;
  text-align: right;
}
.motion-easing { grid-area: easing; margin-top: 0; }

/* ── Key buttons ───────────────────────────────────────────────────── */
.motion-keybtns { display: flex; gap: 6px; }
.motion-kb { margin-top: 8px; font-size: 11px; padding: 8px 6px; }
.draw-btn:disabled { opacity: 0.42; cursor: default; }
.draw-btn:disabled:hover { border-color: var(--border-mid); color: var(--text-sec); }
.draw-btn--primary:disabled:hover { background: #6b62c8; border-color: #6b62c8; color: #fff; }

/* Touch sizing, matching the rest of the phone tier. */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  /* Bigger dots need a bigger inset, for the same reason. */
  .motion-track { height: 54px; margin-left: 17px; margin-right: 17px; }
  .mt-dot { width: 30px; height: 30px; top: 2px; }
  .mt-n { line-height: 26px; font-size: 12px; }
  .mt-end { top: 32px; }
  .mt-line { top: 16px; }
  .mt-head { height: 34px; }
  .motion-step { width: 64px; height: 40px; font-size: 13px; }
  .motion-scrub { height: 40px; }
  .motion-pct { font-size: 14px; }
  .motion-kb { font-size: 12px; padding: 12px 6px; }
}

/* Draw's tab bar and grab handle exist only on the phone tier. Hidden here
   so no other tier ever has to override them. */
#draw-tabbar,
#draw-sheet-close { display: none; }

/* ══════════════════════════════════════════════════════════════════════
   DRAW MODE — phone tier

   Matches Q_PHONE in js/ui/phoneShell.js and js/draw/drawPhone.js. Keep
   the three in sync.

   The earlier version of this block stacked both rails into a sheet pinned
   at 52vh and stopped there. That made Draw *work* on a phone without
   making it useful: half of a 915px screen was permanently controls, on a
   surface whose whole purpose is looking at a figure, and seven sections
   in one 52vh box put the export buttons four flicks from the light ball.

   So the phone tier borrows the editor's own answer (phoneShell.js): a tab
   bar in the thumb zone raising ONE group at a time, and tapping the raised
   tab puts it away. Draw opens with the sheet DOWN — figure full-bleed,
   four labelled tabs beneath it.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {

  body.draw-mode { --canvas-right: 0px; --canvas-left: 0px; }

  /* The editor's own navigator stands down: it points at four panels that
     do not exist in this mode. The thumbstick goes with it — Draw is for
     looking at the figure and marking it up, and a rotation control floating
     over the drawing is both a distraction and something to mis-tap. The
     !important is doing real work: the stick's own rule sets display on an
     ID selector inside this same media query. */
  body.draw-mode #phone-tabbar,
  body.draw-mode #phone-siderail,
  body.draw-mode #phone-stick,
  body.draw-mode #phone-sheet { display: none !important; }

  /* So does the floating camera bar. It is six buttons across the top of a
     412px screen — the single most valuable strip on the phone, directly
     above the figure's head — and every angle it offers is already in the
     View tab's ANGLE row. On desktop it costs nothing and stays. */
  body.draw-mode #cam-preset-bar { display: none !important; }

  /* ── Tab bar ───────────────────────────────────────────────────────
     Same geometry as #phone-tabbar so the thumb lands in the same place
     on both surfaces, and #bottom-bar's existing offset above
     --ph-tabbar-h keeps working without a second rule. */
  body.draw-mode #draw-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 230;
    display: flex;
    height: calc(var(--ph-tabbar-h, 58px) + var(--ph-safe-b, 0px));
    padding: 0 var(--ph-safe-r, 0px) var(--ph-safe-b, 0px) var(--ph-safe-l, 0px);
    background: rgba(15, 14, 26, 0.96);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-top: 1px solid var(--border);
  }

  .dt-btn {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 10px;
    font-weight: 500;
    padding: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color 0.15s;
  }
  .dt-btn svg { width: 21px; height: 21px; }
  .dt-btn.is-active { color: #a79dff; }
  /* A bar across the top of the raised tab, so which sheet is up is
     readable without reading the labels. */
  .dt-btn.is-active::before {
    content: '';
    position: absolute;
    top: 0;
    width: 26px;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: #6b62c8;
  }
  .dt-btn { position: relative; }

  /* ── Sheet ─────────────────────────────────────────────────────────
     Floats over the canvas rather than shrinking it: the figure keeps the
     whole screen and the controls are a temporary overlay on top. */
  #draw-shell {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--ph-tabbar-h, 58px) + var(--ph-safe-b, 0px));
    top: auto;
    width: 100%;
    max-height: calc(46vh / var(--ui-scale, 1));
    z-index: 225;
    background: rgba(19, 17, 31, 0.97);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-top: 1px solid var(--border-mid);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -10px 34px rgba(0, 0, 0, 0.5);
    overflow-y: auto;
    overscroll-behavior: contain;

    /* Down by default. Transform rather than display so it slides, and so
       the contents stay laid out while it leaves.

       `visibility` is switched with a DELAY, not a duration. Transitioning it
       over 0.22s makes the computed value interpolate — it reads `visible`
       for the whole slide and only flips at the very end, which is both hard
       to observe and dependent on the compositor actually running. A zero
       duration delayed by the slide is deterministic: instantly visible on
       the way in, and hidden the moment the slide out finishes. Without the
       hidden state at all, the sheet would keep swallowing taps meant for the
       figure while parked off-screen. */
    transform: translateY(101%);
    visibility: hidden;
    transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
                visibility 0s linear 0.22s;
  }
  body.draw-sheet-open #draw-shell {
    transform: translateY(0);
    visibility: visible;
    transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
                visibility 0s linear 0s;
  }

  /* Both rails become plain content inside the one sheet.

     By id for the same reason as the narrow tier: #draw-panel-left carries
     `width: var(--draw-w-left)` and a right border at id specificity, so a
     `.draw-rail` rule leaves the sheet holding a 252px column with a rule
     down the middle of it and the sliders clipped to half the screen. */
  #draw-panel-left,
  #draw-panel {
    position: static;
    width: 100%;
    max-height: none;
    border: none;
    overflow: visible;
    background: transparent;
  }
  /* The tab already names the group, so the rail headers are two lines of
     redundant chrome on the tier that can least afford them. */
  body.draw-mode .draw-panel-head { display: none; }

  /* ── One group at a time ───────────────────────────────────────────
     This is the phone equivalent of the desktop's two rails: the split is
     across time instead of space. */
  body.draw-mode .draw-rail .draw-section { display: none; }
  body.draw-mode[data-draw-sheet="light"]   .draw-section[data-phone-tab="light"],
  body.draw-mode[data-draw-sheet="style"]   .draw-section[data-phone-tab="style"],
  body.draw-mode[data-draw-sheet="view"]    .draw-section[data-phone-tab="view"],
  body.draw-mode[data-draw-sheet="session"] .draw-section[data-phone-tab="session"] {
    display: block;
  }

  /* With one or two sections showing, collapsing them buys nothing and
     costs a tap. Heads stay as labels; the chevron and the toggle go. */
  body.draw-mode .draw-section .draw-section-body { display: block; }
  body.draw-mode .draw-chev { display: none; }
  body.draw-mode .draw-section-head {
    pointer-events: none;
    padding: 12px 16px 4px;
    color: var(--text-muted);
  }
  /* A single-section tab does not need a heading at all — the tab said it. */
  body.draw-mode[data-draw-sheet="light"] .draw-section-head { display: none; }

  /* ── Grab handle ───────────────────────────────────────────────────*/
  body.draw-mode #draw-sheet-close {
    display: block;
    position: sticky;
    top: 0;
    z-index: 2;
    width: 100%;
    padding: 9px 0 7px;
    background: transparent;
    border: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .dsc-grip {
    display: block;
    width: 38px;
    height: 4px;
    margin: 0 auto;
    border-radius: 2px;
    background: var(--border-focus);
  }

  /* ── Touch sizing ──────────────────────────────────────────────────
     14px checkboxes and 26px swatches are a pointer-sized assumption.
     These are the same controls at thumb size. */
  .draw-section-body { padding: 2px 16px 18px; }
  .draw-check { padding: 12px 0; font-size: 14px; }
  .draw-check input { width: 21px; height: 21px; }
  .draw-row { grid-template-columns: 66px 1fr 46px; gap: 12px; margin-top: 14px; }
  .draw-row > label { font-size: 12px; }
  .draw-row input[type="range"] { height: 30px; }
  .draw-select, .draw-number { font-size: 15px; padding: 10px; }
  .draw-paper { width: 44px; height: 38px; }
  .draw-papers { gap: 9px; }
  .draw-lens { padding: 13px 0; font-size: 13px; }
  .draw-angle { padding: 11px 14px; font-size: 13px; }
  .draw-btn { padding: 14px; font-size: 14px; }
  .draw-style { padding: 13px 10px; font-size: 13px; }
  .draw-styles { gap: 8px; }

  /* The ball is a drag target for a fingertip, not a mouse pointer. */
  .draw-ball-wrap { padding: 4px 0 8px; }
  #draw-light-ball { cursor: default; }

  /* ── Session clock ─────────────────────────────────────────────────
     Above the sheet when it is up, above the ribbon when it is down, so it
     never sits under the controls or over the figure's head. */
  #gesture-hud {
    top: auto;
    bottom: calc(var(--ph-tabbar-h, 58px) + var(--ph-safe-b, 0px) + var(--ph-ribbon-h, 44px) + 10px);
    left: 8px;
    right: 8px;
    transform: none;
    min-width: 0;
    transition: bottom 0.22s cubic-bezier(0.22, 1, 0.36, 1);
  }
  body.draw-sheet-open #gesture-hud {
    bottom: calc(var(--ph-tabbar-h, 58px) + var(--ph-safe-b, 0px) + calc(46vh / var(--ui-scale, 1)) + 8px);
  }
  .gh-name { display: none; }

  /* The tab bar is hidden in Focus Mode by the draw-mode block above, which
     now covers every tier — one authority, so no rule for it here. */
  body.pf-focus #gesture-hud { bottom: 14px; }

  /* ── The floating stack ────────────────────────────────────────────
     Hung under the account chip rather than filed inside a tab group: these
     act on the whole screen, like the chip does, and controls whose job is
     putting the controls away — or holding a view you just framed — must
     not themselves be behind the controls.

     The 9px right offset is not a guess: it centres a 40px button on the
     chip's axis. The chip is a 24px avatar in 4px of padding and a 1px
     transparent border, against the phone topbar's `12px + safe-r`, so its
     centre lands 29px + safe-r in from the edge and 9 + 20 meets it.
     Measured, not eyeballed — a near-miss on a vertical alignment is worse
     than an obvious offset, because it reads as a mistake, not a choice. */
  body.draw-mode #draw-float-stack {
    display: flex;
    flex-direction: column;
    gap: 8px;
    position: fixed;
    top: calc(var(--topbar-h) + 10px);
    right: calc(9px + var(--ph-safe-r, 0px));
    z-index: 19;                 /* under the sheet (20), over the canvas */
  }

  .draw-float-btn {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: 1px solid var(--border-mid);
    border-radius: 999px;
    background: rgba(19, 17, 31, 0.72);
    color: var(--text-sec);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    -webkit-tap-highlight-color: transparent;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
  }
  .draw-float-btn svg { width: 19px; height: 19px; display: block; }
  .draw-float-btn:active { background: rgba(19, 17, 31, 0.9); }

  /* A toggle that is ON says so in the same amber the lock badge uses, so
     the floating control and the badge over the figure read as one fact
     rather than two unrelated warnings. */
  .draw-float-btn.is-on {
    border-color: var(--warning);
    color: var(--warning);
    background: rgba(19, 17, 31, 0.88);
  }
  .draw-float-btn .fb-ic-on         { display: none; }
  .draw-float-btn.is-on .fb-ic-off  { display: none; }
  .draw-float-btn.is-on .fb-ic-on   { display: block; }

  /* Gone while the sheet is up. In landscape the sheet docks to this very
     edge and would sit on top of it, and in portrait it is noise: the sheet
     already holds the Camera lock checkbox, and a user who is in the
     controls is not asking to hide the controls. */
  body.draw-mode.draw-sheet-open #draw-float-stack { display: none; }

  /* Clear of the home indicator: in Focus the badge is the exit, and an exit
     under the gesture bar is an exit you cannot press. */
  body.pf-focus .artist-badge { bottom: calc(16px + var(--ph-safe-b, 0px)); }

  /* A 30px circle is a cursor target. Grown for a thumb, and sat next to a
     control that leaves the mode — the cost of mis-tapping is high. */
  .artist-badge-fs { width: 38px; height: 38px; margin: -6px 0 -6px 5px; }
  .artist-badge-fs svg { width: 17px; height: 17px; }
}

/* ── Reduced motion ────────────────────────────────────────────────────
   A panel that slides in from the edge of the screen is exactly the kind of
   large-area movement that triggers vestibular symptoms, and this one can
   fire dozens of times in a drawing session. Honour the preference: the
   sheet still appears and disappears in the same places, it just stops
   travelling to get there.

   Kept as its own block rather than folded into the rules above so the
   motion and the no-motion cases are each readable on their own. */
@media (prefers-reduced-motion: reduce) {
  #draw-shell,
  body.draw-sheet-open #draw-shell {
    transition: none;
  }
  #gesture-hud { transition: none; }
  .gh-fill { transition: background 0.25s; }
  .draw-chev { transition: none; }
}

/* ── Landscape phone ───────────────────────────────────────────────────
   Held sideways a phone is about 915x412. A bottom sheet at 46vh leaves
   190px of figure, which is not a reference — it is a letterbox. So the
   sheet docks to the right edge instead and takes width, which landscape
   has to spare, while the tab bar stays in the thumb zone. Same markup,
   same states, same JS. */
@media (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {

  #draw-shell {
    left: auto;
    right: 0;
    top: var(--topbar-h);
    bottom: calc(var(--ph-tabbar-h, 58px) + var(--ph-safe-b, 0px));
    width: min(340px, calc(46vw / var(--ui-scale, 1)));
    max-height: none;
    border-radius: 14px 0 0 0;
    border-left: 1px solid var(--border-mid);
    border-top: none;
    box-shadow: -10px 0 34px rgba(0, 0, 0, 0.5);
    transform: translateX(101%);
  }
  body.draw-sheet-open #draw-shell { transform: translateX(0); }

  /* The figure gets the space the sheet is not using, rather than being
     covered by it — in landscape there is enough width to share. */
  body.draw-mode.draw-sheet-open { --canvas-right: min(340px, calc(46vw / var(--ui-scale, 1))); }

  #gesture-hud,
  body.draw-sheet-open #gesture-hud {
    bottom: calc(var(--ph-tabbar-h, 58px) + var(--ph-safe-b, 0px) + 8px);
    left: 8px;
    right: auto;
    max-width: 320px;
  }

  .draw-row { margin-top: 10px; }
  .draw-check { padding: 9px 0; }
}

/* ═══════════════════════════════════════════════════════════════════════
   PHONE THUMBSTICK  (#phone-stick — js/ui/phoneStick.js)

   A HUD, not a panel. It floats over the lower-right of the viewport with a
   see-through base so the figure stays readable underneath — the whole reason
   it is not a card is that a card there would cover the model it exists to
   adjust. Presents like the sheet's Movement Pad: joint name above, and each
   direction labelled with what it does to THIS joint rather than an axis
   letter.

   z-index 200 puts it above the canvas and the Artist Mode badge (30) and
   below the sheet (210) and tab bar (230).
   ═══════════════════════════════════════════════════════════════════════ */
#phone-stick { display: none; }

@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  body.phone-shell #phone-stick {
    display: flex;
    position: fixed;
    right: 12px;
    /* Clears ribbon + tab bar + home indicator in one token, and --ph-chrome-b
       is already redefined for landscape where the tab bar becomes a rail. */
    bottom: calc(var(--ph-chrome-b) + 12px);
    z-index: 200;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    pointer-events: none;          /* only the controls inside are hittable */
  }

  /* [hidden] must still win: the rule above sets display on an ID selector,
     which outranks the UA stylesheet's `[hidden] { display: none }`. Without
     this the stick stayed on screen with no joint selected. */
  body.phone-shell #phone-stick[hidden] { display: none; }

  #phone-stick > * { pointer-events: auto; }

  /* Joint name — the role #selected-joint-name plays in the sheet's pad. */
  .ps-name {
    max-width: 150px;
    padding: 2px 8px;
    border-radius: 999px;
    background: rgba(13, 12, 24, 0.55);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: var(--text);
    font-size: 11px;
    font-weight: 600;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
  }

  /* Pad plus its four edge labels. Positioned, not in flow, so the circle
     stays a circle however long "Straighten" is. */
  .ps-wrap {
    position: relative;
    display: flex;
    justify-content: center;
    padding: 13px 0;
  }
  .ps-edge {
    position: absolute;
    font-size: 9px;
    color: var(--text-sec);
    white-space: nowrap;
    pointer-events: none;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  }
  .ps-edge--up    { top: 0;      left: 50%; transform: translateX(-50%); }
  .ps-edge--down  { bottom: 0;   left: 50%; transform: translateX(-50%); }
  .ps-edge--left  { left: -6px;  top: 50%;  transform: translate(-100%, -50%); }
  .ps-edge--right { right: -6px; top: 50%;  transform: translate(100%, -50%); }

  .ps-base {
    position: relative;
    width: 104px;
    height: 104px;
    border-radius: 50%;
    /* See-through on purpose: enough edge to find with a thumb, not enough
       fill to hide a leg behind it. */
    background: radial-gradient(circle,
      rgba(13, 12, 24, 0.34) 0%, rgba(13, 12, 24, 0.16) 70%, rgba(13, 12, 24, 0.05) 100%);
    border: 1px solid rgba(255, 255, 255, 0.18);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    touch-action: none;            /* the stick owns its gesture */
    cursor: grab;
  }
  #phone-stick.is-active .ps-base { cursor: grabbing; }

  /* Subtle axis indicators — enough to say "two directions", not a target. */
  .ps-cross { position: absolute; background: rgba(255,255,255,0.13); pointer-events: none; }
  .ps-cross--h { left: 16%; right: 16%; top: 50%;  height: 1px; }
  .ps-cross--v { top: 16%; bottom: 16%; left: 50%; width: 1px; }

  .ps-knob {
    position: absolute;
    left: 50%; top: 50%;
    width: 42px; height: 42px;
    margin: -21px 0 0 -21px;
    border-radius: 50%;
    background: rgba(78, 205, 196, 0.85);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
    pointer-events: none;
    transition: transform 140ms ease-out;
  }
  #phone-stick.is-active .ps-knob { transition: none; }   /* follow the thumb */

  .ps-foot { display: flex; align-items: center; gap: 6px; }

  .ps-chip {
    padding: 3px 9px;
    border-radius: var(--radius-sm);
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: rgba(13, 12, 24, 0.5);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: var(--text-sec);
    font-size: 10px;
    font-weight: 600;
    cursor: pointer;
  }
  .ps-chip.is-active {
    color: var(--purple);
    border-color: var(--purple);
    background: rgba(139, 126, 200, 0.22);
  }

}

@media (prefers-reduced-motion: reduce) {
  .ps-knob { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CAMERA BAR — narrow window, mouse pointer

   The phone tier grows the presets because a thumb needs the room. It
   turns on at ≤699px on width alone, though, so a desktop browser dragged
   narrow got thumb-sized presets it has no thumb for: 24px at 700px
   became 48px at 699px, a jump you can see happen.

   The touch sizing stays exactly as it was — this only walks it back for
   a fine pointer, to the same numbers the 700–899px band uses, so nothing
   changes as you cross the boundary. Everything else the phone shell does
   to this bar (content-width, centred, no wrap, scrollable) is about
   width, not input, and is left alone. Coarse-pointer emulation in
   devtools reports `pointer: coarse`, so device mode still shows touch.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 699px) and (pointer: fine) {
  body.phone-shell #cam-preset-bar {
    gap: 1px;
    padding: 3px;
    border-radius: var(--radius-btn);
  }

  body.phone-shell .cam-preset-btn {
    height: 24px;
    min-width: 0;
    padding: 0 8px;
    font-size: 11px;
  }

  body.phone-shell .cam-preset-free { margin-left: 8px; padding: 0 9px; }
  body.phone-shell .cam-preset-free::before { left: -4px; }

  body.phone-shell .cam-preset-reset {
    height: 24px;
    min-width: 0;
    padding: 0 7px;
    font-size: 12px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   TOOLTIP — the single hint surface (js/ui/tooltip.js)

   Sits above everything, including the prompt modal at 9999, because it
   explains controls that live inside those layers too.

   Opaque on purpose. This floats over a lit 3D viewport whose brightness
   is whatever the user's pose and lighting happen to be, so a translucent
   panel would have unreadable text roughly half the time. It is also the
   lightest surface in the app — panels are --panel, cards are --card, and
   this is nearer still to the viewer.
   ═══════════════════════════════════════════════════════════════════════ */
#pf-tip {
  position: fixed;
  z-index: 10000;
  max-width: 260px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;

  background: #221f3a;
  border: 1px solid #3a3760;
  border-radius: var(--radius-btn);
  box-shadow:
    0 8px 24px -6px rgba(0, 0, 0, 0.65),
    0 2px 6px -2px rgba(0, 0, 0, 0.5);

  font-size: 12px;
  line-height: 1.45;
  color: var(--text);
  text-align: left;
  /* A tooltip is an annotation, not prose — it wraps on its own terms and
     never inherits a parent's centring or letter-spacing. */
  letter-spacing: 0;
  white-space: normal;
  overflow-wrap: break-word;

  opacity: 0;
  transition:
    opacity   150ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 190ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* Rises out of the control it describes, so the eye is carried from the
   thing to its explanation rather than to an unrelated box appearing. */
#pf-tip[data-side="top"]    { transform: translateY(4px); }
#pf-tip[data-side="bottom"] { transform: translateY(-4px); }
#pf-tip[data-side="left"]   { transform: translateX(4px); }
#pf-tip[data-side="right"]  { transform: translateX(-4px); }

#pf-tip[data-open="true"] {
  opacity: 1;
  transform: none;
}

.pf-tip-title {
  font-size: 12px;
  font-weight: 650;
  color: var(--text);
  line-height: 1.3;
}

.pf-tip-body {
  color: var(--text);
}

/* Demoted only when a title is carrying the naming — hierarchy by weight
   and value, never by a second hue. */
.pf-tip-title + .pf-tip-body {
  color: var(--text-sec);
}

/* ── Shortcut chips ──────────────────────────────────────────────────── */
.pf-tip-keys {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-top: 2px;
}

.pf-tip-keys kbd {
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 10px;
  line-height: 1;
  padding: 3px 5px;
  color: var(--text);
  background: #2e2b4d;
  border: 1px solid #454170;
  border-bottom-width: 2px;
  border-radius: 4px;
}

/* The separator between chips. --text-muted disappears against the tip's
   own fill; this is punctuation, so it does not need body contrast, but it
   does need to be seen. */
.pf-tip-keys i {
  font-style: normal;
  font-size: 10px;
  color: var(--text-sec);
}

/* ── Arrow ───────────────────────────────────────────────────────────── */
/* A rotated square carrying the panel's own fill and two of its borders, so
   the seam between arrow and body never shows. */
.pf-tip-arrow {
  position: absolute;
  width: 8px;
  height: 8px;
  background: #221f3a;
  border: 1px solid #3a3760;
  transform: rotate(45deg);
}

#pf-tip[data-side="top"] .pf-tip-arrow {
  bottom: -5px;
  margin-left: -4px;
  border-top: 0;
  border-left: 0;
}
#pf-tip[data-side="bottom"] .pf-tip-arrow {
  top: -5px;
  margin-left: -4px;
  border-bottom: 0;
  border-right: 0;
}
#pf-tip[data-side="left"] .pf-tip-arrow {
  right: -5px;
  margin-top: -4px;
  border-top: 0;
  border-right: 0;
}
#pf-tip[data-side="right"] .pf-tip-arrow {
  left: -5px;
  margin-top: -4px;
  border-bottom: 0;
  border-left: 0;
}

/* Touch tips are read after the finger lifts, from further away, and on a
   narrower screen — so they get more room and a larger size. */
@media (max-width: 699px) {
  #pf-tip {
    max-width: min(280px, calc(calc(100vw / var(--ui-scale, 1)) - 24px));
    font-size: 12.5px;
    padding: 9px 11px;
  }
}

/* The tip's job is to arrive and be read. Users who asked for less motion
   get it without the travel; it still fades so its arrival is noticed. */
@media (prefers-reduced-motion: reduce) {
  #pf-tip {
    transition: opacity 100ms linear;
    transform: none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   OPTIMISTIC WRITE — the pending pose card

   A pose card now appears the instant it is saved or duplicated, before the
   database has been told. That trade is only honest if the card says so, so
   these seconds get a visible state rather than a silent one.

   Marked, not disabled. The pose is fully usable while it settles — it can
   be applied, favourited, renamed — because it really is in the library. The
   only thing not yet true is durability, and that is what the badge claims.
   ═══════════════════════════════════════════════════════════════════════ */
.pose-card.is-pending {
  border-color: var(--border-focus);
}

/* Anchors the badge. The thumb already carries aspect-ratio: 1, so adding a
   positioning context costs no layout. */
.pose-thumb {
  position: relative;
}

.pose-saving {
  position: absolute;
  left: 8px;
  bottom: 8px;
  display: flex;
  align-items: center;
  gap: 5px;

  padding: 3px 8px 3px 7px;
  border-radius: 999px;
  background: rgba(13, 12, 24, 0.82);
  border: 1px solid var(--border-mid);

  font-size: 10px;
  letter-spacing: 0.03em;
  color: var(--text);
  /* Over a photographic thumbnail whose brightness is unknown, so it carries
     its own ground rather than trusting the image behind it. */
  backdrop-filter: blur(3px);
}

.pose-saving::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--teal);
  animation: pose-saving-pulse 1.4s ease-in-out infinite;
}

@keyframes pose-saving-pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.7); }
}

/* The dot is the only moving thing here, and its job is to say "still going".
   Held steady, the badge's own text still says it. */
@media (prefers-reduced-motion: reduce) {
  .pose-saving::before { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   BOOT COVER — see #pf-boot in index.html

   Above everything (the sign-in modal is 3000, the prompt modal 9999, the
   tooltip 10000) because for the length of boot there is nothing it should
   ever be behind. It is removed, not merely hidden, once auth resolves.

   Opaque, and painted in the first frame: the whole point is that no part
   of the editor is visible before the app knows who is looking at it.
   ═══════════════════════════════════════════════════════════════════════ */
#pf-boot {
  position: fixed;
  inset: 0;
  z-index: 10050;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Frosted by default; admins set how see-through it is in Settings
     (--boot-alpha, applied by the inline script under #pf-boot). The blur
     keeps anything behind it illegible, and the cover no longer lifts
     before sign-in is known, so the editor is never usable behind it. */
  background: color-mix(in srgb, var(--bg, #0d0c18) calc(var(--boot-alpha, 0.55) * 100%), transparent);
  -webkit-backdrop-filter: blur(18px) saturate(120%);
  backdrop-filter: blur(18px) saturate(120%);
  /* Transitioned out; `.is-gone` starts the fade, and the element is
     removed on transitionend so it can never trap a click. */
  opacity: 1;
  transition: opacity 260ms cubic-bezier(0.16, 1, 0.3, 1);
}

#pf-boot.is-gone { opacity: 0; }

/* The cover's own content leaves FIRST, and faster than the ground it sits
   on. Fading both together showed the boot wordmark and the sign-in card's
   wordmark stacked on each other for the length of the crossfade — briefly
   two PoseForges, which reads as a rendering fault rather than a transition. */
.pf-boot-inner {
  transition: opacity 110ms linear;
}
#pf-boot.is-gone .pf-boot-inner { opacity: 0; }

.pf-boot-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  width: min(260px, calc(76vw / var(--ui-scale, 1)));
  /* Sits a little above centre. Optically centred beats mathematically
     centred for a short stack like this one. */
  margin-bottom: calc(6vh / var(--ui-scale, 1));
}

.pf-boot-brand {
  text-align: center;
  font-size: 19px;
  font-weight: 650;
  letter-spacing: 0.02em;
  color: var(--text);
}

/* Indeterminate on purpose. Boot waits on two network round trips whose
   duration is unknowable, and a percentage that is not measuring anything
   is a lie told with a progress bar. */
.pf-boot-track {
  position: relative;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--border-mid);
  overflow: hidden;
}

.pf-boot-fill {
  position: absolute;
  inset: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--teal), transparent);
  transform: translateX(-100%);
  animation: pf-boot-sweep 1.15s ease-in-out infinite;
}

@keyframes pf-boot-sweep {
  to { transform: translateX(100%); }
}

.pf-boot-step {
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: var(--text-sec);
  text-align: center;
  min-height: 1.2em;      /* holds its line so the stack cannot jump */
}

/* The step text still changes, so the wait is still legible without the
   sweep. Holding the bar as a static rule is honest: it says "there is a
   process here" without implying motion that is not measured anyway. */
@media (prefers-reduced-motion: reduce) {
  #pf-boot { transition: none; }
  .pf-boot-fill {
    animation: none;
    transform: none;
    background: var(--teal);
    opacity: 0.4;
  }
}

/* Boot figure — a stick figure stepping through a few poses while boot runs.
   Each limb rotates about its own joint; origins are in viewBox units. */
.pf-boot-figure {
  width: 56px;
  height: 75px;
  overflow: visible;
  animation: pf-fig-bob 2.6s ease-in-out infinite;
}
.pf-boot-figure line,
.pf-boot-figure circle {
  stroke: var(--teal);
  stroke-width: 3.2;
  stroke-linecap: round;
  fill: none;
}
.pf-fig-head { fill: var(--teal-dim); }
.pf-fig-limb {
  transform-box: view-box;
  animation-duration: 2.6s;
  animation-timing-function: cubic-bezier(0.45, 0, 0.25, 1);
  animation-iteration-count: infinite;
}
.pf-fig-arm-l, .pf-fig-arm-r { transform-origin: 30px 25px; }
.pf-fig-leg-l, .pf-fig-leg-r { transform-origin: 30px 46px; }
.pf-fig-arm-l { animation-name: pf-fig-arm-l; }
.pf-fig-arm-r { animation-name: pf-fig-arm-r; }
.pf-fig-leg-l { animation-name: pf-fig-leg-l; }
.pf-fig-leg-r { animation-name: pf-fig-leg-r; }

/* rest → star jump → T-pose → wave → rest */
@keyframes pf-fig-arm-l {
  0%, 12%   { transform: rotate(0deg); }
  25%, 37%  { transform: rotate(115deg); }
  50%, 62%  { transform: rotate(43deg); }
  75%, 90%  { transform: rotate(8deg); }
  100%      { transform: rotate(0deg); }
}
@keyframes pf-fig-arm-r {
  0%, 12%   { transform: rotate(0deg); }
  25%, 37%  { transform: rotate(-115deg); }
  50%, 62%  { transform: rotate(-43deg); }
  75%       { transform: rotate(-140deg); }
  80%       { transform: rotate(-115deg); }
  85%       { transform: rotate(-140deg); }
  90%       { transform: rotate(-115deg); }
  100%      { transform: rotate(0deg); }
}
@keyframes pf-fig-leg-l {
  0%, 12%   { transform: rotate(0deg); }
  25%, 37%  { transform: rotate(22deg); }
  50%, 62%  { transform: rotate(0deg); }
  75%, 90%  { transform: rotate(6deg); }
  100%      { transform: rotate(0deg); }
}
@keyframes pf-fig-leg-r {
  0%, 12%   { transform: rotate(0deg); }
  25%, 37%  { transform: rotate(-22deg); }
  50%, 62%  { transform: rotate(0deg); }
  75%, 90%  { transform: rotate(-6deg); }
  100%      { transform: rotate(0deg); }
}
@keyframes pf-fig-bob {
  0%, 100% { transform: translateY(0); }
  25%      { transform: translateY(-4px); }
  31%      { transform: translateY(0); }
}

.pf-boot-step { transition: opacity 160ms ease; }

@media (prefers-reduced-motion: reduce) {
  .pf-boot-figure, .pf-fig-limb { animation: none; }
}

/* Quick actions on Overview — three things an administrator arrives to do,
   all of which already existed somewhere less reachable. */
.ov-quick { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }

/* A metric with no collection path, said plainly. The old card rendered
   "0.0 / 5" over 44 unrated poses, which reads as a score of zero. */
.stat-nodata { font-size: 13px; font-weight: 500; color: var(--text-muted); }

/* System ▸ Maintenance */
.mt-note {
  font-size: 12.5px;
  line-height: var(--lh-body);
  color: var(--text-sec);
  margin: 0 0 12px;
  max-width: 70ch;
}
.mt-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* ═══ USERS (js/admin/users.js) ═════════════════════════════════════════ */

.usr-table-wrap { overflow-x: auto; margin-top: 4px; }
.usr-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.usr-table th {
  text-align: left;
  padding: 8px 12px 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--letter-section);
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
.usr-table td {
  padding: 10px 12px 10px 0;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.usr-table tr:last-child td { border-bottom: none; }
.usr-num { text-align: right; font-variant-numeric: tabular-nums; }
.usr-dim { color: var(--text-sec); white-space: nowrap; }
.usr-name { color: var(--text); font-weight: 500; }
.usr-email { color: var(--text-muted); font-size: 11.5px; margin-top: 1px; }

/* Marks your own row, where the role control is deliberately locked. */
.usr-you {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--purple-dim);
  color: var(--purple);
  font-size: 10px;
  font-weight: 600;
}

/* Carries the SERVER's sentence when a role change is refused — the UI's own
   checks are a courtesy, and this is where the authority speaks. */
.usr-note { font-size: 12px; color: var(--text-sec); margin: 12px 0 0; min-height: 1em; }
.usr-note.is-error { color: var(--danger); }
.usr-note.is-ok { color: var(--success); }

/* Verification state, reported next to the address it belongs to. Read only —
   there is no server route to change somebody else's, by design. */
.usr-verif {
  display: inline-block;
  margin-left: 4px;
  padding: 0 5px;
  border-radius: 3px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: 1px;
}
.usr-verif.is-ok      { background: var(--success-dim); color: var(--success); }
.usr-verif.is-pending { background: var(--warning-dim); color: var(--warning); }

.usr-actions-col { text-align: right; }
.usr-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  white-space: nowrap;
}

.usr-btn {
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  padding: 4px 10px;
  font-size: 11.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.usr-btn:hover:not(:disabled) { background: var(--card-hover); border-color: var(--border-focus); }
.usr-btn:disabled { opacity: 0.38; cursor: not-allowed; }

/* Destructive, and coloured only on hover. A row of permanently red Delete
   buttons trains the eye to stop seeing them, which is the opposite of what
   the colour is for. */
.usr-btn.is-danger:hover:not(:disabled) {
  background: var(--danger-dim);
  border-color: var(--danger);
  color: var(--danger);
}

/* ── One account's saved poses, read only ─────────────────────────────── */
.usr-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(6, 5, 14, 0.78);
}

.usr-modal {
  display: flex;
  flex-direction: column;
  width: min(860px, 100%);
  max-height: 100%;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
}

.usr-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}

.usr-modal-title { font-size: 13px; font-weight: 600; color: var(--text); }
.usr-modal-sub   { font-size: 11.5px; color: var(--text-sec); margin-top: 2px; }

.usr-modal-x {
  background: none;
  border: 0;
  color: var(--text-sec);
  font-size: 22px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}
.usr-modal-x:hover { color: var(--text); }

.usr-modal-body {
  overflow-y: auto;
  padding: 14px 16px 18px;
}

.usr-pose-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 12px;
  margin-top: 10px;
}

.usr-pose { min-width: 0; }

/* Fixed 3:4 frame so a grid of mixed-aspect thumbnails still reads as rows.
   contain, not cover: a cropped pose thumbnail can lose the very limb that
   makes the pose recognisable. */
.usr-pose-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.usr-pose-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.usr-pose-none { font-size: 10.5px; color: var(--text-muted); }

.usr-pose figcaption {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 5px;
  min-width: 0;
}

.usr-pose-name {
  font-size: 12px;
  color: var(--text);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.usr-pose-meta { font-size: 10.5px; color: var(--text-muted); }

/* ═══════════════════════════════════════════════════════════════════════════
   EMAIL VERIFICATION BANNER  (js/core/auth.js renderVerifyBanner)
   ═══════════════════════════════════════════════════════════════════════════
   Bottom-anchored, not top. The topbar already carries the account chip and
   the "view as user" strip, and a third bar up there pushes the editor
   viewport down on every load for a message that is not urgent. */
.verify-banner {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 9800;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(620px, calc(calc(100vw / var(--ui-scale, 1)) - 32px));
  padding: 10px 12px 10px 16px;
  border-radius: 8px;
  border: 1px solid var(--border-mid);
  background: var(--card);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text);
}

.verify-banner--warn { border-color: var(--warning); background: var(--warning-dim); }
.verify-banner--ok   { border-color: var(--success); background: var(--success-dim); }

.verify-banner-text { flex: 1; min-width: 0; }

.verify-banner-btn {
  flex: none;
  background: var(--border-mid);
  color: var(--text);
  border: 1px solid var(--border-focus);
  border-radius: var(--radius-sm);
  padding: 5px 12px;
  font-size: 11.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.verify-banner-btn:hover:not(:disabled) { background: var(--border-focus); }
.verify-banner-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.verify-banner-x {
  flex: none;
  background: none;
  border: 0;
  color: var(--text-sec);
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}
.verify-banner-x:hover { color: var(--text); }

@media (max-width: 640px) {
  .verify-banner {
    left: 12px;
    right: 12px;
    bottom: 12px;
    max-width: none;
    transform: none;
    flex-wrap: wrap;
  }
  .usr-modal-overlay { padding: 0; }
  .usr-modal { width: 100%; height: 100%; border: 0; border-radius: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   FEATURE FLAGS (js/core/features.js)
   ═══════════════════════════════════════════════════════════════════════════
   Markup declares its own membership with data-feature="<id>"; the module
   writes one class on <body> and this rule does the hiding. !important is
   deliberate and not laziness: switchView() writes inline display on some of
   these elements (#motion-strip is in EDITOR_ELS), and an inline style beats
   a class selector. A flag that a navigation click can undo is not a flag. */

body:not(.feature-animation-on) [data-feature="animation"] { display: none !important; }
body:not(.feature-workspaces-on) [data-feature="workspaces"] { display: none !important; }
body:not(.feature-librarySuggestions-on) [data-feature="librarySuggestions"] { display: none !important; }
body:not(.feature-depthCheck-on) [data-feature="depthCheck"] { display: none !important; }
body:not(.feature-cameraBar-on) [data-feature="cameraBar"] { display: none !important; }
body:not(.feature-drawCamera-on) [data-feature="drawCamera"] { display: none !important; }
body:not(.feature-advancedBodyShape-on) [data-feature="advancedBodyShape"] { display: none !important; }
body:not(.feature-character-on) [data-feature="character"] { display: none !important; }
body:not(.feature-wardrobe-on) [data-feature="wardrobe"],
body:not(.feature-wardrobe-on) .set-row--wardrobe { display: none !important; }
body:not(.feature-recentStrip-on) [data-feature="recentStrip"] { display: none !important; }
body:not(.feature-focusMode-on) [data-feature="focusMode"] { display: none !important; }

/* The switch itself is NOT redefined here. `.pf-switch` already exists in the
   TOGGLES block near the top of this file — the house switch, applied to the
   input element itself — and that block opens by explaining what four rival
   checkbox implementations cost this project. Adding a fifth for two new
   screens would have been the same mistake with a nicer comment. */

/* ═══ ADMIN ▸ SYSTEM ▸ FEATURES (js/admin/features.js) ════════════════════ */

.ff-intro {
  color: var(--text-sec);
  font-size: 13px;
  line-height: var(--lh-body);
  max-width: 68ch;
  margin: 0 0 16px;
}
.ff-card {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 18px 20px;
  margin-bottom: 12px;
}
/* An active flag is the exception, so it is the one that gets a colour. */
.ff-card.is-on { border-color: rgba(78, 205, 196, 0.38); }
.ff-main { flex: 1; min-width: 0; }
.ff-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.ff-name {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.ff-pill {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--border-mid);
  color: var(--text-sec);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.ff-pill--on { background: var(--teal-dim); color: var(--teal); }
.ff-pill--muted {
  background: transparent;
  border: 1px dashed var(--border-mid);
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
}
.ff-blurb {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: var(--lh-body);
  color: var(--text-sec);
  max-width: 62ch;
}
.ff-surfaces {
  border-left: 2px solid var(--border-mid);
  padding-left: 12px;
  margin-bottom: 12px;
}
.ff-surfaces-label {
  display: block;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: var(--letter-section);
  color: var(--text-muted);
  margin-bottom: 4px;
}
.ff-surfaces ul { margin: 0; padding-left: 16px; }
.ff-surfaces li {
  font-size: 12px;
  color: var(--text-sec);
  line-height: 1.6;
}
.ff-scope {
  margin: 0;
  font-size: 11.5px;
  color: var(--text-muted);
  line-height: 1.5;
  max-width: 62ch;
}
.ff-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  flex: none;
}
.ff-reset {
  background: none;
  border: none;
  padding: 0;
  color: var(--text-muted);
  font-size: 11.5px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.ff-reset:hover { color: var(--text-sec); }

@media (max-width: 640px) {
  .ff-card { flex-direction: column; gap: 14px; }
  .ff-side { flex-direction: row; align-items: center; align-self: stretch; justify-content: space-between; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SETTINGS (renderSettings in index.html)
   ═══════════════════════════════════════════════════════════════════════════
   The page used to borrow the admin panel's header and section classes, which
   is why it read as a stripped-down admin screen rather than a settings page.
   It has its own vocabulary now, built on one row shape — text on the left,
   control on the right — so every switch in the column lands on the same
   vertical line and the eye can scan the controls without reading the prose.

   Groups are role-scoped in the markup, not here: CSS should not be the thing
   that decides who is allowed to see a preference. */

.set-page {
  max-width: 780px;
  margin: 0 auto;
  padding-bottom: 48px;
}

/* ── Page header ─────────────────────────────────────────────────────── */
.set-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 20px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--border);
}
.set-title {
  margin: 0 0 4px;
  font-size: 24px;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--text);
}
.set-sub {
  margin: 0;
  font-size: 13px;
  line-height: var(--lh-body);
  color: var(--text-sec);
  max-width: 56ch;
}
.set-role {
  flex: none;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--border-mid);
  background: var(--card);
  color: var(--text-sec);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.set-role--admin {
  border-color: rgba(139, 126, 200, 0.45);
  background: var(--purple-dim);
  color: var(--purple);
}

/* ── Account card ────────────────────────────────────────────────────── */
.set-account {
  display: flex;
  align-items: center;
  gap: 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 18px 20px;
  margin-bottom: 28px;
}
.set-avatar {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--teal-dim);
  color: var(--teal);
  font-size: 18px;
  font-weight: 650;
}
.set-avatar--empty { background: var(--border-mid); color: var(--text-muted); }
.set-account-text { flex: 1; min-width: 0; }
.set-account-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.set-account-mail {
  font-size: 12.5px;
  color: var(--text-sec);
  margin-top: 1px;
  line-height: var(--lh-body);
}
.set-account-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.set-account-sub { font-size: 11.5px; color: var(--text-muted); }
.set-account-actions {
  flex: none;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* ── Chips ───────────────────────────────────────────────────────────── */
.set-chip {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--border-mid);
  color: var(--text-sec);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.set-chip--admin   { background: var(--purple-dim); color: var(--purple); }
.set-chip--on      { background: var(--teal-dim);   color: var(--teal); }
/* The lens is a temporary state, so it is the one chip that is coloured like
   a notice rather than like an attribute. */
.set-chip--preview { background: var(--warning-dim); color: var(--warning); }

/* ── Groups ──────────────────────────────────────────────────────────── */
.set-group { margin-bottom: 28px; }
.set-group-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: var(--fs-section);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--letter-section);
  color: var(--text-sec);
}
.set-group-note {
  margin: 0 0 10px;
  font-size: 12.5px;
  line-height: var(--lh-body);
  color: var(--text-sec);
  max-width: 66ch;
}
.set-tag {
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid rgba(139, 126, 200, 0.4);
  color: var(--purple);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: none;
}
.set-tag--soft {
  border-color: var(--border-mid);
  color: var(--text-muted);
  font-weight: 500;
  margin-left: 6px;
}

.set-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  overflow: hidden;
}

/* ── Rows ────────────────────────────────────────────────────────────── */
.set-row {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.set-row:last-child { border-bottom: none; }
.set-row-text { flex: 1; min-width: 0; }
.set-row-label {
  font-size: 13.5px;
  font-weight: 550;
  color: var(--text);
}
.set-row-help {
  margin: 4px 0 0;
  font-size: 12px;
  line-height: var(--lh-body);
  color: var(--text-sec);
  max-width: 58ch;
}
.set-row-control {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
}
.set-row-control .draw-select { max-width: 200px; }

/* A control that is wider than a switch — a button with a result message, a
   stat strip — drops under the text instead of squeezing it. */
.set-row--stack {
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}
.set-row--stack .set-row-control { flex-wrap: wrap; }

.set-metric {
  font-size: 20px;
  font-weight: 650;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.set-note { font-size: 12px; color: var(--text-sec); }

/* ── Library totals strip (admin) ────────────────────────────────────── */
.set-stats { display: flex; gap: 10px; flex-wrap: wrap; }
.set-stat {
  flex: 1 1 110px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-input);
  background: var(--bg);
}
.set-stat-n {
  font-size: 22px;
  font-weight: 650;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.set-stat-l {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: var(--letter-section);
  color: var(--text-muted);
}

/* ── Narrow ──────────────────────────────────────────────────────────── */
@media (max-width: 700px) {
  #settings-panel { padding: 18px 16px; }
  .set-head { flex-direction: column; align-items: flex-start; gap: 10px; }
  .set-account { flex-wrap: wrap; }
  .set-account-actions { width: 100%; justify-content: flex-start; }
  .set-row { flex-direction: column; align-items: flex-start; gap: 12px; }
  .set-row-control { align-self: flex-start; }
}

/* ═══ ALL POSES — status chips and the card's ⋯ menu ════════════════════ */

.pm-count { color: var(--text-sec); font-size: 11px; }

.pm-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 16px;
}
.pm-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  background: transparent;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  color: var(--text-sec);
  font-size: 12px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.pm-chip:hover { color: var(--text); border-color: var(--border-focus); }
.pm-chip.is-active {
  background: var(--purple-dim);
  border-color: var(--purple);
  color: var(--text);
  font-weight: 600;
}
.pm-chip:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* The count is the reason the chip is worth more than a select option. */
.pm-chip-n {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--text-muted);
}
.pm-chip.is-active .pm-chip-n { color: var(--purple); }

/* ── The card's overflow menu ─────────────────────────────────────────
   Seven buttons became Edit plus this. Positioned relative to the card so
   it opens over the grid rather than pushing the row apart. */
.pose-more { position: relative; display: inline-block; }
.pose-more-btn { min-width: 30px; padding: 0 8px; line-height: 1; }

.pose-more-menu {
  position: absolute;
  /* Anchored LEFT, not right. The ⋯ button sits just after Edit, near the
     card's left edge, so right:0 threw a 168px menu leftwards out of the
     card and under the sidebar, with half its labels clipped. Opening
     rightward from the button keeps it inside the card at every grid width. */
  left: 0;
  top: calc(100% + 4px);
  z-index: 40;
  min-width: 168px;
  padding: 4px;
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
}
.pose-more-menu[hidden] { display: none; }

.pose-more-item {
  display: block;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: none;
  border-radius: var(--radius-btn);
  color: var(--text-sec);
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
}
.pose-more-item:hover { background: var(--card-hover); color: var(--text); }
.pose-more-item:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

/* Last, separated, and the only red thing on the card — it is the one
   action with no undo. */
.pose-more-item.is-danger {
  color: var(--danger);
  margin-top: 4px;
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--radius-btn) var(--radius-btn);
}
.pose-more-item.is-danger:hover { background: var(--danger-dim); color: var(--danger); }

/* ═══ PUBLISHED ════════════════════════════════════════════════════════ */

.pub-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-top: 8px;
  font-size: 11px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.pub-warn {
  margin: 0 0 14px;
  padding: 9px 12px;
  border: 1px solid rgba(245, 158, 11, 0.35);
  border-radius: var(--radius-card);
  background: var(--warning-dim);
  color: var(--warning);
  font-size: 12.5px;
}

/* An open ⋯ menu has to out-paint the cards after it in the grid. The menu's
   own z-index cannot do that on its own: .pose-card is position:static, so
   the menu has no stacking context to win in and a later card's thumbnail
   draws straight over it — confirmed with elementFromPoint, which returned
   the IMG of the card below. Raising the owning card fixes it for the one
   card that needs it, and only while it needs it. */
.pose-card:has(.pose-more-menu:not([hidden])) {
  position: relative;
  z-index: 50;
  /* .pose-card clips with overflow:hidden to round its thumbnail, which also
     cuts the menu off at the card edge — the real reason it was invisible,
     found after z-index alone changed nothing. Released only while a menu is
     open, and the thumbnail takes over its own rounding for that moment so
     the corners do not square off. 9px = the 10px card radius less its 1px
     border. */
  overflow: visible;
}
.pose-card:has(.pose-more-menu:not([hidden])) .pose-thumb {
  border-radius: 9px 9px 0 0;
}

/* ── Keyboard shortcut reference (Settings) ─────────────────────────────
   A description of js/ui/controls.js, not a rebinding UI. Laid out as a
   definition list because that is what it is: a key, and what it does. */
.set-keys { margin: 0; padding: 0; width: 100%; }
.set-key-row {
  display: grid;
  grid-template-columns: 76px 1fr;
  gap: 12px;
  align-items: baseline;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
}
.set-key-row:last-child { border-bottom: none; }
.set-key-row dt { margin: 0; }
.set-key-row dd { margin: 0; }
.set-key-what { display: block; font-size: 13px; color: var(--text); }
.set-key-why  { display: block; font-size: 12px; color: var(--text-sec); margin-top: 2px; line-height: var(--lh-body); }

kbd {
  display: inline-block;
  min-width: 22px;
  padding: 2px 7px;
  border: 1px solid var(--border-mid);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font: 600 11.5px/1.5 ui-monospace, SFMono-Regular, Consolas, monospace;
  text-align: center;
  white-space: nowrap;
}

@media (max-width: 700px) {
  .set-key-row { grid-template-columns: 1fr; gap: 4px; }
}

/* A control that is a widget plus a line about it — the select and the live
   pixel-ratio readout under it — without giving up the row's right edge. */
.set-ctl-col {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  max-width: 230px;
}
.set-ctl-col .set-note { text-align: right; line-height: 1.45; }
@media (max-width: 700px) {
  .set-ctl-col { align-items: flex-start; max-width: none; }
  .set-ctl-col .set-note { text-align: left; }
}

/* ═══ PASSWORD REVEAL (js/core/auth.js) ════════════════════════════════
   An eye toggle inside every password field on the auth cards. A typo in a
   password you cannot see is indistinguishable from a wrong password, and on
   the register and reset cards — where the field is typed twice and there is
   no "correct" value to compare against — that is the difference between
   finishing and giving up.

   The wrapper is created at runtime around the input, because the button has
   to align to the FIELD and the label above it holds the caption too. */
.pw-wrap {
  position: relative;
  display: block;
}

/* Room for the button, so a long password never runs underneath it. Matches
   the .auth-card input padding otherwise. */
.pw-wrap input[type="password"],
.pw-wrap input[type="text"] {
  padding-right: 40px;
}

.pw-reveal {
  position: absolute;
  top: 4px;              /* clears the input's own margin-top */
  right: 1px;            /* inside the 1px border */
  bottom: 1px;
  width: 36px;
  display: grid;
  place-items: center;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 0 5px 5px 0;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.pw-reveal svg { width: 17px; height: 17px; display: block; }
.pw-reveal:hover { color: var(--text-sec); }

/* Revealed is a state worth seeing at a glance — it means a password is
   currently legible on screen. */
.pw-reveal.is-on { color: var(--purple); }

/* tabIndex is -1 so Tab goes password → submit, but a click still focuses it
   and it must not look broken when it does. */
.pw-reveal:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: -2px;
  border-radius: 5px;
}

/* ═══════════════════════════════════════════════════════════════════
   WORKFLOW PASS — Reference → Pose → Adjust → Save

   Appended last so it layers over the rules above without editing them.
   Every rule here is presentation only: no id is renamed, nothing that
   carries a control is hidden on a smaller screen, and each block names
   the markup it styles.
   ═══════════════════════════════════════════════════════════════════ */

/* ── CTA hierarchy ────────────────────────────────────────────────────
   Two filled buttons in the editor, and only two: Import (the first act)
   and Save (the last). Everything else stays outlined or quiet, so the
   accent means "this is the next step" instead of decoration. */
.btn-browse-sm.btn-browse-primary {
  background: var(--teal);
  border-color: var(--teal);
  color: #0d0c18;
  font-weight: 700;
}
.btn-browse-sm.btn-browse-primary:hover {
  background: var(--teal);
  border-color: var(--teal);
  filter: brightness(1.08);
}

.sb-btn--save {
  background: var(--teal);
  border-color: var(--teal);
  color: #0d0c18;
}
.sb-btn--save:hover {
  background: var(--teal);
  border-color: var(--teal);
  color: #0d0c18;
  filter: brightness(1.08);
}

/* Keyboard focus on the chrome that had none of its own. Additive: an
   outline on an element that already draws one just draws the same ring. */
.btn-browse-sm:focus-visible,
.sb-btn:focus-visible,
.topbar-btn:focus-visible,
.nav-btn:focus-visible,
.ws-btn:focus-visible,
.cam-preset-btn:focus-visible,
.pm-btn:focus-visible,
.joint-action-btn:focus-visible,
.joint-nav-btn:focus-visible,
.rot-pad-btn:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* ── Canvas empty state ───────────────────────────────────────────────
   #pf-empty-cta in index.html, driven by body.pf-ref-empty from
   setReferenceState(). It exists for the layouts where the REFERENCE
   panel is not on screen on arrival — the compact band collapses the
   left rail, and the phone shell starts with every sheet down — which
   left a first-time user facing a figure with nothing saying "import a
   photo". Where the panel IS open its own drop zone is the CTA, so this
   stands down there rather than offering the same action twice. */
#pf-empty-cta { display: none; }

body.pf-ref-empty #pf-empty-cta {
  position: fixed;
  z-index: 150;
  left: calc(var(--canvas-left) + (calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right)) / 2);
  bottom: calc(var(--bottom-h) + 72px);
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: max-content;
  max-width: min(320px, calc(calc(100vw / var(--ui-scale, 1)) - 32px));
  padding: 16px 20px 14px;
  text-align: center;
  background: rgba(19, 17, 31, 0.94);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

/* Stand down: the panel's drop zone is visible, a pose already exists
   (anything undoable, or a joint in hand), or another mode owns the
   surface. :has() is progressive — without it the card simply stays up
   until a photo is imported, which is still a correct instruction.
   Never shown on the phone shell: the card covered the model there. */
body.pf-ref-empty:not(.phone-shell):not(.left-collapsed):not(.rail-collapsed) #pf-empty-cta,
body.pf-ref-empty.pf-has-joint #pf-empty-cta,
body.pf-ref-empty.draw-mode #pf-empty-cta,
body.pf-ref-empty.pf-focus #pf-empty-cta,
body.pf-ref-empty.phone-shell #pf-empty-cta,
body.pf-ref-empty:has(#btn-undo:not(:disabled)) #pf-empty-cta {
  display: none;
}

.pf-empty-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
}
.pf-empty-sub {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text-sec);
}
.pf-empty-cta-btn {
  margin-top: 6px;
  height: 38px;
  padding: 0 20px;
  font-size: 14px;
  font-weight: 700;
}
.pf-empty-cta-btn:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }
.pf-empty-alt {
  font-size: 11px;
  color: var(--text-muted);
}
#pf-empty-cta.drag-over {
  border-color: var(--teal);
  background: rgba(19, 40, 44, 0.96);
}

/* ── Reference panel: detection failure ───────────────────────────────
   #ref-detect-error. The status bar used to be the only place a failed
   detection was reported ("No person detected"), in 13px at the bottom
   of the window, while the rail showed the photo as if it had worked. */
.ref-detect-error {
  margin-top: 8px;
  padding: 9px 10px;
  border: 1px solid rgba(245, 158, 11, 0.45);
  border-radius: var(--radius-sm);
  background: var(--warning-dim);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
}
.ref-detect-error strong {
  display: block;
  margin-bottom: 2px;
  color: var(--warning);
}
.ref-detect-error .btn-sm { margin-top: 8px; width: 100%; }
.ref-detect-error[hidden] { display: none; }

/* ── Side photo: depth check ─────────────────────────────────────────── */
.ref-sideview-verdict { font-weight: 600; }
.ref-sideview-verdict.is-note { font-weight: 400; }

/* ── Selected joint: gizmo options disclosure ─────────────────────────
   #gizmo-options wraps GIZMO DISPLAY and RING ORIENTATION. They were two
   rows of segmented controls above the thing you came for, on every
   selection, and are set once. Closed by default; the choice persists
   (responsiveShell.js). The phone tier already hides both rows. */
.gizmo-options {
  margin-bottom: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.gizmo-options > summary {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 28px;
  padding: 0 9px;
  list-style: none;
  cursor: pointer;
  user-select: none;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-sec);
}
.gizmo-options > summary::-webkit-details-marker { display: none; }
.gizmo-options > summary::before {
  content: '';
  border-left: 4px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 0.15s ease;
}
.gizmo-options[open] > summary::before { transform: rotate(90deg); }
.gizmo-options > summary:hover { color: var(--text); }
.gizmo-options > summary:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }
.gizmo-options-body { padding: 2px 9px 0; }
.phone-shell .gizmo-options { display: none; }
@media (prefers-reduced-motion: reduce) {
  .gizmo-options > summary::before { transition: none; }
}

/* ── Rotation step: a target you can hit ─────────────────────────────
   The Free / 5° / 15° / 45° cells measured 20px tall with a mouse. Touch
   already gets 34px from the (pointer: coarse) block. */
@media (pointer: fine) {
  .snap-seg-btn { min-height: 24px; }
}

/* ── Camera bar: centred on the canvas, not the window ────────────────
   Window-centring kept the bar still while rails collapsed, but at
   1440×900 with every column open the usable canvas runs 312→882px and a
   bar centred at 720px spans ~500→940px: it sat on top of the docked
   SELECTED JOINT panel and clipped its header. A landmark that covers a
   control is worse than one that moves when a rail does.

   Scoped to the docked editor only. Draw mode and the phone shell keep
   their own rules, and the admin Pose Editor hides #topbar and the left
   panel inline without touching the body tokens this reads, so it is
   excluded by that same inline state. Where :has() is unsupported the
   selector is dropped whole and the original centring applies. */
/* Superseded by request: the bar is window-centred and static again, so it
   no longer moves when a panel collapses (see #cam-preset-bar). */

/* 1200–1439px with every column open leaves ~410px of canvas, and the bar
   at full padding is ~438px, so centring it there wrapped Free and the
   reset icon onto a second row. Tighter cells bring it to ~350px, one row,
   with the same seven targets. */
@media (min-width: 1200px) and (max-width: 1439px) {
  body:not(.phone-shell):not(.draw-mode) .cam-preset-btn { padding: 0 6px; font-size: 11.5px; }
  body:not(.phone-shell):not(.draw-mode) .cam-preset-free { margin-left: 6px; }
}

/* Fullscreen toggle in the top bar — icon only, same height as Undo/Redo.
   Shows the glyph for what a click will do. */
.topbar-icon-btn { min-width: 34px; justify-content: center; padding-left: 8px; padding-right: 8px; }
.topbar-icon-btn .fs-exit { display: none; }
.topbar-icon-btn.is-full .fs-enter { display: none; }
.topbar-icon-btn.is-full .fs-exit { display: block; }

/* ─────────────────────────────────────────────────────────────
   START CHOICE (shown once, right after sign-in) — js/ui/startChoice.js
   ───────────────────────────────────────────────────────────── */
.start-choice-modal {
  position: fixed;
  inset: 0;
  z-index: 2900; /* under .auth-modal, which is fading out as this appears */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(8, 7, 16, 0.72);
  animation: start-choice-in 220ms cubic-bezier(0.16, 1, 0.3, 1);
  transition: opacity 200ms ease;
}
.start-choice-modal.is-leaving { opacity: 0; pointer-events: none; }
@keyframes start-choice-in { from { opacity: 0; } to { opacity: 1; } }

.start-choice-card {
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-lg);
  padding: 28px;
  width: 100%;
  max-width: 560px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}
.start-choice-card h2 {
  font-size: 18px;
  color: var(--text);
  text-align: center;
  margin: 0 0 6px;
}
.start-choice-sub {
  text-align: center;
  font-size: 12px;
  color: var(--text-sec);
  margin: 0 0 20px;
}
.start-choice-options {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.start-choice-opt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 18px 12px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  transition: border-color 150ms, background 150ms;
}
.start-choice-opt:hover,
.start-choice-opt:focus-visible {
  border-color: var(--teal);
  background: var(--teal-dim);
  outline: none;
}
.start-choice-opt svg { width: 30px; height: 30px; color: var(--teal); margin-bottom: 4px; }
.start-choice-label { font-size: 13px; font-weight: 600; }
.start-choice-hint  { font-size: 11px; color: var(--text-sec); }

@media (max-width: 520px) {
  .start-choice-options { grid-template-columns: 1fr; }
  .start-choice-opt { flex-direction: row; justify-content: flex-start; text-align: left; flex-wrap: wrap; }
  .start-choice-opt svg { margin-bottom: 0; }
  .start-choice-hint { flex-basis: 100%; padding-left: 36px; margin-top: -4px; }
}

/* Library picker view of the start card. Reuses the Create panel's .cp-tile. */
.start-choice-card.is-library {
  max-width: 760px;
  display: flex;
  flex-direction: column;
  max-height: min(680px, calc(100vh - 40px));
}
.start-lib-head {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  margin-bottom: 14px;
}
.start-lib-head h2 { margin: 0; }
.start-lib-back {
  position: absolute;
  left: 0;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 10px 6px 6px;
  background: none;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-btn);
  color: var(--text-sec);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.start-lib-back svg { width: 16px; height: 16px; }
.start-lib-back:hover, .start-lib-back:focus-visible { color: var(--text); border-color: var(--teal); outline: none; }

.start-lib-cats {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 10px;
  margin-bottom: 4px;
  flex: none;
}
.start-lib-cat {
  flex: none;
  padding: 5px 12px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  color: var(--text-sec);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.start-lib-cat span { color: var(--text-muted); margin-left: 4px; font-size: 11px; }
.start-lib-cat:hover { color: var(--text); }
.start-lib-cat.is-active { color: var(--teal); border-color: var(--teal); background: var(--teal-dim); }

.start-lib-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 10px;
  overflow-y: auto;
  min-height: 0;
  flex: 1 1 auto;
  padding: 2px;
}
.start-lib-grid .cp-tile-name { font-size: 11px; }
.cp-tile.is-selected .cp-tile-thumb {
  border-color: var(--teal);
  box-shadow: 0 0 0 2px var(--teal-glow);
}
.cp-tile.is-selected .cp-tile-name { color: var(--teal); }
.start-lib-empty { text-align: center; color: var(--text-sec); font-size: 13px; padding: 40px 0; }

.start-lib-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  flex: none;
}
.start-lib-picked { font-size: 12px; color: var(--text-sec); min-width: 0; }
.start-lib-picked strong { color: var(--text); }
.start-lib-go {
  padding: 9px 18px;
  background: var(--teal);
  border: none;
  border-radius: var(--radius-btn);
  color: #0d0c18;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.start-lib-go:disabled { background: var(--border-mid); color: var(--text-muted); cursor: not-allowed; }
.start-lib-go:not(:disabled):hover { filter: brightness(1.08); }

@media (max-width: 520px) {
  .start-lib-head { flex-direction: column; gap: 8px; align-items: flex-start; }
  .start-lib-back { position: static; }
  .start-lib-grid { grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); }
  .start-lib-go { width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CHARACTER PANEL — refreshed look (js/ui/morphPanel.js)
   Appended last so it settles the older morph rules above without editing
   each of them in place.
   ═══════════════════════════════════════════════════════════════════════ */

/* Body type: silhouette tiles. 6-track grid so the second row of two sits
   centred under the first row of three. */
.morph-bodytype-row {
  grid-template-columns: repeat(6, 1fr);
  gap: 6px;
  padding: 6px;
  background: var(--bg);
  border-radius: var(--radius-card);
}
.morph-bodytype-btn {
  grid-column: span 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 9px 4px 8px;
  min-height: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  font-size: 11px;
  font-weight: 600;
  color: var(--text-sec);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.morph-bodytype-btn:nth-child(4) { grid-column: 2 / span 2; }
.morph-bodytype-btn:hover:not(.is-active) {
  background: var(--card);
  border-color: var(--border-mid);
  color: var(--text);
}
.morph-bodytype-btn.is-active {
  background: var(--teal-dim);
  border-color: var(--teal);
  color: var(--teal);
  box-shadow: 0 0 0 1px var(--teal-dim), 0 4px 14px -6px var(--teal-glow);
}
.morph-bt-icon {
  width: 20px;
  height: 27px;
  overflow: visible;
  opacity: 0.75;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.morph-bt-icon circle,
.morph-bt-icon path:first-of-type { fill: currentColor; stroke: none; }
.morph-bt-icon path + path { fill: none; stroke: currentColor; stroke-linecap: round; }
.morph-bodytype-btn:hover .morph-bt-icon,
.morph-bodytype-btn.is-active .morph-bt-icon { opacity: 1; }
.morph-bodytype-btn.is-active .morph-bt-icon { transform: translateY(-1px); }

/* Shared chevron for both disclosure levels. */
.morph-chevron {
  width: 14px;
  height: 14px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--text-muted);
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), color 0.15s;
}
.morph-advanced-summary::before,
.morph-cat-summary::before { content: none; }

/* Advanced disclosure: a proper header row with a subtitle. */
.morph-advanced {
  margin-top: 14px;
  padding-top: 0;
  border-top: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  overflow: hidden;
}
.morph-advanced-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px;
  text-transform: none;
  letter-spacing: 0;
  transition: background 0.15s;
}
.morph-advanced-summary:hover { background: var(--card); }
.morph-advanced-summary:hover .morph-chevron { color: var(--text); }
.morph-advanced[open] > .morph-advanced-summary .morph-chevron { transform: rotate(90deg); color: var(--teal); }
.morph-advanced[open] > .morph-advanced-summary { border-bottom: 1px solid var(--border); }
.morph-advanced-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.morph-advanced-title { font-size: 12.5px; font-weight: 600; color: var(--text); }
.morph-advanced-sub   { font-size: 11px; font-weight: 400; color: var(--text-sec); }
.morph-advanced-body  { padding: 10px; }

/* Search with an icon. */
.morph-search-wrap {
  position: relative;
  display: block;
  margin-bottom: 8px;
}
.morph-search-wrap svg {
  position: absolute;
  left: 10px;
  top: 50%;
  width: 14px;
  height: 14px;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
  transition: stroke 0.15s;
}
.morph-search-wrap:focus-within svg { stroke: var(--teal); }
.morph-search {
  margin: 0;
  padding: 8px 10px 8px 32px;
  background: var(--bg);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-btn);
  font-family: inherit;
}
.morph-search::placeholder { color: var(--text-muted); }
.morph-search:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-dim);
}

/* Category rows. */
.morph-cat {
  border-bottom: none;
  border-radius: var(--radius-btn);
  transition: background 0.15s;
}
.morph-cat + .morph-cat { margin-top: 2px; }
.morph-cat[open] { background: var(--card); }
.morph-cat-summary {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  padding: 7px 8px;
  border-radius: var(--radius-btn);
  font-size: 12px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  transition: background 0.15s;
}
.morph-cat-summary:hover { color: var(--text); background: var(--card-hover); }
.morph-cat-summary:hover .morph-chevron { color: var(--text); }
.morph-cat[open] > .morph-cat-summary .morph-chevron { transform: rotate(90deg); color: var(--teal); }
.morph-cat-summary:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

.morph-cat-icon {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 7px;
  background: var(--teal-dim);
  color: var(--teal);
}
.morph-cat-icon svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.morph-cat-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* "This group has been adjusted" — only drawn when it is true. */
.morph-cat-dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-dim);
  display: none;
}
.morph-cat.is-edited .morph-cat-dot { display: block; }

.morph-cat-count {
  min-width: 22px;
  text-align: center;
  padding: 2px 6px;
  font-size: 10.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-sec);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
}

/* Sliders inside an open group. */
.morph-cat-body {
  padding: 4px 12px 10px;
  margin: 0 4px;
  border-top: 1px solid var(--border);
}
.morph-cat-body .morph-row {
  padding: 8px 0 2px;
  border-bottom: none;
}
.morph-cat-body .morph-header { margin-bottom: 0; }
.morph-name { color: var(--text-sec); font-size: 11.5px; }
.morph-row:hover .morph-name,
.morph-row:focus-within .morph-name { color: var(--text); }
.morph-val {
  min-width: 34px;
  padding: 1px 5px;
  text-align: center;
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  background: var(--bg);
  border-radius: 4px;
  transition: color 0.15s;
}
.morph-row.is-set .morph-val { color: var(--teal); }

/* Track: visible groove, teal up to the handle. --fill is set by
   paintFill() in morphPanel.js. */
.morph-slider {
  --fill: 0%;
  height: 22px;
}
.morph-slider::-webkit-slider-runnable-track {
  height: 4px;
  border: none;
  border-radius: 2px;
  box-shadow: none;
  background: linear-gradient(to right,
    var(--teal) 0, var(--teal) var(--fill),
    var(--border-mid) var(--fill), var(--border-mid) 100%);
}
.morph-slider::-moz-range-track {
  height: 4px;
  border: none;
  border-radius: 2px;
  box-shadow: none;
  background: var(--border-mid);
}
.morph-slider::-moz-range-progress {
  height: 4px;
  border-radius: 2px;
  background: var(--teal);
}
.morph-slider::-webkit-slider-thumb {
  width: 14px;
  height: 14px;
  margin-top: -5px;             /* centres the 14px handle on the 4px track */
  background: var(--text);
  border: 3px solid var(--teal);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
.morph-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  box-sizing: border-box;
  background: var(--text);
  border: 3px solid var(--teal);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
.morph-slider:hover::-webkit-slider-thumb,
.morph-slider:active::-webkit-slider-thumb { transform: scale(1.2); }
.morph-slider:hover::-moz-range-thumb,
.morph-slider:active::-moz-range-thumb { transform: scale(1.2); }
.morph-slider:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5), 0 0 0 4px var(--teal-glow);
}
.morph-slider:focus-visible::-moz-range-thumb {
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5), 0 0 0 4px var(--teal-glow);
}

@media (prefers-reduced-motion: reduce) {
  .morph-chevron, .morph-bt-icon { transition: none; }
}

/* (Merged right column removed by request: the Joint Selector and Selected
   Joint stay two side-by-side panels, each toggled from #panel-rail.) */

/* ═══════════════════════════════════════════════════════════════════════
   POSE TITLE — name + save state, centred in the top bar (js/ui/poseTitle.js)
   ═══════════════════════════════════════════════════════════════════════ */
.pose-title {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: min(420px, 34vw);
  height: 32px;
  padding: 0 12px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.pose-title:hover { background: var(--card); border-color: var(--border-mid); }
.pose-title:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

.pt-name {
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pt-state {
  flex: none;
  font-size: 11.5px;
  color: var(--text-sec);
  white-space: nowrap;
}
.pt-state:empty { display: none; }
.pt-state::before { content: '·'; margin-right: 8px; color: var(--text-muted); }

.pt-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border-mid);
  transition: background 0.2s, box-shadow 0.2s;
}
.pose-title[data-state="dirty"]  .pt-dot { background: var(--warning); box-shadow: 0 0 0 3px var(--warning-dim); }
.pose-title[data-state="dirty"]  .pt-state { color: var(--warning); }
.pose-title[data-state="saving"] .pt-dot { background: var(--teal); animation: pt-pulse 1s ease-in-out infinite; }
.pose-title[data-state="saved"]  .pt-dot { background: var(--success); box-shadow: 0 0 0 3px var(--success-dim); }
.pose-title[data-state="local"]  .pt-dot { background: var(--teal); box-shadow: 0 0 0 3px var(--teal-dim); }
.pose-title[data-state="failed"] .pt-dot { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-dim); }
.pose-title[data-state="failed"] .pt-state { color: var(--danger); }
.pose-title[data-state="clean"]  .pt-name { color: var(--text-sec); font-weight: 500; }

@keyframes pt-pulse { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .pose-title[data-state="saving"] .pt-dot { animation: none; }
}

/* The bar is too narrow to centre anything below this, and the phone tier
   and Draw have their own top-bar contents. */
@media (max-width: 899px) { .pose-title { display: none !important; } }
body.phone-shell .pose-title,
body.draw-mode .pose-title { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════
   SELECTED JOINT — one look for the whole panel
   Header: eyebrow, name with its body region, actions. Every section below
   is the same card (surface, edge, radius) with the same heading style, all
   from theme tokens so dark, neon, studio and light agree.
   ═══════════════════════════════════════════════════════════════════════ */
#mini-gizmo-panel {
  --sj-radius: 12px;
}
.gizmo-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2px;
}
.gizmo-eyebrow {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.gizmo-help,
#mini-gizmo-panel .gizmo-collapse {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border-mid);
  border-radius: 8px;
  background: transparent;
  color: var(--text-sec);
  font: 700 12px/1 inherit;
  cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}
.gizmo-help:hover,
#mini-gizmo-panel .gizmo-collapse:hover { color: var(--text); background: var(--card-hover); border-color: var(--teal); }
.gizmo-help:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

#mini-gizmo-panel .selected-joint-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 2px 0 12px;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--text);
}
#mini-gizmo-panel .selected-joint-name[data-region]::after {
  content: attr(data-region);
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--teal);
  background: var(--teal-dim, rgba(78, 205, 196, 0.12));
}

/* Cards */
#mini-gizmo-panel .gizmo-options,
#mini-gizmo-panel #joint-joystick,
#mini-gizmo-panel .rot-pad {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--sj-radius);
  box-shadow: none;
}
#mini-gizmo-panel .gizmo-options { margin-bottom: 10px; }
#mini-gizmo-panel .gizmo-options > summary { min-height: 36px; padding: 0 12px; }
#mini-gizmo-panel #joint-joystick { margin: 10px 0 0; padding: 12px; }
#mini-gizmo-panel .rot-pad { margin-top: 10px; padding: 12px; }

.gizmo-canvas {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  background: radial-gradient(circle at 50% 45%, var(--card) 0%, var(--bg) 100%);
  border: 1px solid var(--border);
  border-radius: var(--sj-radius, 12px);
}

/* One heading style for every card. */
#mini-gizmo-panel .gizmo-options > summary,
#mini-gizmo-panel .jj-title,
#mini-gizmo-panel .rot-pad-title,
#mini-gizmo-panel .rot-pad-snap-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-sec);
}

/* Step control: themed track instead of a fixed dark strip. */
#mini-gizmo-panel .rot-pad-snap {
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: 8px;
  padding: 2px;
}
#mini-gizmo-panel .rot-pad-snap .seg-opt { border-radius: 6px; padding: 4px 8px; }
#mini-gizmo-panel .rot-pad-snap .seg-opt:not(.is-active) { color: var(--text-sec); }

/* Value rows */
#mini-gizmo-panel .rot-pad-btn {
  background: var(--panel);
  border-color: var(--border-mid);
  color: var(--text);
  border-radius: 8px;
}
#mini-gizmo-panel .rot-pad-btn:hover { background: var(--card-hover); border-color: var(--teal); color: var(--text); }
#mini-gizmo-panel .rot-pad-input {
  background: var(--panel);
  border-color: var(--border-mid);
  color: var(--text);
  border-radius: 8px;
}

/* ═══════════════════════════════════════════════════════════════════════
   GALLERY — tab counts and empty states (js/gallery/*)
   ═══════════════════════════════════════════════════════════════════════ */
.gal-count {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 18px;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-sec);
  background: var(--card-hover);
  vertical-align: 1px;
}
.gal-count.is-zero { opacity: 0.6; }
.admin-tab.active .gal-count { color: var(--teal); background: var(--teal-dim, rgba(78, 205, 196, 0.12)); }

.gal-empty {
  grid-column: 1 / -1;
  justify-self: center;
  width: min(520px, 100%);
  margin: 32px auto;
  padding: 40px 32px 36px;
  text-align: center;
  background: var(--panel);
  border: 1px dashed var(--border-mid);
  border-radius: 18px;
}
.gal-empty-icon {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin: 0 auto 16px;
  border-radius: 20px;
  color: var(--teal);
  background: linear-gradient(135deg, var(--teal-dim, rgba(78, 205, 196, 0.12)), var(--purple-dim, rgba(139, 126, 200, 0.12)));
}
.gal-empty-icon svg { width: 28px; height: 28px; }
.gal-empty-title {
  margin: 0 0 6px;
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
}
.gal-empty-text {
  margin: 0 auto;
  max-width: 400px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-sec);
}
.gal-empty-text strong { color: var(--text); }
.gal-empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 20px;
}

/* ═══════════════════════════════════════════════════════════════════════
   DRAW INSIDE THE EDITOR (docked tiers only)
   The workspace rail stays: Create and Draw are two modes of one surface.
   FIGURE takes Create Pose's place beside the rail, STUDY takes the Joint
   Selector's place on the right, and Create in the rail is the way back —
   so the header's "← Editor" button is only needed where the rail is not
   on screen (phone and narrow tiers, or a rail the user folded away).
   ═══════════════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  body.draw-mode:not(.phone-shell):not(.rail-collapsed):not(.pf-focus) {
    --rail-w: 72px;
    --canvas-left: calc(var(--rail-w) + var(--draw-w-left) + var(--overlay-gap));
  }
  body.draw-mode:not(.phone-shell):not(.rail-collapsed):not(.pf-focus) #workspace-rail {
    display: flex !important;
  }
  body.draw-mode:not(.phone-shell):not(.rail-collapsed) #draw-panel-left {
    left: var(--rail-w);
  }
  body.draw-mode:not(.phone-shell):not(.rail-collapsed) #btn-exit-draw {
    display: none;
  }

  /* Rail state: Draw is the lit button, the workspace buttons are not. */
  body.draw-mode:not(.phone-shell) #workspace-rail .ws-btn.is-active {
    color: var(--text-sec);
    background: transparent;
    border-color: transparent;
  }
  body.draw-mode:not(.phone-shell) #workspace-rail .ws-btn.is-active:hover {
    color: var(--text);
    background: var(--card-hover);
  }
  body.draw-mode:not(.phone-shell) .ws-draw {
    color: var(--teal);
    background: var(--teal-dim, rgba(78, 205, 196, 0.10));
    border-color: var(--teal);
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   ADMIN POSE EDITOR — themed
   The header, sidebar, value fields, joint step buttons and recent strip
   were painted classic navy and only partly patched per theme. They now
   read the theme tokens directly, so every theme gets them right.
   ═══════════════════════════════════════════════════════════════════════ */
/* The right-hand panel rail is not part of this surface, and the header
   is 60px rather than the top bar's 52. */
body.admin-authoring {
  --prail-w: 0px;
  --topbar-h: 60px;
}
body.admin-authoring #right-panel { padding-top: 16px !important; }

#admin-editor-topnav {
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 6px 20px -16px rgba(0, 0, 0, 0.45);
}
#admin-editor-topnav .admin-panel-label--editor {
  align-self: center;
  line-height: 1.3;
  font-size: 16px;
  font-weight: 700;
}

#admin-editor-sidebar {
  top: calc(60px + 16px);
  max-height: calc(calc(100vh / var(--ui-scale, 1)) - 60px - 32px);
  width: 288px;
  padding: 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 18px 40px -24px rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
#admin-editor-sidebar .admin-side-section { padding: 14px 0; }
#admin-editor-sidebar .admin-side-title {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text-sec);
  margin-bottom: 12px;
}
#admin-editor-sidebar label {
  margin-bottom: 12px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-sec);
}
#admin-editor-sidebar input,
#admin-editor-sidebar select,
#admin-editor-sidebar textarea {
  min-height: 36px;
  margin-top: 6px;
  padding: 7px 10px;
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: 9px;
  color: var(--text);
  font-size: 12.5px;
  font-weight: 500;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
#admin-editor-sidebar input[type="range"] {
  min-height: 0;
  padding: 0;
  background: transparent;
  border: none;
}
#admin-editor-sidebar input:focus,
#admin-editor-sidebar select:focus,
#admin-editor-sidebar textarea:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-dim, rgba(78, 205, 196, 0.15));
}
#admin-editor-sidebar textarea::placeholder { color: var(--text-muted); }
#admin-editor-sidebar .admin-side-hint { color: var(--text-muted); }
#admin-editor-sidebar .admin-side-nav-btn {
  background: var(--card);
  border-color: var(--border-mid);
  color: var(--text-sec);
  border-radius: 8px;
}
#admin-editor-sidebar .admin-side-nav-btn:hover { color: var(--text); border-color: var(--teal); }

/* Joint step and action buttons (← Reset Deselect →). */
.joint-nav-btn,
.joint-action-btn {
  height: 34px;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-mid);
  border-radius: 9px;
}
.joint-nav-btn:hover:not(:disabled),
.joint-action-btn:hover:not(:disabled) { border-color: var(--teal); color: var(--text); background: var(--card-hover); }
.joint-action-btn:disabled { color: var(--text-muted); border-color: var(--border); }

/* Recent strip */
#recent-pose-strip {
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 12px 30px -18px rgba(0, 0, 0, 0.5);
}
#recent-pose-strip .rps-label { color: var(--text-sec); }
#recent-pose-strip .rps-name  { color: var(--text); }

/* Joint Selector card in narrow panels: the name may wrap, never clip. */
.bm-caption { white-space: normal; overflow-wrap: anywhere; }
.bm-region  { white-space: nowrap; }

/* Admin task pages (Delete Library Pose, Clear Cache). */
.admin-task-note {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--border-mid);
  border-left: 3px solid var(--danger);
  border-radius: 10px;
  background: var(--card);
  color: var(--text-sec);
  font-size: 13px;
  line-height: 1.5;
}
.admin-task-note strong { color: var(--text); }
.admin-task-text { max-width: 620px; margin: 6px 0 16px; color: var(--text-sec); font-size: 13px; line-height: 1.55; }
.admin-cache-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.admin-cache-note { color: var(--text-sec); font-size: 12px; }
.admin-cache-note.is-done { color: var(--success); }

/* Admin menu: four entries (Library Pose, Review Submitted Pose, Manage User
   Account, Manage System Setting). A group header is now a peer of the plain
   entries, so it looks like one, with a chevron; its tasks show only while
   it is the open group. Narrow screens keep the flat strip (headers hidden
   there), so nothing is collapsed below 900px. */
@media (min-width: 900px) {
  .admin-nav-link.is-group {
    color: var(--text-sec);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
    padding-top: 8px;
  }
  .admin-nav-link.is-group:hover { background: var(--card-hover); color: var(--text); }
  .admin-nav-group.is-open > .admin-nav-link.is-group { color: var(--text); font-weight: 600; }
  .admin-nav-link.is-group::after {
    content: "";
    margin-left: auto;
    width: 6px;
    height: 6px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform 0.15s ease;
    opacity: 0.7;
  }
  .admin-nav-group.is-open > .admin-nav-link.is-group::after { transform: rotate(45deg); }
  .admin-nav-group:not(.is-open) > .admin-nav-sub { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   ADMIN DASHBOARD (js/admin/dashboard.js)
   ═══════════════════════════════════════════════════════════════════════ */
.db { max-width: 1200px; }
.db-head { margin-bottom: 24px; }
.db-title { margin: 0; font-size: 24px; font-weight: 700; color: var(--text); }
.db-sub { margin: 4px 0 0; font-size: 13px; color: var(--text-sec); }

.db-section { margin-bottom: 24px; }
.db-section-title {
  margin: 0 0 12px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-sec);
}
.db-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
}
#db-personal-card { display: contents; }
.db-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 18px;
  text-align: left;
  font: inherit;
  color: inherit;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
button.db-card { cursor: pointer; }
button.db-card:hover {
  border-color: var(--teal);
  transform: translateY(-1px);
  box-shadow: 0 10px 24px -18px rgba(0, 0, 0, 0.6);
}
button.db-card:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.db-card-label { font-size: 12px; font-weight: 600; color: var(--text-sec); }
.db-card-value { font-size: 30px; font-weight: 700; line-height: 1.1; color: var(--text); font-variant-numeric: tabular-nums; }
.db-card-sub { font-size: 11.5px; color: var(--text-muted); }
.db-card--warn { border-left: 3px solid var(--warning); }
.db-card--warn .db-card-value { color: var(--warning); }
.db-card--good { border-left: 3px solid var(--teal); }
.db-card--good .db-card-value { color: var(--teal); }

.db-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 12px;
}
.db-panel {
  padding: 16px 18px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
}
.db-panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.db-panel-head .db-section-title { margin: 0; }
.db-link {
  padding: 4px 8px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--teal);
  font: 600 12px/1 inherit;
  cursor: pointer;
}
.db-link:hover { background: var(--teal-dim, rgba(78, 205, 196, 0.12)); }
.db-list { list-style: none; margin: 0; padding: 0; }
.db-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}
.db-row:last-child { border-bottom: none; }
.db-row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.db-row-main small { font-size: 11.5px; font-weight: 400; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; }
.db-row-side { font-size: 12px; color: var(--text-sec); white-space: nowrap; }
.db-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 12px;
  font-weight: 700;
  color: #0d0c18;
  background: var(--purple);
}
.db-avatar[data-role="admin"] { background: var(--teal); }
.db-chip {
  flex: none;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-sec);
  background: var(--card-hover);
}
.db-note { font-size: 13px; color: var(--text-muted); padding: 8px 0; margin: 0; }
.db-cards > .db-note { grid-column: 1 / -1; }

/* ═══════════════════════════════════════════════════════════════════════
   MANAGE USER ACCOUNT — summary, toolbar, table (js/admin/users.js)
   ═══════════════════════════════════════════════════════════════════════ */
.usr-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  margin: 16px 0 18px;
}
.usr-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.usr-stat-value { font-size: 22px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.usr-stat-label { font-size: 11.5px; font-weight: 600; color: var(--text-sec); }

.usr-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.usr-search {
  flex: 1 1 240px;
  max-width: 360px;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 12px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: 10px;
  color: var(--text-muted);
}
.usr-search:focus-within { border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-dim, rgba(78, 205, 196, 0.15)); }
.usr-search svg { width: 15px; height: 15px; flex: none; }
.usr-search input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 13px;
}
.usr-filters {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: 10px;
}
.usr-filter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--text-sec);
  font: 600 12.5px/1 inherit;
  cursor: pointer;
}
.usr-filter span {
  min-width: 18px;
  padding: 2px 5px;
  border-radius: 999px;
  background: var(--card-hover);
  font-size: 10.5px;
  text-align: center;
}
.usr-filter:hover { color: var(--text); }
.usr-filter.is-active { background: var(--teal-dim, rgba(78, 205, 196, 0.14)); color: var(--teal); }
.usr-filter.is-active span { background: transparent; }
.usr-sort { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; font-size: 12px; color: var(--text-sec); }

.usr-table-wrap {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow-x: auto;
}
.usr-table th {
  position: sticky;
  top: 0;
  padding: 12px 14px;
  background: var(--card);
  border-bottom: 1px solid var(--border);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-sec);
}
.usr-table td { padding: 12px 14px; vertical-align: middle; }
.usr-table tbody tr { transition: background 0.12s ease; }
.usr-table tbody tr:hover { background: var(--card); }
.usr-table tbody tr.is-self { background: var(--teal-dim, rgba(78, 205, 196, 0.06)); }

.usr-account { display: flex; align-items: center; gap: 12px; min-width: 0; }
.usr-account-text { min-width: 0; }
.usr-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-size: 13px;
  font-weight: 700;
  color: #0d0c18;
  background: var(--purple);
}
.usr-avatar[data-role="admin"] { background: var(--teal); }
.usr-name { font-size: 13.5px; font-weight: 600; }
.usr-email { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 12px; }

.usr-role-select {
  height: 30px;
  padding: 0 28px 0 12px;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  background-color: var(--card);
  color: var(--text);
  font: 600 12px/1 inherit;
  cursor: pointer;
}
.usr-role-select[data-role="admin"] { color: var(--teal); border-color: rgba(78, 205, 196, 0.45); }
.usr-role-select:disabled { opacity: 0.6; cursor: not-allowed; }
.usr-role-select:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

.usr-count { font-weight: 600; color: var(--text); }
.usr-count.is-zero { font-weight: 400; color: var(--text-muted); }
.usr-muted { color: var(--text-muted); }
.usr-empty { padding: 28px 14px !important; text-align: center; color: var(--text-muted); }

.usr-actions { gap: 6px; }
.usr-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 10px;
  border-radius: 8px;
}
.usr-btn svg { width: 14px; height: 14px; }
.usr-btn:disabled { opacity: 0.35; }
/* Delete stays neutral until hover — see the note on .usr-btn.is-danger above. */
.usr-actions-cell { text-align: right; }
.usr-table th.usr-actions-col,
.usr-table th.usr-num { text-align: right; }

/* ═══════════════════════════════════════════════════════════════════════
   JOINT SELECTOR — 2D / 3D toggle and the 3D view (js/ui/bodyMap3d.js)
   ═══════════════════════════════════════════════════════════════════════ */
.bm-mode {
  display: flex;
  width: fit-content;
  margin: 0 auto 10px;
  padding: 3px;
  gap: 2px;
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: 9px;
}
.bm-mode-opt {
  min-width: 44px;
  height: 26px;
  padding: 0 12px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-sec);
  font: 700 11.5px/1 inherit;
  letter-spacing: 0.04em;
  cursor: pointer;
}
.bm-mode-opt:hover { color: var(--text); }
.bm-mode-opt.is-active { background: var(--teal-dim, rgba(78, 205, 196, 0.14)); color: var(--teal); }
.bm-mode-opt:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

.bm-3d {
  position: relative;
  width: 100%;
  height: 250px;
  border-radius: 12px;
  background: radial-gradient(circle at 50% 40%, var(--card) 0%, transparent 75%);
  overflow: hidden;
}
.bm-3d[hidden] { display: none; }
.bm3d-canvas { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; }
.bm3d-canvas:active { cursor: grabbing; }
.bm-3d-wait {
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
  font-size: 12px;
  color: var(--text-muted);
  pointer-events: none;
}
.bm-3d.is-waiting .bm-3d-wait { display: grid; }
body.phone-shell .bm-3d { height: calc(40vh / var(--ui-scale, 1)); }

/* ═══════════════════════════════════════════════════════════════════════
   CREATE POSE — import box (same words and buttons, refreshed look)
   Scoped to the ids so it wins over the per-theme button overrides; every
   colour is a theme token, so each theme still reads right.
   ═══════════════════════════════════════════════════════════════════════ */
#editor-drop-zone.editor-upload-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 22px 16px 18px;
  border: 1.5px dashed var(--border-mid);
  border-radius: 16px;
  background:
    radial-gradient(circle at 50% 0%, var(--teal-dim, rgba(78, 205, 196, 0.08)) 0%, transparent 70%),
    var(--card);
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
}
#editor-drop-zone.editor-upload-zone:hover {
  border-color: var(--teal);
}
#editor-drop-zone.editor-upload-zone.drag-over {
  border-style: solid;
  border-color: var(--teal);
  background-color: var(--teal-dim, rgba(78, 205, 196, 0.12));
  box-shadow: 0 0 0 4px var(--teal-dim, rgba(78, 205, 196, 0.12));
}

#editor-drop-zone .editor-upload-icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  margin: 0 0 12px;
  border-radius: 50%;
  color: var(--teal);
  background: var(--teal-dim, rgba(78, 205, 196, 0.12));
  transition: transform 0.2s ease;
}
#editor-drop-zone .editor-upload-icon svg { width: 26px; height: 26px; }
#editor-drop-zone:hover .editor-upload-icon,
#editor-drop-zone.drag-over .editor-upload-icon { transform: translateY(-2px); }

#editor-drop-zone .editor-upload-title {
  margin: 0 0 4px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}
#editor-drop-zone .editor-upload-sub {
  margin: 0 0 16px;
  max-width: 200px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-sec);
}

#editor-drop-zone .editor-upload-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  width: 100%;
  margin: 0 0 14px;
}
#editor-drop-zone .btn-browse-sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 36px;
  padding: 0 14px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: 10px;
  color: var(--text);
  font: 600 12.5px/1 inherit;
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease, color 0.12s ease;
}
#editor-drop-zone .btn-browse-sm[hidden] { display: none; }
#editor-drop-zone .btn-browse-sm svg { width: 15px; height: 15px; flex: none; color: var(--teal); }
#editor-drop-zone .btn-browse-sm:hover { border-color: var(--teal); background: var(--card-hover); }
#editor-drop-zone .btn-browse-sm:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

#editor-drop-zone .format-tags-sm { display: flex; justify-content: center; gap: 6px; margin: 0; }
#editor-drop-zone .fmt-tag-sm {
  padding: 3px 8px;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
}

/* Reset to auto-detected — a quiet secondary action under the box. */
#btn-reset-pose-controls.btn-reset-auto {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 36px;
  margin-top: 12px;
  background: transparent;
  border: 1px solid var(--border-mid);
  border-radius: 10px;
  color: var(--text-sec);
  font: 600 12.5px/1 inherit;
  cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}
#btn-reset-pose-controls.btn-reset-auto svg { width: 14px; height: 14px; }
#btn-reset-pose-controls.btn-reset-auto:hover { color: var(--text); border-color: var(--teal); background: var(--card); }
#btn-reset-pose-controls.btn-reset-auto:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════════════
   ADMIN PANEL — narrow screens (< 900px)
   The sidebar becomes a bottom tab bar of five short labels, the open
   group's pages sit in a chip row at the top, and the header and cards
   tighten. Desktop keeps the sidebar and never shows the chip row.
   ═══════════════════════════════════════════════════════════════════════ */
.admin-nav-short, .admin-subnav { display: none; }

@media (max-width: 899px) {
  .admin-shell { grid-template-rows: minmax(0, 1fr); }
  .admin-shell .admin-subnav { flex: none; }

  /* Bottom tab bar */
  .admin-shell .admin-sidebar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 30;
    display: block;
    padding: 6px 6px calc(6px + env(safe-area-inset-bottom, 0px));
    overflow: visible;
    background: var(--panel);
    border-top: 1px solid var(--border);
    border-bottom: none;
    box-shadow: 0 -8px 24px -18px rgba(0, 0, 0, 0.5);
  }
  .admin-shell .admin-nav {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 2px;
  }
  .admin-shell .admin-nav > li { min-width: 0; }
  .admin-shell .admin-nav-sub { display: none !important; }
  .admin-shell .admin-nav-link,
  .admin-shell .admin-nav-link.is-group {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    width: 100%;
    min-height: 52px;
    padding: 6px 2px;
    border-radius: 10px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: normal;
    text-transform: none;
    color: var(--text-sec);
    white-space: nowrap;
  }
  .admin-shell .admin-nav-link.is-group::after { display: none; }
  .admin-shell .admin-nav-icon { width: auto; font-size: 17px; line-height: 1; }
  .admin-shell .admin-nav-label { display: none; }
  .admin-shell .admin-nav-short { display: block; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  .admin-shell .admin-nav-link.is-active,
  .admin-shell .admin-nav-group.is-open > .admin-nav-link.is-group {
    color: var(--teal);
    background: var(--teal-dim, rgba(78, 205, 196, 0.12));
  }
  .admin-shell .admin-nav-badge { position: absolute; transform: translate(14px, -14px); }
  .admin-shell .admin-nav-link { position: relative; }

  /* Pages of the open group */
  .admin-shell .admin-subnav {
    display: flex;
    gap: 6px;
    padding: 10px 16px;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    border-bottom: 1px solid var(--border);
    background: var(--panel);
  }
  .admin-shell .admin-subnav::-webkit-scrollbar { display: none; }
  .admin-subnav-chip {
    flex: none;
    height: 32px;
    padding: 0 14px;
    scroll-snap-align: start;
    border: 1px solid var(--border-mid);
    border-radius: 999px;
    background: transparent;
    color: var(--text-sec);
    font: 600 12.5px/1 inherit;
    white-space: nowrap;
    cursor: pointer;
  }
  .admin-subnav-chip.is-active { border-color: var(--teal); background: var(--teal-dim, rgba(78, 205, 196, 0.12)); color: var(--teal); }

  /* Compact header */
  .admin-shell .admin-topbar { padding: 10px 16px; gap: 8px; }
  .admin-shell .admin-crumb { font-size: 15px; font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .admin-shell .admin-crumb .admin-crumb-sep,
  .admin-shell .admin-subnav + .admin-topbar .admin-crumb { font-size: 14px; }
  .admin-shell .admin-actions .btn-sm { height: 32px; padding: 0 10px; font-size: 12px; border-radius: 9px; white-space: nowrap; }
  .admin-shell .admin-actions { flex: none; gap: 6px; }
  /* The chip row above already names the group. */
  .admin-shell .admin-crumb-group { display: none; }

  /* Room for the tab bar */
  .admin-shell .admin-body { padding: 16px 14px calc(96px + env(safe-area-inset-bottom, 0px)); }

  /* Dashboard */
  .db-title { font-size: 20px; }
  .db-head { margin-bottom: 16px; }
  .db-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .db-card { padding: 12px 14px; border-radius: 12px; }
  .db-card-value { font-size: 24px; }
  .db-card-sub { font-size: 11px; }
  .db-panels { grid-template-columns: 1fr; }

  /* Users */
  .usr-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .usr-sort { margin-left: 0; }
}

@media (max-width: 380px) {
  .admin-shell .admin-actions .btn-sm { padding: 0 8px; font-size: 11.5px; }
  .db-card-value { font-size: 21px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   PHONE — editor actions and the Gallery
   ═══════════════════════════════════════════════════════════════════════ */
/* The ribbon above the tab bar is the editor's only home for Save, Reset
   pose and Reset view on a phone. As bare icons (two of them the same
   arrow) they read as decoration; here they get their words back and share
   the ribbon evenly. The detection readout makes way for them. */
@media (max-width: 699px) {
  body.phone-shell #bottom-bar .sb-groups { display: none; }
  body.phone-shell #bottom-bar .bottom-actions {
    flex: 1;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
  }
  body.phone-shell #bottom-bar .sb-btn {
    width: auto;
    min-height: 36px;
    gap: 6px;
    padding: 0 8px;
    border-radius: 10px;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
  }
  body.phone-shell #bottom-bar .sb-btn span { display: inline; overflow: hidden; text-overflow: ellipsis; }
  body.phone-shell #bottom-bar .sb-btn svg { flex: none; }
}

/* Gallery on a phone: one scrolling row of tabs, tighter page, two cards
   across. */
@media (max-width: 699px) {
  #admin-panel:not(.has-shell) { padding: 12px 14px 32px; }
  .gal-header { margin-bottom: 14px; padding-bottom: 0; }
  .gal-header .gal-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    margin: 0 -14px;
    padding: 0 14px;
    scroll-snap-type: x proximity;
  }
  .gal-header .gal-tabs::-webkit-scrollbar { display: none; }
  .gal-header .admin-tab {
    flex: none;
    padding: 12px 12px;
    white-space: nowrap;
    scroll-snap-align: start;
  }
  #admin-panel:not(.has-shell) .pose-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
  }
  .gal-empty { margin: 12px auto; padding: 28px 18px 24px; border-radius: 16px; }
  .gal-empty-icon { width: 56px; height: 56px; border-radius: 16px; }
  .gal-empty-title { font-size: 16px; }
  .gal-empty-actions { flex-direction: column; align-items: stretch; }
  .gal-empty-actions .btn-sm { height: 40px; }
  #admin-panel:not(.has-shell) .admin-toolbar { flex-wrap: wrap; gap: 8px; }
  #admin-panel:not(.has-shell) .admin-toolbar input { flex: 1 1 100%; }
  /* The phone grid is a fixed two columns (above), so a size control here
     would move nothing. */
  #admin-panel:not(.has-shell) .gal-size { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   ADMIN POSE EDITOR SIDEBAR — sections, pinned Save
   ═══════════════════════════════════════════════════════════════════════ */
#admin-editor-sidebar { padding: 12px 12px 0; }
#admin-editor-sidebar .admin-side-section {
  margin-bottom: 10px;
  padding: 12px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
}
#admin-editor-sidebar .admin-side-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text);
}
#admin-editor-sidebar .admin-side-title.is-toggle { cursor: pointer; user-select: none; flex-wrap: wrap; }
#admin-editor-sidebar .admin-side-title.is-toggle::after { order: 2; }
#admin-editor-sidebar .admin-side-title .adm-muted { order: 3; flex-basis: 100%; margin-top: -4px; }
#admin-editor-sidebar .admin-side-title.is-toggle::after {
  content: '';
  margin-left: auto;
  flex: none;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid var(--text-sec);
  border-bottom: 1.5px solid var(--text-sec);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.15s ease;
}
#admin-editor-sidebar .admin-side-title.is-toggle:focus-visible { outline: 2px solid var(--teal); outline-offset: 4px; border-radius: 4px; }
#admin-editor-sidebar .admin-side-section.is-collapsed { padding-bottom: 12px; }
#admin-editor-sidebar .admin-side-section.is-collapsed > .admin-side-title { margin-bottom: 0; }
#admin-editor-sidebar .admin-side-section.is-collapsed > .admin-side-title::after { transform: rotate(-45deg); }
#admin-editor-sidebar .admin-side-section.is-collapsed > :not(.admin-side-title) { display: none !important; }
#admin-editor-sidebar .adm-muted {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-muted);
}
#admin-editor-sidebar .adm-wide { width: 100%; height: 34px; margin-top: 8px; border-radius: 9px; }

/* Status shows its state as a colour on the field's edge. */
#admin-meta-stat { border-left-width: 4px; }
#admin-meta-stat[data-status="Draft"]     { border-left-color: var(--text-muted); }
#admin-meta-stat[data-status="Testing"]   { border-left-color: var(--warning); }
#admin-meta-stat[data-status="Approved"]  { border-left-color: var(--purple); }
#admin-meta-stat[data-status="Published"] { border-left-color: var(--teal); }

/* Posture presets: four equal tiles. */
#admin-editor-sidebar .adm-presets {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  margin-top: 10px;
}
#admin-editor-sidebar .adm-presets .btn-sm {
  height: 44px;
  padding: 4px 2px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: 11px;
  border-radius: 9px;
}

/* Reference photos */
#admin-editor-sidebar .adm-ref-status {
  margin: 8px 0;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-sec);
}
#admin-editor-sidebar .adm-ref-drop {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 12px;
  border: 1.5px dashed var(--border-mid);
  border-radius: 10px;
  background: var(--panel);
  color: var(--text-sec);
  font-size: 12px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
#admin-editor-sidebar .adm-ref-drop svg { width: 18px; height: 18px; color: var(--teal); flex: none; }
#admin-editor-sidebar .adm-ref-drop:hover { border-color: var(--teal); color: var(--text); }
#admin-editor-sidebar .adm-ref-list { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }

/* Save: pinned to the bottom of the scrolling sidebar. */
#admin-editor-sidebar .admin-side-save {
  position: sticky;
  bottom: 0;
  z-index: 2;
  margin: 0 -12px;
  padding: 12px 12px 14px;
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: var(--panel);
  box-shadow: 0 -10px 20px -14px rgba(0, 0, 0, 0.5);
}
#admin-editor-sidebar .admin-side-save #admin-save-btn { width: 100%; height: 40px; font-size: 13.5px; border-radius: 10px; }
#admin-editor-sidebar .admin-side-save .admin-side-hint { margin: 8px 0 0; font-size: 11px; }

/* ═══════════════════════════════════════════════════════════════════════
   ADMIN POSE EDITOR — laid out like the user editor
   The sidebar docks to the left edge the way Create Pose does: full height,
   one divider, flat sections with the same headings. The status bar is not
   shown here, so its height is handed back to the side panels, and the
   canvas overlays centre on the space between the two panels.
   ═══════════════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  body.admin-authoring {
    --rail-w: 0px;
    --left-w: 288px;
    --bottom-h: 0px;
  }

  body.admin-authoring #admin-editor-sidebar {
    top: 60px;
    left: 0;
    bottom: 0;
    width: var(--left-w);
    max-height: none;
    padding: 0;
    background: var(--panel);
    border: none;
    border-right: 1px solid var(--border);
    border-radius: 0;
    box-shadow: none;
  }

  /* Flat sections with dividers, like the Create Pose panel. */
  body.admin-authoring #admin-editor-sidebar .admin-side-section {
    margin: 0;
    padding: 18px 20px;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--border);
    border-radius: 0;
  }
  body.admin-authoring #admin-editor-sidebar .admin-side-section.is-collapsed { padding: 16px 20px; }
  body.admin-authoring #admin-editor-sidebar .admin-side-title {
    font-size: 11.5px;
    letter-spacing: 0.08em;
    color: var(--text-sec);
  }
  body.admin-authoring #admin-editor-sidebar .admin-side-section:not(.is-collapsed) > .admin-side-title { color: var(--text); }

  body.admin-authoring #admin-editor-sidebar .admin-side-save {
    margin: 0;
    padding: 14px 20px 16px;
    border-bottom: none;
    border-top: 1px solid var(--border);
    /* Pinned over the scrolling sections, so it needs its own ground. */
    background: var(--panel);
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   ADMIN POSE EDITOR — phone (< 700px)
   Compact header; the sidebar, Joint Selector and Selected Joint become
   bottom sheets behind a three-tab bar, so the model stays visible above.
   ═══════════════════════════════════════════════════════════════════════ */
#admin-sheet-tabs { display: none; }
.tn-ic { display: none; }

@media (max-width: 699px) {
  body.admin-authoring {
    --topbar-h: 52px;
    --ap-tabs-h: calc(60px + env(safe-area-inset-bottom, 0px));
    --ap-sheet-h: min(48vh, 460px);
  }

  /* Header: one row, icons where words do not fit. */
  body.admin-authoring #admin-editor-topnav {
    height: 52px;
    min-height: 52px;
    padding: 0 8px;
  }
  body.admin-authoring #admin-editor-topnav .admin-topnav-row { gap: 8px; }
  body.admin-authoring #admin-editor-topnav .btn-back {
    flex: none;
    width: 38px;
    height: 38px;
    padding: 0;
    justify-content: center;
    border-radius: 10px;
  }
  body.admin-authoring #admin-editor-topnav .btn-back .tn-tx { display: none; }
  body.admin-authoring #admin-editor-topnav .admin-panel-label--editor {
    font-size: 15px;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }
  body.admin-authoring #admin-editor-topnav .admin-actions { flex: none; gap: 6px; }
  body.admin-authoring #admin-editor-topnav .admin-actions .btn-sm {
    height: 36px;
    min-width: 38px;
    padding: 0 10px;
    border-radius: 10px;
    white-space: nowrap;
  }
  body.admin-authoring #admin-editor-topnav .tn-ic { display: inline; }
  body.admin-authoring #admin-editor-topnav #admin-topnav-limits .tn-tx,
  body.admin-authoring #admin-editor-topnav #admin-topnav-reset .tn-tx,
  body.admin-authoring #admin-editor-topnav #admin-topnav-save .tn-tx { display: none; }

  /* Tab bar */
  body.admin-authoring #admin-sheet-tabs {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 240;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    height: var(--ap-tabs-h);
    padding: 0 6px env(safe-area-inset-bottom, 0px);
    background: var(--panel);
    border-top: 1px solid var(--border);
  }
  #admin-sheet-tabs button {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    margin: 6px 3px;
    border: none;
    border-radius: 12px;
    background: transparent;
    color: var(--text-sec);
    font: 600 11px/1 inherit;
    cursor: pointer;
  }
  #admin-sheet-tabs button svg { width: 20px; height: 20px; }
  #admin-sheet-tabs button.is-active { color: var(--teal); background: var(--teal-dim, rgba(78, 205, 196, 0.12)); }

  /* The three sheets share one frame above the tab bar. */
  body.admin-authoring #admin-editor-sidebar,
  body.admin-authoring #right-panel,
  body.admin-authoring #mini-gizmo-panel {
    position: fixed !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    bottom: var(--ap-tabs-h) !important;
    width: auto !important;
    height: var(--ap-sheet-h) !important;
    max-height: var(--ap-sheet-h) !important;
    margin: 0 !important;
    padding-top: 12px !important;
    overflow-y: auto !important;
    border: none !important;
    border-top: 1px solid var(--border-mid) !important;
    border-radius: 18px 18px 0 0 !important;
    background: var(--panel) !important;
    box-shadow: 0 -16px 40px -20px rgba(0, 0, 0, 0.6) !important;
    z-index: 230 !important;
  }
  body.admin-authoring:not(.admin-sheet-details) #admin-editor-sidebar,
  body.admin-authoring:not(.admin-sheet-joints)  #right-panel,
  body.admin-authoring:not(.admin-sheet-rotate)  #mini-gizmo-panel { display: none !important; }
  body.admin-authoring.admin-sheet-details #admin-editor-sidebar { display: block !important; }
  body.admin-authoring.admin-sheet-joints  #right-panel          { display: flex !important; }
  body.admin-authoring.admin-sheet-rotate  #mini-gizmo-panel     { display: flex !important; }

  /* Grab-handle look at the top of each sheet. */
  body.admin-authoring #admin-editor-sidebar::before,
  body.admin-authoring #right-panel::before,
  body.admin-authoring #mini-gizmo-panel::before {
    content: '';
    display: block;
    flex: none;
    width: 40px;
    height: 4px;
    margin: 0 auto 10px;
    border-radius: 999px;
    background: var(--border-mid);
  }
  body.admin-authoring #admin-editor-sidebar .admin-side-save { margin: 0 !important; }

  /* The user editor's phone shell parks the Joint Selector and Selected
     Joint inside #phone-sheet, which the admin view hides. Dissolve that
     box here so the two panels lay out as sheets of their own. */
  body.admin-authoring #phone-sheet { display: contents !important; }
  body.admin-authoring #phone-sheet > :not(#right-panel):not(#mini-gizmo-panel) { display: none !important; }
  /* The closed sheet is also visibility:hidden, which its children inherit. */
  body.admin-authoring #right-panel,
  body.admin-authoring #mini-gizmo-panel { visibility: visible !important; opacity: 1 !important; transform: none !important; }

  /* Canvas overlays clear the header; the hint is not needed here. */
  body.admin-authoring #cam-preset-bar { top: calc(52px + 8px) !important; }
  body.admin-authoring #canvas-hint { display: none !important; }
}

/* "User view" pill in the top bar (auth.js renderViewAsBanner). */
.view-as-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
  height: 32px;
  margin-right: 8px;
  padding: 0 4px 0 12px;
  border: 1px solid var(--warning);
  border-radius: 999px;
  background: var(--warning-dim);
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.view-as-pill .vab-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--warning);
  box-shadow: 0 0 0 3px rgba(245, 166, 35, 0.18);
}
.view-as-pill button {
  height: 24px;
  padding: 0 10px;
  border: none;
  border-radius: 999px;
  background: var(--warning);
  color: #0d0c18;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}
.view-as-pill button:hover { filter: brightness(1.08); }
.view-as-pill button:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
@media (max-width: 699px) {
  .view-as-pill { margin-right: 4px; padding-left: 10px; gap: 6px; }
  .view-as-pill .vab-text { display: none; }
}

/* Boot cover: "slow connection" Reload button (index.html BootCover.watchdog). */
.pf-boot-slow {
  margin-top: 16px;
  height: 34px;
  padding: 0 18px;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  background: var(--card);
  color: var(--text);
  font: 600 12.5px/1 inherit;
  cursor: pointer;
}
.pf-boot-slow:hover { border-color: var(--teal); }

.set-boot-alpha { display: flex; align-items: center; gap: 10px; min-width: 180px; }
.set-boot-alpha input[type="range"] { flex: 1; accent-color: var(--teal); }
.set-boot-alpha .set-note { min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; }

/* Save pose / Reset pose / Reset view belong to posing (Create). Draw
   freezes the pose, so the three buttons stand down while it is open. */
body.draw-mode #bottom-bar .bottom-actions { display: none !important; }

/* Draw ▸ View style ▸ Grid (js/draw/drawGrid.js). Centred on the view so
   the middle lines cross at its centre; only shown inside Draw. */
#draw-grid-overlay {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: none;
  pointer-events: none;
  --grid-line: rgba(128, 132, 158, 0.35);
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: var(--grid-cell, 80px) var(--grid-cell, 80px);
  background-position: calc(50% - 0.5px) calc(50% - 0.5px);
}
#draw-grid-overlay::before,
#draw-grid-overlay::after {
  content: "";
  position: absolute;
  background: rgba(128, 132, 158, 0.7);
}
#draw-grid-overlay::before { top: 0; bottom: 0; left: calc(50% - 0.75px); width: 1.5px; }
#draw-grid-overlay::after  { left: 0; right: 0; top: calc(50% - 0.75px); height: 1.5px; }
body.draw-mode.draw-grid-on #draw-grid-overlay { display: block; }

/* ═══════════════════════════════════════════════════════════════════════
   NEON THEME — html[data-theme="neon"] (js/core/theme.js)

   Same layout, different surface: deeper base, translucent glass chrome
   over the lit 3D studio (main.js), a teal→violet accent gradient on the
   primary actions, and the Manrope typeface. Settings ▸ Appearance ▸
   Classic, or ?theme=classic, turns all of it off.
   ═══════════════════════════════════════════════════════════════════════ */
html[data-theme="neon"] {
  --bg:           #07060f;
  --panel:        rgba(15, 12, 34, 0.74);
  --card:         rgba(32, 27, 66, 0.66);
  --card-hover:   rgba(46, 39, 92, 0.78);
  --border:       rgba(150, 135, 235, 0.13);
  --border-mid:   rgba(150, 135, 235, 0.22);
  --border-focus: rgba(150, 135, 235, 0.45);
  --purple:       #a48cff;
  --purple-dim:   rgba(164, 140, 255, 0.16);
  --text:         #eeecff;
  --text-sec:     #a9a4d4;
  --text-muted:   #8b86ba;
  --grad:         linear-gradient(135deg, #4ecdc4 0%, #7c8cff 55%, #a78bfa 100%);
  --grad-soft:    linear-gradient(135deg, rgba(78, 205, 196, 0.18), rgba(167, 139, 250, 0.18));
  --glow-teal:    0 0 22px -6px rgba(78, 205, 196, 0.6);
  --glass:        blur(20px) saturate(150%);
  font-family: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
}
html[data-theme="neon"] body { background: var(--bg); }

/* ── Glass chrome ─────────────────────────────────────────────────────── */
html[data-theme="neon"] #topbar,
html[data-theme="neon"] #workspace-rail,
html[data-theme="neon"] #panel-rail,
html[data-theme="neon"] #bottom-bar,
html[data-theme="neon"] body:not(.left-collapsed) #left-panel,
html[data-theme="neon"] body:not(.right-collapsed) #right-panel,
html[data-theme="neon"] #mini-gizmo-panel,
html[data-theme="neon"] #cam-preset-bar,
html[data-theme="neon"] #phone-sheet,
html[data-theme="neon"] #phone-tabbar,
html[data-theme="neon"] .account-menu,
html[data-theme="neon"] #pf-tip {
  -webkit-backdrop-filter: var(--glass);
  backdrop-filter: var(--glass);
}
/* A faint top-down sheen so panels read as lit objects, not flat fills. */
html[data-theme="neon"] body:not(.left-collapsed) #left-panel,
html[data-theme="neon"] body:not(.right-collapsed) #right-panel,
html[data-theme="neon"] #workspace-rail,
html[data-theme="neon"] #panel-rail {
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0) 30%);
}
html[data-theme="neon"] #topbar {
  background-color: rgba(10, 8, 24, 0.7);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03), 0 10px 30px -18px rgba(0, 0, 0, 0.9);
}
/* A thin accent line along the top edge of the top bar. */
html[data-theme="neon"] #topbar::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--grad);
  opacity: 0.85;
  pointer-events: none;
}
html[data-theme="neon"] #bottom-bar {
  background-color: rgba(10, 8, 24, 0.72);
}
html[data-theme="neon"] #cam-preset-bar {
  background: rgba(15, 12, 34, 0.6);
  border-color: var(--border-mid);
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.7);
}

/* ── Brand ────────────────────────────────────────────────────────────── */
html[data-theme="neon"] .brand-word,
html[data-theme="neon"] .auth-brand,
html[data-theme="neon"] .pf-boot-brand {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* ── Section titles: tracked eyebrow labels in the accent gradient ───── */
html[data-theme="neon"] .panel-title,
html[data-theme="neon"] .morph-section-title {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.14em;
  background: linear-gradient(90deg, #7fe3dc, #b3a2ff 60%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
html[data-theme="neon"] .sec-toggle { -webkit-text-fill-color: initial; }

/* ── Navigation ───────────────────────────────────────────────────────── */
html[data-theme="neon"] .nav-btn.active {
  background: var(--grad-soft);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border-mid);
}
html[data-theme="neon"] .ws-btn:hover { background: var(--card); color: var(--text); }
html[data-theme="neon"] :is(.ws-btn, .psr-btn).is-active {
  color: #7fe3dc;
  background: var(--grad-soft);
  border-color: rgba(78, 205, 196, 0.45);
  box-shadow: var(--glow-teal);
}
html[data-theme="neon"] .cam-preset-btn.is-active {
  background: var(--grad-soft);
  box-shadow: var(--glow-teal);
}

/* ── Primary actions: the accent gradient ─────────────────────────────── */
html[data-theme="neon"] .btn-primary,
html[data-theme="neon"] .sb-btn--save,
html[data-theme="neon"] .start-lib-go:not(:disabled),
html[data-theme="neon"] .auth-card button[type="submit"],
html[data-theme="neon"] .seg-opt.is-active,
html[data-theme="neon"] .btn-browse-sm.btn-browse-primary {
  background: var(--grad);
  border-color: transparent;
  color: #0a0918;
  box-shadow: 0 8px 22px -10px rgba(124, 140, 255, 0.85), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
html[data-theme="neon"] .btn-primary:hover,
html[data-theme="neon"] .sb-btn--save:hover,
html[data-theme="neon"] .start-lib-go:not(:disabled):hover,
html[data-theme="neon"] .auth-card button[type="submit"]:hover,
html[data-theme="neon"] .btn-browse-sm.btn-browse-primary:hover {
  background: var(--grad);
  border-color: transparent;
  filter: brightness(1.1);
  transform: translateY(-1px);
}

/* ── Secondary controls lift on hover ─────────────────────────────────── */
html[data-theme="neon"] .btn-secondary,
html[data-theme="neon"] .sb-btn,
html[data-theme="neon"] .topbar-btn {
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.15s, box-shadow 0.15s;
}
html[data-theme="neon"] .btn-secondary:hover:not(:disabled),
html[data-theme="neon"] .sb-btn:not(.sb-btn--save):hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 18px -10px rgba(0, 0, 0, 0.9);
  border-color: var(--border-focus);
}

/* ── Cards and tiles ──────────────────────────────────────────────────── */
html[data-theme="neon"] .cp-tile-thumb {
  background: radial-gradient(circle at 50% 35%, #2a2360, #0e0c22 75%);
  border-color: var(--border-mid);
}
html[data-theme="neon"] .cp-tile:hover .cp-tile-thumb {
  border-color: rgba(78, 205, 196, 0.7);
  box-shadow: 0 10px 24px -12px rgba(78, 205, 196, 0.6);
  transform: translateY(-2px);
}
html[data-theme="neon"] .start-choice-card,
html[data-theme="neon"] .auth-card {
  background: linear-gradient(160deg, rgba(40, 33, 86, 0.9), rgba(14, 11, 32, 0.94));
  border-color: var(--border-mid);
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.85), 0 0 0 1px rgba(255, 255, 255, 0.03) inset;
}
html[data-theme="neon"] .start-choice-opt {
  background: rgba(10, 8, 26, 0.6);
  transition: background 0.15s, border-color 0.15s, transform 0.15s, box-shadow 0.15s;
}
html[data-theme="neon"] .start-choice-opt:hover,
html[data-theme="neon"] .start-choice-opt:focus-visible {
  background: var(--grad-soft);
  box-shadow: var(--glow-teal);
  transform: translateY(-2px);
}

/* Sign-in: a lit backdrop instead of a blank field. */
html[data-theme="neon"] .auth-modal {
  background:
    radial-gradient(ellipse 60% 50% at 50% 38%, rgba(78, 205, 196, 0.16), transparent 70%),
    radial-gradient(ellipse 70% 60% at 75% 80%, rgba(167, 139, 250, 0.18), transparent 70%),
    #07060f;
}

/* ── Body map: glowing joints ─────────────────────────────────────────── */
html[data-theme="neon"] .bm-skull,
html[data-theme="neon"] .bm-torso { fill: rgba(150, 135, 235, 0.16); }
html[data-theme="neon"] .bm-dot { filter: drop-shadow(0 0 3px rgba(78, 205, 196, 0.5)); }
html[data-theme="neon"] .bm-node:hover .bm-dot,
html[data-theme="neon"] .bm-node.is-active .bm-dot {
  filter: drop-shadow(0 0 6px rgba(78, 205, 196, 0.9));
}

/* ── Sliders, inputs, scrollbars ──────────────────────────────────────── */
html[data-theme="neon"] .morph-slider::-webkit-slider-runnable-track {
  background: linear-gradient(to right,
    #4ecdc4 0, #8b8cff var(--fill),
    var(--border-mid) var(--fill), var(--border-mid) 100%);
}
html[data-theme="neon"] .morph-slider::-moz-range-progress { background: var(--grad); }
html[data-theme="neon"] .morph-slider::-webkit-slider-thumb { box-shadow: 0 0 10px rgba(78, 205, 196, 0.55); }
html[data-theme="neon"] input[type="text"]:focus,
html[data-theme="neon"] input[type="search"]:focus,
html[data-theme="neon"] input[type="email"]:focus,
html[data-theme="neon"] input[type="password"]:focus {
  box-shadow: 0 0 0 3px rgba(124, 140, 255, 0.22);
}
/* Standard properties: in Chrome they take precedence over the older
   ::-webkit-scrollbar rules, so every panel gets the same thin themed bar
   instead of the white system one some of them fell back to. */
html[data-theme="neon"] {
  scrollbar-width: thin;
  scrollbar-color: rgba(150, 135, 235, 0.35) transparent;
}

/* ── Pose title chip ──────────────────────────────────────────────────── */
html[data-theme="neon"] .pose-title {
  background: rgba(255, 255, 255, 0.03);
  border-color: var(--border);
}
html[data-theme="neon"] .pose-title:hover { background: var(--grad-soft); border-color: var(--border-mid); }

@media (prefers-reduced-motion: reduce) {
  html[data-theme="neon"] .btn-primary:hover,
  html[data-theme="neon"] .sb-btn--save:hover,
  html[data-theme="neon"] .btn-secondary:hover,
  html[data-theme="neon"] .sb-btn:hover,
  html[data-theme="neon"] .cp-tile:hover .cp-tile-thumb,
  html[data-theme="neon"] .start-choice-opt:hover { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   DESIGN PICKER — Settings ▸ Workspace ▸ Design (index.html renderSettings)
   Each card carries a miniature of its theme, drawn with fixed colours so
   the previews look the same whichever theme is active.
   ═══════════════════════════════════════════════════════════════════════ */
.theme-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px;
  width: 100%;
}
.theme-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  padding: 8px 8px 10px;
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
}
.theme-card:hover { border-color: var(--border-focus); transform: translateY(-1px); }
.theme-card:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.theme-card.is-active {
  border-color: var(--teal);
  box-shadow: 0 0 0 2px var(--teal-dim);
}
.theme-card-name { font-size: 12.5px; font-weight: 700; margin-top: 4px; }
.theme-card-hint { font-size: 11px; color: var(--text-sec); }

.theme-swatch {
  position: relative;
  display: block;
  height: 64px;
  border-radius: 7px;
  overflow: hidden;
  border: 1px solid rgba(128, 128, 160, 0.25);
}
.theme-swatch i { position: absolute; display: block; }
.ts-rail  { left: 0; top: 0; bottom: 0; width: 26%; }
.ts-stage { left: 26%; top: 0; right: 0; bottom: 0; }
.ts-stage b {
  position: absolute;
  left: 50%; top: 22%;
  width: 8px; height: 30px;
  margin-left: -4px;
  border-radius: 4px 4px 2px 2px;
  opacity: 0.9;
}
.ts-btn { right: 8%; bottom: 10%; width: 30%; height: 10px; border-radius: 5px; }

.theme-swatch--classic .ts-rail  { background: #13111f; border-right: 1px solid #1e1c35; }
.theme-swatch--classic .ts-stage { background: #080818; }
.theme-swatch--classic .ts-stage b { background: #c9a68a; }
.theme-swatch--classic .ts-btn   { background: #4ecdc4; }

.theme-swatch--studio .ts-rail  { background: #1a1a1d; border-right: 1px solid #2a2a2f; }
.theme-swatch--studio .ts-stage { background: radial-gradient(circle at 55% 40%, #45454c, #111113 75%); }
.theme-swatch--studio .ts-stage b { background: #e8c7a8; }
.theme-swatch--studio .ts-btn   { background: #4ecdc4; }

.theme-swatch--neon .ts-rail  { background: linear-gradient(180deg, #1b1740, #0f0c22); border-right: 1px solid rgba(150, 135, 235, 0.25); }
.theme-swatch--neon .ts-stage { background: radial-gradient(circle at 55% 40%, #3a2f86, #07060f 75%); }
.theme-swatch--neon .ts-stage b { background: #c9a68a; box-shadow: 0 0 8px rgba(78, 205, 196, 0.8); }
.theme-swatch--neon .ts-btn   { background: linear-gradient(135deg, #4ecdc4, #7c8cff, #a78bfa); }

.theme-swatch--light .ts-rail  { background: #ffffff; border-right: 1px solid #e2e4ee; }
.theme-swatch--light .ts-stage { background: radial-gradient(circle at 55% 40%, #ffffff, #d6d8e8 80%); }
.theme-swatch--light .ts-stage b { background: #c9a68a; }
.theme-swatch--light .ts-btn   { background: #0f9f96; }


/* ═══════════════════════════════════════════════════════════════════════
   A · STUDIO — html[data-theme="studio"]
   Neutral charcoal instead of navy, Inter, solid surfaces with real
   shadows, and teal as the only colour. Quiet on purpose: the figure,
   lit warm in main.js, is the brightest thing on screen.
   ═══════════════════════════════════════════════════════════════════════ */
html[data-theme="studio"] {
  --bg:           #0f0f11;
  --panel:        #18181b;
  --card:         #212125;
  --card-hover:   #2a2a2f;
  --border:       #26262b;
  --border-mid:   #34343a;
  --border-focus: #52525b;
  --purple:       #9aa0b4;
  --purple-dim:   rgba(154, 160, 180, 0.14);
  --text:         #ededf0;
  --text-sec:     #a3a3ad;
  --text-muted:   #8b8b96;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  scrollbar-width: thin;
  scrollbar-color: #3f3f46 transparent;
}
html[data-theme="studio"] body { background: var(--bg); }

html[data-theme="studio"] #topbar,
html[data-theme="studio"] #bottom-bar { background: #141416; }
html[data-theme="studio"] #topbar { box-shadow: 0 1px 0 #26262b, 0 8px 24px -16px rgba(0, 0, 0, 0.9); }
html[data-theme="studio"] body:not(.left-collapsed) #left-panel { box-shadow: 8px 0 24px -18px rgba(0, 0, 0, 0.9); }
html[data-theme="studio"] body:not(.right-collapsed) #right-panel,
html[data-theme="studio"] #mini-gizmo-panel { box-shadow: -8px 0 24px -18px rgba(0, 0, 0, 0.9); }
html[data-theme="studio"] #mini-gizmo-panel { background: var(--panel); border-color: var(--border); }
html[data-theme="studio"] #cam-preset-bar {
  background: rgba(24, 24, 27, 0.9);
  border-color: var(--border-mid);
  box-shadow: 0 8px 24px -12px rgba(0, 0, 0, 0.8);
}

html[data-theme="studio"] .brand-word { letter-spacing: -0.02em; }
html[data-theme="studio"] .brand-word::after { content: "."; color: var(--teal); }
html[data-theme="studio"] .panel-title,
html[data-theme="studio"] .morph-section-title {
  color: var(--text-sec);
  letter-spacing: 0.08em;
}
html[data-theme="studio"] .nav-btn.active { background: var(--card); box-shadow: inset 0 -2px 0 var(--teal); }
html[data-theme="studio"] .ws-btn:hover { background: var(--card); color: var(--text); }
html[data-theme="studio"] :is(.ws-btn, .psr-btn).is-active {
  color: var(--text);
  background: var(--card);
  border-color: transparent;
  box-shadow: inset 3px 0 0 var(--teal);
}
html[data-theme="studio"] .btn-primary,
html[data-theme="studio"] .sb-btn--save,
html[data-theme="studio"] .auth-card button[type="submit"] {
  box-shadow: 0 6px 16px -8px rgba(78, 205, 196, 0.55);
}
html[data-theme="studio"] .auth-card,
html[data-theme="studio"] .start-choice-card {
  background: var(--panel);
  border-color: var(--border-mid);
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.9);
}
html[data-theme="studio"] .auth-modal {
  background: radial-gradient(ellipse 55% 50% at 50% 40%, #2a2a30, #0f0f11 75%);
}
html[data-theme="studio"] .auth-card input { background: #111113; }
html[data-theme="studio"] .cp-tile-thumb { background: radial-gradient(circle at 50% 35%, #34343a, #151517 75%); }
html[data-theme="studio"] .bm-skull,
html[data-theme="studio"] .bm-torso { fill: #2e2e34; }
html[data-theme="studio"] .ws-btn:hover,
html[data-theme="studio"] .pose-title:hover { background: var(--card); }
/* #pf-boot takes its colour from --bg and its see-through amount from the
   admin setting (--boot-alpha), so no per-theme fill here. */


/* ═══════════════════════════════════════════════════════════════════════
   C · LIGHT — html[data-theme="light"]
   White surfaces on a pale ground, darker teal so accents keep their
   contrast, soft shadows in place of borders. The stylesheet was written
   dark-first, so the hard-coded dark fills it relies on are listed here
   one by one.
   ═══════════════════════════════════════════════════════════════════════ */
html[data-theme="light"] {
  color-scheme: light;
  --bg:           #eef0f6;
  --panel:        #ffffff;
  --card:         #f4f5fa;
  --card-hover:   #eaecf5;
  --border:       #e3e5ee;
  --border-mid:   #d3d6e4;
  --border-focus: #a9aec8;
  --teal:         #0e9189;
  --teal-dim:     rgba(14, 145, 137, 0.10);
  --teal-glow:    rgba(14, 145, 137, 0.28);
  --purple:       #6a58cc;
  --purple-dim:   rgba(106, 88, 204, 0.12);
  --success:      #0c8f6b;
  --success-dim:  rgba(12, 143, 107, 0.12);
  --warning:      #b86e00;
  --warning-dim:  rgba(184, 110, 0, 0.12);
  --danger:       #d33b3b;
  --danger-dim:   rgba(211, 59, 59, 0.10);
  --text:         #1a1c2c;
  --text-sec:     #525873;
  --text-muted:   #6a7090;
  font-family: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  scrollbar-width: thin;
  scrollbar-color: #c6c9da transparent;
}
html[data-theme="light"] body { background: var(--bg); color: var(--text); }

/* Surfaces: shadow instead of hard dark edges. */
html[data-theme="light"] #topbar {
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: 0 1px 0 var(--border), 0 6px 20px -14px rgba(40, 44, 80, 0.35);
}
html[data-theme="light"] #bottom-bar {
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: 0 -6px 20px -14px rgba(40, 44, 80, 0.3);
}
html[data-theme="light"] body:not(.left-collapsed) #left-panel { box-shadow: 6px 0 24px -16px rgba(40, 44, 80, 0.35); }
html[data-theme="light"] body:not(.right-collapsed) #right-panel { box-shadow: -6px 0 24px -16px rgba(40, 44, 80, 0.35); }
html[data-theme="light"] #mini-gizmo-panel {
  background: #ffffff;
  border-color: var(--border);
  box-shadow: -6px 0 24px -16px rgba(40, 44, 80, 0.35);
  color: var(--text);
}
html[data-theme="light"] #mini-gizmo-canvas { background: #f4f5fa !important; border-color: var(--border) !important; }
html[data-theme="light"] #gizmo-head > div:first-child { color: var(--text-muted) !important; }
html[data-theme="light"] #selected-joint-name { color: var(--text) !important; }
html[data-theme="light"] #btn-gizmo-help { border-color: var(--border-mid) !important; color: var(--text-sec) !important; }
html[data-theme="light"] #joint-instruction { color: var(--text-sec) !important; }
html[data-theme="light"] #joint-instruction kbd {
  background: var(--card) !important;
  border-color: var(--border-mid) !important;
  color: var(--text);
}
html[data-theme="light"] #cam-preset-bar {
  background: rgba(255, 255, 255, 0.9);
  border-color: var(--border);
  box-shadow: 0 8px 24px -12px rgba(40, 44, 80, 0.35);
}
html[data-theme="light"] #canvas-hint {
  background: rgba(255, 255, 255, 0.85);
  color: var(--text-sec);
  border-color: var(--border);
}

/* Text and controls that were written as light-on-dark. */
html[data-theme="light"] .brand,
html[data-theme="light"] .brand-word { color: var(--text); }
html[data-theme="light"] .brand-word::after { content: "."; color: var(--teal); }
html[data-theme="light"] .panel-title,
html[data-theme="light"] .morph-section-title { color: var(--text-sec); }
html[data-theme="light"] .nav-btn.active { background: var(--teal-dim); color: var(--teal); }
html[data-theme="light"] .ws-btn:hover,
html[data-theme="light"] .pose-title:hover { background: var(--card); color: var(--text); }
html[data-theme="light"] .ws-btn.is-active { background: var(--teal-dim); }
html[data-theme="light"] .btn-primary,
html[data-theme="light"] .sb-btn--save,
html[data-theme="light"] .seg-opt.is-active,
html[data-theme="light"] .start-lib-go:not(:disabled),
html[data-theme="light"] .auth-card button[type="submit"] {
  color: #ffffff;
}
html[data-theme="light"] .btn-primary,
html[data-theme="light"] .sb-btn--save,
html[data-theme="light"] .auth-card button[type="submit"] {
  box-shadow: 0 6px 16px -8px rgba(14, 145, 137, 0.6);
}
html[data-theme="light"] .morph-bodytype-btn.is-active { color: var(--teal); }
html[data-theme="light"] .morph-bodytype-row,
html[data-theme="light"] .morph-search,
html[data-theme="light"] .morph-val,
html[data-theme="light"] .morph-cat-count { box-shadow: none; }
html[data-theme="light"] .morph-slider::-webkit-slider-thumb { background: #ffffff; }
html[data-theme="light"] .morph-slider::-moz-range-thumb { background: #ffffff; }
html[data-theme="light"] .shape-slider::-webkit-slider-thumb,
html[data-theme="light"] .draw-row input[type="range"]::-webkit-slider-thumb { border-color: #ffffff; }

html[data-theme="light"] input[type="text"],
html[data-theme="light"] input[type="search"],
html[data-theme="light"] input[type="email"],
html[data-theme="light"] input[type="password"],
html[data-theme="light"] input[type="number"],
html[data-theme="light"] select,
html[data-theme="light"] textarea {
  background: #ffffff;
  color: var(--text);
  border-color: var(--border-mid);
}

html[data-theme="light"] .auth-modal {
  background:
    radial-gradient(ellipse 55% 45% at 50% 38%, rgba(14, 145, 137, 0.10), transparent 70%),
    radial-gradient(ellipse 60% 50% at 80% 85%, rgba(106, 88, 204, 0.10), transparent 70%),
    #eef0f6;
}
html[data-theme="light"] .auth-card,
html[data-theme="light"] .start-choice-card {
  background: #ffffff;
  border-color: var(--border);
  box-shadow: 0 30px 70px -24px rgba(40, 44, 80, 0.35);
}
html[data-theme="light"] .auth-card input { background: #f7f8fc; }
html[data-theme="light"] .start-choice-modal { background: rgba(230, 232, 242, 0.7); }
html[data-theme="light"] .start-choice-opt { background: var(--card); }
html[data-theme="light"] .start-lib-go { color: #ffffff; }
html[data-theme="light"] .auth-loading,
html[data-theme="light"] .auth-loading-title { color: var(--text); }

html[data-theme="light"] .pf-boot-figure .pf-fig-head { fill: var(--teal-dim); }

html[data-theme="light"] .cp-tile-thumb { background: radial-gradient(circle at 50% 35%, #ffffff, #e2e4ef 80%); }
html[data-theme="light"] .bm-skull,
html[data-theme="light"] .bm-torso { fill: #dfe2ee; }
html[data-theme="light"] #gizmo-tutorial { background: rgba(40, 44, 80, 0.35); }
html[data-theme="light"] #gizmo-tutorial .tutorial-content {
  background: #ffffff;
  border-color: var(--border);
  color: var(--text);
  box-shadow: 0 30px 70px -24px rgba(40, 44, 80, 0.4);
}
html[data-theme="light"] .account-menu {
  background: #ffffff;
  border-color: var(--border);
  box-shadow: 0 16px 40px -16px rgba(40, 44, 80, 0.35);
}


/* ── Studio + Light: surfaces the stylesheet paints the classic navy ───
   Class-only list on purpose: an id inside :is() would lend every entry id
   specificity and repaint the Save button, which must keep its accent. */
:is(html[data-theme="studio"], html[data-theme="light"]) :is(
  .rot-pad, .rot-pad-btn, .joint-action-btn, .coverage-bar, .pose-tag, .role-option,
  .admin-side-nav-btn, .coverage-card-progress, .phase-bar, .sb-btn:not(.sb-btn--save),
  .rail-collapse, .ws-mode, .admin-modal-content textarea
) {
  background-color: var(--card);
}
:is(html[data-theme="studio"], html[data-theme="light"]) #workspace-rail > .rail-collapse,
:is(html[data-theme="studio"], html[data-theme="light"]) #admin-editor-sidebar textarea {
  background-color: var(--card);
}
:is(html[data-theme="studio"], html[data-theme="light"]) :is(.rot-pad, .rot-pad-btn, .joint-action-btn, .ws-mode, .sb-btn:not(.sb-btn--save)) {
  border-color: var(--border-mid);
}
html[data-theme="studio"] .gizmo-empty { background: rgba(24, 24, 27, 0.94); }
html[data-theme="light"]  .gizmo-empty { background: rgba(255, 255, 255, 0.94); }
html[data-theme="light"]  .rot-pad-btn { color: var(--text); }
html[data-theme="studio"] body.phone-shell #phone-tabbar { background: rgba(20, 20, 22, 0.96); }
html[data-theme="light"]  body.phone-shell #phone-tabbar {
  background: rgba(255, 255, 255, 0.96);
  border-top-color: var(--border);
  box-shadow: 0 -6px 20px -14px rgba(40, 44, 80, 0.3);
}
html[data-theme="light"] .sb-btn:not(.sb-btn--save) { color: var(--text-sec); }
html[data-theme="light"] .sb-btn:not(.sb-btn--save):hover { background-color: var(--card-hover); color: var(--text); }
html[data-theme="light"] .btn-browse-sm:not(.btn-browse-primary) {
  background: #ffffff;
  border-color: var(--border-mid);
  color: var(--text);
}
html[data-theme="light"] .btn-browse-sm:not(.btn-browse-primary):hover { background: var(--card-hover); }

/* Body map silhouette. */
html[data-theme="studio"] .bm-limbs line { stroke: #2e2e34; }
html[data-theme="studio"] .bm-links line { stroke: #45454d; }
html[data-theme="light"]  .bm-limbs line { stroke: #dfe2ee; }
html[data-theme="light"]  .bm-links line { stroke: #c2c6d8; }

/* ═══════════════════════════════════════════════════════════════════════
   SETTINGS — side navigation + search (index.html wireSettingsNav)
   Desktop: a sticky list of sections beside the page. Narrow: the list
   becomes a sticky, swipeable row of tabs above it.
   ═══════════════════════════════════════════════════════════════════════ */
.set-page {
  max-width: 1040px;
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 36px;
  align-items: start;
}
.set-main { min-width: 0; max-width: 780px; }

/* Search rows toggle [hidden]; these elements set their own display. */
.set-row[hidden],
.set-key-row[hidden],
.set-group[hidden],
.set-account[hidden],
.set-nav-list li[hidden] { display: none !important; }

.set-nav {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 4px;
}
.set-search {
  position: relative;
  display: block;
}
.set-search svg {
  position: absolute;
  left: 10px;
  top: 50%;
  width: 15px;
  height: 15px;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
}
.set-search input {
  width: 100%;
  height: 36px;
  padding: 0 10px 0 32px;
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-btn);
  color: var(--text);
  font: inherit;
  font-size: 13px;
}
.set-search input::placeholder { color: var(--text-muted); }
.set-search input:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-dim);
}

.set-nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.set-nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-btn);
  color: var(--text-sec);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.set-nav-link svg { width: 17px; height: 17px; flex: none; opacity: 0.8; }
.set-nav-link:hover { background: var(--card); color: var(--text); }
.set-nav-link:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }
.set-nav-link.is-active {
  background: var(--teal-dim);
  color: var(--teal);
  font-weight: 600;
}
.set-nav-link.is-active svg { opacity: 1; }

/* Section headings: sentence case with an icon, so each block is easy to
   find while scrolling. */
.set-group { scroll-margin-top: 12px; }
.set-group-title {
  font-size: 15px;
  font-weight: 700;
  text-transform: none;
  letter-spacing: -0.005em;
  color: var(--text);
  margin-bottom: 4px;
}
.set-group-icon {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--teal-dim);
  color: var(--teal);
}
.set-group-icon svg { width: 15px; height: 15px; }
.set-group-note { margin-left: 34px; }
.set-row-label { font-weight: 600; }

.set-empty {
  padding: 40px 0;
  text-align: center;
  color: var(--text-sec);
  font-size: 13px;
}

@media (max-width: 860px) {
  #settings-panel { padding: 18px 16px; }
  .set-page {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }
  .set-nav {
    top: -18px;                       /* the panel's own top padding */
    z-index: 5;
    margin: -18px -16px 0;
    padding: 12px 16px 10px;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    gap: 10px;
  }
  .set-nav-list {
    flex-direction: row;
    overflow-x: auto;
    scrollbar-width: none;
    gap: 6px;
  }
  .set-nav-list::-webkit-scrollbar { display: none; }
  .set-nav-link {
    padding: 7px 12px;
    border: 1px solid var(--border-mid);
    border-radius: 999px;
    font-size: 12.5px;
  }
  .set-nav-link svg { width: 15px; height: 15px; }
  .set-search input { font-size: 16px; }   /* no iOS zoom on focus */
  .set-group-note { margin-left: 0; }
  .theme-picker { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .theme-swatch { height: 48px; }
}

/* ── Gallery / Admin / Settings panels follow the design ───────────────
   #admin-panel paints a fixed navy (#0f0f1e) that suits Classic only; in
   the other designs it left the Gallery a different colour from the rest
   of the app, and in Light its dark text sat on that navy. */
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #admin-panel {
  background: var(--bg);
}
html[data-theme="neon"] #admin-panel {
  background:
    radial-gradient(ellipse 70% 50% at 50% 0%, rgba(124, 140, 255, 0.10), transparent 70%),
    var(--bg);
}
html[data-theme="neon"] #settings-panel {
  background:
    radial-gradient(ellipse 70% 50% at 50% 0%, rgba(124, 140, 255, 0.10), transparent 70%),
    var(--bg);
}
html[data-theme="light"] .admin-tab.active { color: var(--teal); border-bottom-color: var(--teal); }
html[data-theme="studio"] .admin-tab.active { border-bottom-color: var(--teal); }
html[data-theme="neon"] .admin-tab.active { border-image: var(--grad) 1; }

/* Pose cards: thumbnail ground and tag edge follow the design. */
html[data-theme="studio"] .pose-thumb { background: radial-gradient(circle at 50% 35%, #2a2a30, #121214 75%); }
html[data-theme="neon"]   .pose-thumb { background: radial-gradient(circle at 50% 35%, #2a2360, #0b0a20 75%); }
html[data-theme="light"]  .pose-thumb { background: radial-gradient(circle at 50% 35%, #ffffff, #e2e4ef 80%); }
:is(html[data-theme="studio"], html[data-theme="light"], html[data-theme="neon"]) .pose-tag:not(.pose-tag--review):not(.pose-tag--live):not(.pose-tag--rejected) { border-color: var(--border-mid); }
html[data-theme="neon"] .pose-card { transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s; }
html[data-theme="neon"] .pose-card:hover {
  transform: translateY(-2px);
  border-color: var(--border-mid);
  box-shadow: 0 14px 30px -18px rgba(124, 140, 255, 0.6);
}
html[data-theme="light"] .pose-card { box-shadow: 0 6px 18px -14px rgba(40, 44, 80, 0.35); }
html[data-theme="neon"] .pose-more-menu {
  -webkit-backdrop-filter: var(--glass);
  backdrop-filter: var(--glass);
  background: rgba(22, 18, 48, 0.92);
}

/* ── Tooltip ───────────────────────────────────────────────────────────
   Its panel and arrow are hard-coded navy while its text uses the theme
   tokens, so each design sets the panel to match its own text colour. */
html[data-theme="light"] #pf-tip,
html[data-theme="light"] .pf-tip-arrow {
  background: #ffffff;
  border-color: var(--border-mid);
}
html[data-theme="light"] #pf-tip { box-shadow: 0 12px 30px -12px rgba(40, 44, 80, 0.35); }
html[data-theme="light"] .pf-tip-keys kbd { background: var(--card); border-color: var(--border-mid); }

html[data-theme="studio"] #pf-tip,
html[data-theme="studio"] .pf-tip-arrow {
  background: #232327;
  border-color: #3a3a41;
}
html[data-theme="studio"] .pf-tip-keys kbd { background: #2e2e33; border-color: #45454d; }

/* ── Recent poses strip (bottom of the viewport) ─────────────────────── */
html[data-theme="light"] #recent-pose-strip {
  background: rgba(255, 255, 255, 0.9);
  border-color: var(--border);
  box-shadow: 0 10px 28px -16px rgba(40, 44, 80, 0.4);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}
html[data-theme="studio"] #recent-pose-strip {
  background: rgba(24, 24, 27, 0.92);
  border-color: var(--border-mid);
}
html[data-theme="neon"] #recent-pose-strip {
  background: rgba(15, 12, 34, 0.7);
  border-color: var(--border-mid);
  -webkit-backdrop-filter: var(--glass);
  backdrop-filter: var(--glass);
}
:is(html[data-theme="light"], html[data-theme="studio"], html[data-theme="neon"]) :is(.rps-label, .rps-empty) {
  color: var(--text-sec);
}
:is(html[data-theme="light"], html[data-theme="studio"], html[data-theme="neon"]) .rps-name { color: var(--text); }
:is(html[data-theme="light"], html[data-theme="studio"], html[data-theme="neon"]) .rps-card { border-color: var(--border-mid); }
:is(html[data-theme="light"], html[data-theme="studio"], html[data-theme="neon"]) .rps-card:hover { border-color: var(--teal); }
html[data-theme="light"]  .rps-card img,
html[data-theme="light"]  .rps-no-thumb { background: radial-gradient(circle at 50% 35%, #ffffff, #e2e4ef 80%); color: var(--text-muted); }
html[data-theme="studio"] .rps-no-thumb { background: #121214; color: var(--text-muted); }

/* Joint step / action buttons under the rotation panel: navy fill and pale
   text are hard-coded for Classic. */
:is(html[data-theme="light"], html[data-theme="studio"]) :is(.joint-nav-btn, .joint-action-btn) {
  background-color: var(--card);
  border-color: var(--border-mid);
  color: var(--text);
}
:is(html[data-theme="light"], html[data-theme="studio"]) :is(.joint-nav-btn, .joint-action-btn):hover:not(:disabled) {
  border-color: var(--teal);
  color: var(--text);
}
:is(html[data-theme="light"], html[data-theme="studio"]) .joint-action-btn:disabled {
  border-color: var(--border);
  color: var(--text-muted);
}

/* ═══════════════════════════════════════════════════════════════════════
   PHONE SURFACES FOLLOW THE DESIGN
   The phone sheet, both tab bars, the phone bottom bar and Draw's sheet
   were painted a fixed navy, so Light and Studio showed dark slabs with
   light-design controls inside them. One token per design now.
   ═══════════════════════════════════════════════════════════════════════ */
html[data-theme="studio"] { --sheet-bg: rgba(24, 24, 27, 0.97); }
html[data-theme="neon"]   { --sheet-bg: rgba(16, 13, 36, 0.88); }
html[data-theme="light"]  { --sheet-bg: rgba(255, 255, 255, 0.97); }

:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) :is(
  body.phone-shell #phone-sheet,
  body.phone-shell #phone-siderail,
  body.phone-shell #phone-tabbar,
  body.phone-shell #bottom-bar,
  body.draw-mode #draw-tabbar,
  body.draw-mode #draw-shell
) {
  background: var(--sheet-bg);
  border-color: var(--border);
}
html[data-theme="neon"] :is(body.phone-shell #phone-sheet, body.phone-shell #phone-siderail, body.draw-mode #draw-shell, body.draw-mode #draw-tabbar) {
  -webkit-backdrop-filter: var(--glass);
  backdrop-filter: var(--glass);
}
html[data-theme="light"] body.phone-shell #phone-siderail {
  box-shadow: 0 8px 22px -12px rgba(40, 44, 80, 0.35);
}
html[data-theme="light"] :is(body.phone-shell #phone-sheet, body.draw-mode #draw-shell) {
  box-shadow: 0 -10px 30px -12px rgba(40, 44, 80, 0.3);
}
/* Panels re-anchored into the sheet keep their own fills; inside the sheet
   they should read as the sheet itself. */
:is(html[data-theme="studio"], html[data-theme="light"]) body.phone-shell :is(#left-panel, #right-panel, #mini-gizmo-panel) {
  background: transparent;
  box-shadow: none;
}

/* Draw tab bar: the active tab uses the design accent, not a fixed violet. */
:is(html[data-theme="studio"], html[data-theme="light"]) .dt-btn.is-active { color: var(--teal); }
:is(html[data-theme="studio"], html[data-theme="light"]) .dt-btn.is-active::before { background: var(--teal); }
html[data-theme="neon"] .dt-btn.is-active { color: #7fe3dc; }
html[data-theme="neon"] .dt-btn.is-active::before { background: var(--grad); }

/* The Draw exit sits first in the bar, set apart by a divider. */
#draw-tabbar .dt-exit { color: var(--text-sec); }
#draw-tabbar .dt-exit::after {
  content: "";
  position: absolute;
  right: 0;
  top: 22%;
  bottom: 22%;
  width: 1px;
  background: var(--border-mid);
}

/* Draw's selected style / lens / paper: a fixed violet edge in Classic. */
:is(html[data-theme="studio"], html[data-theme="light"]) :is(.draw-style, .draw-lens, .draw-paper).is-active {
  border-color: var(--teal);
  background-color: var(--teal-dim);
}

/* "Viewing as User" banner: one short line on a phone. */
@media (max-width: 699px) {
  .view-as-banner { padding: 6px 6px 6px 12px; }
  .view-as-banner .vab-long { display: none; }
}
html[data-theme="light"] .view-as-banner { box-shadow: 0 8px 20px -12px rgba(40, 44, 80, 0.35); }

/* Phone: the Draw entry is a chip like the workspace chips beside it,
   not the desktop's under-a-rule tile (whose rule floated above it). */
@media (max-width: 699px), (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  body.phone-shell .ws-draw {
    flex-direction: row;
    width: auto;
    min-height: 48px;
    margin: 0;
    padding: 0 14px;
    gap: 8px;
    border-radius: 999px;
    background: var(--card);
    border-color: var(--border);
    color: var(--text-sec);
    font-size: 12.5px;
    white-space: nowrap;
  }
  body.phone-shell .ws-draw::before { content: none; }
  body.phone-shell .ws-draw svg { width: 17px; height: 17px; }
  body.phone-shell .ws-draw span { display: inline; }

}

/* Phone nav scroll edges: the covers must match the TOP BAR's fill, which
   in the designs is not --panel, or the purple edge shows at rest. */
html[data-theme="studio"] { --nav-cover: #141416; }
html[data-theme="neon"]   { --nav-cover: #0c0a1c; }
html[data-theme="light"]  { --nav-cover: #ffffff; }
@media (max-width: 699px), (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  :is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .phone-shell #editor-nav {
    background:
      linear-gradient(to right, var(--nav-cover) 0 14px, transparent 22px) left  center / 22px 100% no-repeat local,
      linear-gradient(to left,  var(--nav-cover) 0 14px, transparent 22px) right center / 22px 100% no-repeat local,
      linear-gradient(to right, var(--purple), transparent) left  center / 14px 100% no-repeat scroll,
      linear-gradient(to left,  var(--purple), transparent) right center / 14px 100% no-repeat scroll;
  }
  html[data-theme="neon"] .phone-shell #topbar { background-color: var(--nav-cover); }
}

/* ═══════════════════════════════════════════════════════════════════════
   PHONE THUMBSTICK — easier to steer
   Larger pad, a knob that travels to the rim, readable direction labels
   that light up while driven, and colours that follow the design.
   ═══════════════════════════════════════════════════════════════════════ */
#phone-stick {
  --ps-ground: rgba(13, 12, 24, 0.42);
  --ps-rim:    rgba(255, 255, 255, 0.28);
  --ps-mark:   rgba(255, 255, 255, 0.22);
  --ps-label-bg: rgba(13, 12, 24, 0.62);
  --ps-label:  #d9d6ef;
}
html[data-theme="light"] #phone-stick {
  --ps-ground: rgba(255, 255, 255, 0.62);
  --ps-rim:    rgba(40, 44, 80, 0.22);
  --ps-mark:   rgba(40, 44, 80, 0.16);
  --ps-label-bg: rgba(255, 255, 255, 0.88);
  --ps-label:  #3d4260;
}
html[data-theme="studio"] #phone-stick {
  --ps-ground: rgba(20, 20, 22, 0.5);
  --ps-label-bg: rgba(20, 20, 22, 0.7);
}

@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  body.phone-shell #phone-stick { gap: 8px; }
  .ps-wrap { padding: 20px 0; }

  .ps-base {
    width: 136px;
    height: 136px;
    background: radial-gradient(circle, var(--ps-ground) 0%, var(--ps-ground) 62%, transparent 100%);
    border: 2px solid var(--ps-rim);
    box-shadow: 0 6px 22px -10px rgba(0, 0, 0, 0.45), inset 0 0 0 10px rgba(255, 255, 255, 0.02);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
  }
  /* Inner ring: where full speed begins. */
  .ps-base::before {
    content: "";
    position: absolute;
    inset: 22%;
    border-radius: 50%;
    border: 1px dashed var(--ps-mark);
    pointer-events: none;
  }
  /* Direction arrows at the rim. */
  .ps-base::after {
    content: "";
    position: absolute;
    inset: 5px;
    border-radius: 50%;
    pointer-events: none;
    background:
      conic-gradient(from -8deg, var(--ps-rim) 0 16deg, transparent 16deg 82deg,
                     var(--ps-rim) 82deg 98deg, transparent 98deg 172deg,
                     var(--ps-rim) 172deg 188deg, transparent 188deg 262deg,
                     var(--ps-rim) 262deg 278deg, transparent 278deg);
    -webkit-mask: radial-gradient(circle, transparent 60px, #000 61px);
    mask: radial-gradient(circle, transparent 60px, #000 61px);
  }
  .ps-cross { background: var(--ps-mark); }
  .ps-cross--h { left: 12%; right: 12%; }
  .ps-cross--v { top: 12%; bottom: 12%; }

  .ps-knob {
    width: 48px;
    height: 48px;
    margin: -24px 0 0 -24px;
    background: radial-gradient(circle at 35% 30%, #8ff0e8, var(--teal) 60%, #2c9d95);
    border: 2px solid rgba(255, 255, 255, 0.85);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    transition: transform 160ms cubic-bezier(0.2, 0.9, 0.3, 1.2);
  }
  #phone-stick.is-active .ps-base {
    border-color: var(--teal);
    box-shadow: 0 0 0 4px var(--teal-dim), 0 6px 22px -10px rgba(0, 0, 0, 0.45);
  }
  #phone-stick.is-active .ps-knob { box-shadow: 0 0 0 6px var(--teal-dim), 0 4px 14px rgba(0, 0, 0, 0.35); }

  /* Labels: pills that stay readable over the model and the backdrop. */
  .ps-edge {
    padding: 2px 7px;
    border-radius: 999px;
    background: var(--ps-label-bg);
    color: var(--ps-label);
    font-size: 10.5px;
    font-weight: 600;
    text-shadow: none;
    transition: background 0.12s, color 0.12s;
  }
  .ps-edge--left  { left: -4px; }
  .ps-edge--right { right: -4px; }
  .ps-edge.is-driving { background: var(--teal); color: #fff; }

  .ps-name {
    max-width: 170px;
    padding: 3px 10px;
    background: var(--ps-label-bg);
    color: var(--text);
    font-size: 12px;
  }
  .ps-chip {
    min-height: 32px;
    padding: 0 14px;
    border-radius: 999px;
    border-color: var(--ps-rim);
    background: var(--ps-label-bg);
    color: var(--ps-label);
    font-size: 11.5px;
  }
  .ps-chip.is-active { color: #fff; background: var(--teal); border-color: var(--teal); }
}
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  /* Room for the right-hand direction label, which sits outside the pad. */
  body.phone-shell #phone-stick { right: calc(34px + var(--ph-safe-r, 0px)); }
}

/* ═══════════════════════════════════════════════════════════════════════
   SEE-THROUGH SHEET (phone) — #phone-sheet-seethru, phoneShell.js
   Drops the sheet's fill so the figure stays visible behind the controls.
   Cards keep a faint tint and text gets a halo, so the controls stay
   readable over whatever part of the model is behind them.
   ═══════════════════════════════════════════════════════════════════════ */
#phone-sheet-seethru { display: none; }
:root {
  --seethru-card: rgba(13, 12, 24, 0.40);
  --seethru-halo: 0 1px 2px rgba(0, 0, 0, 0.85), 0 0 6px rgba(0, 0, 0, 0.6);
}
html[data-theme="light"] {
  --seethru-card: rgba(255, 255, 255, 0.48);
  --seethru-halo: 0 0 2px rgba(255, 255, 255, 0.95), 0 0 6px rgba(255, 255, 255, 0.85);
}

@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  #phone-sheet-seethru {
    position: absolute;
    top: 6px;
    right: 48px;
    height: 28px;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 11px;
    border-radius: 999px;
    border: 1px solid var(--border-mid);
    background: var(--card);
    color: var(--text-sec);
    font: inherit;
    font-size: 11.5px;
    font-weight: 600;
    -webkit-tap-highlight-color: transparent;
    z-index: 2;
  }
  #phone-sheet-seethru.is-on {
    background: var(--teal);
    border-color: var(--teal);
    color: #fff;
  }
  #phone-sheet-seethru:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

  body.phone-shell.sheet-seethru #phone-sheet,
  body.phone-shell.sheet-seethru :is(#left-panel, #right-panel, #mini-gizmo-panel) {
    background: transparent !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    box-shadow: none !important;
  }
  body.phone-shell.sheet-seethru #phone-sheet { border-top: 1px dashed var(--border-mid); }

  /* Cards and controls inside keep a faint tint instead of a solid fill. */
  body.phone-shell.sheet-seethru :is(#left-panel, #right-panel, #mini-gizmo-panel) {
    --card: var(--seethru-card);
    --panel: transparent;
    --bg: var(--seethru-card);
    text-shadow: var(--seethru-halo);
  }
  body.phone-shell.sheet-seethru :is(#left-panel, #right-panel, #mini-gizmo-panel)
    :is(.rot-pad, .rot-pad-btn, .joint-action-btn, .joint-nav-btn, .gizmo-empty, .ws-mode,
        .morph-bodytype-row, .panel-section, input, select) {
    background-color: var(--seethru-card) !important;
  }
  body.phone-shell.sheet-seethru #mini-gizmo-canvas { background: transparent !important; }
}
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  /* The floating stick normally sits behind the opaque sheet. With the sheet
     see-through it would show as a second joystick on top of the sheet's own
     Movement pad, so it steps aside while a sheet is open. */
  body.phone-shell.sheet-seethru.sheet-open #phone-stick { display: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   DIALOGS FOLLOW THE DESIGN
   PromptModal (.pm-*: "Choose the model", save, confirm) and the rotation
   gizmo tutorial were written with fixed navy fills and light text. In
   Studio, Neon and Light they take the design's surfaces and type instead.
   The tutorial's heading, close button and footer note carry inline
   colours, hence the !important on those three.
   ═══════════════════════════════════════════════════════════════════════ */
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) {
  --dlg-bg:      var(--panel);
  --dlg-overlay: rgba(0, 0, 0, 0.55);
  --dlg-shadow:  0 24px 60px -18px rgba(0, 0, 0, 0.7);
}
html[data-theme="studio"] { --dlg-bg: #1c1c20; }
html[data-theme="neon"] {
  --dlg-bg: linear-gradient(160deg, rgba(40, 33, 86, 0.96), rgba(14, 11, 32, 0.97));
  --dlg-overlay: rgba(5, 4, 14, 0.6);
}
html[data-theme="light"] {
  --dlg-bg: #ffffff;
  --dlg-overlay: rgba(40, 44, 80, 0.32);
  --dlg-shadow: 0 30px 70px -24px rgba(40, 44, 80, 0.4);
}

/* ── PromptModal ─────────────────────────────────────────────────────── */
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-overlay {
  background: var(--dlg-overlay);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-modal {
  background: var(--dlg-bg);
  color: var(--text);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-lg);
  box-shadow: var(--dlg-shadow);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-title { color: var(--text); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-message { color: var(--text-sec); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-input {
  background: var(--bg);
  color: var(--text);
  border-color: var(--border-mid);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-input:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-dim);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-choice {
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-mid);
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-choice-icon { color: var(--purple); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-choice:hover {
  background: var(--card-hover);
  border-color: var(--border-focus);
  transform: translateY(-1px);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-choice:hover .pm-choice-icon { color: var(--teal); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-choice.is-default {
  background: var(--teal-dim);
  border-color: var(--teal);
  color: var(--teal);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-choice.is-default .pm-choice-icon { color: var(--teal); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-btn-cancel {
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-mid);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .pm-btn-cancel:hover { background: var(--card-hover); }
:is(html[data-theme="studio"], html[data-theme="light"]) .pm-btn-confirm { background: var(--teal); color: #fff; }
html[data-theme="studio"] .pm-btn-confirm { color: #0d0c18; }
html[data-theme="neon"] .pm-btn-confirm {
  background: var(--grad);
  color: #0a0918;
  box-shadow: 0 8px 22px -10px rgba(124, 140, 255, 0.85);
}
html[data-theme="light"] .pm-btn-danger { background: var(--danger); }

/* ── Rotation gizmo tutorial ─────────────────────────────────────────── */
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial {
  background: var(--dlg-overlay);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial .tutorial-content {
  background: var(--dlg-bg);
  border-color: var(--border-mid);
  color: var(--text);
  border-radius: var(--radius-lg);
  box-shadow: var(--dlg-shadow);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial .tutorial-header {
  border-bottom-color: var(--border);
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial .tutorial-header h3 {
  color: var(--text) !important;
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #btn-tutorial-close {
  color: var(--text-muted) !important;
}
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial .tut-title { color: var(--text); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial .tut-text { color: var(--text-sec); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial .tut-text strong { color: var(--text); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #gizmo-tutorial .tutorial-content > div:last-child > div {
  color: var(--text-muted) !important;
}
:is(html[data-theme="studio"], html[data-theme="light"]) #gizmo-tutorial .tut-num {
  background: var(--teal);
  color: #fff;
}
html[data-theme="neon"] #gizmo-tutorial .tut-num { background: var(--grad); color: #0a0918; }

/* ── Reference card (Create ▸ imported / library photo) ──────────────────
   The thumbnail frame, its "2D reference" badge and the detection label
   were fixed navy; the label's colours are inline, hence !important. */
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .ref-card .ref-thumb-wrap {
  background: var(--card);
  border-color: var(--border-mid);
}
html[data-theme="light"] .ref-card .ref-thumb-wrap { background: #ffffff; }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) .ref-thumb-badge {
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  color: var(--text-sec);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
html[data-theme="light"] .ref-thumb-badge { background: rgba(255, 255, 255, 0.9); }
:is(html[data-theme="studio"], html[data-theme="neon"], html[data-theme="light"]) #pose-class-label {
  background: var(--card) !important;
  border-color: var(--border-mid) !important;
  color: var(--text) !important;
}

/* Light: the gizmo mode options and the X/Y/Z axis letters were tuned for
   a dark ground. Same hues, darker, so they keep their contrast on white. */
html[data-theme="light"] .gizmo-mode-opt:not(:has(input:checked)) { color: var(--text-sec); }
html[data-theme="light"] .gizmo-mode-opt:has(input:checked) { color: #ffffff; }
html[data-theme="light"] .rot-pad-axis[data-axis="x"] { color: #c0392b !important; }
html[data-theme="light"] .rot-pad-axis[data-axis="y"] { color: #1e8e3e !important; }
html[data-theme="light"] .rot-pad-axis[data-axis="z"] { color: #1f5fd1 !important; }

/* ═══════════════════════════════════════════════════════════════════════
   MAPPING LOADER — #pf-mapping (js/ui/mappingOverlay.js)
   Centred over the canvas area while a photo is detected and mapped.
   Every animation is transform/opacity, so it keeps moving while MediaPipe
   and the mapping pass hold the main thread.
   ═══════════════════════════════════════════════════════════════════════ */
#pf-mapping {
  position: fixed;
  top: var(--topbar-h);
  bottom: var(--bottom-h);
  left: var(--canvas-left, 0px);
  right: var(--canvas-right, 0px);
  z-index: 190;                     /* above the canvas HUDs, below sheets and modals */
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}
#pf-mapping[hidden] { display: none; }
#pf-mapping.is-on { opacity: 1; }

.pfm-card {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  max-width: min(380px, calc(100% - 32px));
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-lg);
  box-shadow: 0 18px 50px -18px rgba(0, 0, 0, 0.7);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  transform: translateY(6px) scale(0.98);
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
#pf-mapping.is-on .pfm-card { transform: none; }
html[data-theme="light"] .pfm-card { box-shadow: 0 18px 50px -20px rgba(40, 44, 80, 0.4); }

.pfm-visual {
  position: relative;
  flex: none;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--teal-dim);
}
.pfm-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid transparent;
  border-top-color: var(--teal);
  border-right-color: var(--teal);
  animation: pfm-spin 0.9s linear infinite;
}
.pfm-figure {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 30px;
  height: 40px;
  transform: translate(-50%, -50%);
  fill: none;
  stroke: var(--teal);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pfm-figure circle { fill: var(--teal); stroke: none; }
/* A scan line sweeping the figure: "reading the body". */
.pfm-scan {
  position: absolute;
  left: 14%;
  right: 14%;
  top: 0;
  height: 3px;
  border-radius: 2px;
  background: var(--teal);
  box-shadow: 0 0 10px 2px var(--teal-glow);
  animation: pfm-scan 1.4s ease-in-out infinite;
}
@keyframes pfm-spin { to { transform: rotate(360deg); } }
@keyframes pfm-scan {
  0%, 100% { transform: translateY(14px);  opacity: 0.35; }
  50%      { transform: translateY(46px);  opacity: 1; }
}

.pfm-text { min-width: 0; flex: 1; }
.pfm-title { font-size: 14px; font-weight: 700; color: var(--text); }
.pfm-sub   { margin-top: 2px; font-size: 12px; line-height: 1.4; color: var(--text-sec); }
.pfm-steps { display: flex; gap: 5px; margin-top: 8px; }
.pfm-step {
  width: 18px;
  height: 4px;
  border-radius: 2px;
  background: var(--border-mid);
}
.pfm-step.is-done { background: var(--teal); }
.pfm-step.is-now  { background: var(--teal); animation: pfm-pulse 1s ease-in-out infinite; }
@keyframes pfm-pulse { 50% { opacity: 0.35; } }

.pfm-cancel {
  flex: none;
  align-self: center;
  padding: 6px 12px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--border-mid);
  background: var(--card);
  color: var(--text-sec);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.pfm-cancel:hover { color: var(--text); border-color: var(--border-focus); }

html[data-theme="neon"] .pfm-ring { border-top-color: #7fe3dc; border-right-color: #a78bfa; }
html[data-theme="neon"] .pfm-card {
  background: linear-gradient(160deg, rgba(40, 33, 86, 0.92), rgba(14, 11, 32, 0.94));
}

@media (max-width: 699px) {
  .pfm-card { gap: 12px; padding: 12px 14px; }
  .pfm-visual { width: 52px; height: 52px; }
  @keyframes pfm-scan {
    0%, 100% { transform: translateY(11px); opacity: 0.35; }
    50%      { transform: translateY(38px); opacity: 1; }
  }
}
@media (prefers-reduced-motion: reduce) {
  .pfm-ring, .pfm-scan, .pfm-step.is-now { animation: none; }
  .pfm-scan { transform: translateY(28px); }
}

/* ═══════════════════════════════════════════════════════════════════════
   LOADING MOMENTS — the figure acts out what is happening
   data-anim on .pf-boot-figure (boot cover in index.html, sign-in screen
   in auth.js _enterApp):
     cycle    reload while signed in / design change — the pose cycle
     stretch  opening while signed out — a slow stretch
     wave     signing in — waves hello
     jump     new account — star jumps
     goodbye  signing out / account deleted — waves goodbye, steps away
     turn     switching between the admin and user view — turns around
   The limb rules carry .pf-fig-limb twice-over specificity so they beat
   the default cycle names; the figure keeps its bob unless a moment
   replaces it.
   ═══════════════════════════════════════════════════════════════════════ */
.pf-boot-figure[data-anim]:not([data-anim="cycle"]) .pf-fig-limb { animation-name: none; }

/* stretch */
.pf-boot-figure[data-anim="stretch"] .pf-fig-limb.pf-fig-arm-l { animation: pf-fig-stretch-l 2.4s ease-in-out infinite alternate; }
.pf-boot-figure[data-anim="stretch"] .pf-fig-limb.pf-fig-arm-r { animation: pf-fig-stretch-r 2.4s ease-in-out infinite alternate; }
.pf-boot-figure[data-anim="stretch"] { animation: pf-fig-rise 2.4s ease-in-out infinite alternate; }
@keyframes pf-fig-stretch-l { from { transform: rotate(0deg); }  to { transform: rotate(160deg); } }
@keyframes pf-fig-stretch-r { from { transform: rotate(0deg); }  to { transform: rotate(-160deg); } }
@keyframes pf-fig-rise      { from { transform: translateY(0); }  to { transform: translateY(-3px); } }

/* wave (hello) — right arm up, waving */
.pf-boot-figure[data-anim="wave"] .pf-fig-limb.pf-fig-arm-r { animation: pf-fig-wave-r 0.9s ease-in-out infinite; }
.pf-boot-figure[data-anim="wave"] .pf-fig-limb.pf-fig-arm-l { transform: rotate(8deg); }
@keyframes pf-fig-wave-r {
  0%, 100% { transform: rotate(-70deg); }
  50%      { transform: rotate(-108deg); }
}

/* jump (new account) — star jumps */
.pf-boot-figure[data-anim="jump"] { animation: pf-fig-hop 0.8s ease-in-out infinite; }
.pf-boot-figure[data-anim="jump"] .pf-fig-limb.pf-fig-arm-l { animation: pf-fig-jack-al 0.8s ease-in-out infinite; }
.pf-boot-figure[data-anim="jump"] .pf-fig-limb.pf-fig-arm-r { animation: pf-fig-jack-ar 0.8s ease-in-out infinite; }
.pf-boot-figure[data-anim="jump"] .pf-fig-limb.pf-fig-leg-l { animation: pf-fig-jack-ll 0.8s ease-in-out infinite; }
.pf-boot-figure[data-anim="jump"] .pf-fig-limb.pf-fig-leg-r { animation: pf-fig-jack-lr 0.8s ease-in-out infinite; }
@keyframes pf-fig-hop     { 0%, 100% { transform: translateY(0); }       50% { transform: translateY(-7px); } }
@keyframes pf-fig-jack-al { 0%, 100% { transform: rotate(0deg); }        50% { transform: rotate(120deg); } }
@keyframes pf-fig-jack-ar { 0%, 100% { transform: rotate(0deg); }        50% { transform: rotate(-120deg); } }
@keyframes pf-fig-jack-ll { 0%, 100% { transform: rotate(0deg); }        50% { transform: rotate(22deg); } }
@keyframes pf-fig-jack-lr { 0%, 100% { transform: rotate(0deg); }        50% { transform: rotate(-22deg); } }

/* goodbye — left arm waves while the figure drifts away and fades */
.pf-boot-figure[data-anim="goodbye"] { animation: pf-fig-leave 2.6s ease-in-out infinite; }
.pf-boot-figure[data-anim="goodbye"] .pf-fig-limb.pf-fig-arm-l { animation: pf-fig-wave-l 0.9s ease-in-out infinite; }
.pf-boot-figure[data-anim="goodbye"] .pf-fig-limb.pf-fig-arm-r { transform: rotate(-8deg); }
@keyframes pf-fig-wave-l {
  0%, 100% { transform: rotate(70deg); }
  50%      { transform: rotate(108deg); }
}
@keyframes pf-fig-leave {
  0%   { transform: translateX(0);    opacity: 1; }
  70%  { transform: translateX(10px); opacity: 0.55; }
  100% { transform: translateX(0);    opacity: 1; }
}

/* turn — switching view: the figure turns around in place */
.pf-boot-figure[data-anim="turn"] { animation: pf-fig-turn 1.6s ease-in-out infinite; transform-origin: 50% 50%; }
@keyframes pf-fig-turn {
  0%, 100% { transform: scaleX(1); }
  25%      { transform: scaleX(0.15); }
  50%      { transform: scaleX(-1); }
  75%      { transform: scaleX(0.15); }
}

/* The sign-in screen uses the same figure; give it the boot size. */
.auth-loading .pf-boot-figure { width: 56px; height: 75px; }

@media (prefers-reduced-motion: reduce) {
  .pf-boot-figure,
  .pf-boot-figure * { animation: none !important; }
}

/* ── Selected Joint only exists while a joint is selected ───────────────
   Desktop: the panel and its column go away on deselect (the canvas takes
   the width back), and reappear on the next pick. The phone keeps the
   panel inside its Rotate sheet, which phoneShell.js gates instead. */
body:not(.pf-has-joint):not(.phone-shell) { --mid-w: 0px; }
body:not(.pf-has-joint):not(.phone-shell) #mini-gizmo-panel { display: none !important; }

/* The two ways to open it, disabled until there is a joint. */
#panel-rail [data-toggle="mid"]:disabled,
#phone-tabbar .pt-btn[data-sheet="rotate"]:disabled {
  opacity: 0.38;
  cursor: not-allowed;
}
#panel-rail [data-toggle="mid"]:disabled { background: transparent; border-color: transparent; box-shadow: none; }

/* ═══════════════════════════════════════════════════════════════════════
   KEYBOARD SHORTCUTS OVERLAY — "?" (js/ui/shortcuts.js)
   ═══════════════════════════════════════════════════════════════════════ */
#pf-kbd-help {
  position: fixed;
  inset: 0;
  z-index: 3100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  animation: kbd-in 0.18s ease-out;
}
html[data-theme="light"] #pf-kbd-help { background: rgba(40, 44, 80, 0.3); }
@keyframes kbd-in { from { opacity: 0; } to { opacity: 1; } }
.kbd-card {
  width: min(640px, 100%);
  max-height: calc(100vh / var(--ui-scale, 1) - 40px);
  overflow: auto;
  padding: 22px 24px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px -18px rgba(0, 0, 0, 0.7);
  color: var(--text);
}
html[data-theme="neon"] .kbd-card { background: linear-gradient(160deg, rgba(40, 33, 86, 0.97), rgba(14, 11, 32, 0.98)); }
.kbd-head { display: flex; align-items: center; justify-content: space-between; }
.kbd-head h2 { margin: 0; font-size: 17px; font-weight: 700; }
.kbd-close {
  width: 30px; height: 30px;
  border: none; border-radius: 8px;
  background: transparent; color: var(--text-muted);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.kbd-close:hover { background: var(--card); color: var(--text); }
.kbd-sub { margin: 4px 0 16px; font-size: 12px; color: var(--text-sec); }
.kbd-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px 24px;
}
.kbd-group h3 {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--teal);
}
.kbd-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; font-size: 13px; }
.kbd-card kbd,
.kbd-sub kbd {
  flex: none;
  min-width: 26px;
  padding: 2px 7px;
  border-radius: 5px;
  border: 1px solid var(--border-mid);
  border-bottom-width: 2px;
  background: var(--card);
  color: var(--text);
  font-family: 'JetBrains Mono', Consolas, monospace;
  font-size: 11.5px;
  text-align: center;
}
.kbd-what { color: var(--text-sec); }

/* ═══════════════════════════════════════════════════════════════════════
   CHANGE POSE — one button, Replace image + Camera inside
   (index.html #ref-photo-actions, controls.js initChangePoseMenu)
   ═══════════════════════════════════════════════════════════════════════ */
.change-pose { width: 100%; display: flex; flex-direction: column; gap: 6px; }
.change-pose-btn {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.change-pose-btn span { flex: 0 1 auto; }
.change-pose-chev { margin-left: auto; transition: transform 0.18s ease; opacity: 0.7; }
.change-pose-btn svg:first-child { flex: none; }
.change-pose-btn.is-open { border-color: var(--teal); color: var(--text); }
.change-pose-btn.is-open .change-pose-chev { transform: rotate(180deg); }

.change-pose-menu {
  display: flex;
  flex-direction: column;
  padding: 4px;
  background: var(--card);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-btn);
  animation: change-pose-in 0.16s ease-out;
}
.change-pose-menu[hidden] { display: none; }
@keyframes change-pose-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

.change-pose-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.change-pose-item[hidden] { display: none; }
.change-pose-item svg { flex: none; color: var(--teal); }
.change-pose-item:hover,
.change-pose-item:focus-visible { background: var(--teal-dim); outline: none; }
.cpi-text { display: flex; flex-direction: column; min-width: 0; }
/* Pushed to the trailing edge so both items line up whatever their label
   length. Dimmed until the row is hovered — it is a reminder, not a
   control, and it should not compete with the label it belongs to. */
.cpi-key {
  margin-left: auto;
  flex: none;
  min-width: 20px;
  padding: 1px 6px;
  font-size: 10.5px;
  opacity: 0.55;
  transition: opacity 0.14s ease;
}
.change-pose-item:hover .cpi-key,
.change-pose-item:focus-visible .cpi-key { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .cpi-key { transition: none; } }
/* Touch has no keyboard to press it with. */
body.phone-shell .cpi-key { display: none; }
.cpi-label { font-size: 12.5px; font-weight: 600; }
.cpi-hint  { font-size: 11px; color: var(--text-sec); }

@media (prefers-reduced-motion: reduce) {
  .change-pose-menu { animation: none; }
  .change-pose-chev { transition: none; }
}

/* Admin pose card: Delete sits beside Edit, marked as destructive. It is a
   text button, so it takes Edit's size rather than the square glyph size the
   row gives other secondary buttons. */
.pose-row-actions .pose-delete-btn {
  padding: 0 14px;
  font-size: 12.5px;
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: transparent;
}
.pose-row-actions .pose-delete-btn:hover {
  background: var(--danger-dim);
  border-color: var(--danger);
  color: var(--danger);
}

/* ═══════════════════════════════════════════════════════════════════════
   FORGOT / RESET PASSWORD — auth.js _cardHtml('forgot' | 'reset'),
   _showResetSent, _showResetDone, _wirePasswordMeter
   ═══════════════════════════════════════════════════════════════════════ */
.fp-card { text-align: center; }
.fp-card form { text-align: left; }
.fp-card h2 { font-size: 20px; margin-bottom: 6px; }

/* Badge: a key at rest; while sending, the key steps aside and a paper
   plane loops out and back. Transform/opacity only. */
.fp-badge {
  position: relative;
  width: 60px;
  height: 60px;
  margin: 0 auto 14px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--teal);
  background: var(--teal-dim);
  overflow: hidden;
  animation: fp-pop 0.45s cubic-bezier(.2, 1.4, .4, 1) both;
}
.fp-badge svg { width: 26px; height: 26px; }
.fp-badge-key { display: grid; place-items: center; transition: transform 0.3s ease, opacity 0.3s ease; }
.fp-plane { position: absolute; opacity: 0; }
.fp-badge.is-sending .fp-badge-key { transform: scale(0.4); opacity: 0; }
.fp-badge.is-sending .fp-plane { animation: fp-fly 1.1s ease-in-out infinite; }
.fp-badge.is-sending::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  border: 2px solid transparent;
  border-top-color: var(--teal);
  animation: auth-spin 0.9s linear infinite;
}
@keyframes fp-fly {
  0%   { transform: translate(-16px, 14px) scale(0.7); opacity: 0; }
  25%  { opacity: 1; }
  70%  { transform: translate(8px, -8px) scale(1);    opacity: 1; }
  100% { transform: translate(22px, -22px) scale(0.8); opacity: 0; }
}
@keyframes fp-pop { from { opacity: 0; transform: scale(0.6); } }

/* Sent: envelope with a check that pops in after it. */
.fp-badge--done { overflow: visible; }
.fp-check {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--success);
  color: #fff;
  box-shadow: 0 0 0 3px var(--card);
  animation: fp-pop 0.4s 0.35s cubic-bezier(.2, 1.4, .4, 1) both;
}
.fp-check svg { width: 13px; height: 13px; }
.fp-email {
  display: inline-block;
  max-width: 100%;
  margin: 2px 0 14px;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fp-tips {
  margin: 0 0 16px;
  padding: 10px 12px 10px 28px;
  text-align: left;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-sec);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.fp-resend { width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
.fp-resend:disabled { opacity: 0.6; cursor: not-allowed; }
.fp-count { font-variant-numeric: tabular-nums; }
.fp-resend .auth-spinner { border-color: var(--border-mid); border-top-color: var(--teal); }

/* Email field with an icon. */
.fp-field { position: relative; display: block; margin-top: 4px; }
.fp-field-icon {
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  color: var(--text-muted);
  pointer-events: none;
}
.fp-field-icon svg { width: 16px; height: 16px; }
.auth-card .fp-field input { margin-top: 0; padding-left: 36px; }
.fp-field:focus-within .fp-field-icon { color: var(--teal); }

/* New password: strength meter and match line. */
.fp-meter {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  margin: -4px 0 12px;
}
.fp-meter span {
  height: 4px;
  border-radius: 2px;
  background: var(--border-mid);
  transition: background 0.2s;
}
.fp-meter em {
  grid-column: 1 / -1;
  font-style: normal;
  font-size: 11px;
  color: var(--text-muted);
}
.fp-meter[data-score="1"] span:nth-child(-n+1) { background: var(--danger); }
.fp-meter[data-score="2"] span:nth-child(-n+2) { background: var(--warning); }
.fp-meter[data-score="3"] span:nth-child(-n+3) { background: var(--teal); }
.fp-meter[data-score="4"] span:nth-child(-n+4) { background: var(--success); }
.fp-meter[data-score="1"] em { color: var(--danger); }
.fp-meter[data-score="4"] em { color: var(--success); }
.fp-match { min-height: 16px; margin: -6px 0 10px; font-size: 11.5px; }
.fp-match.is-ok  { color: var(--success); }
.fp-match.is-bad { color: var(--warning); }

@media (prefers-reduced-motion: reduce) {
  .fp-badge, .fp-check, .fp-badge.is-sending .fp-plane, .fp-badge.is-sending::after { animation: none; }
  .fp-badge.is-sending .fp-plane { opacity: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════
   REFERENCE PHOTO LIBRARY — admin Pose Editor ▸ Reference photos
   (js/admin/index.js buildRefLibrary / handleFiles)
   ═══════════════════════════════════════════════════════════════════════ */
.adm-ref-drop { flex-direction: column; gap: 2px; }
.adm-ref-drop-sub { font-size: 10px; color: var(--text-muted); }
.rl-import-row { display: flex; gap: 6px; margin: 8px 0; }
.rl-import-row .btn-sm { flex: 1 1 0; }

.rl-progress {
  margin: 8px 0;
  padding: 8px 10px;
  background: var(--card);
  border: 1px solid var(--teal);
  border-radius: var(--radius-btn);
}
.rl-progress[hidden] { display: none; }
.rl-progress-head { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-sec); }
.rl-progress-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rl-progress-text strong { color: var(--text); }
.rl-spin {
  width: 12px; height: 12px; flex: none;
  border-radius: 50%;
  border: 2px solid var(--border-mid);
  border-top-color: var(--teal);
  animation: auth-spin 0.8s linear infinite;
}
.rl-cancel {
  flex: none;
  padding: 2px 8px;
  border: 1px solid var(--border-mid);
  border-radius: 5px;
  background: transparent;
  color: var(--text-sec);
  font: inherit;
  font-size: 10.5px;
  cursor: pointer;
}
.rl-cancel:hover { color: var(--danger); border-color: var(--danger); }
.rl-bar { height: 4px; margin-top: 7px; border-radius: 2px; background: var(--border-mid); overflow: hidden; }
.rl-bar i { display: block; height: 100%; background: var(--teal); transition: width 0.3s ease; }

.rl-empty { font-size: 11px; color: var(--text-muted); margin: 8px 0; text-align: center; }

.rl-toolbar { display: flex; flex-wrap: wrap; gap: 4px; margin: 8px 0 6px; }
.rl-chip {
  padding: 3px 8px;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  background: transparent;
  color: var(--text-sec);
  font: inherit;
  font-size: 10.5px;
  cursor: pointer;
}
.rl-chip span { color: var(--text-muted); margin-left: 2px; }
.rl-chip.is-on { color: var(--teal); border-color: var(--teal); background: var(--teal-dim); }

.rl-selbar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
  padding: 5px 8px;
  border-radius: 6px;
  background: var(--teal-dim);
  font-size: 11px;
  color: var(--text);
}
.rl-selbar[hidden] { display: none; }
.rl-selbar span { flex: 1; }
.rl-selbar button {
  padding: 2px 8px;
  border-radius: 5px;
  border: 1px solid var(--border-mid);
  background: var(--card);
  color: var(--text-sec);
  font: inherit;
  font-size: 10.5px;
  cursor: pointer;
}
.rl-selbar .rl-sel-delete { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }

.rl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: 6px;
}
.rl-card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  cursor: pointer;
  transition: border-color 0.15s, transform 0.15s;
}
.rl-card:hover { border-color: var(--teal); transform: translateY(-1px); }
.rl-card.is-selected { border-color: var(--teal); box-shadow: 0 0 0 2px var(--teal-dim); }
.rl-thumb {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: 5px;
  overflow: hidden;
  background: var(--bg);
}
.rl-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rl-noimg { position: absolute; inset: 0; display: grid; place-items: center; font-size: 9px; color: var(--text-muted); }
.rl-badge {
  position: absolute;
  left: 3px;
  bottom: 3px;
  padding: 1px 4px;
  border-radius: 3px;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #fff;
  background: rgba(0, 0, 0, 0.6);
}
.rl-badge--full         { background: var(--teal); color: #0a0918; }
.rl-badge--partial      { background: #e0a030; color: #1a1205; }
.rl-badge--insufficient { background: #c05050; }
.rl-check, .rl-del {
  position: absolute;
  top: 3px;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  opacity: 0;
  transition: opacity 0.12s;
}
.rl-check { left: 3px; cursor: pointer; }
.rl-check input { margin: 0; width: 12px; height: 12px; cursor: pointer; }
.rl-del { right: 3px; border: none; font-size: 11px; cursor: pointer; }
.rl-del:hover { background: #c05050; }
.rl-card:hover .rl-check, .rl-card:hover .rl-del,
.rl-card.is-selected .rl-check,
.rl-check:focus-within, .rl-del:focus-visible { opacity: 1; }
.rl-meta { display: flex; align-items: baseline; gap: 3px; padding: 0 2px; font-size: 9.5px; }
.rl-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-sec); }
.rl-cov { flex: none; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.rl-dots { display: flex; gap: 3px; padding: 0 2px 2px; }
.rl-dots i { flex: 1; height: 3px; border-radius: 2px; background: var(--border-mid); }
.rl-dots i.ok  { background: var(--teal); }
.rl-dots i.mid { background: #e0a030; }
.rl-dots i.bad { background: #c05050; }
.rl-clear { margin-top: 8px; }

@media (hover: none) {
  .rl-check, .rl-del { opacity: 1; }
}
.rl-import-row .btn-sm { white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════════════
   POSE EDITOR PANEL — Add vs Edit (js/admin/index.js renderAdminSidebar)
   ═══════════════════════════════════════════════════════════════════════ */
.pe-head {
  margin: 12px 12px 4px;
  padding: 12px;
  border-radius: var(--radius-card);
  border: 1px solid var(--border-mid);
  background: var(--card);
}
.pe-head--add {
  border-color: color-mix(in srgb, var(--teal) 45%, transparent);
  background: linear-gradient(160deg, var(--teal-dim), transparent 70%), var(--card);
}
.pe-head--edit { display: flex; gap: 10px; align-items: center; }
.pe-head-thumb {
  flex: none;
  width: 52px;
  height: 64px;
  border-radius: 7px;
  overflow: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
  display: grid;
  place-items: center;
}
.pe-head-thumb img { width: 100%; height: 100%; object-fit: cover; }
.pe-head-thumb span { font-size: 9px; color: var(--text-muted); text-align: center; }
.pe-head-text { min-width: 0; }
.pe-kicker {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--purple);
}
.pe-kicker--new { color: var(--teal); }
.pe-head-name {
  margin-top: 2px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pe-head-sub { margin: 4px 0 0; font-size: 11px; line-height: 1.45; color: var(--text-sec); }
.pe-head-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.pe-tag {
  padding: 1px 6px;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  font-size: 9.5px;
  color: var(--text-sec);
}

/* Fields: small uppercase label, full-width control, quiet help line. */
.pe-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; min-width: 0; }
.pe-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-sec);
}
.pe-opt { font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--text-muted); }
.pe-req { color: var(--danger); }
.pe-help { font-size: 10.5px; line-height: 1.4; color: var(--text-muted); }
.pe-help strong { color: var(--text-sec); }
.pe-count { align-self: flex-end; font-variant-numeric: tabular-nums; }
.pe-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.pe-grid-2 .pe-field { margin-bottom: 4px; }
#admin-scope-hint { display: block; margin: 0 0 10px; }
.pe-field select,
.pe-field textarea,
.pe-field input { width: 100%; min-width: 0; }
.pe-btn-row { display: flex; gap: 6px; }
.pe-btn-row .btn-sm { flex: 1 1 0; white-space: nowrap; }

.admin-side-save .pe-saveas { margin-top: 6px; }

/* Top bar: "Editing · Name" / "New · Pose". */
.pe-top-kicker {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--purple);
  background: var(--purple-dim);
  vertical-align: middle;
}
.pe-top-kicker--new { color: var(--teal); background: var(--teal-dim); }

/* ═══════════════════════════════════════════════════════════════════════
   RESET POSE CHOICE — #pf-reset-menu (js/ui/statusBar.js)
   ═══════════════════════════════════════════════════════════════════════ */
#pf-reset-menu {
  position: fixed;
  z-index: 3000;
  width: 290px;
  max-width: calc(100vw - 16px);
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-card);
  box-shadow: 0 18px 44px -14px rgba(0, 0, 0, 0.7);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  animation: rm-in 0.16s ease-out;
}
html[data-theme="light"] #pf-reset-menu { box-shadow: 0 18px 44px -18px rgba(40, 44, 80, 0.4); }
html[data-theme="neon"] #pf-reset-menu { background: linear-gradient(160deg, rgba(40, 33, 86, 0.96), rgba(14, 11, 32, 0.97)); }
@keyframes rm-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.rm-title {
  padding: 6px 8px 4px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.rm-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.rm-item svg { width: 18px; height: 18px; flex: none; color: var(--teal); }
.rm-item:hover:not(:disabled),
.rm-item:focus-visible { background: var(--teal-dim); outline: none; }
.rm-item:disabled { cursor: not-allowed; opacity: 0.55; }
.rm-item:disabled svg { color: var(--text-muted); }
.rm-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rm-label { font-size: 13px; font-weight: 600; }
.rm-hint { font-size: 11px; line-height: 1.35; color: var(--text-sec); }
.rm-item kbd {
  flex: none;
  padding: 1px 6px;
  border: 1px solid var(--border-mid);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--card);
  color: var(--text-sec);
  font-family: 'JetBrains Mono', Consolas, monospace;
  font-size: 10px;
}
.rm-foot { padding: 4px 8px 4px; font-size: 10.5px; color: var(--text-muted); border-top: 1px solid var(--border); margin-top: 4px; }
@media (prefers-reduced-motion: reduce) { #pf-reset-menu { animation: none; } }

/* ═══════════════════════════════════════════════════════════════════════
   MANAGE USER ACCOUNT — upgraded layout (js/admin/users.js)
   Header + four summary tiles + toolbar + a table that turns into cards
   when the panel is narrow (a container query, so it follows the admin
   body's width, not the window's — the sidebar eats into it on desktop).
   ═══════════════════════════════════════════════════════════════════════ */
.usr { container: usr / inline-size; }
.usr-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* ── Header ─────────────────────────────────────────────────────────── */
.usr-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}
.usr-title { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
.usr-sub { margin: 3px 0 0; font-size: 12.5px; color: var(--text-sec); }
.usr-live { display: inline-flex; align-items: center; gap: 5px; color: var(--success); font-weight: 600; }
.usr-live i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--success);
  box-shadow: 0 0 0 0 var(--success); animation: usr-pulse 2s ease-out infinite;
}
@keyframes usr-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(56, 217, 169, 0.55); }
  100% { box-shadow: 0 0 0 7px rgba(56, 217, 169, 0); }
}
.usr-refresh {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 12px;
  border: 1px solid var(--border-mid); border-radius: 9px;
  background: var(--panel); color: var(--text-sec);
  font: 600 12px/1 inherit; cursor: pointer;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.usr-refresh:hover { color: var(--text); border-color: var(--border-focus); background: var(--card); }
.usr-refresh svg { width: 14px; height: 14px; }
.usr-refresh.is-spinning svg { animation: usr-spin 0.8s linear infinite; }
@keyframes usr-spin { to { transform: rotate(360deg); } }

.usr-info {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 0 0 16px; padding: 9px 12px;
  border-radius: 10px;
  background: var(--teal-dim, rgba(78, 205, 196, 0.1));
  color: var(--text-sec);
  font-size: 12.5px; line-height: 1.45;
}
.usr-info svg { flex: none; width: 15px; height: 15px; margin-top: 1px; color: var(--teal); }
.usr-info strong { color: var(--text); }

/* ── Summary tiles ──────────────────────────────────────────────────── */
.usr .usr-stats {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin: 0 0 18px;
}
.usr .usr-stat {
  --tone: var(--teal);
  position: relative;
  flex-direction: row;
  align-items: flex-start;
  gap: 12px;
  padding: 14px;
  border-radius: 14px;
  overflow: hidden;
}
.usr .usr-stat[data-tone="purple"] { --tone: var(--purple); }
.usr .usr-stat[data-tone="green"]  { --tone: var(--success); }
.usr .usr-stat[data-tone="amber"]  { --tone: var(--warning); }
.usr-stat-icon {
  flex: none; display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: 10px;
  color: var(--tone);
  background: color-mix(in srgb, var(--tone) 14%, transparent);
}
.usr-stat-icon svg { width: 18px; height: 18px; }
.usr-stat-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.usr .usr-stat-value { font-size: 22px; line-height: 1.1; }
.usr .usr-stat-value small { font-size: 13px; font-weight: 600; color: var(--text-muted); }
.usr .usr-stat-label { font-size: 12px; color: var(--text); }
.usr-stat-sub { font-size: 11px; color: var(--text-muted); line-height: 1.3; }
.usr-stat-bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: color-mix(in srgb, var(--tone) 14%, transparent);
}
.usr-stat-bar i { display: block; height: 100%; background: var(--tone); border-radius: 0 3px 3px 0; }

/* ── Toolbar ────────────────────────────────────────────────────────── */
.usr .usr-toolbar { margin-bottom: 8px; }
.usr .usr-search { max-width: none; height: 38px; }
.usr .usr-filters { height: 38px; align-items: center; }
.usr .usr-filter { height: 30px; }
.usr .usr-sort select { height: 38px; border-radius: 10px; }
.usr-count-line { margin: 0 2px 8px; font-size: 11.5px; color: var(--text-muted); }

/* ── Table ──────────────────────────────────────────────────────────── */
.usr .usr-table-wrap { margin-top: 0; }
.usr .usr-table th:first-child,
.usr .usr-table td:first-child { padding-left: 16px; }
.usr .usr-table th:last-child,
.usr .usr-table td:last-child { padding-right: 16px; }
.usr .usr-table td { padding-top: 12px; padding-bottom: 12px; }
.usr .usr-table tbody tr.is-self { box-shadow: inset 3px 0 0 var(--teal); }

.usr .usr-avatar {
  position: relative;
  width: 38px; height: 38px;
  color: #fff;
  background: linear-gradient(140deg, hsl(var(--av-h, 262) 62% 60%), hsl(calc(var(--av-h, 262) + 28) 58% 48%));
  box-shadow: 0 0 0 2px var(--panel);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.18);
}
.usr .usr-avatar[data-role="admin"] {
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 4px color-mix(in srgb, var(--teal) 70%, transparent);
}
.usr-avatar.is-online::after {
  content: ''; position: absolute; right: -1px; bottom: -1px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--success); border: 2px solid var(--panel);
}

.usr .usr-name { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 13.5px; }
.usr-name-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usr .usr-you {
  flex: none; margin-left: 0;
  padding: 1px 7px; font-size: 10px; letter-spacing: 0.02em;
}
.usr-role-badge {
  flex: none; display: inline-flex; align-items: center; gap: 3px;
  padding: 1px 7px 1px 5px; border-radius: 999px;
  background: var(--teal-dim, rgba(78, 205, 196, 0.14)); color: var(--teal);
  font-size: 10px; font-weight: 700;
}
.usr-role-badge svg { width: 11px; height: 11px; }
.usr .usr-email { flex-wrap: nowrap; margin-top: 2px; min-width: 0; }
.usr-email-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.usr .usr-verif {
  flex: none; margin-left: 0;
  padding: 1px 7px; border-radius: 999px;
  font-size: 9.5px; letter-spacing: 0.03em;
}

.usr-role-wrap { position: relative; display: inline-flex; align-items: center; }
.usr .usr-role-select { height: 32px; min-width: 132px; }
.usr-role-wrap.is-locked .usr-role-select {
  padding-right: 30px;
  -webkit-appearance: none; appearance: none; background-image: none;
}
.usr-lock {
  position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
  display: grid; color: var(--text-muted); pointer-events: none;
}
.usr-lock svg { width: 13px; height: 13px; }

.usr-online-txt { color: var(--success); font-weight: 600; }

.usr .usr-btn { height: 32px; border-radius: 9px; }
.usr .usr-btn em {
  font-style: normal; margin-left: 2px;
  padding: 0 6px; border-radius: 999px;
  background: var(--card-hover); font-size: 10.5px;
}

/* Empty search result */
.usr .usr-empty { padding: 34px 16px !important; }
.usr-empty { text-align: center; }
.usr-empty > * { display: block; margin: 0 auto; }
.usr-empty strong { color: var(--text); font-size: 13.5px; margin-top: 8px; }
.usr-empty > span:not(.usr-empty-icon) { font-size: 12px; margin-top: 3px; }
.usr-empty .usr-btn { display: inline-flex; margin-top: 12px; }
.usr-empty-icon {
  width: 44px; height: 44px; border-radius: 50%;
  display: grid !important; place-items: center;
  background: var(--card); color: var(--text-muted);
}
.usr-empty-icon svg { width: 20px; height: 20px; }

/* ── Loading skeleton ───────────────────────────────────────────────── */
.usr-skel {
  border-radius: 12px;
  background: linear-gradient(90deg, var(--card) 0%, var(--card-hover) 50%, var(--card) 100%);
  background-size: 200% 100%;
  animation: usr-shimmer 1.2s ease-in-out infinite;
}
.usr-skel--head { width: 180px; height: 30px; margin-bottom: 18px; }
.usr-skel--stat { height: 76px; }
.usr-skel--bar  { height: 38px; margin-bottom: 14px; }
.usr-skel--row  { height: 58px; margin-bottom: 8px; }
@keyframes usr-shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }

/* ── Medium: two tiles per row ──────────────────────────────────────── */
@container usr (max-width: 820px) {
  .usr .usr-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .usr .usr-search { flex-basis: 100%; }
  .usr .usr-sort { margin-left: auto; }
}

/* ── Narrow: each account becomes a card ────────────────────────────── */
@container usr (max-width: 760px) {
  .usr .usr-table-wrap { background: transparent; border: 0; overflow: visible; }
  .usr .usr-table,
  .usr .usr-table tbody { display: block; }
  .usr .usr-table thead { display: none; }
  .usr .usr-table tbody tr {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-template-areas:
      "acct acct acct acct"
      "lib  per  join act"
      "role role btns btns";
    gap: 12px 8px;
    margin-bottom: 10px;
    padding: 14px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 14px;
  }
  .usr .usr-table tbody tr:hover { background: var(--panel); border-color: var(--border-mid); }
  .usr .usr-table tbody tr.is-self { box-shadow: inset 3px 0 0 var(--teal); }
  .usr .usr-table tbody tr.usr-empty-row { display: block; }
  .usr .usr-table td,
  .usr .usr-table td:first-child,
  .usr .usr-table td:last-child { display: block; padding: 0; border: 0; min-width: 0; }
  .usr .usr-c-acct { grid-area: acct; }
  .usr .usr-c-role { grid-area: role; align-self: center; }
  .usr .usr-c-act  { grid-area: btns; align-self: center; }
  .usr .usr-c-meta:nth-of-type(3) { grid-area: lib; }
  .usr .usr-c-meta:nth-of-type(4) { grid-area: per; }
  .usr .usr-c-meta:nth-of-type(5) { grid-area: join; }
  .usr .usr-c-meta:nth-of-type(6) { grid-area: act; }
  .usr .usr-c-meta {
    padding: 8px 10px !important;
    border-radius: 10px;
    background: var(--card);
    text-align: left;
    font-size: 12.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .usr .usr-c-meta::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 2px;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .usr .usr-role-wrap, .usr .usr-role-select { width: 100%; }
  .usr .usr-actions { justify-content: flex-end; }
}

/* ── Phone: stack the stats row two by two, buttons go icon + label full ─ */
@container usr (max-width: 460px) {
  .usr .usr-table tbody tr {
    grid-template-columns: minmax(0, 0.8fr) minmax(0, 0.9fr) minmax(0, 1.25fr) minmax(0, 0.7fr) auto;
    grid-template-areas:
      "acct acct acct acct acct"
      "lib  per  join act  act"
      "role role role role btns";
    gap: 10px 6px;
    padding: 12px;
  }
  .usr .usr-c-meta:nth-of-type(6) { grid-area: act; }
  .usr .usr-c-meta { padding: 6px 8px !important; font-size: 12px; }
  .usr .usr-c-meta::before { font-size: 8.5px; letter-spacing: 0.04em; }
  /* Icon-only buttons; the label stays for screen readers. */
  .usr .usr-actions .usr-btn { width: 40px; padding: 0; justify-content: center; }
  .usr .usr-actions .usr-btn > span {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .usr .usr-stat { padding: 12px; gap: 10px; }
  .usr-stat-icon { width: 32px; height: 32px; }
  .usr .usr-stat-value { font-size: 19px; }
  .usr-stat-sub { display: none; }
  .usr-title { font-size: 19px; }
  .usr-refresh span { display: none; }
  .usr .usr-filters { flex: 1; }
  .usr .usr-filter { flex: 1; justify-content: center; padding: 0 8px; }
  .usr .usr-sort { margin-left: 0; width: 100%; }
  .usr .usr-sort select { flex: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .usr-live i, .usr-skel, .usr-refresh.is-spinning svg { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   FEATURE FLAGS — upgraded layout (js/admin/features.js)
   Header with an on-count meter, one "only this browser" note instead of a
   copy on every card, search + On/Off filter, flags grouped by area in a
   two-column grid that drops to one column when the panel is narrow.
   ═══════════════════════════════════════════════════════════════════════ */
.ff { container: ff / inline-size; }

.ff-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}
.ff-title { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
.ff-sub { margin: 3px 0 0; font-size: 12.5px; color: var(--text-sec); max-width: 60ch; }

.ff-meter {
  flex: none;
  display: grid;
  grid-template-columns: auto auto;
  align-items: baseline;
  column-gap: 6px;
  min-width: 150px;
  padding: 10px 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.ff-meter-n { font-size: 20px; font-weight: 700; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.ff-meter-n b { color: var(--teal); }
.ff-meter-label { font-size: 11.5px; font-weight: 600; color: var(--text-sec); }
.ff-meter-bar {
  grid-column: 1 / -1;
  height: 4px;
  margin-top: 8px;
  border-radius: 999px;
  background: var(--card-hover);
  overflow: hidden;
}
.ff-meter-bar i { display: block; height: 100%; background: var(--teal); border-radius: inherit; transition: width 0.25s ease; }

.ff-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 0 0 16px; padding: 10px 12px;
  border-radius: 10px;
  background: var(--warning-dim, rgba(245, 158, 11, 0.1));
  color: var(--text-sec);
  font-size: 12.5px; line-height: 1.45;
}
.ff-note svg { flex: none; width: 15px; height: 15px; margin-top: 1px; color: var(--warning); }
.ff-note strong { color: var(--text); }

/* ── Toolbar ────────────────────────────────────────────────────────── */
.ff-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 18px; }
.ff-search {
  flex: 1 1 220px;
  display: flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 12px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: 10px;
  color: var(--text-muted);
}
.ff-search:focus-within { border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-dim, rgba(78, 205, 196, 0.15)); }
.ff-search svg { width: 15px; height: 15px; flex: none; }
.ff-search input {
  flex: 1; min-width: 0; border: 0; outline: 0; background: transparent;
  color: var(--text); font: inherit; font-size: 13px;
}
.ff-filters {
  display: inline-flex; gap: 2px; padding: 3px; height: 38px; align-items: center;
  background: var(--panel); border: 1px solid var(--border-mid); border-radius: 10px;
}
.ff-filter {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 12px;
  border: 0; border-radius: 7px; background: transparent;
  color: var(--text-sec); font: 600 12.5px/1 inherit; cursor: pointer;
}
.ff-filter span {
  min-width: 18px; padding: 2px 5px; border-radius: 999px;
  background: var(--card-hover); font-size: 10.5px; text-align: center;
}
.ff-filter:hover { color: var(--text); }
.ff-filter.is-active { background: var(--teal-dim, rgba(78, 205, 196, 0.14)); color: var(--teal); }
.ff-filter.is-active span { background: transparent; }
.ff-reset-all {
  display: inline-flex; align-items: center; gap: 6px;
  height: 38px; padding: 0 12px;
  border: 1px solid var(--border-mid); border-radius: 10px;
  background: var(--panel); color: var(--text-sec);
  font: 600 12px/1 inherit; cursor: pointer;
}
.ff-reset-all svg { width: 14px; height: 14px; }
.ff-reset-all:hover:not(:disabled) { color: var(--text); border-color: var(--border-focus); background: var(--card); }
.ff-reset-all:disabled { opacity: 0.4; cursor: default; }

/* ── Groups ─────────────────────────────────────────────────────────── */
.ff-group { margin-bottom: 22px; }
.ff-group[hidden] { display: none; }
.ff-group-head { display: flex; align-items: center; gap: 9px; margin: 0 2px 10px; }
.ff-group-icon {
  display: grid; place-items: center;
  width: 28px; height: 28px; border-radius: 8px;
  background: var(--purple-dim, rgba(139, 126, 200, 0.15)); color: var(--purple);
}
.ff-group-icon svg { width: 15px; height: 15px; }
.ff-group-title { margin: 0; font-size: 14px; font-weight: 700; color: var(--text); }
.ff-group-count { margin-left: auto; font-size: 11.5px; color: var(--text-muted); }

.ff-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

/* ── Cards ──────────────────────────────────────────────────────────── */
.ff .ff-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  margin: 0;
  padding: 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.ff .ff-card[hidden] { display: none; }
.ff .ff-card:hover { border-color: var(--border-mid); }
.ff .ff-card.is-on {
  border-color: color-mix(in srgb, var(--teal) 45%, transparent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--teal) 6%, var(--panel)), var(--panel) 60%);
}
.ff .ff-card.is-flash { animation: ff-flash 0.7s ease-out; }
@keyframes ff-flash {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--teal) 45%, transparent); }
  100% { box-shadow: 0 0 0 8px transparent; }
}

.ff-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ff-titles { min-width: 0; }
.ff .ff-name { font-size: 14.5px; line-height: 1.3; }
.ff-state { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.ff .ff-pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; font-size: 10px; letter-spacing: 0.05em;
  background: var(--card-hover);
}
.ff .ff-pill i { width: 6px; height: 6px; border-radius: 50%; background: var(--text-muted); }
.ff .ff-pill--on { background: var(--teal-dim); color: var(--teal); }
.ff .ff-pill--on i { background: var(--teal); box-shadow: 0 0 0 3px color-mix(in srgb, var(--teal) 22%, transparent); }
.ff .ff-pill--muted { background: transparent; border: 1px dashed var(--border-mid); padding: 1px 7px; }
.ff .ff-pill--changed { background: var(--purple-dim); color: var(--purple); }
.ff .ff-reset {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 6px; border-radius: 6px;
  text-decoration: none; font-size: 11px; font-weight: 600;
}
.ff .ff-reset svg { width: 12px; height: 12px; }
.ff .ff-reset:hover { background: var(--card-hover); color: var(--text); }

.ff-switch { flex: none; display: inline-flex; padding: 4px; margin: -4px; cursor: pointer; }

.ff .ff-blurb { margin: 0; font-size: 12.5px; max-width: none; }

.ff-req {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin: 0; padding: 7px 10px;
  border-radius: 9px;
  background: var(--warning-dim, rgba(245, 158, 11, 0.1));
  color: var(--text-sec); font-size: 12px;
}
.ff-req svg { width: 13px; height: 13px; color: var(--warning); flex: none; }
.ff-req strong { color: var(--text); }
.ff-link {
  margin-left: auto; padding: 0; border: 0; background: none;
  color: var(--teal); font: 600 12px/1.3 inherit; cursor: pointer;
}
.ff-link:hover { text-decoration: underline; text-underline-offset: 2px; }

.ff .ff-surfaces { margin: auto 0 0; padding: 10px 0 0; border: 0; border-top: 1px solid var(--border); }
.ff .ff-surfaces-label { font-size: 9.5px; font-weight: 700; margin-bottom: 6px; }
.ff .ff-surfaces ul { display: flex; flex-wrap: wrap; gap: 6px; padding: 0; list-style: none; }
.ff .ff-surfaces li {
  display: inline-flex; align-items: center; flex-wrap: wrap;
  padding: 3px 9px; border-radius: 7px;
  background: var(--card); border: 1px solid var(--border);
  font-size: 11.5px; line-height: 1.4;
}
.ff-sep { margin: 0 5px; color: var(--text-muted); font-weight: 700; }

.ff-empty {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 36px 16px; text-align: center; color: var(--text-muted); font-size: 12.5px;
}
.ff-empty[hidden] { display: none; }
.ff-empty svg { width: 22px; height: 22px; margin-bottom: 6px; }
.ff-empty strong { color: var(--text); font-size: 13.5px; }

@container ff (max-width: 720px) {
  .ff-grid { grid-template-columns: 1fr; }
}
@container ff (max-width: 520px) {
  .ff-head { flex-direction: column; align-items: stretch; gap: 10px; }
  .ff-meter { grid-template-columns: auto 1fr; }
  .ff-title { font-size: 19px; }
  .ff-filters { flex: 1; }
  .ff-filter { flex: 1; justify-content: center; }
  .ff .ff-card { padding: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .ff .ff-card.is-flash { animation: none; }
  .ff-meter-bar i { transition: none; }
}

/* ── Account pictures in admin lists (AdminShared.avatarImg) ─────────────
   The initial is always underneath; the picture fades in over it only once
   it has loaded, so a missing picture or Gravatar leaves the initial. */
.db-avatar { position: relative; }
.pf-av-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border-radius: 50%;
  object-fit: cover;
  background: var(--card);
  opacity: 0;
  transition: opacity 0.15s ease;
}
.pf-av-img.is-loaded { opacity: 1; }

/* ═══════════════════════════════════════════════════════════════════════
   USERS ▸ SIMPLE VIEW + DETAILS PANEL (js/admin/users.js)
   The list answers "who is here, what may they do, are they around"; every
   other column moved into a panel that opens for one account at a time.
   ═══════════════════════════════════════════════════════════════════════ */
.usr-viewtoggle {
  display: inline-flex; gap: 2px; padding: 3px; height: 38px; align-items: center;
  background: var(--panel); border: 1px solid var(--border-mid); border-radius: 10px;
}
.usr-viewbtn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 10px;
  border: 0; border-radius: 7px; background: transparent;
  color: var(--text-sec); font: 600 12.5px/1 inherit; cursor: pointer;
}
.usr-viewbtn svg { width: 14px; height: 14px; }
.usr-viewbtn:hover { color: var(--text); }
.usr-viewbtn.is-active { background: var(--teal-dim, rgba(78, 205, 196, 0.14)); color: var(--teal); }

/* Role, as a label rather than a control — changing it is a panel action. */
.usr-role-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px;
  background: var(--card); border: 1px solid var(--border);
  color: var(--text-sec); font-size: 12px; font-weight: 600;
}
.usr-role-tag svg { width: 12px; height: 12px; }
.usr-role-tag.is-admin {
  background: var(--teal-dim, rgba(78, 205, 196, 0.14));
  border-color: color-mix(in srgb, var(--teal) 40%, transparent);
  color: var(--teal);
}

.usr-act { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-sec); white-space: nowrap; }
.usr-act i { width: 7px; height: 7px; border-radius: 50%; background: var(--text-muted); flex: none; }
.usr-act.is-online { color: var(--success); font-weight: 600; }
.usr-act.is-online i { background: var(--success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 22%, transparent); }
.usr-act.is-never { color: var(--text-muted); }
.usr-act.is-never i { background: transparent; border: 1px dashed var(--border-focus); }
/* NOT display:flex here — a <td> that becomes a flex container stops being a
   table cell, which knocks the column out of alignment. Card mode below can,
   because the row is a grid there and the cell is a block. */
.usr-c-status { white-space: nowrap; }
.usr-c-status .usr-verif { margin-left: 8px; }

.usr-btn.is-more { color: var(--text); }
.usr-btn.is-more svg { color: var(--teal); }
.usr-btn.is-more:hover:not(:disabled) { border-color: color-mix(in srgb, var(--teal) 50%, transparent); }

/* ── The panel ──────────────────────────────────────────────────────────── */
.usr-drawer-overlay {
  position: fixed; inset: 0; z-index: 1400;
  display: flex; justify-content: flex-end;
  background: rgba(8, 7, 18, 0.5);
  opacity: 0; transition: opacity 0.18s ease;
}
.usr-drawer-overlay.is-open { opacity: 1; }
.usr-drawer {
  width: min(420px, 100%);
  display: flex; flex-direction: column;
  /* --panel is translucent in some themes; over the page it would show the
     table through the panel. Painting it on top of --bg makes it opaque
     without hard-coding a colour. */
  background: var(--bg);
  background-image: linear-gradient(var(--panel), var(--panel));
  border-left: 1px solid var(--border);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.28);
  transform: translateX(16px);
  transition: transform 0.18s ease;
}
.usr-drawer-overlay.is-open .usr-drawer { transform: none; }
.usr-drawer-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 18px 16px;
  border-bottom: 1px solid var(--border);
}
.usr-drawer-head .usr-account { flex: 1; min-width: 0; }
.usr-drawer-head .usr-avatar { width: 44px; height: 44px; font-size: 16px; }
.usr-drawer-head .usr-name { font-size: 15px; }
.usr-drawer-body { flex: 1; overflow-y: auto; padding: 4px 16px 24px; }

.usr-dsec { padding: 16px 0; border-bottom: 1px solid var(--border); }
.usr-dsec:last-child { border-bottom: 0; }
.usr-dsec-title {
  margin: 0 0 10px;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted);
}
.usr-dsec .usr-role-wrap, .usr-dsec .usr-role-select { width: 100%; }
.usr-dhint { margin: 8px 0 0; font-size: 12px; line-height: 1.5; color: var(--text-sec); }
.usr-dsec .usr-note { margin-top: 6px; min-height: 0; }
.usr-dsec .usr-btn { width: 100%; justify-content: center; margin-top: 12px; height: 36px; }

.usr-facts { display: grid; gap: 8px; }
.usr-fact {
  display: grid; grid-template-columns: 96px 1fr; align-items: baseline; gap: 4px 10px;
  padding: 8px 10px; border-radius: 10px; background: var(--card);
}
.usr-fact-label { font-size: 11.5px; font-weight: 600; color: var(--text-muted); }
.usr-fact-value { font-size: 13px; color: var(--text); }
.usr-fact-hint { grid-column: 2; font-size: 11px; color: var(--text-muted); }

.usr-dsec--danger .usr-dsec-title { color: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  .usr-drawer-overlay, .usr-drawer { transition: none; }
}

/* ── Simple rows as cards, when the panel is narrow ─────────────────────── */
@container usr (max-width: 760px) {
  .usr .usr-table tbody tr.is-simple {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "acct acct"
      "role status"
      "btns btns";
    gap: 12px;
  }
  .usr .usr-c-status {
    grid-area: status;
    display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap;
  }
  .usr .usr-table tbody tr.is-simple .usr-actions-cell { width: 100%; }
  .usr .usr-table tbody tr.is-simple .usr-btn { width: 100%; justify-content: center; }
}
@container usr (max-width: 460px) {
  .usr .usr-table tbody tr.is-simple {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "acct acct"
      "role status"
      "btns btns";
  }
  /* The one button here says what it does — keep its label. */
  .usr .usr-table tbody tr.is-simple .usr-actions .usr-btn { width: 100%; padding: 0 10px; }
  .usr .usr-table tbody tr.is-simple .usr-actions .usr-btn > span {
    position: static; width: auto; height: auto; clip: auto;
  }
  .usr-drawer { width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   SYSTEM GROUPS — the tab strip over a grouped page (js/admin/sysGroups.js)
   Five System Setting pages became two destinations; this is the row that
   chooses which of them is showing. It is prepended INTO #admin-body, so it
   sits above whatever that page draws and needs no change from the page.
   ═══════════════════════════════════════════════════════════════════════ */
.sysgrp-tabs {
  display: inline-flex;
  width: -moz-fit-content;
  width: fit-content;
  max-width: 100%;
  flex-wrap: wrap;
  gap: 2px;
  margin: 0 0 18px;
  padding: 3px;
  background: var(--panel);
  border: 1px solid var(--border-mid);
  border-radius: 11px;
}
.sysgrp-tab {
  flex: 0 0 auto;
  min-width: 0;
  height: 32px;
  padding: 0 14px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-sec);
  font: 600 12.5px/1 inherit;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.sysgrp-tab:hover { color: var(--text); }
.sysgrp-tab.is-active {
  background: var(--teal-dim, rgba(78, 205, 196, 0.14));
  color: var(--teal);
}
.sysgrp-tab:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* The grouped pages open with their own heading right underneath, so the
   gap the strip leaves is enough — no extra top padding on the page. */
.sysgrp-tabs + .admin-section { margin-top: 0; }

@media (max-width: 480px) {
  .sysgrp-tabs { display: flex; width: auto; }
  .sysgrp-tab { flex: 1 1 100%; text-align: left; }
}

/* Feature flags: the scope note switches from "everyone" (teal) to a warning
   when the server could not be reached, and a card greys out while saving. */
.ff-note.is-warn { background: var(--danger-dim, rgba(239, 68, 68, 0.1)); }
.ff-note.is-warn svg { color: var(--danger); }
.ff-card.is-saving { opacity: 0.6; pointer-events: none; }

/* ═══════════════════════════════════════════════════════════════════════
   PHONE TOP BAR — the preview strip, and thumb-sized actions
   ═══════════════════════════════════════════════════════════════════════
   Measured at 390px with an admin previewing: the "User view" pill costs
   86px inside #topbar-actions, which left #editor-nav 85px for 91px of
   destinations — so Gallery was clipped mid-word, and at 360px the nav had
   55px. The row simply has no 86px to give.

   So the pill stops being a row item and becomes the row BELOW: full width,
   with the words back (they were hidden to save space that is no longer
   contested) and the Return button at the end where a thumb reaches it.
   --topbar-h grows by exactly the strip, so everything positioned under the
   bar — panels, sheets, the camera pill — moves down with it rather than
   being covered. */
body.phone-shell.pf-previewing { --topbar-h: 82px; }

/* The first row keeps its own 48px; without this the brand and nav would
   centre themselves over the taller bar and sit on top of the strip. */
body.phone-shell.pf-previewing #topbar > * {
  align-self: flex-start;
  height: 48px;
  display: flex;
  align-items: center;
}
body.phone-shell.pf-previewing #topbar { align-items: flex-start; }

body.phone-shell .view-as-pill {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 34px;
  margin: 0;
  /* Fallbacks: these are declared inside the phone media block, and a
     missing var would drop the whole declaration. */
  padding: 0 calc(12px + var(--ph-safe-r, 0px)) 0 calc(12px + var(--ph-safe-l, 0px));
  gap: 8px;
  border: 0;
  border-top: 1px solid var(--warning);
  border-radius: 0;
  font-size: 12px;
}
/* Room again for the words — a lone amber dot beside "Return" does not say
   what state you are in, which is the whole job of this strip. */
body.phone-shell .view-as-pill .vab-text { display: inline; }
body.phone-shell .view-as-pill button {
  margin-left: auto;
  height: 26px;
  padding: 0 14px;
}

/* Thumb-sized actions. 36x32 was a mouse target on a surface that has no
   mouse; these are the controls used mid-pose, one-handed. */
body.phone-shell #topbar-actions { gap: 2px; }
body.phone-shell #topbar-actions .topbar-btn {
  min-width: 40px;
  min-height: 40px;
  padding: 0;
}
body.phone-shell #topbar-actions .topbar-btn:active:not(:disabled) { background: var(--card-hover); }

/* ═══════════════════════════════════════════════════════════════════════
   MOTION BAR — js/ui/poseTools.js

   The camera bar's partner at the bottom edge of the canvas. Same grammar —
   one pill, segments with no chrome at rest, teal tint-and-border for the
   one in force, a hairline rule before the control that changes what
   dragging means — so the viewport reads as one instrument: how you look
   at the figure across the top, how you move it across the bottom.

   What it holds is the joint in hand and THAT joint's motions, named the
   way people describe it (a knee flexes, a head nods), each with its angle
   from rest in tabular figures. Figure switches the bar to the whole body.
   ═══════════════════════════════════════════════════════════════════════ */
#motion-bar {
  position: absolute;
  bottom: calc(var(--bottom-h) + 16px);
  /* Centred on the canvas, not the window — it belongs to the figure,
     which is framed in the canvas. Same formula as the Pose Lock badge. */
  left: calc(var(--canvas-left) + (calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right)) / 2);
  transform: translateX(-50%);
  max-width: calc(calc(100vw / var(--ui-scale, 1)) - var(--canvas-left) - var(--canvas-right) - var(--overlay-gap) * 2);
  z-index: 95;
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 4px;
  overflow: hidden;
  background: rgba(20, 19, 42, 0.85);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-input);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.30);
  font-family: inherit;
}

/* The subject switch: the joint in hand, or the whole figure. Named, not
   boxed — the boxed segment in this bar is always the motion, just as the
   camera bar boxes only the view you are in. The chosen subject is the one
   in full text with a teal glyph; the other waits in muted text. */
.mb-subjects { display: flex; align-items: center; gap: 1px; flex: none; }
.mb-subject {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  max-width: 160px;
  height: var(--h-btn-sm);
  padding: 0 9px;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease;
}
.mb-subject svg { width: 16px; height: 16px; flex: none; }
.mb-subject:hover { color: var(--text); background: rgba(255, 255, 255, 0.05); }
.mb-subject:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.mb-subject[aria-checked="true"] { color: var(--text); font-weight: 600; }
.mb-subject[aria-checked="true"] svg { color: var(--teal); }
.mb-name { overflow: hidden; text-overflow: ellipsis; }
#motion-bar[data-state="empty"] #mb-joint { color: var(--text-muted); font-weight: 500; }
#motion-bar[data-state="empty"] #mb-joint svg { color: inherit; }

/* Empty: one line that says what to do, in place of motions that would
   belong to nothing. It also carries the orbit hint the canvas used to
   float in the same spot. */
.mb-hint {
  display: none;
  padding: 0 10px 0 2px;
  color: var(--text-muted);
  font-size: 12px;
  white-space: nowrap;
}
#motion-bar[data-state="empty"] .mb-hint { display: block; }

.mb-group { display: flex; align-items: center; gap: 1px; }
#motion-bar[data-subject="joint"] .mb-group[data-for="figure"],
#motion-bar[data-subject="figure"] .mb-group[data-for="joint"],
#motion-bar[data-state="empty"] .mb-group[data-for="joint"] { display: none; }

.mb-seg {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  height: var(--h-btn-sm);
  padding: 0 8px;
  color: var(--text-sec);
  background: transparent;
  border: 1px solid transparent;   /* reserved, so the active state never shifts */
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.mb-seg:hover { color: var(--text); background: rgba(255, 255, 255, 0.05); }
/* The motions are drawn, not spelled: an icon in the bar's own handle
   language, with the joint's word in the tooltip and for screen readers.
   An icon with no number beside it (Rings, Centre) gets a square hit area. */
.mb-icon { width: 18px; height: 18px; flex: none; display: block; }
.mb-seg--quiet { padding: 0 6px; }
.mb-seg:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.mb-seg[aria-pressed="true"] {
  color: var(--teal);
  background: var(--teal-dim);
  border-color: rgba(78, 205, 196, 0.55);
}

/* The angle from rest. A readout, so it stays out of the accent and in a
   fixed column: tabular figures, right-aligned, wide enough for "−180°",
   so the label beside it never shifts while the joint is dragged. */
.mb-val {
  min-width: 4.6ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-sec);
}
/* At rest the number steps down a tone; it comes up once the joint has
   moved. A colour step, not opacity, so it keeps its 4.5:1. */
.mb-val[data-zero="true"] { color: var(--text-muted); }
.mb-seg[aria-pressed="true"] .mb-val { color: var(--text); }

/* Rings and Centre change what the bar does rather than pick a motion —
   quieter labels, same hit size. */
.mb-seg--quiet { color: var(--text-muted); }

.mb-rule {
  flex: none;
  width: 1px;
  height: 16px;
  margin: 0 5px;
  background: var(--border-focus);
}

/* Narrow canvas: shed the readouts first, then the names (the joint's is in
   the Selected Joint panel too), leaving the glyphs. poseTools.js measures
   and sets these; the bar never wraps. */
#motion-bar.is-compact .mb-val,
#motion-bar.is-compact .mb-hint-more { display: none; }
#motion-bar.is-tight .mb-val,
#motion-bar.is-tight .mb-name,
#motion-bar.is-tight .mb-figure-label,
#motion-bar.is-tight .mb-hint-more { display: none; }
#motion-bar.is-tight .mb-subject { padding: 0 6px; gap: 0; }

/* ── The live value riding the bead during a drag ───────────────────── */
.mb-tag {
  position: absolute;
  z-index: 96;
  transform: translate(-50%, -50%);
  pointer-events: none;
  padding: 2px 8px;
  background: rgba(13, 12, 24, 0.88);
  border: 1px solid rgba(78, 205, 196, 0.55);
  border-radius: 999px;
  color: var(--text);
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mb-tag[hidden] { display: none; }

/* Where the bar does not belong: Draw is not for posing, and under Pose Lock
   the badge takes this spot and says why nothing moves. */
body.draw-mode #motion-bar,
body.pf-paper #motion-bar,
body.pose-locked #motion-bar,
body.draw-mode .mb-tag { display: none; }
/* Focus Mode has no bottom bar, so this drops to where it was. */
body.pf-focus #motion-bar { bottom: 20px; }
/* The orbit hint now lives in the bar's empty state. */
body.pf-motion-bar #canvas-hint { display: none; }

/* ── Themes ──────────────────────────────────────────────────────────── */
html[data-theme="light"] #motion-bar {
  background: rgba(255, 255, 255, 0.9);
  border-color: var(--border);
  box-shadow: 0 8px 24px -12px rgba(40, 44, 80, 0.35);
}
html[data-theme="light"] .mb-seg:hover { background: var(--card); }
html[data-theme="light"] .mb-tag {
  background: rgba(255, 255, 255, 0.95);
  border-color: rgba(20, 163, 154, 0.55);
}

html[data-theme="studio"] #motion-bar {
  background: rgba(24, 24, 27, 0.9);
  border-color: var(--border-mid);
  box-shadow: 0 8px 24px -12px rgba(0, 0, 0, 0.8);
}

html[data-theme="neon"] #motion-bar {
  background: rgba(15, 12, 34, 0.6);
  border-color: var(--border-mid);
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.7);
  -webkit-backdrop-filter: var(--glass);
  backdrop-filter: var(--glass);
}
html[data-theme="neon"] .mb-seg[aria-pressed="true"] {
  background: var(--grad-soft);
  box-shadow: var(--glow-teal);
}

/* Mid-drag, the motion in hand is the only one that matters. */
#motion-bar.is-dragging .mb-seg:not([aria-pressed="true"]),
#motion-bar.is-dragging .mb-rule { opacity: 0.45; }


/* ── Phone tier ─────────────────────────────────────────────────────────
   The same bar, in the thumb zone: docked just above the ribbon, where the
   tab bar and the Save row already teach the thumb to reach. Every target
   is 44px, each motion stacks its degrees under its icon so four of them
   fit across a phone, and the subject switch drops to its glyphs — the
   stick above already names the joint. Rings goes: the phone has no ring
   gizmo to hand the joint to. The stick moves up to sit on the bar. */
@media (max-width: 699px),
       (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  body.phone-shell #motion-bar {
    bottom: calc(var(--ph-chrome-b) + 8px);
    padding: 4px;
    border-radius: 14px;
    max-width: calc(calc(100vw / var(--ui-scale, 1)) - 16px - var(--ph-safe-l, 0px) - var(--ph-safe-r, 0px));
  }
  body.phone-shell .mb-subject {
    height: 44px;
    min-width: 44px;
    justify-content: center;
    padding: 0 10px;
  }
  body.phone-shell .mb-subject svg { width: 20px; height: 20px; }
  body.phone-shell .mb-name,
  body.phone-shell .mb-figure-label,
  body.phone-shell .mb-hint-more,
  body.phone-shell .mb-seg[data-motion="free"] { display: none; }
  body.phone-shell .mb-rule { height: 24px; margin: 0 4px; }
  body.phone-shell .mb-seg {
    flex-direction: column;
    justify-content: center;
    gap: 3px;
    height: 44px;
    min-width: 52px;
    padding: 0 6px;
  }
  body.phone-shell .mb-icon { width: 20px; height: 20px; }
  body.phone-shell .mb-val {
    min-width: 0;
    font-size: 10.5px;
    line-height: 1;
    text-align: center;
  }
  body.phone-shell .mb-hint { padding: 0 12px 0 4px; font-size: 12.5px; }
  body.phone-shell .mb-tag { font-size: 13px; padding: 4px 11px; }

  /* The stick sits on the bar instead of the ribbon. */
  body.phone-shell.pf-motion-bar:not(.pose-locked) #phone-stick {
    bottom: calc(var(--ph-chrome-b) + 8px + 54px + 10px);
  }
  /* A see-through sheet would show the bar through the sheet's own controls;
     the stick steps aside for the same reason (see PHONE THUMBSTICK). */
  body.phone-shell.sheet-seethru.sheet-open #motion-bar { display: none; }
  /* Focus Mode puts the ribbon and tab bar away; the bar drops with them,
     as the Pose Lock badge does. */
  body.phone-shell.pf-focus #motion-bar { bottom: calc(16px + var(--ph-safe-b, 0px)); }
}

/* Landscape phone: the tab bar is a rail down the left and the stick stands
   at the right edge, clear of the centred bar — so the stick keeps its own
   place instead of climbing under the top bar. */
@media (max-height: 520px) and (orientation: landscape) and (pointer: coarse) {
  body.phone-shell.pf-motion-bar:not(.pose-locked) #phone-stick {
    bottom: calc(var(--ph-chrome-b) + 12px);
  }
}
