/* ═══════════════════════════════════════════════════
   FEATURE — Character Viewer Tab
   All rules scoped under .maker .tab-character ONLY.
   PROTECTED VALUES — never change from another file:
     sidebar width: 220px
     imageSmoothingEnabled: false
     API version: GMS/83 only
   ═══════════════════════════════════════════════════ */

/* Use the independent `scale` property (not `transform`) so it composes
   with the `translate` we set inline for foot-anchor centering. Modern
   browser support — Chrome 104+, Safari 14.1+, Firefox 72+. */
@keyframes cv-preview-bounce {
  0%   { scale: 1    1;    }
  18%  { scale: 1.06 0.92; }
  38%  { scale: 0.94 1.07; }
  58%  { scale: 1.02 0.97; }
  78%  { scale: 0.99 1.01; }
  100% { scale: 1    1;    }
}
.cv-preview-bouncing {
  animation: cv-preview-bounce 0.42s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

/* ── Panel body — sidebar 220px — PROTECTED ── */
.maker .tab-character .panel-body {
  grid-template-columns: 220px 1fr; /* sidebar 220px — PROTECTED */
}

/* ── VIEWER / EDIT mode toggle (Sprint 4 C2, /studio/edit/ only) ──
   Matches the .cv-export-tab segmented-control pattern used in the
   Advanced Export dock so the visual language stays consistent.       */
/* Design-system 2026-08-06: segmented = joined group @ 12px (Derek pick B). */
.maker .tab-character .cv-mode-tabs {
  display: flex; gap: 0;
  border: 1px solid var(--border-lo);
  border-radius: var(--r); overflow: hidden;
}
.maker .tab-character .cv-mode-tab {
  flex: 1;
  padding: var(--sp-2) 0;
  font-size: var(--fs-4);
  border: 0;
  background: var(--input-bg);
  cursor: pointer;
  color: var(--text-dim);
}
.maker .tab-character .cv-mode-tab + .cv-mode-tab { border-left: 1px solid var(--border-lo); }
.maker .tab-character .cv-mode-tab.active {
  background: var(--accent, #4a90e2);
  color: #fff;
}

/* Mode panels — both share the controls-col flex layout. */
.maker .tab-character .cv-viewer-panel,
.maker .tab-character .cv-edit-panel {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  flex: 1;
  min-height: 0;
}
.maker .tab-character .cv-viewer-panel[hidden],
.maker .tab-character .cv-edit-panel[hidden] {
  display: none;
}

/* ── Studio Shell sidebar (Sprint 5 C1) ───────────────────────────
   Replaces the View/Edit mode toggle on /studio/edit/. Text-only —
   thumbnails + search live in the bottom dock; the sidebar holds
   settings exclusively. Sections after Load Character reveal once a
   char is active (cvShellSyncSections in character-viewer.js).
   Section headers re-use the existing .groupbox-label visual:
   accent left-bar + bottom rule. */
.maker .tab-character .cv-shell-btn {
  width: 100%;
  text-align: center;
  white-space: nowrap;
}

/* cv-shell-section + .is-collapsible chrome lives in
   styles/maker/shell-sections.css (shared across all studio
   tabs). Character-only extensions stay here. */
.maker .tab-character .cv-shell-select {
  width: 100%;
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--fs-4);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--input-bg, #fff);
  color: var(--text);
  cursor: pointer;
}
.maker .tab-character .cv-shell-select:focus {
  outline: none;
  border-color: var(--accent);
}

/* Animation controls (Sprint 5 C1) — the .cv-playback-row block was
   PROMOTED to styles/components/controls.css (scoped to
   .cv-shell-section) so every viewer tab shares one copy. /studio/
   keeps its legacy look (controls.css is /maker-linked only). */

/* EDIT-mode sidebar — two states share the panel via [hidden]. */
.maker .tab-character .cv-edit-state {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  flex: 1;
  min-height: 0;
}
.maker .tab-character .cv-edit-state[hidden] {
  display: none;
}
.maker .tab-character .cv-edit-new-btn {
  width: 100%;
}
.maker .tab-character .cv-edit-placeholder {
  font-size: var(--fs-3);
  color: var(--text-dim);
  line-height: 1.5;
  margin: 0;
}

/* Category list — vertical button stack in the sidebar (active state).
   Matches the .cv-options-grid visual language used by panel toggles.
   Intentionally not scrollable: the 15 categories always fit. */
/* Edit-category list — moved into the item dock's rail (UX-polish P3).
   Rows use the shared .bd-rail-row language from browse-dock.css.
   The container class survives as the New-Character section's wrapper. */
.maker .tab-character .cv-edit-categories {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-1) 0;
}
.maker .tab-character .cv-picker-content {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Sprint 8 — region picker. Slides down under the Create New button
   when expanded. Each region option is itself a click-to-create
   button (no radios, no separate confirm step). The collapse state
   uses max-height + opacity + collapsed padding/border so the open
   animation reads as a smooth slide-down. */
.maker .tab-character .cv-create-region-picker {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: rgba(0, 0, 0, 0.025);
  border: 0 solid var(--border);
  border-radius: var(--r);
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  padding: 0 var(--sp-4);
  margin: 0;
  transition:
    max-height 200ms ease,
    opacity   150ms ease 30ms,
    padding   180ms ease,
    margin    180ms ease,
    border-width 180ms ease;
}
.maker .tab-character .cv-create-region-picker.is-open {
  max-height: 200px;
  opacity: 1;
  padding: var(--sp-3) var(--sp-4);
  margin: var(--sp-1) 0;
  border-width: 1px;
}
.maker .tab-character .cv-create-region-label {
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--text-dim);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0 0 2px;
}
.maker .tab-character .cv-create-region-options {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
/* Each option is a borderless button — looks like a sidebar link, acts
   like an item in a list. Hover/active states give it click affordance.
   No checkbox/radio chrome; the label IS the click target. */
.maker .tab-character .cv-create-region-option {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: var(--r);
  padding: var(--sp-2) var(--sp-3);
  font: inherit;
  font-size: var(--fs-4);
  color: var(--text);
  cursor: pointer;
}
.maker .tab-character .cv-create-region-name { flex: 0 0 auto; }
.maker .tab-character .cv-create-region-size {
  flex: 0 0 auto;
  font-size: var(--fs-2);
  color: var(--text-dim);
  letter-spacing: 0.02em;
}
.maker .tab-character .cv-create-region-option:hover {
  background: rgba(0, 0, 0, 0.05);
}
.maker .tab-character .cv-create-region-option:active {
  background: rgba(0, 0, 0, 0.08);
}
/* Subtle dot in front of the last-used region as a soft "you picked
   this last time" hint — no full highlight (the user hasn't committed
   yet, just hovering this label). */
.maker .tab-character .cv-create-region-option.is-last-used::before {
  content: '·';
  display: inline-block;
  width: 8px;
  color: var(--accent, #4a90e2);
  font-weight: 700;
}
.maker .tab-character .cv-create-region-option:not(.is-last-used)::before {
  content: '';
  display: inline-block;
  width: 8px;
}

/* Save section (Sprint 4 C10) — name input + save button + hint
   line below the category rail in EDIT-mode active state. */
.maker .tab-character .cv-edit-save-section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.maker .tab-character .cv-edit-save-name {
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-4);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--input-bg, #fff);
  color: var(--text);
}
.maker .tab-character .cv-edit-save-name:focus {
  outline: none;
  border-color: var(--accent);
}
.maker .tab-character .cv-edit-save-btn {
  width: 100%;
}
.maker .tab-character .cv-edit-save-hint {
  font-size: var(--fs-2);
  color: var(--text-dim);
  margin: 0;
  min-height: 14px;
  line-height: 1.3;
}
.maker .tab-character .cv-edit-save-hint.is-error {
  color: var(--red, #c0392b);
}

/* ── Search input ── */
.maker .tab-character .npc-search-wrap { display: flex; }

/* ── Grid status message ── */
.maker .tab-character .npc-grid-msg {
  grid-column: 1 / -1;
  padding: var(--sp-5) var(--sp-3);
  font-size: var(--fs-3);
  color: var(--text-dim);
  text-align: center;
  line-height: 1.5;
}

/* ── Character list ── */
.maker .tab-character .cv-char-list-wrap {
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--input-bg);
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  overflow-x: hidden;
  overflow-y: auto;
  max-height: 240px;
  scrollbar-width: thin;
}

.maker .tab-character .cv-char-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-1);
  padding: var(--sp-2);
  box-sizing: border-box;
  width: 100%;
}

/* ── Character list item ── */
.maker .tab-character .cv-char-btn {
  background: linear-gradient(180deg, #f6f6f6 0%, #e8e8e8 100%);
  border: none;
  outline: none;
  border-radius: var(--r);
  padding: var(--sp-1) 2px 3px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 2px;
  width: 100%;
  height: 72px;
  position: relative;
  overflow: hidden;
  transform: translateY(0);
  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), background 0.15s ease;
  will-change: transform;
}

.maker .tab-character .cv-char-btn:hover {
  background: linear-gradient(180deg, #f0f0f0 0%, #e2e2e2 100%);
  transform: translateY(-3px);
}

.maker .tab-character .cv-char-btn.active {
  background: linear-gradient(180deg, rgba(74,158,192,0.2) 0%, rgba(42,126,160,0.12) 100%);
}

.maker .tab-character .cv-char-btn.active:hover { transform: translateY(0); }
.maker .tab-character .cv-char-btn:focus { outline: none; }

/* Fixed-size crop window — flex centered */
.maker .tab-character .cv-char-thumb {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  pointer-events: none;
}

.maker .tab-character .cv-char-thumb img {
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
  flex-shrink: 0;
  image-rendering: pixelated;
  display: block;
}

/* Library-mode thumb: image foot-anchored via JS-applied inline left/top.
   Wrapper keeps overflow:hidden + position:relative from base rule. */
.maker .tab-character .cv-char-thumb.cv-char-thumb-lib {
  display: block;
}
.maker .tab-character .cv-char-thumb.cv-char-thumb-lib img {
  position: absolute;
}

.maker .tab-character .cv-char-label {
  font-size: var(--fs-1);
  color: var(--text-dim);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: calc(100% - 4px);
  line-height: 1.3;
  pointer-events: none;
}

.maker .tab-character .cv-char-btn.active .cv-char-label {
  color: var(--accent-text);
}

/* ── Shimmer ── */
.maker .tab-character .cv-char-shimmer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.maker .tab-character .cv-char-btn:hover .cv-char-shimmer {
  opacity: 1;
}

/* ── List status messages ── */
.maker .tab-character .cv-list-msg {
  padding: var(--sp-5) var(--sp-3);
  font-size: var(--fs-3);
  color: var(--text-dim);
  text-align: center;
  line-height: 1.5;
}

/* ── Animation chip grid — inherits NPC styles via shared class names ── */
.maker .tab-character .anim-grid-wrap {
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--input-bg);
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  overflow-x: hidden;
  overflow-y: auto;
  max-height: 140px;
  scrollbar-width: thin;
}

.maker .tab-character .anim-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  padding: var(--sp-2);
  box-sizing: border-box;
}

.maker .tab-character .anim-btn {
  background: var(--chrome-lo);
  border: 1px solid var(--border-lo);
  border-radius: var(--r);
  padding: 3px var(--sp-4);
  font-size: var(--fs-3);
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
  white-space: nowrap;
}

.maker .tab-character .anim-btn:hover {
  background: #fff;
  border-color: var(--accent);
}

.maker .tab-character .anim-btn.active {
  background: rgba(74,158,192,0.15);
  border-color: var(--accent);
  color: var(--accent-text);
}

/* Library mode: pose known to fail. Stays clickable so REST takes
   over on click; dimmed state signals the renderer switched. */
.maker .tab-character .anim-btn.cv-anim-btn-lib-failed {
  opacity: 0.45;
}

/* ── Playback row ── */
.maker .tab-character .cv-playback-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.maker .tab-character .cv-playback-row .cv-play-btn {
  flex: 1;
}

/* LRDAZT — icon-only play + inline pose/emote select per row */

/* In-app dropdown triggers come from SF2_SELECT (.sf2-select-btn in
   styles/components.css); the trigger copies the select's classes so
   .cv-anim-select's flex sizing above applies to it too. */

/* The whole "0 / 3" area is a scrub surface (SF2_SCRUB opts.surface). */

/* LRDAZT — Frame Picker as a bordered panel-row: reads as "opens a
   panel", not an action button, but stays visibly affordant (Derek's
   discoverability concern). */

.maker .tab-character .cv-frame-nav {
  display: flex;
  align-items: center;
  gap: 3px;
}

/* LRDAZT — scrubbable frame counter (SF2_SCRUB input + "/ max" suffix) */

/* ── Play button ── */
.maker .tab-character .cv-play-btn {
  background: var(--chrome-lo);
  border: 1px solid var(--border-lo);
  border-radius: var(--r);
  padding: 3px var(--sp-4);
  font-size: var(--fs-3);
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
  white-space: nowrap;
}

.maker .tab-character .cv-play-btn:hover {
  background: #fff;
  border-color: var(--accent);
}

.maker .tab-character .cv-play-btn.playing {
  background: rgba(74,158,192,0.15);
  border-color: var(--accent);
  color: var(--accent-text);
}

.maker .tab-character .cv-play-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ── Bottom controls ── */
.maker .tab-character .controls-bottom {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: auto;
}

/* ── Preview column — needs relative positioning for floating panel ── */
.maker .tab-character .preview-col {
  position: relative;
}

/* ── Preview canvas ── */
.maker .tab-character #cv-preview-canvas {
  image-rendering: pixelated;
  display: none;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.2);
  border-radius: var(--r);
  background: transparent !important;
  /* Sprint 7 W4.2 — foot-locked positioning. left/top set per draw by
     _cvDrawIntoPreview so the foot pixel pins to a fixed point in
     #cv-canvas-frame. Replaces flex-centering; bbox growth no longer
     shifts the foot on screen. transform-origin also set per draw to
     match the foot pixel so the bounce squashes in place. */
  position: absolute;
}

.maker .tab-character #cv-preview-canvas.no-border { box-shadow: none; }

/* Library-fallback banner — shown when REST is rendering this character
   because the library couldn't load one or more items. Anchored to the
   top-left of the canvas frame so it doesn't fight the canvas's
   flex-centered position. */
.maker .tab-character .cv-lib-fallback-banner {
  position: absolute;
  top: 12px;
  left: 12px;
  max-width: calc(100% - 24px);
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  background: rgba(255, 230, 200, 0.95);
  border: 1px solid #c97b1a;
  border-radius: var(--r);
  font-size: var(--fs-3);
  color: #6b3a0a;
  box-shadow: 0 2px 6px rgba(0,0,0,0.12);
  z-index: 4;
  pointer-events: none;
}
.maker .tab-character .cv-lib-fallback-banner:not([hidden]) {
  display: flex;
}
.maker .tab-character .cv-lib-fallback-icon {
  font-size: 14px;
  line-height: 1;
  flex-shrink: 0;
}
.maker .tab-character .cv-lib-fallback-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  line-height: 1.35;
}
.maker .tab-character .cv-lib-fallback-detail {
  color: #8a4a0d;
}
.maker .tab-character .cv-lib-fallback-hint {
  color: #8a6a4a;
  font-style: italic;
}
.maker .tab-character .cv-lib-fallback-hint a {
  color: #6b3a0a;
  pointer-events: auto;
  text-decoration: underline;
}
.maker .tab-character .cv-lib-fallback-hint a:hover {
  color: #c97b1a;
}

/* Layer-row red highlight — item whose library data is unrecoverable
   (no per-pose bitmap available anywhere). Renders via REST only. */
.maker .tab-character .cv-layer-row.lib-unrecoverable {
  background: rgba(220, 70, 70, 0.10);
  border-left: 3px solid #c93030;
  padding-left: var(--sp-2);
}
.maker .tab-character .cv-layer-row.lib-unrecoverable .cv-layer-name {
  color: #a52020;
}

/* Duplicate single-instance-slot import (maples.im / MapleSim) — an extra item
   in a slot a character can only fill once (two weapons, two hats, …),
   force-hidden. Red like unrecoverable + a ⚠ marker; the row is already eye-off
   (the item is visible:false). */
.maker .tab-character .cv-layer-row.dup-conflict {
  background: rgba(220, 70, 70, 0.12);
  border-left: 3px solid #c93030;
  padding-left: var(--sp-2);
}
.maker .tab-character .cv-layer-row.dup-conflict .cv-layer-name {
  color: #a52020;
  text-decoration: line-through;
}
.maker .tab-character .cv-layer-row.dup-conflict .cv-layer-name::after {
  content: ' ⚠';
  text-decoration: none;
  display: inline-block;
}

/* Item id not present in the character's tagged region/version (e.g. a modern
   item on a Pre-BB character) — it renders nothing. Amber (a version mismatch,
   not corruption) + a ⚠ marker, distinct from the red dup/unrecoverable rows. */
.maker .tab-character .cv-layer-row.region-missing {
  background: rgba(228, 158, 52, 0.12);
  border-left: 3px solid #d08a2a;
  padding-left: var(--sp-2);
}
.maker .tab-character .cv-layer-row.region-missing .cv-layer-name {
  color: #b5791f;
}
.maker .tab-character .cv-layer-row.region-missing .cv-layer-name::after {
  content: ' ⚠';
  display: inline-block;
}

/* Auto-disabled by the library fallback chain (weapon-pose incompatible).
   Renders identically to a manual hide but additionally strikes through
   the name so the user can tell at-a-glance which layers the engine
   removed vs. which they hid themselves. */
.maker .tab-character .cv-layer-row.lib-auto-disabled .cv-layer-name,
.maker .tab-character .cv-layer-row.lib-auto-disabled .cv-layer-slot {
  text-decoration: line-through;
  text-decoration-color: rgba(120, 60, 0, 0.6);
}

/* ── Toolbar layers toggle button ── */
.maker .tab-character .cv-layers-toggle {
  background: var(--chrome-lo);
  border: 1px solid var(--border-lo);
  border-radius: var(--r);
  padding: 2px var(--sp-3);
  font-size: var(--fs-3);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, border-color 0.12s;
}
.maker .tab-character .cv-layers-toggle:hover { background: #fff; border-color: var(--accent); }
.maker .tab-character .cv-layers-toggle.active:hover { background: var(--accent-hi, var(--accent)); }
.maker .tab-character .cv-layers-toggle:disabled { opacity: 0.4; cursor: default; }

.maker .tab-character .cv-options-grid {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.maker .tab-character .cv-options-grid .cv-layers-toggle {
  width: 100%;
  justify-content: flex-start;
}

/* ── Floating layer panel — full-height slide from right ── */
.maker .tab-character .cv-layer-panel {
  position: absolute;
  top: 0; right: 0; bottom: 0;       /* full height of preview-col */
  width: 220px;
  background: var(--chrome-hi);
  border-left: 1px solid var(--border);
  box-shadow: -4px 0 16px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 10;
  transform: translateX(100%);
  transition: transform 0.2s ease;
}
.maker .tab-character .cv-layer-panel.open  { transform: translateX(0); }
.maker .tab-character .cv-layer-panel[hidden] { display: none; }

/* ── Pose Picker side dock (Sprint 5 C4) ──────────────────────────
   Full-height left slide-in matching the Zip Export dock chrome.
   Hosts the pose + emote chip strips, replacing the bottom-up
   panels on /studio/edit/. Single side-dock slot — opening this
   closes Zip Export and vice versa. */
.maker .tab-character .cv-pose-picker-panel {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  /* Default width HUGS four frame cards per pose row (ASY76B — 540px left
     a dead strip right of the grid); user-resizable via the right-edge
     handle for wider poses. */
  width: var(--cv-fp-w, 352px);
  background: var(--chrome-hi);
  border-right: 1px solid var(--border);
  box-shadow: 4px 0 16px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 10;
  transform: translateX(-100%);
  transition: transform 0.2s ease;
}
.maker .tab-character .cv-pose-picker-panel.open    { transform: translateX(0); }
.maker .tab-character .cv-pose-picker-panel[hidden] { display: none; }

/* Width-drag handle on the dock's right edge (panel clips overflow, so the
   handle sits inside the edge rather than straddling it). */
.maker .tab-character .cv-fp-resize {
  position: absolute; right: 0; top: 0; bottom: 0; width: 6px;
  cursor: ew-resize; z-index: 3;
}
.maker .tab-character .cv-fp-resize:hover,
.maker .tab-character .cv-fp-resize.dragging { background: var(--accent-glow); }

/* Right-click mini-menu on the preview character (native Copy Image copies
   the raw hi-DPR backing store — unfixable — so this offers the app's real
   export paths). Appended to <body>, so no .maker scope. */
.cv-ctx-menu {
  position: fixed;
  z-index: 10020;
  min-width: 160px;
  background: var(--chrome-hi);
  border: 1px solid var(--border-lo);
  border-radius: var(--r);
  box-shadow: 0 8px 24px rgba(20, 24, 40, 0.18);
  padding: var(--sp-1);
  display: flex;
  flex-direction: column;
}
.cv-ctx-menu-item {
  border: 0; background: none; text-align: left; cursor: pointer;
  font: inherit; font-size: var(--fs-4); font-weight: 600; color: var(--text);
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r);
}
.cv-ctx-menu-item:hover { background: var(--chrome-lo); }

/* ── SF2_FIELDS popovers — in-app <select> / colour-input replacements.
   Global (not .tab-character) so the Maps + Scenes colour docks can adopt
   them. Both ride .cv-ctx-menu's shell so they read as one menu system. ── */
.sf2-field-menu { min-width: 132px; max-height: 280px; overflow-y: auto; }
.sf2-field-menu-item { font-size: 11.5px; font-weight: 500; padding: var(--sp-2) var(--sp-3) var(--sp-2) 20px; position: relative; }
.sf2-field-menu-item.is-current { font-weight: 700; color: var(--accent, #4a90e2); }
.sf2-field-menu-item.is-current::before {
  content: '✓'; position: absolute; left: 7px; font-size: var(--fs-2);
}
.sf2-color-pop { min-width: 0; width: 192px; gap: var(--sp-3); padding: var(--sp-3); }
.sf2-color-recents { display: flex; flex-wrap: wrap; gap: 3px; min-height: 16px; }
.sf2-color-recents-empty { font-size: var(--fs-2); color: var(--text-dim); line-height: 16px; }
.sf2-color-recent {
  width: 16px; height: 16px; padding: 0; border-radius: var(--r); cursor: pointer;
  border: 1px solid rgba(0, 0, 0, 0.22);
}
.sf2-color-recent:hover { outline: 2px solid var(--accent, #4a90e2); outline-offset: 1px; }
.sf2-color-sv, .sf2-color-hue {
  display: block; border-radius: var(--r); cursor: crosshair; touch-action: none;
  border: 1px solid var(--border-lo);
}
.sf2-color-foot { display: flex; align-items: center; gap: var(--sp-2); }
.sf2-color-preview {
  width: 22px; height: 22px; flex: 0 0 auto; border-radius: var(--r);
  border: 1px solid var(--border-lo);
}
.sf2-color-hex {
  flex: 1 1 auto; min-width: 0; height: 22px; font: inherit; font-size: var(--fs-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--chrome-hi); color: var(--text); padding: 0 var(--sp-2);
}

/* Sidebar toggle buttons (Flatten Hair) — pressed state. */
.maker .tab-character .cv-shell-btn.is-active {
  background: var(--accent-btn);
  border-color: var(--accent-lo);
  color: var(--text-inv);
}

.maker .tab-character .cv-pose-picker-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.maker .tab-character .cv-pose-picker-section {
  display: flex;
  flex-direction: column;
}
.maker .tab-character .cv-pose-picker-section-label {
  font-size: var(--fs-2);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  padding: var(--sp-4) var(--sp-5) var(--sp-2);
  border-bottom: 1px solid var(--border);
  background: var(--chrome-lo, #f8f8fa);
}

/* ── EDIT-mode layers panel (Sprint 4 C8) ─────────────────────────
   Mirrors cv-layer-panel's right-side slide-in, but populated from
   the draft's selectedItems (one row per equipped slot). Reuses the
   .layers-open class on .preview-col so the picker dock + canvas
   padding rules already account for it. */
.maker .tab-character .cv-edit-layers-panel {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 220px;
  background: var(--chrome-hi);
  border-left: 1px solid var(--border);
  box-shadow: -4px 0 16px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 10;
  transform: translateX(100%);
  transition: transform 0.2s ease;
}
.maker .tab-character .cv-edit-layers-panel.open    { transform: translateX(0); }
.maker .tab-character .cv-edit-layers-panel[hidden] { display: none; }

/* ── Adjustments BOTTOM dock (Derek 2026-08-03 — Maps/Scenes parity).
   Was a 264px side dock at right:220px (UX-polish R3-S6). Two problems:
   it stacked with the layers panel and padded the canvas frame 508px,
   decentering the character on every open; and 264px forced Looks /
   Adjust / Fill+Gradients into one scroll column, so a slider and a
   blend mode were never visible together. Now a slide-up overlay with
   the three groups as side-by-side columns — same shape as
   .map-color-dock. Height via --cv-adjust-h (drag the top edge —
   SF2_PANEL_RESIZE axis 'y'). ── */
.maker .tab-character .cv-adjust-panel {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: var(--cv-adjust-h, 290px);
  min-height: 170px; max-height: 62%;
  background: var(--chrome-hi);
  border-top: 2px solid var(--accent);
  border-radius: var(--r) var(--r) 0 0;
  box-shadow: 0 -6px 20px rgba(0,0,0,0.18);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Above the item picker / pose dock (z-index 9) — this dock OVERLAYS
     whichever bottom dock is open rather than stacking above it, exactly
     like Maps. Closing it reveals the picker again. */
  z-index: 30;
  transform: translateY(105%);
  transition: transform 0.24s cubic-bezier(.3,.9,.3,1);
}
.maker .tab-character .cv-adjust-panel.open    { transform: translateY(0); }
.maker .tab-character .cv-adjust-panel[hidden] { display: none; }
/* Stop before the layers column / after the left docks — same slots the
   item picker respects, so you can still switch which layer you're
   coloring (and read its palette dot) while the dock is open. */
.maker .tab-character .preview-col.layers-open      .cv-adjust-panel { right: 220px; }
.maker .tab-character .preview-col.export-open      .cv-adjust-panel { left:  280px; }
.maker .tab-character .preview-col.pose-picker-open .cv-adjust-panel { left:  280px; }
.maker .tab-character .cv-adjust-resize {
  position: absolute; left: 0; right: 0; top: -3px; height: 7px;
  cursor: ns-resize; z-index: 3;
}
/* Row 1a — title, actions, close. */
.maker .tab-character .cv-adjust-topbar {
  flex: 0 0 auto; display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5) 0; font-size: 11.5px; font-weight: 700; color: var(--text);
}
.maker .tab-character .cv-adjust-topbar .cv-adjust-title {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Row 1b — Looks, full dock width. At 706px of inner width all 13 built-in
   cards (~46px each) sit on ONE line with no scroll track; user-saved looks
   spill into a horizontal scroll rather than stealing height. Giving Looks its
   own row is what frees the whole second row for Adjust + fx. */
.maker .tab-character .cv-adjust-looks-row {
  flex: 0 0 auto; display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-1) var(--sp-5) 0;
}
.maker .tab-character .cv-adjust-looks-row .cv-adjust-section-label { flex: 0 0 auto; }
.maker .tab-character .cv-adjust-presets {
  display: flex; flex-wrap: nowrap; gap: var(--sp-1);
  flex: 1 1 auto; min-width: 0; overflow-x: auto; overflow-y: hidden; margin: 0;
  padding-bottom: 2px;
}
.maker .tab-character .cv-adjust-presets .cv-adjust-card {
  flex: 0 0 auto; width: 42px; padding: var(--sp-1) 2px 3px;
}
/* Row 2 — Adjust | Fill & Gradients. Only two columns now, so at a 1200px
   viewport (dock = 730px, it stops before the 220px layers column like the
   item picker) fx gets ~478px instead of the ~266px the three-column layout
   left it — which had squeezed the opacity sliders down to 32px. */
.maker .tab-character .cv-adjust-cols {
  flex: 1 1 auto; min-height: 0;
  display: flex; gap: 16px; align-items: stretch;
  overflow-x: auto; padding: var(--sp-2) var(--sp-5) var(--sp-4);
}
.maker .tab-character .cv-adjust-col {
  display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; min-height: 0;
}
/* Both columns FLEX with equal grow, and fx starts one "fixed-parts delta"
   wider (Derek 2026-08-03 — Adjust was pinned at 212px while fx absorbed all
   the slack, so the two slider tracks were nowhere near the same length).
   Measured non-slider parts: an Adjust row spends 106px (label + number +
   gaps), an fx row 278px (head + blend + number + gaps). Seeding fx's basis
   with that 172px difference means the free space splits evenly and BOTH
   slider tracks land on the same width at any dock size. */
.maker .tab-character .cv-adjust-col-sliders { flex: 1 1 0;     min-width: 190px; }
.maker .tab-character .cv-adjust-col-fx      { flex: 1 1 172px; min-width: 300px; }
.maker .tab-character .cv-adjust-section-label {
  font-size: var(--fs-1); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-dim); margin: 0;
}
.maker .tab-character .cv-adjust-card {
  position: relative;
  appearance: none; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--chrome-lo, #f8f8fa); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: var(--sp-2) 3px var(--sp-1); font: inherit; color: var(--text-mid);
}
.maker .tab-character .cv-adjust-card:hover { border-color: var(--accent); }
.maker .tab-character .cv-adjust-card.is-active {
  border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent);
  color: var(--text);
}
.maker .tab-character .cv-adjust-card-thumb {
  width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
}
.maker .tab-character .cv-adjust-card-thumb canvas { image-rendering: pixelated; }
.maker .tab-character .cv-adjust-card-label {
  font-size: var(--fs-2); line-height: 1.1; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.maker .tab-character .cv-adjust-card-del {
  position: absolute; top: -5px; right: -5px; width: 14px; height: 14px;
  display: none; align-items: center; justify-content: center;
  background: var(--red, #d64545); color: #fff; border-radius: 50%;
  font-size: var(--fs-2); line-height: 1;
}
.maker .tab-character .cv-adjust-card:hover .cv-adjust-card-del { display: flex; }

/* ── R5-V6 — Fill & Gradients compositing tools.
   One COMPACT ROW per effect (2026-08-03): the bottom dock is short, so the
   old two-row card (head over controls) fit only ~2 effects before scrolling.
   Row order: toggle+label · colour swatches · blend · opacity. ── */
.maker .tab-character .cv-adjust-fx {
  display: flex; flex-direction: column; gap: var(--sp-2); margin: 0;
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
}
.maker .tab-character .cv-fx-group {
  border: 1px solid var(--border); border-radius: var(--r); padding: var(--sp-1) var(--sp-3);
  display: flex; flex-direction: row; align-items: center; gap: var(--sp-3);
  background: var(--chrome-lo, #f8f8fa);
}
/* FIXED width, not auto (Derek 2026-08-03). Fill carries ONE colour swatch and
   the other three carry TWO, so an auto-width head measured 88px vs 112-115px
   and pushed Fill's blend + opacity 24px left of everyone else's. Sizing the
   head for the two-swatch case parks every row's controls on the same x. */
.maker .tab-character .cv-fx-head {
  display: flex; align-items: center; gap: var(--sp-1);
  flex: 0 0 116px;   /* 68 label + 4 + 20 swatch + 4 + 20 swatch */
}
.maker .tab-character .cv-fx-on {
  flex: 0 0 68px; display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-3); font-weight: 600; color: var(--text-mid); cursor: pointer;
  white-space: nowrap;   /* "Grad Map" wrapped and made its row taller than the rest */
}
.maker .tab-character .cv-fx-group.is-on .cv-fx-on { color: var(--text); }
/* Colour swatch — an in-app button (SF2_FIELDS.colorPicker), not
   <input type="color"> whose popup is an OS window we can't style or put a
   recent-colours row on. `--sw` carries the value so sync only writes a var. */
.maker .tab-character .cv-fx-swatch {
  flex: 0 0 auto; width: 20px; height: 17px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--sw, #888); cursor: pointer;
}
.maker .tab-character .cv-fx-swatch:hover { border-color: var(--accent); }
.maker .tab-character .cv-fx-controls { display: flex; align-items: center; gap: var(--sp-2); flex: 1 1 auto; min-width: 0; }
.maker .tab-character .cv-fx-group:not(.is-on) .cv-fx-controls,
.maker .tab-character .cv-fx-group:not(.is-on) .cv-fx-swatch { opacity: 0.45; pointer-events: none; }
/* Blend picker — same in-app menu family as the right-click menus. Fixed width
   so the blend column lines up across rows; ▾ marks it as a menu. */
.maker .tab-character .cv-fx-blend {
  flex: 0 0 88px; height: 21px; font: inherit; font-size: var(--fs-3);
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--chrome-hi); color: var(--text);
  text-align: left; padding: 0 16px 0 var(--sp-2); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-size: 4px 4px, 4px 4px;
  background-position: right 8px center, right 4px center;
  background-repeat: no-repeat;
}
.maker .tab-character .cv-fx-blend:hover { border-color: var(--accent); }
/* Opacity keeps BOTH the drag and the exact-entry field. The range absorbs all
   the slack in the row — with the two-row layout that's ~220px at a 1200px
   viewport (it was 32px when fx was one of three columns). */
.maker .tab-character .cv-fx-controls input[type="range"] { flex: 1 1 auto; min-width: 40px; }
.maker .tab-character .cv-fx-controls input[type="number"] {
  flex: 0 0 auto; width: 40px; height: 21px; font-size: var(--fs-3); padding: 0 2px;
  border: 1px solid var(--border); border-radius: var(--r); background: var(--chrome-hi); color: var(--text);
  text-align: center;    /* 100 clipped at 36px/left-aligned */
}
/* One shared hint under the fx column (it used to be per-group, hidden until
   that effect was on — four copies of near-identical text in a short dock). */
.maker .tab-character .cv-fx-hint {
  margin: var(--sp-2) 0 0; font-size: var(--fs-2); line-height: 1.3; color: var(--text-dim); flex: 0 0 auto;
}
#cv-fx-handles { image-rendering: auto; touch-action: none; }

.maker .tab-character .cv-edit-layers-header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--border);
  background: var(--chrome-lo, #f8f8fa);
  font-size: var(--fs-4);
  font-weight: 600;
  flex: 0 0 auto;
}
.maker .tab-character .cv-edit-layers-explode-btn {
  margin-left: auto;
  padding: 2px var(--sp-3);
  font-size: var(--fs-3);
  font-weight: 500;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r);
  cursor: pointer;
  color: var(--text);
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.maker .tab-character .cv-edit-layers-explode-btn:hover {
  background: rgba(0, 0, 0, 0.05);
}

.maker .tab-character .cv-edit-layers-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-1);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.maker .tab-character .cv-edit-layers-list.dragging { user-select: none; cursor: pointer; }
/* Character-layer group: collapsing the header hides every layer row
   below it. Header stays visible. Click on header toggles. */
.maker .tab-character .cv-edit-layers-list.is-group-collapsed .cv-edit-layer-row,
.maker .tab-character .cv-edit-layers-list.is-group-collapsed .cv-edit-layers-empty {
  display: none;
}
.maker .tab-character .cv-edit-layer-group-header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  cursor: pointer;
  user-select: none;
  border-radius: var(--r);
  font-weight: 600;
}
.maker .tab-character .cv-edit-layer-group-header:hover {
  background: rgba(255, 255, 255, 0.04);
}
.maker .tab-character .cv-edit-layer-group-header:focus-visible {
  outline: 1px solid rgba(100, 160, 220, 0.5);
  outline-offset: -1px;
}
.maker .tab-character .cv-edit-layer-group-caret {
  pointer-events: none; /* clicks fall through to header */
  transition: transform 0.12s ease;
}
.maker .tab-character .cv-edit-layer-group-header[data-collapsed="true"] .cv-edit-layer-group-caret {
  transform: rotate(-90deg);
}
/* Library-style foot-anchored thumb: clip overflow, position img
   absolutely. Same pattern as .cv-char-thumb-lib in the saved-chars
   list, scaled down to the 28×28 layer-row box. */
.maker .tab-character .cv-edit-layer-group-thumb {
  overflow: hidden;
  position: relative;
}
.maker .tab-character .cv-edit-layer-group-thumb img {
  display: block;
  image-rendering: pixelated;
  max-width: none;
  max-height: none;
}
.maker .tab-character .cv-edit-layer-group-thumb.loading {
  background: rgba(255, 255, 255, 0.04);
  border-radius: var(--r);
}
/* "Character Layer" sublabel — same muted style as item slot labels. */
.maker .tab-character .cv-edit-layer-group-header .cv-edit-layer-slot {
  font-weight: 600;
}

/* W3.2 — Flatten Hair sub-row: indented, smaller, checkbox-style
   toggle. Appears in exploded view directly after the front-most
   hair piece. Acts as a master "force hairOverHead visible" switch. */
.maker .tab-character .cv-edit-layer-row.cv-edit-layer-subrow {
  padding-left: 16px;
  font-size: var(--fs-3);
  cursor: pointer;
  opacity: 0.9;
}
.maker .tab-character .cv-edit-layer-row.cv-edit-layer-subrow:hover {
  background: rgba(255, 255, 255, 0.04);
}
.maker .tab-character .cv-edit-layer-flatten-hair.is-on {
  opacity: 1;
}
.maker .tab-character .cv-edit-layer-flatten-hair-toggle {
  /* Borrow the standard layer-btn box but display the checkbox glyph
     plain; .is-active gets the accent color. */
  font-size: 14px;
}
.maker .tab-character .cv-edit-layer-flatten-hair-toggle.is-active {
  color: #6cf;
}
/* Keep the thumb column's footprint so the row's eye/text columns
   line up with sibling layer rows, but don't render a thumb. */
.maker .tab-character .cv-edit-layer-flatten-hair-spacer {
  visibility: hidden;
}

.maker .tab-character .cv-edit-layer-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r);
  background: transparent;
  transition: background 100ms, box-shadow 100ms;
}

/* Eye + Solo column on the left of the icon (Sprint 5 C3) — mirrors
   the legacy viewer layer panel arrangement. Buttons stack side by
   side; row layout reads: [👁️ S] [thumb] [text] [🎨 ✕]. */
.maker .tab-character .cv-edit-layer-eye-solo {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.maker .tab-character .cv-edit-layer-eye-solo .cv-edit-layer-btn {
  width: 20px;
  height: 20px;
  font-size: var(--fs-4);
}
.maker .tab-character .cv-edit-layer-eye-solo .cv-edit-layer-solo {
  font-size: var(--fs-2);
  font-weight: 600;
}
.maker .tab-character .cv-edit-layer-row:hover {
  background: rgba(0, 0, 0, 0.04);
}
/* Highlighted when the color popover is bound to this row. */
.maker .tab-character .cv-edit-layer-row.is-palette-active {
  background: rgba(74, 144, 226, 0.10);
  box-shadow: inset 2px 0 0 var(--accent, #4a90e2);
}

/* Palette button — "has-mods" dot signals the item carries any
   non-default color modifier without forcing the user to open the
   popover to check. */
.maker .tab-character .cv-edit-layer-palette {
  position: relative;
}
.maker .tab-character .cv-edit-layer-palette.has-mods::after {
  content: '';
  position: absolute;
  top: 1px;
  right: 1px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent, #4a90e2);
  box-shadow: 0 0 0 1px var(--chrome-hi);
}

/* ── Color palette popover (Sprint 4 C9) ──────────────────────────
   Floating panel positioned to the left of the layers dock, anchored
   to whichever row owns _cvColorPopoverItem. Five mods (hue / sat /
   brightness / contrast / alpha) each as a slider + paired number
   input. Copy/paste replicates mods across items in one click. */
/* Slider rows + footer below live INSIDE the adjust side dock now
   (R3-S6) — the floating .cv-edit-color-popover shell is gone. */
.maker .tab-character .cv-edit-color-popover-btn {
  background: transparent;
  border: 1px solid var(--border-lo);
  border-radius: var(--r);
  padding: 2px var(--sp-2);
  font-size: var(--fs-3);
  cursor: pointer;
  color: var(--text);
  height: 22px;
  line-height: 1;
}
.maker .tab-character .cv-edit-color-popover-btn:hover:not(:disabled) {
  background: rgba(0, 0, 0, 0.05);
}
.maker .tab-character .cv-edit-color-popover-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Lives in the bottom dock's sliders column — the column supplies the
   padding, so no inset here (2026-08-03). */
.maker .tab-character .cv-edit-color-popover-body {
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.maker .tab-character .cv-edit-color-row {
  display: grid;
  grid-template-columns: 56px 1fr 38px;   /* sized for the bottom dock's sliders column */
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-3);
}
.maker .tab-character .cv-edit-color-row label {
  color: var(--text);
}
.maker .tab-character .cv-edit-color-row input[type="range"] {
  margin: 0;
  width: 100%;
  cursor: pointer;
}
.maker .tab-character .cv-edit-color-row input[type="number"] {
  padding: 2px var(--sp-1);
  font-size: var(--fs-3);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--input-bg, #fff);
  color: var(--text);
  width: 100%;
  font-variant-numeric: tabular-nums;
  -moz-appearance: textfield;
}
.maker .tab-character .cv-edit-color-row input[type="number"]::-webkit-outer-spin-button,
.maker .tab-character .cv-edit-color-row input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Actions row, pinned to the bottom of the dock's Looks column (was a
   full-width bar across the old side panel). */
/* Actions, inline in the dock's top bar beside the title (2026-08-03 — was a
   stacked block at the foot of the Looks column, which cost that column ~30px
   of height and wrapped onto two lines). */
.maker .tab-character .cv-edit-color-popover-footer {
  flex: 0 0 auto;
  padding: 0;
  border-top: none;
  background: none;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.maker .tab-character .cv-edit-color-popover-footer-actions {
  display: flex;
  gap: 3px;
}
.maker .tab-character .cv-edit-color-popover-footer .btn {
  font-size: var(--fs-3);
  padding: 3px var(--sp-3);
  width: auto;
}

.maker .tab-character .cv-edit-layer-thumb {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  position: relative;
}
.maker .tab-character .cv-edit-layer-thumb canvas {
  image-rendering: pixelated;
  max-width: 100%;
  max-height: 100%;
}
.maker .tab-character .cv-edit-layer-thumb.loading {
  border-radius: var(--r);
  animation: cvPickerThumbShimmer 1.2s ease-in-out infinite;
}

.maker .tab-character .cv-edit-layer-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}
.maker .tab-character .cv-edit-layer-slot {
  font-size: var(--fs-1);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.maker .tab-character .cv-edit-layer-name {
  font-size: var(--fs-3);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.maker .tab-character .cv-edit-layer-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.maker .tab-character .cv-edit-layer-btn {
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  color: var(--text-dim);
  border-radius: var(--r);
}
.maker .tab-character .cv-edit-layer-btn:hover {
  background: rgba(0, 0, 0, 0.08);
  color: var(--text);
}
.maker .tab-character .cv-edit-layer-row.is-hidden .cv-edit-layer-thumb,
.maker .tab-character .cv-edit-layer-row.is-hidden .cv-edit-layer-name {
  opacity: 0.4;
}

/* Solo button (Sprint 5 C3) — circle indicator mirroring the legacy
   .cv-layer-solo style. Empty circle by default; accent-filled when
   active on this row. Pop animation on toggle for tactile feedback. */
.maker .tab-character .cv-edit-layer-eye-solo .cv-edit-layer-solo {
  background: none;
  border: 1.5px solid currentColor;   /* icon-color parity (Derek 2026-07-27) */
  border-radius: 50%;
  width: 12px;
  height: 12px;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.1s, border-color 0.1s, transform 0.1s;
}
.maker .tab-character .cv-edit-layer-eye-solo .cv-edit-layer-solo:hover {
  border-color: var(--accent);
  background: transparent;
}
.maker .tab-character .cv-edit-layer-eye-solo .cv-edit-layer-solo.is-active {
  background: var(--accent, #4a90e2);
  border-color: var(--accent, #4a90e2);
}
.maker .tab-character .cv-edit-layer-eye-solo .cv-edit-layer-solo.is-active:hover {
  background: var(--accent, #4a90e2);
}

/* Skeleton rows (Body / Head) — required for render. Remove button
   disabled; row tinted slightly so the user knows it's anchored. */
.maker .tab-character .cv-edit-layer-row.is-skeleton {
  background: rgba(0, 0, 0, 0.02);
}
.maker .tab-character .cv-edit-layer-row.is-skeleton .cv-edit-layer-slot {
  color: var(--text-dim);
}
.maker .tab-character .cv-edit-layers-list:has(.cv-edit-layer-row.is-soloed) .cv-edit-layer-row:not(.is-soloed) .cv-edit-layer-thumb,
.maker .tab-character .cv-edit-layers-list:has(.cv-edit-layer-row.is-soloed) .cv-edit-layer-row:not(.is-soloed) .cv-edit-layer-text {
  opacity: 0.4;
}
.maker .tab-character .cv-edit-layers-empty {
  padding: 16px var(--sp-5);
  font-size: var(--fs-3);
  color: var(--text-dim);
  text-align: center;
  line-height: 1.5;
}

/* ── EDIT-mode picker — slides up from the BOTTOM (Sprint 4 C4) ── */
.maker .tab-character .cv-picker-panel {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: var(--cv-picker-h, 320px);
  min-height: 140px;
  background: var(--chrome-hi);
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 16px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 9;
  transform: translateY(100%);
  transition: transform 0.2s ease, left 0.2s ease, right 0.2s ease;
}
.maker .tab-character .cv-picker-panel.open    { transform: translateY(0); }
.maker .tab-character .cv-picker-panel[hidden] { display: none; }

/* Coexistence with side panels — mirror the pose/emote layout: stop
   before the layers column from the right, start after the export
   dock from the left. */
.maker .tab-character .preview-col.layers-open .cv-picker-panel { right: 220px; }
.maker .tab-character .preview-col.export-open .cv-picker-panel { left:  280px; }
/* Sprint 7 W5 — Pose Picker side dock occupies the same left slot as
   Zip Export (mutex'd in JS) so the bottom picker shifts the same way. */
.maker .tab-character .preview-col.pose-picker-open .cv-picker-panel { left: 280px; }

.maker .tab-character .cv-picker-header {
  /* Sprint 5 — 3-column grid so the centre slot (filter row) stays
     anchored regardless of which category is open. Left and right
     each take 1fr; centre auto-sizes to the filter, so when the
     filter is hidden the centre column collapses and title/actions
     don't shift to fill the gap. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-5);
  border-bottom: 1px solid var(--border);
  background: var(--chrome-lo, #f8f8fa);
  font-size: var(--fs-4);
  font-weight: 600;
  flex: 0 0 auto;
  gap: var(--sp-5);
}
.maker .tab-character .cv-picker-actions {
  justify-self: end;
}
.maker .tab-character .cv-picker-header-left {
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
}
/* Centre slot of the header grid. Holds the color row OR filter row
   (mutually exclusive across categories — Hair has colors, gear has
   filters, skeleton slots have neither). Flex so when both happen to
   be visible they sit side-by-side instead of stacking. */
.maker .tab-character .cv-picker-header-center {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}
.maker .tab-character .cv-picker-title {
  letter-spacing: 0.02em;
  white-space: nowrap;
}
/* Sprint 9 — header acts as a collapse toggle (parity with the
   Chat tab's bottom dock). The chevron lives next to the title;
   the whole header is clickable except the action/filter regions
   (handled in JS). Cursor + hover tint give the affordance. */
.maker .tab-character .cv-picker-header {
  cursor: pointer;
  user-select: none;
  transition: background 0.12s, color 0.12s;
}
.maker .tab-character .cv-picker-header:hover {
  background: rgba(0, 0, 0, 0.04);
}
.maker .tab-character .cv-picker-header-center,
.maker .tab-character .cv-picker-actions {
  cursor: default; /* re-allow normal cursor over interactive regions */
}
.maker .tab-character .cv-picker-collapse-chevron {
  font-size: var(--fs-2);
  color: var(--text-dim);
  margin-left: 2px;
  transition: transform 0.18s ease;
}
.maker .tab-character .cv-picker-panel.is-collapsed .cv-picker-collapse-chevron {
  transform: rotate(-90deg);
}
.maker .tab-character .cv-picker-panel.is-collapsed {
  /* !important beats the inline height variable (--cv-picker-h)
     while collapsed; clearing the class restores the user height. */
  height: 32px !important;
  min-height: 32px;
}
.maker .tab-character .cv-picker-panel.is-collapsed > :not(.cv-picker-header) {
  display: none !important;
}
.maker .tab-character .cv-picker-panel.is-collapsed .cv-dock-resize-handle {
  display: none;
}
.maker .tab-character .cv-picker-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}
.maker .tab-character .cv-picker-density-btn {
  border: 1px solid var(--border);
  background: transparent;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-3);
  padding: 3px var(--sp-4);
  color: var(--text);
  border-radius: var(--r);
}
.maker .tab-character .cv-picker-density-btn:hover {
  background: rgba(0, 0, 0, 0.04);
}

.maker .tab-character .cv-picker-grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-5);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: var(--sp-4);
  align-content: start;
}

/* ── Color picker row (Sprint 4 — Hair) ────────────────────────────
   Lives inside the picker header, to the right of the title. Each
   swatch is a solid color square — clicking re-renders every grid
   cell to the matching color variant (cells whose style lacks that
   color stay on the base). Hidden for non-variant slots. */
.maker .tab-character .cv-picker-color-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-weight: 500;
}
.maker .tab-character .cv-picker-color-row[hidden] {
  display: none;
}
.maker .tab-character .cv-picker-color-label {
  font-size: var(--fs-3);
  color: var(--text-dim);
  font-weight: 500;
  letter-spacing: 0.02em;
}
.maker .tab-character .cv-picker-color-swatches {
  display: flex;
  gap: var(--sp-1);
}
.maker .tab-character .cv-picker-color-swatch {
  width: 18px;
  height: 18px;
  border: 1px solid rgba(0, 0, 0, 0.18);
  border-radius: var(--r);
  cursor: pointer;
  padding: 0;
  background-color: var(--swatch-color, #888);
  box-shadow: 0 0 0 1px transparent;
  transition: transform 100ms, box-shadow 100ms;
}
.maker .tab-character .cv-picker-color-swatch:hover {
  transform: scale(1.1);
}
.maker .tab-character .cv-picker-color-swatch.active {
  box-shadow: 0 0 0 2px var(--accent, #4a90e2);
  border-color: rgba(0, 0, 0, 0.35);
}

/* ── Filter row (Sprint 4 C6) ──────────────────────────────────────
   Same position/scale as the color row. Class pills are solo-click
   (single active = pin; click again = restore all). Gender chips
   toggle independently. State persists across sessions via
   localStorage (sf2_cv_picker_filter). */
.maker .tab-character .cv-picker-filter-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  font-weight: 500;
}
.maker .tab-character .cv-picker-filter-row[hidden] {
  display: none;
}
.maker .tab-character .cv-picker-filter-label {
  font-size: var(--fs-3);
  color: var(--text-dim);
  letter-spacing: 0.02em;
}
.maker .tab-character .cv-picker-filter-pills {
  display: flex;
  gap: 3px;
}
.maker .tab-character .cv-picker-filter-pills button {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r);
  padding: 2px 3px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.12s, border-color 0.12s, background 0.12s;
  opacity: 0.35;
}
/* Per-version chips (PIRATE hides on MCW/1): our display:flex above beats
   the UA [hidden] rule, so restate it at higher specificity. */
.maker .tab-character .cv-picker-filter-pills button[hidden] {
  display: none;
}
.maker .tab-character .cv-picker-filter-pills button img {
  display: block;
  width: 32px;
  height: 22px;
  image-rendering: pixelated;
}
.maker .tab-character .cv-picker-filter-pills button:hover:not(.active) {
  opacity: 0.7;
}
.maker .tab-character .cv-picker-filter-pills button.active {
  opacity: 1;
  border-color: var(--accent);
  background: rgba(74, 144, 226, 0.12);
}

/* Sprint 7 W5 — responsive collapse. JS toggles these classes off
   .cv-picker-filter-row based on header content fit. First the
   "Filter" label drops, then the whole row (and the filter state
   resets to all-on so nothing stays hidden-but-active). */
.maker .tab-character .cv-picker-filter-row.cv-filter-label-collapsed
  .cv-picker-filter-label { display: none; }
.maker .tab-character .cv-picker-filter-row.cv-filter-row-collapsed { display: none; }

/* ── Picker item button (Sprint 4 C5) ─────────────────────────────── */
.maker .tab-character .cv-picker-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-1);
  border: 1px solid transparent;
  background: rgba(0, 0, 0, 0.025);
  border-radius: var(--r);
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-2);
  transition: background 100ms, border-color 100ms;
}
.maker .tab-character .cv-picker-item:hover {
  background: rgba(0, 0, 0, 0.08);
}
.maker .tab-character .cv-picker-item.active {
  background: rgba(74, 144, 226, 0.14);
  border-color: var(--accent, #4a90e2);
}

/* Phase E (DF-only initiative): items whose worn sprite won't render at the
   active version (placeholder/missing WZ bitmap). Dimmed + desaturated with a
   small warning badge, but still SELECTABLE — the gray is advisory, not a
   block. Verdict comes from the lazy worn-sprite probe (_cvProbeItemRenderable). */
.maker .tab-character .cv-picker-item.is-unrenderable {
  position: relative;
}
.maker .tab-character .cv-picker-item.is-unrenderable .cv-picker-item-thumb,
.maker .tab-character .cv-picker-item.is-unrenderable .cv-picker-item-id {
  opacity: 0.32;
  filter: grayscale(1);
}
.maker .tab-character .cv-picker-item.is-unrenderable::after {
  content: "⚠";
  position: absolute;
  top: 2px;
  right: 3px;
  font-size: var(--fs-1);
  line-height: 1;
  opacity: 0.55;
  pointer-events: none;
}

.maker .tab-character .cv-picker-item-thumb {
  min-width: 36px;
  min-height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  position: relative;
}
.maker .tab-character .cv-picker-item-thumb canvas {
  image-rendering: pixelated;
  display: block;
}

/* ── Library mode (Sprint 5 C2) ───────────────────────────────────
   Bottom dock's default state on /studio/edit/. Shows saved chars
   as click-to-load cards + a search row. Mutually exclusive with
   picker mode via cvDockMode: is-library-mode hides .cv-picker-grid
   and the picker-only header chunks (color row, filter chips,
   Compact toggle). */
.maker .tab-character .cv-picker-panel.is-library-mode .cv-picker-color-row,
.maker .tab-character .cv-picker-panel.is-library-mode .cv-picker-filter-row,
.maker .tab-character .cv-picker-panel.is-library-mode .cv-picker-density-btn,
.maker .tab-character .cv-picker-panel.is-library-mode .cv-picker-grid {
  display: none;
}
.maker .tab-character .cv-picker-panel:not(.is-library-mode) .cv-library-body {
  display: none;
}

.maker .tab-character .cv-library-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.maker .tab-character .cv-library-body[hidden] { display: none; }

.maker .tab-character .cv-library-search-row {
  padding: var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--border);
  background: var(--chrome-lo, #f8f8fa);
}
.maker .tab-character .cv-library-search {
  width: 100%;
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-4);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--input-bg, #fff);
  color: var(--text);
}
.maker .tab-character .cv-library-search:focus {
  outline: none;
  border-color: var(--accent);
}

.maker .tab-character .cv-library-grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-5);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--sp-3);
  align-content: start;
}

.maker .tab-character .cv-library-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-1);
  border: 1px solid transparent;
  background: rgba(0, 0, 0, 0.025);
  border-radius: var(--r);
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-3);
  text-align: center;
  transition: background 100ms, border-color 100ms;
}
.maker .tab-character .cv-library-card:hover {
  background: rgba(0, 0, 0, 0.08);
}
.maker .tab-character .cv-library-card.is-active {
  background: rgba(74, 144, 226, 0.14);
  border-color: var(--accent, #4a90e2);
}

/* Thumb crop window — mirrors the legacy .cv-char-thumb pattern from
   the sidebar char-list: fixed-size overflow:hidden box, image at
   natural pixel size, bottom-anchored so feet land on the lower
   edge (matches how foot-anchored character renders are framed
   elsewhere). No background fill. */
.maker .tab-character .cv-library-card-thumb {
  width: 70px;
  height: 80px;
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  position: relative;
  pointer-events: none;
  background: transparent;
}
.maker .tab-character .cv-library-card-thumb img {
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
  flex-shrink: 0;
  image-rendering: pixelated;
  display: block;
}
.maker .tab-character .cv-library-card-name {
  font-size: var(--fs-3);
  color: var(--text);
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
}
.maker .tab-character .cv-library-empty {
  grid-column: 1 / -1;
  font-size: var(--fs-4);
  color: var(--text-dim);
  text-align: center;
  padding: 32px 16px;
  line-height: 1.5;
}

/* Skeleton-loading shimmer — applied while a thumb is fetching its
   icon (intersection observer has fired, image not yet decoded).
   Animation only runs on cells in flight, so 600+ offscreen cells
   stay cheap. Removed when the canvas is appended or the load fails. */
@keyframes cvPickerThumbShimmer {
  0%, 100% { background: rgba(0, 0, 0, 0.04); }
  50%      { background: rgba(0, 0, 0, 0.12); }
}
.maker .tab-character .cv-picker-item-thumb.loading {
  border-radius: var(--r);
  animation: cvPickerThumbShimmer 1.2s ease-in-out infinite;
}
.maker .tab-character .cv-picker-item-thumb-fail::after {
  content: '?';
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
}
.maker .tab-character .cv-picker-item-id {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-1);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Compact density — hide the label, shrink the cells, tighter grid.
   `auto-fill` keeps the grid responsive to picker width. */
.maker .tab-character .cv-picker-panel.density-compact .cv-picker-grid {
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: var(--sp-2);
  padding: var(--sp-4);
}
.maker .tab-character .cv-picker-panel.density-compact .cv-picker-item {
  padding: var(--sp-1);
}
.maker .tab-character .cv-picker-panel.density-compact .cv-picker-item-id {
  display: none;
}
.maker .tab-character .cv-picker-panel.density-compact .cv-picker-item-thumb {
  width: 32px;
  height: 32px;
}
.maker .tab-character .cv-picker-empty {
  grid-column: 1 / -1;
  font-size: var(--fs-4);
  color: var(--text-dim);
  text-align: center;
  padding: 32px 16px;
  line-height: 1.5;
}

/* When picker is open, inset the preview canvas frame from the
   bottom so the character re-centers above the dock. Padding (not
   `bottom`) — keeps the frame the same size, just moves the content
   anchor. Tracks --cv-picker-h so the resize handle stays in sync. */
.maker .tab-character .preview-col.picker-open #cv-canvas-frame {
  padding-bottom: calc(24px + var(--cv-picker-h, 320px));
}

/* ASY76B: same treatment for the LEFT slide-outs — when the Frame Picker
   opens, inset the canvas frame so the character re-centers in the
   remaining visible strip instead of hiding behind the panel. */
.maker .tab-character .preview-col.pose-picker-open #cv-canvas-frame {
  padding-left: calc(16px + var(--cv-fp-w, 352px));
}

/* ── Bottom-dock resize handle ──
   Sits at the very top edge of any bottom dock (picker, poses).
   The 7px hit zone overhangs the panel's border-top by 3px so a
   user landing on the visual edge still grabs the handle. Hover /
   drag light up a 2px accent line — no chrome added at rest. */
.maker .tab-character .cv-dock-resize-handle {
  position: absolute;
  top: -3px;
  left: 0; right: 0;
  height: 7px;
  cursor: ns-resize;
  z-index: 11;
  background: transparent;
}
.maker .tab-character .cv-dock-resize-handle::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 3px;
  height: 2px;
  background: transparent;
  transition: background 100ms;
}
.maker .tab-character .cv-dock-resize-handle:hover::after,
.maker .tab-character .cv-dock-resize-handle.dragging::after {
  background: var(--accent, #4a90e2);
}

/* ── Advanced Export dock — full-height slide from LEFT ── */
.maker .tab-character .cv-export-panel {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 280px;
  background: var(--chrome-hi);
  border-right: 1px solid var(--border);
  box-shadow: 4px 0 16px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 10;
  transform: translateX(-100%);
  transition: transform 0.2s ease;
}
.maker .tab-character .cv-export-panel.open    { transform: translateX(0); }
.maker .tab-character .cv-export-panel[hidden] { display: none; }

.maker .tab-character .cv-export-body {
  overflow-y: auto;
  scrollbar-width: thin;
  flex: 1;
  padding: var(--sp-4) var(--sp-5) var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  font-size: var(--fs-3);
  color: var(--text);
}

.maker .tab-character .cv-export-section { display: flex; flex-direction: column; gap: var(--sp-2); }
.maker .tab-character .cv-export-section[hidden] { display: none; } /* override the .cv-export-section's display:flex when the section is mode-hidden */
.maker .tab-character .cv-export-section-label {
  font-size: var(--fs-2);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim, #777);
  border-bottom: 1px solid var(--border);
  padding-bottom: 2px;
  margin-bottom: 2px;
}
.maker .tab-character .cv-export-section-sublabel {
  font-size: var(--fs-2);
  color: var(--text-dim, #777);
  min-width: 60px;
}

.maker .tab-character .cv-export-mode-tabs,
.maker .tab-character .cv-export-canvas-tabs { display: flex; gap: var(--sp-1); }
.maker .tab-character .cv-export-tab,
.maker .tab-character .cv-export-canvas-tab {
  flex: 1;
  padding: var(--sp-2) var(--sp-2);
  font-size: var(--fs-3);
  border: 1px solid var(--border);
  background: var(--chrome-lo, #f4f4f4);
  border-radius: var(--r);
  cursor: pointer;
  color: var(--text);
}

.maker .tab-character .cv-export-bbox-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--sp-3);
}
.maker .tab-character .cv-export-bbox-val {
  font-variant-numeric: tabular-nums;
  width: 52px;
  text-align: right;
  font-weight: 600;
  font-size: var(--fs-3);
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r);
  padding: 2px var(--sp-1);
  outline: none;
  -moz-appearance: textfield; /* hide spinner in firefox */
}
.maker .tab-character .cv-export-bbox-val::-webkit-outer-spin-button,
.maker .tab-character .cv-export-bbox-val::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } /* hide spinners in chrome/safari */
.maker .tab-character .cv-export-bbox-val:hover:not(:disabled) { border-color: var(--border); }
.maker .tab-character .cv-export-bbox-val:focus            { border-color: var(--accent, #4a90e2); background: #fff; }
.maker .tab-character .cv-export-bbox-val:disabled         { opacity: 0.6; cursor: not-allowed; }

/* Disabled state — Tight Crop hides slider+alignment behavior. We grey
   rather than hide so the user keeps the layout in view. */
.maker .tab-character .cv-export-bbox-row.disabled,
.maker .tab-character .cv-export-align-row.disabled {
  opacity: 0.4;
  pointer-events: none;
}

/* Beefier slider — visible thumb + track, accent fill. */
.maker .tab-character .cv-export-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  background: var(--chrome-lo, #e1e4e8);
  border-radius: var(--r);
  outline: none;
  cursor: pointer;
}
.maker .tab-character .cv-export-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent, #4a90e2);
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
  cursor: pointer;
}
.maker .tab-character .cv-export-slider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent, #4a90e2);
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
  cursor: pointer;
}

.maker .tab-character .cv-export-sub-row {
  margin-left: 18px;
  position: relative;
}
.maker .tab-character .cv-export-sub-row::before {
  content: "↳";
  position: absolute;
  left: -14px;
  color: var(--text-dim, #999);
  font-size: var(--fs-2);
}
.maker .tab-character .cv-export-sub-row input:disabled + * { opacity: 0.45; }
.maker .tab-character .cv-export-sub-row:has(input:disabled) { opacity: 0.5; }

.maker .tab-character .cv-export-align-row,
.maker .tab-character .cv-export-scale-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.maker .tab-character .cv-export-align-row label { display: inline-flex; align-items: center; gap: 3px; }
.maker .tab-character .cv-export-align-row.disabled { opacity: 0.4; pointer-events: none; }

.maker .tab-character .cv-export-scale-btns { display: inline-flex; gap: 2px; }
.maker .tab-character .cv-export-scale-btn {
  padding: 3px var(--sp-2);
  font-size: var(--fs-3);
  border: 1px solid var(--border);
  background: var(--chrome-lo, #f4f4f4);
  border-radius: var(--r);
  cursor: pointer;
  color: var(--text);
  min-width: 28px;
}

.maker .tab-character .cv-export-beta-badge {
  background: #f4b942;
  color: #5c3a00;
  font-size: var(--fs-1);
  font-weight: 700;
  padding: 1px var(--sp-2);
  border-radius: var(--r);
  letter-spacing: 0.05em;
  margin-left: auto;
}

.maker .tab-character .cv-export-tree {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  background: var(--chrome-lo, #f4f4f4);
  border: 1px solid var(--border);
  border-radius: var(--r);
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: var(--fs-3);
  line-height: 1.5;
  color: var(--text);
  white-space: pre;
  overflow-x: auto;
}

.maker .tab-character .cv-export-actions {
  /* PRAFR5: sticky to the panel bottom so Download ZIP never scrolls
     off-screen on short windows. */
  position: sticky;
  bottom: 0;
  background: var(--chrome);
  margin-top: auto;
  padding-top: var(--sp-3);
  padding-bottom: var(--sp-2);
  border-top: 1px solid var(--border);
}
.maker .tab-character .cv-export-actions .btn { width: 100%; }

.maker .tab-character .cv-layer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-3);
  font-weight: 600;
  color: var(--text);
  background: #f0f0f4;
  flex-shrink: 0;
}

.maker .tab-character .cv-layer-close {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
  color: var(--text-dim);
  line-height: 1;
  padding: 0 2px;
}
.maker .tab-character .cv-layer-close:hover { color: var(--text); }

.maker .tab-character .cv-layer-list {
  overflow-y: auto;
  scrollbar-width: thin;
  flex: 1;
  padding: var(--sp-1);
}
.maker .tab-character .cv-layer-list.dragging { user-select: none; cursor: pointer; }

.maker .tab-character .cv-layer-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-2);
  border-radius: var(--r);
  font-size: var(--fs-3);
  color: var(--text);
  height: 36px;
}
.maker .tab-character .cv-layer-row:hover { background: rgba(0,0,0,0.04); }
.maker .tab-character .cv-layer-row.hidden-item  { opacity: 0.45; }
.maker .tab-character .cv-layer-row.solo-excluded { opacity: 0.25; }

.maker .tab-character .cv-layer-eye {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 0;
  flex-shrink: 0;
  color: var(--text-dim);
}
.maker .tab-character .cv-layer-eye:hover { color: var(--text); }
.maker .tab-character .cv-layer-eye:disabled { cursor: default; opacity: 0.5; }

.maker .tab-character .cv-layer-solo {
  background: none;
  border: 1.5px solid var(--border-lo);
  border-radius: 50%;
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  cursor: pointer;
  padding: 0;
  transition: background 0.1s, border-color 0.1s;
}
.maker .tab-character .cv-layer-solo:hover { border-color: var(--accent); }
.maker .tab-character .cv-layer-solo.active {
  background: var(--accent);
  border-color: var(--accent);
}

@keyframes cv-btn-pop {
  0%   { transform: scale(1); }
  30%  { transform: scaleX(1.35) scaleY(0.65); }
  65%  { transform: scaleX(0.85) scaleY(1.2); }
  100% { transform: scale(1); }
}
.cv-layer-btn-pop { animation: cv-btn-pop 0.2s ease-out; }

.maker .tab-character .cv-layer-slot {
  font-weight: 600;
  color: var(--text-dim);
  min-width: 42px;
  font-size: var(--fs-2);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.maker .tab-character .cv-layer-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

.maker .tab-character .cv-layer-icon {
  flex-shrink: 0;
  image-rendering: pixelated;
  display: block;
  width: 32px;
  height: 32px;
  object-fit: none;
  object-position: center center;
  overflow: hidden;
}

/* ── Expand toggle in layer header ── */
.maker .tab-character .cv-layer-expand-btn {
  background: none; border: none; cursor: pointer;
  color: var(--text-dim); font-size: 14px; line-height: 1; padding: 0;
  transition: color 0.12s;
}
.maker .tab-character .cv-layer-expand-btn:hover { color: var(--text); }
.maker .tab-character .cv-layer-expand-btn.active { color: var(--accent); }

/* ── "Explode Layers" toggle — text variant in the layer header ── */
.maker .tab-character .cv-layer-explode-btn {
  margin-left: auto;
  margin-right: var(--sp-1);
  padding: 2px var(--sp-3);
  font-size: var(--fs-3);
  font-weight: 500;
  letter-spacing: 0.02em;
  background: var(--chrome-lo);
  border: 1px solid var(--border-lo);
  border-radius: var(--r);
  color: var(--text);
  line-height: 1.3;
}
.maker .tab-character .cv-layer-explode-btn:hover {
  background: var(--chrome-lo);
  border-color: var(--border-lo);
}
.maker .tab-character .cv-layer-explode-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}

/* ── Sub-rows — indented, slightly smaller than parent rows ── */
.maker .tab-character .cv-layer-subrow {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 0 var(--sp-2) 0 28px;
  height: 26px; font-size: var(--fs-2); color: var(--text-dim);
}
.maker .tab-character .cv-layer-subrow:hover { background: rgba(0,0,0,0.04); }
.maker .tab-character .cv-layer-subrow.hidden-item { opacity: 0.45; }
.maker .tab-character .cv-layer-subrow-loading {
  font-style: italic; padding-left: 28px; height: 22px;
}

/* ── Parent row with some sub-parts hidden — subtle tint on the eye ── */
.maker .tab-character .cv-layer-row.partly-hidden .cv-layer-eye { opacity: 0.55; }

/* ── Poses slide-up panel ── */
.maker .tab-character .cv-pose-panel {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: var(--cv-poses-h, 280px);
  min-height: 140px;
  background: var(--chrome-hi);
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 16px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  z-index: 9;
  transform: translateY(100%);
  transition: transform 0.2s ease;
}
.maker .tab-character .cv-pose-panel.open  { transform: translateY(0); }
.maker .tab-character .cv-pose-panel[hidden] { display: none; }

/* ── Emotes slide-up panel (mirrors pose panel, sits behind it) ── */
.maker .tab-character .cv-emote-panel {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: var(--cv-emote-h, 96px);
  min-height: 60px;
  background: var(--chrome-hi);
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 16px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  z-index: 8;
  transform: translateY(100%);
  transition: transform 0.2s ease, bottom 0.2s ease;
}
.maker .tab-character .cv-emote-panel.open  { transform: translateY(0); }
.maker .tab-character .cv-emote-panel[hidden] { display: none; }

/* When pose panel is open, emote panel shifts up above it */
.maker .tab-character .preview-col.pose-open .cv-emote-panel {
  bottom: var(--cv-poses-h, 280px);
}

/* When layers panel is open, bottom panels stop before the layers column */
.maker .tab-character .preview-col.layers-open .cv-pose-panel,
.maker .tab-character .preview-col.layers-open .cv-emote-panel {
  right: 220px;
}

/* When export panel is open, bottom panels start after the export column */
.maker .tab-character .preview-col.export-open .cv-pose-panel,
.maker .tab-character .preview-col.export-open .cv-emote-panel {
  left: 280px;
}

.maker .tab-character .cv-pose-panel-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-2) var(--sp-5);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-3); font-weight: 600;
  background: var(--chrome-lo);
  flex-shrink: 0;
}

/* ── Vertical scrolling chip grid ── */
.maker .tab-character .cv-pose-strip {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  flex: 1;
}
.maker .tab-character .cv-pose-strip::-webkit-scrollbar { width: 4px; }
.maker .tab-character .cv-pose-strip::-webkit-scrollbar-thumb {
  background: var(--border-lo); border-radius: var(--r);
}

/* ── Emote chips (Sprint 5 C4): compact single-row, soft-fill, no
   text. Mirrors the bottom-tray picker grid aesthetic — emotes
   read as identifiable icons rather than labeled chips. The strip
   itself becomes a horizontal scroller so the whole roster fits
   in one row without resizing the dock. */
.maker .tab-character .cv-pose-picker-section--emotes {
  flex: 0 0 auto;
}
/* Non-emote sections (Poses) fill the remaining vertical space so
   their internal pose strip can scroll without forcing the whole
   picker-body to scroll. */
.maker .tab-character .cv-pose-picker-section:not(.cv-pose-picker-section--emotes) {
  flex: 1 1 0;
  min-height: 0;
}
.maker .tab-character .cv-emote-strip {
  flex: 0 0 auto;
  flex-direction: row;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4);
  cursor: grab;                /* hint that the strip is drag-scrollable */
  user-select: none;
}
.maker .tab-character .cv-emote-strip.is-dragging {
  cursor: grabbing;
}
.maker .tab-character .cv-emote-strip.is-dragging .cv-pose-chip {
  pointer-events: none;        /* don't fire chip hover while drag-scrolling */
}
.maker .tab-character .cv-emote-strip::-webkit-scrollbar { height: 4px; }
.maker .tab-character .cv-emote-strip::-webkit-scrollbar-thumb {
  background: var(--border-lo); border-radius: var(--r);
}
.maker .tab-character .cv-emote-strip .cv-pose-chip {
  width: 36px;
  min-width: 36px;
  height: 36px;
  padding: 3px;
  background: rgba(0, 0, 0, 0.03);
  border: 1px solid transparent;
  border-radius: var(--r);
  gap: 0;
}
.maker .tab-character .cv-emote-strip .cv-pose-chip:hover {
  background: rgba(0, 0, 0, 0.08);
  border-color: transparent;
}
.maker .tab-character .cv-emote-strip .cv-pose-chip.active {
  background: rgba(74, 158, 192, 0.18);
  border-color: var(--accent);
}
.maker .tab-character .cv-emote-strip .cv-pose-chip-thumb {
  width: 100%;
  height: 100%;
  display: flex; align-items: center; justify-content: center;
}
/* Emote PNGs render at NATURAL 1x size (they're ~22px art; stretching
   them to fill the 30px box caused fractional-scale artifacts — R4-U5). */
.maker .tab-character .cv-emote-strip .cv-pose-chip-thumb img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  image-rendering: pixelated;
}
.maker .tab-character .cv-emote-strip .cv-pose-chip-label {
  display: none;
}

/* Frame Picker side dock (was Pose Picker; R4-U5) — frame cards match
   the bottom thumbnail-grid language: each card owns a light gradient
   background, icon area, and an fN label below (mob/pet/NPC parity).
   Scoped to the picker dock so legacy /studio/ keeps its old style. */
.maker .tab-character #cv-pose-picker-panel #cv-pose-strip .cv-pose-chip {
  width: 52px;
  min-width: 52px;
  height: auto;
  padding: var(--sp-1) 2px 3px;
  background: var(--cell-grad, linear-gradient(#fff, #f2f4f6));
  border: 1px solid var(--border-lo);
  border-radius: var(--r);
  gap: 2px;
}
.maker .tab-character #cv-pose-picker-panel #cv-pose-strip .cv-pose-chip:hover {
  background: var(--cell-grad-hover, #fff);
  border-color: var(--accent);
}
.maker .tab-character #cv-pose-picker-panel #cv-pose-strip .cv-pose-chip.active {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.maker .tab-character #cv-pose-picker-panel #cv-pose-strip .cv-pose-chip-thumb {
  width: 44px;
  height: 40px;
  display: flex; align-items: center; justify-content: center;
}
.maker .tab-character #cv-pose-picker-panel #cv-pose-strip .cv-pose-chip-thumb img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  image-rendering: pixelated;
}
.maker .tab-character #cv-pose-picker-panel #cv-pose-strip .cv-pose-chip-label {
  display: block;
  font-size: var(--fs-2);
  line-height: 1;
  color: var(--text-dim);
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.maker .tab-character .cv-pose-chip {
  display: flex; flex-direction: column; align-items: center;
  gap: 3px; flex-shrink: 0;
  background: var(--chrome-lo);
  border: 1.5px solid var(--border-lo);
  border-radius: var(--r);
  padding: var(--sp-1) var(--sp-1) 3px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
  width: 48px;
}
.maker .tab-character .cv-pose-chip:hover { background: #fff; border-color: var(--accent); }
.maker .tab-character .cv-pose-chip.active {
  background: rgba(74,158,192,0.15); border-color: var(--accent);
}

.maker .tab-character .cv-pose-chip-thumb {
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; flex-shrink: 0;
}
.maker .tab-character .cv-pose-chip-thumb img {
  width: 36px; height: 36px;
  object-fit: contain; image-rendering: pixelated;
}
.maker .tab-character .cv-pose-chip-thumb.loading::before {
  content: '';
  display: block; width: 28px; height: 32px;
  background: var(--border-lo); border-radius: var(--r);
  animation: cv-chip-pulse 1.2s ease-in-out infinite;
}
@keyframes cv-chip-pulse {
  0%, 100% { opacity: 0.5; }
  50%       { opacity: 1;   }
}

.maker .tab-character .cv-pose-chip-label {
  font-size: var(--fs-1); color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 44px; text-align: center; line-height: 1.2;
}
.maker .tab-character .cv-pose-chip.active .cv-pose-chip-label {
  color: var(--accent-text); font-weight: 600;
}

/* ── Re-centering: smooth canvas padding when panels open/close ── */
.maker .tab-character #cv-canvas-frame {
  transition: padding-right 0.2s ease, padding-bottom 0.2s ease;
  outline: none;
}
/* W3.1 — subtle focus ring signals that Ctrl/Cmd+C will copy the
   viewport (vs. passing through to text-copy). Inset so it doesn't
   shift layout. */
.maker .tab-character #cv-canvas-frame:focus,
.maker .tab-character #cv-canvas-frame:focus-visible {
  outline: 1px solid rgba(100, 160, 220, 0.5);
  outline-offset: -1px;
}

/* Slot-anchored shimmer overlay (Sprint 5; dormant as of Sprint 7
   — hover-trigger removed in W1.2, click-trigger removed when its
   stationary brightened-layer-in-front-of-bouncing-canvas read as
   the just-equipped layer being stuck in mid-air). Styles kept in
   case we revive a flash-without-overlap variant. The overlay is
   foot-anchored the same way as the main canvas (via
   _cvDrawIntoPreview) and gets its top/left set in JS to match the
   main canvas's offset, so its content lines up exactly with the
   corresponding region of the main render. Its source canvas is
   rendered with `onlySlotCategory` so it contains only the pixels
   for one layer (face / cap / hair / full body / …) — no DOM mask
   needed; the alpha channel is the mask. Default invisible; the
   `is-shimmer` class fires opacity 0→1→0 with a brightness pulse. */
.maker .tab-character #cv-preview-slot-overlay {
  position: absolute;
  pointer-events: none;
  display: block;
  opacity: 0;
  image-rendering: pixelated;
  z-index: 3;
}
.maker .tab-character #cv-preview-slot-overlay.is-shimmer {
  animation: cv-slot-shimmer 0.45s ease-out forwards;
}
@keyframes cv-slot-shimmer {
  0%   { opacity: 1; filter: brightness(1.7); }
  100% { opacity: 0; filter: brightness(1);   }
}

/* Layers panel (220px from right) */
.maker .tab-character .preview-col.layers-open #cv-canvas-frame {
  padding-right: 244px;   /* 24px base + 220px panel */
}

/* Adjust dock open (2026-08-03) — now a BOTTOM dock, so it insets the frame
   from the bottom like the picker/pose docks instead of padding 508px off the
   right (which decentered the character on every open). It OVERLAYS whichever
   other bottom dock is open, so when both are up the padding takes the taller
   of the two — keeps the character and its on-stage gradient handles above the
   dock. The frame's ResizeObserver re-anchors into the remaining viewport
   (same mechanism as every dock); .layers-open's padding-right still applies
   independently since these are different properties. */
.maker .tab-character .preview-col.adjust-open #cv-canvas-frame {
  padding-bottom: calc(24px + var(--cv-adjust-h, 290px));
}
.maker .tab-character .preview-col.adjust-open.picker-open #cv-canvas-frame {
  padding-bottom: calc(24px + max(var(--cv-adjust-h, 290px), var(--cv-picker-h, 320px)));
}
.maker .tab-character .preview-col.adjust-open.pose-open #cv-canvas-frame {
  padding-bottom: calc(24px + max(var(--cv-adjust-h, 290px), var(--cv-poses-h, 280px)));
}
.maker .tab-character .preview-col.adjust-open.picker-open.pose-open #cv-canvas-frame {
  padding-bottom: calc(24px + max(var(--cv-adjust-h, 290px), var(--cv-picker-h, 320px), var(--cv-poses-h, 280px)));
}

/* Export dock + Pose Picker side dock (both 280px from left;
   mutually exclusive — they share the same slot). Shifts the
   character so it stays visible in the part of the viewport not
   covered by the dock. */
.maker .tab-character .preview-col.export-open #cv-canvas-frame,
.maker .tab-character .preview-col.pose-picker-open #cv-canvas-frame {
  padding-left: 304px;    /* 24px base + 280px panel */
}

/* Pose panel alone — tracks --cv-poses-h for live resize */
.maker .tab-character .preview-col.pose-open #cv-canvas-frame {
  padding-bottom: calc(24px + var(--cv-poses-h, 280px));
}

/* Emote panel alone (height tracked via --cv-emote-h JS var) */
.maker .tab-character .preview-col.emote-open #cv-canvas-frame {
  padding-bottom: calc(24px + var(--cv-emote-h, 140px));
}

/* Both pose + emote panels open */
.maker .tab-character .preview-col.pose-open.emote-open #cv-canvas-frame {
  padding-bottom: calc(24px + var(--cv-poses-h, 280px) + var(--cv-emote-h, 140px));
}

/* ── Frame section inside pose panel ── */
.maker .tab-character .cv-pose-frame-section {
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  background: var(--chrome-lo);
  padding: var(--sp-1) var(--sp-4) var(--sp-2);
}
.maker .tab-character .cv-pose-frame-label {
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px 0 var(--sp-1);
}
.maker .tab-character .cv-pose-frame-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
}

/* Frame chips are narrower than pose chips */
.maker .tab-character .cv-frame-chip {
  width: 40px;
}
.maker .tab-character .cv-frame-chip .cv-pose-chip-thumb {
  width: 32px;
  height: 32px;
}
.maker .tab-character .cv-frame-chip .cv-pose-chip-thumb img {
  width: 32px;
  height: 32px;
  object-fit: contain;
}
.maker .tab-character .cv-frame-chip .cv-pose-chip-label {
  max-width: 36px;
}

/* ── Pose panel expand mode — one row per pose ── */
.maker .tab-character .cv-pose-strip.expanded {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4) var(--sp-4);
}
.maker .tab-character .cv-pose-expand-row {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  flex: 0 0 auto;               /* size to chip height; strip handles overflow */
  align-items: center;
  gap: var(--sp-2);
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 2px;          /* room for the row's own scrollbar */
  scrollbar-width: thin;
}
.maker .tab-character .cv-pose-expand-row-label {
  flex: 0 0 56px;
  position: sticky;             /* stays visible when row scrolls horizontally */
  left: 0;
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--text);
  text-transform: lowercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 var(--sp-2) 0 2px;
  background: var(--chrome-hi);  /* hide chips scrolling under sticky cell */
  align-self: stretch;
  display: flex;
  align-items: center;
}
.maker .tab-character .cv-pose-expand-row::-webkit-scrollbar { height: 4px; }
.maker .tab-character .cv-pose-expand-row::-webkit-scrollbar-thumb {
  background: var(--border-lo); border-radius: var(--r);
}

/* Pose name label inside expand-mode frame chips */
.maker .tab-character .cv-frame-chip-pose {
  display: block;
  font-size: var(--fs-1);
  color: var(--text-dim);
  max-width: 36px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.2;
}

/* ─── Snail Loader (MVP) — ZIP export progress overlay ───────────────
   Positioned inside #cv-canvas-frame to cover the canvas viewport
   while the export pipeline runs. A Blue Snail walks the leading
   edge of a progress bar; cancel button on the right wires to an
   AbortController via the JS component. */
.maker .sf2-snail-loader {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(200, 205, 212, 0.55);
  backdrop-filter: blur(3px);
  pointer-events: none;          /* let clicks fall through except on cancel */
  z-index: 20;
  /* Honors the same panel-open padding rules as #cv-canvas-frame
     (see below) so the bar re-centers like the character does. */
  transition: padding-right 0.2s ease, padding-bottom 0.2s ease,
              padding-left  0.2s ease;
}
.maker .sf2-snail-loader-inner {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: min(360px, 80%);
  pointer-events: auto;
}
.maker .sf2-snail-loader-bar-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.maker .sf2-snail-loader-bar {
  position: relative;
  flex: 1;
  height: 18px;
  background: var(--chrome-lo, #ececec);
  border: 1px solid var(--border);
  border-radius: var(--r);
  /* visible so the snail can ride above */
  overflow: visible;
}
.maker .sf2-snail-loader-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 0%;
  background: linear-gradient(90deg, var(--accent-lo, #b8d4e6), var(--accent, #4a9ec0));
  border-radius: var(--r);
  transition: width 150ms ease-out;
}
/* Dual-layer percentage: black (over gray) and white (clipped to fill). */
.maker .sf2-snail-loader-pct {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-3);
  font-weight: 600;
  font-feature-settings: "tnum";
  pointer-events: none;
}
.maker .sf2-snail-loader-pct-bg { color: #1a1a1a; }
.maker .sf2-snail-loader-pct-fg {
  color: #fff;
  /* JS sets clip-path inset from the right by (100 − pct)% so the
     white label is visible only over the teal fill. */
  clip-path: inset(0 100% 0 0);
  transition: clip-path 150ms ease-out;
}
.maker .sf2-snail-loader-snail {
  position: absolute;
  left: 0%;
  bottom: 0;                       /* base sits on bar bottom */
  /* translateX(-50%) re-centers the sprite on the leading edge;
     scaleX(-1) flips the WZ-default left-facing walk so the snail
     walks rightward along the bar. */
  transform: translateX(-50%) scaleX(-1);
  image-rendering: pixelated;
  pointer-events: none;
  transition: left 150ms ease-out;
}
.maker .sf2-snail-loader-detail {
  font-size: var(--fs-2);
  color: var(--text-dim, #777);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.maker .sf2-snail-loader-cancel {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--r);
  width: 22px;
  height: 22px;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  color: var(--text-dim, #777);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.maker .sf2-snail-loader-cancel:hover {
  color: var(--text);
  border-color: var(--text-dim, #777);
}

/* Re-center the loaders' content the same way #cv-canvas-frame does
   when side/bottom panels are open. Both overlays are `inset: 0` so
   their padding pushes the centered inner content out of the panel's
   area, keeping the spinner / snail aligned with where the character
   sits at rest.
   Two loaders here:
     .canvas-loading    — the Loading… overlay during character renders
     .sf2-snail-loader  — the Zip Export progress overlay
   Both react to the same panel states. */
.maker .tab-character .preview-col.layers-open .canvas-loading,
.maker .tab-character .preview-col.layers-open .sf2-snail-loader {
  padding-right: 244px;
}
.maker .tab-character .preview-col.export-open .canvas-loading,
.maker .tab-character .preview-col.export-open .sf2-snail-loader,
.maker .tab-character .preview-col.pose-picker-open .canvas-loading,
.maker .tab-character .preview-col.pose-picker-open .sf2-snail-loader {
  padding-left: 304px;
}
.maker .tab-character .preview-col.pose-open .canvas-loading,
.maker .tab-character .preview-col.pose-open .sf2-snail-loader {
  padding-bottom: calc(24px + var(--cv-poses-h, 280px));
}
.maker .tab-character .preview-col.emote-open .canvas-loading,
.maker .tab-character .preview-col.emote-open .sf2-snail-loader {
  padding-bottom: calc(24px + var(--cv-emote-h, 140px));
}
.maker .tab-character .preview-col.pose-open.emote-open .canvas-loading,
.maker .tab-character .preview-col.pose-open.emote-open .sf2-snail-loader {
  padding-bottom: calc(24px + var(--cv-poses-h, 280px) + var(--cv-emote-h, 140px));
}
.maker .tab-character .preview-col.picker-open .canvas-loading,
.maker .tab-character .preview-col.picker-open .sf2-snail-loader {
  padding-bottom: calc(24px + var(--cv-picker-h, 320px));
}

/* ── Sprint 5 C5 — Save / Share / Discard modals ────────────────
   Three siblings inside .tab-character, all built around the same
   .cv-modal primitive (backdrop + dialog). Hidden by default; JS
   removes the `hidden` attribute and toggles the `is-open` class
   for the fade-in. */
.maker .tab-character .cv-modal {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.maker .tab-character .cv-modal[hidden] { display: none; }
/* The discard confirm is the top-most interrupt: it can open ON TOP of the
   New Character version picker (both are siblings at 10000, and the picker
   comes later in the DOM, so equal z-indexes painted it OVER the confirm —
   Discord feedback round, 2026-07-26). Cancel still returns to the picker. */
.maker .tab-character #cv-discard-modal { z-index: 10001; }
.maker .tab-character .cv-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}
.maker .tab-character .cv-modal-dialog {
  position: relative;
  background: #fff;
  border-radius: var(--r);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
  min-width: 360px;
  max-width: 520px;
  padding: 0;
  display: flex;
  flex-direction: column;
  font-size: 13px;
  color: var(--text);
}
.maker .tab-character .cv-modal-dialog--narrow { min-width: 320px; max-width: 360px; }
.maker .tab-character .cv-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-6) 18px var(--sp-4);
  border-bottom: 1px solid var(--border-lo);
}
.maker .tab-character .cv-modal-title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
}
.maker .tab-character .cv-modal-close {
  appearance: none;
  background: transparent;
  border: 0;
  font-size: 22px;
  line-height: 1;
  color: var(--text-dim);
  cursor: pointer;
  padding: 0 var(--sp-1);
}
.maker .tab-character .cv-modal-close:hover { color: var(--text); }
.maker .tab-character .cv-modal-body {
  padding: var(--sp-6) 18px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.maker .tab-character .cv-modal-prompt {
  margin: 0;
  color: var(--text-dim);
  line-height: 1.4;
}
.maker .tab-character .cv-modal-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.maker .tab-character .cv-modal-field-label {
  font-size: var(--fs-3);
  color: var(--text-dim);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.maker .tab-character .cv-modal-input {
  appearance: none;
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: var(--sp-3) var(--sp-4);
  font: inherit;
  background: var(--input-bg, #fff);
}
.maker .tab-character .cv-modal-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(74, 158, 192, 0.18);
}
/* Sprint 10 W2 — save-time privacy + copy-access controls */
.maker .tab-character .cv-save-privacy { gap: var(--sp-3); }
.maker .tab-character .cv-priv-opts {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.maker .tab-character .cv-priv-opt {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.maker .tab-character .cv-priv-opt:hover { border-color: var(--accent); }
/* The display:flex above beats [hidden]'s UA style — re-assert it (parked Public). */
.maker .tab-character .cv-priv-opt[hidden] { display: none; }
/* Private is Discord-auth only — locked for guests (click shows the explainer). */
.maker .tab-character .cv-priv-opt.is-locked { opacity: 0.5; cursor: not-allowed; }
.maker .tab-character .cv-priv-opt.is-locked:hover { border-color: var(--border); }
.maker .tab-character .cv-priv-opt:has(input:checked) {
  border-color: var(--accent);
  background: rgba(74, 158, 192, 0.08);
}
.maker .tab-character .cv-priv-opt input { margin-top: 2px; flex: none; }
.maker .tab-character .cv-priv-opt-text {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}
.maker .tab-character .cv-priv-opt-text small {
  color: var(--text-dim);
  font-size: var(--fs-3);
}
.maker .tab-character .cv-priv-clone {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  cursor: pointer;
  font-size: 13px;
}
.maker .tab-character .cv-priv-clone input { flex: none; }
.maker .tab-character .cv-priv-note {
  margin: 0;
  font-size: var(--fs-3);
  color: var(--text-dim);
  line-height: 1.4;
}

/* Sprint 10 — the same privacy block reused in the Tile Map save modal, which
   lives outside .tab-character so the rules above don't reach it. Same inner
   classes (cv-priv-*); re-scope under the .tiles-save-privacy wrapper. */
.maker .tiles-save-privacy { display: flex; flex-direction: column; gap: var(--sp-3); margin: var(--sp-1) 0 2px; text-align: left; }
.maker .tiles-save-privacy .cv-modal-field-label { font-size: var(--fs-4); font-weight: 600; color: var(--text-dim); }
.maker .tiles-save-privacy .cv-priv-opts { display: flex; flex-direction: column; gap: var(--sp-2); }
.maker .tiles-save-privacy .cv-priv-opt {
  display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r); cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.maker .tiles-save-privacy .cv-priv-opt:hover { border-color: var(--accent); }
.maker .tiles-save-privacy .cv-priv-opt:has(input:checked) {
  border-color: var(--accent); background: rgba(74, 158, 192, 0.08);
}
.maker .tiles-save-privacy .cv-priv-opt input { margin-top: 2px; flex: none; }
.maker .tiles-save-privacy .cv-priv-opt-text { display: flex; flex-direction: column; line-height: 1.3; }
.maker .tiles-save-privacy .cv-priv-opt-text small { color: var(--text-dim); font-size: var(--fs-3); }
.maker .tiles-save-privacy .cv-priv-clone { display: flex; align-items: center; gap: var(--sp-3); cursor: pointer; font-size: 13px; }
.maker .tiles-save-privacy .cv-priv-clone input { flex: none; }
.maker .tiles-save-privacy .cv-priv-note { margin: 0; font-size: var(--fs-3); color: var(--text-dim); line-height: 1.4; }
.maker .tab-character .cv-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  padding: var(--sp-4) 18px var(--sp-6);
  border-top: 1px solid var(--border-lo);
}
.maker .tab-character .cv-modal-btn {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--chrome-lo);
  border-radius: var(--r);
  padding: var(--sp-3) var(--sp-6);
  font: inherit;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.maker .tab-character .cv-modal-btn:hover { background: #fff; border-color: var(--accent); }
.maker .tab-character .cv-modal-btn-primary:hover {
  background: var(--accent);
  filter: brightness(1.05);
}
.maker .tab-character .cv-modal-btn-danger {
  background: #c0392b;
  border-color: #c0392b;
  color: #fff;
}
.maker .tab-character .cv-modal-btn-danger:hover {
  background: #c0392b;
  filter: brightness(1.05);
}

.maker .tab-character .cv-share-row {
  display: flex;
  gap: var(--sp-2);
}
.maker .tab-character .cv-share-input {
  flex: 1;
  min-width: 0;
  background: var(--chrome-lo);
  cursor: text;
  user-select: all;
}

/* ● dirty indicator inside the Save Character button — the literal
   bullet glyph in the button text is enough, but bump weight so it
   reads against the button background. */
.maker .tab-character #cv-edit-save-btn:not(:disabled) { color: var(--accent-text); }

/* ── R6: Characters rail section + gated categories + create modal ── */
.maker .tab-character .cv-rail-sep {
  flex: 0 0 auto;
  height: 1px;
  margin: var(--sp-1) var(--sp-2);
  background: var(--border);
}
.maker .tab-character .bd-rail-row.is-disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.maker .tab-character .cv-library-card-new {
  border: 1px dashed var(--border-lo);
  background: var(--chrome);
}
.maker .tab-character .cv-library-card-thumb-new {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  color: var(--text-dim);
}
.maker .tab-character .cv-library-card-new:hover {
  border-color: var(--accent);
  background: var(--accent-glow);
}
/* Region cards inside the create modal — bigger, stacked, clickable. */
.maker .tab-character .cv-create-region-options--modal {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.maker .tab-character .cv-create-region-options--modal .cv-create-region-option {
  padding: var(--sp-5) var(--sp-6);
  font-size: 13px;
}

/* ── View mode (library-model L2, revised): editing is OPEN — tweak anything;
   only Save is gated (the Make-a-copy CTA is the persistence path). ── */
.maker .tab-character.is-view-only #cv-edit-save-btn { pointer-events: none; opacity: 0.5; }
/* The banner sits below the top toolbar, sandwiched clear of the side docks. */
.maker .tab-character .preview-col.layers-open #cv-view-banner { margin-right: 220px; }
.maker .tab-character .preview-col.export-open #cv-view-banner,
.maker .tab-character .preview-col.pose-picker-open #cv-view-banner { margin-left: 280px; }

/* ─── Snail loader: indeterminate mode (LOAD-6) ───────────────────────────
   For a phase with no honest count — the map loader's "Map data" step, one
   opaque fetch+parse measured between 0.4s and 4.4s. The fill sweeps instead of
   claiming a percentage, and the % label is hidden until a real number exists
   (D4: a bar that lies is worse than a spinner). */
.maker .sf2-snail-loader.is-indeterminate .sf2-snail-loader-pct { opacity: 0; }
.maker .sf2-snail-loader.is-indeterminate .sf2-snail-loader-fill {
  width: 38%;
  animation: sf2-snail-sweep 1.25s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
.maker .sf2-snail-loader.is-indeterminate .sf2-snail-loader-snail {
  animation: sf2-snail-sweep-snail 1.25s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes sf2-snail-sweep       { 0% { margin-left: -38%; } 100% { margin-left: 100%; } }
@keyframes sf2-snail-sweep-snail { 0% { left: 0%; }          100% { left: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .maker .sf2-snail-loader.is-indeterminate .sf2-snail-loader-fill,
  .maker .sf2-snail-loader.is-indeterminate .sf2-snail-loader-snail { animation: none; }
  .maker .sf2-snail-loader.is-indeterminate .sf2-snail-loader-fill { width: 100%; opacity: 0.35; }
}

/* Embedded mode — the bar sits inside a container that owns its own positioning
   (Maps' #map-canvas-loading), so drop the full-bleed scrim and let it flow. */
.maker .sf2-snail-loader.is-embedded {
  position: static;
  inset: auto;
  background: none;
  backdrop-filter: none;
  padding: 0;
  margin-top: var(--sp-4);
  z-index: auto;
  display: block;
}
.maker .sf2-snail-loader.is-embedded .sf2-snail-loader-inner { width: 240px; }
/* No AbortController wired on the map path yet — hide the dead cancel button
   rather than showing one that does nothing. */
.maker .sf2-snail-loader.is-embedded .sf2-snail-loader-cancel { display: none; }
.maker .sf2-snail-loader.is-embedded .sf2-snail-loader-detail {
  font-size: var(--fs-3);
  color: var(--text-dim);
  text-align: center;
}

/* View-only preset in the library grid — mirrors the Files tab's ref bookmark
   so "not yours" reads the same on both surfaces (library parity, 2026-08-02). */
.maker .cv-library-card.is-ref .cv-library-card-thumb { position: relative; }
.maker .cv-library-card-ref {
  position: absolute; top: 3px; right: 3px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; border-radius: var(--r);
  background: var(--chrome-hi); border: 1px solid var(--border);
  color: var(--accent); pointer-events: none;
}
