/* Mojavatar Maker - page-specific styles.
   The card layout, form language, buttons, steppers, sliders and the pinnable floating
   preview pane all come from /styles/tool-cards.css (shared with the pixel-art tag
   generator, which this page's design mirrors). This file keeps only what is unique to
   the 3D viewer: the stage and canvas, its overlays, the transform rails, the parts
   outliner, the body-type pills, the skin entry card, and the modals.
   Load order: tool-cards.css first, then this file. */

/* ===== VISIBILITY CONTRACT =====
   All show/hide goes through setVisible(), which writes the `hidden` attribute. The browser's own
   `[hidden] { display: none }` rule is weak: any class here with a `display` of its own would beat
   it and the element would refuse to hide. So state it loudly, once, and never write inline
   display from JS again. */
#viewer-section [hidden] {
  display: none !important;
}

/* Buttons do not inherit the page font by default (UA gives them Arial on Windows), and the
   tag generator's text buttons all carry the site font via their shared classes. This page's
   own button classes (.vp-btn, .vp-seg-btn, .vp-btn-text, ...) get the same treatment in one
   stroke; icon-only buttons render no text, so the blanket is safe. */
#viewer-section button,
.vp-modal button {
  font-family: inherit;
}

/* A dependent control (shadow intensity, outline width) is dimmed in place rather than hidden, so
   ticking its parent checkbox does not make the panel jump under the cursor. */
.is-disabled {
  opacity: 0.45;
}
.is-disabled input,
.is-disabled select,
.is-disabled button {
  cursor: not-allowed;
}

#viewer-section { margin-top: 20px; }

/* ============================================================================
   THE STAGE
   The screen inside the Preview card. The canvas carries its own height via
   aspect-ratio, which is WIDTH-driven, so a mobile URL bar hiding on scroll can
   never change it (the vh/dvh scroll-jump lesson).
   ============================================================================ */
/* .preview-card centers its children (align-items from tool-cards.css), so the stage
   must claim the full card width itself. */
#viewer-section .preview-card .vp-stage {
  display: flex;
  flex-direction: column;
  width: 100%;
  min-height: 0;
  border-radius: 12px;
  overflow: hidden;
  background: #4a4a4a;
}
#viewer-section .vp-canvas-col {
  position: relative;
  width: 100%;
  min-width: 0;
  min-height: 0;   /* the canvas buffer size must never become the column's floor, or the
                      column can grow with the renderer but never shrink back (ratchet) */
  aspect-ratio: 16 / 9;
  max-height: 560px;
}

/* NOTHING may be placed inside #viewerContainer: the raycast maths assumes the
   container rect IS the canvas rect. Overlays are siblings, in .vp-overlay. */
#viewer-section .viewer-container {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
  background: #4a4a4a;
  cursor: grab;
  touch-action: auto;
}
#viewer-section .viewer-container:active { cursor: grabbing; }
#viewer-section .viewer-container canvas {
  display: block;
  width: 100% !important;                   /* load-bearing: renderer.setSize writes inline px
                                               during the 512 to 4096 square exports */
  height: 100% !important;
  touch-action: none;                       /* the canvas eats the gesture; the page still
                                               scrolls from the gutters either side */
}

.viewer-loading {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 10px;
  color: #ccc;
  font-size: 15px;
  z-index: 2;
}
.viewer-loading .spinner { animation: spin 1s linear infinite; }
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Overlay layer: a SIBLING of #viewerContainer, never a child. */
#viewer-section .vp-overlay { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
#viewer-section .vp-overlay > * { pointer-events: auto; }

/* The export viewfinder. Full canvas height at the export aspect, centered; the huge
   box-shadow dims everything outside the frame like a camera crop, and the clip wrapper
   stops that shadow dead at the canvas edge. Never interactive. */
#viewer-section .vp-frame-clip {
  position: absolute; inset: 0;
  overflow: hidden;
  pointer-events: none !important;
  z-index: 1;                     /* under the badge (4), drop target (5) and loading text */
}
#viewer-section .vp-frame {
  position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  aspect-ratio: var(--export-ar, 1);
  max-width: 100%;
  border: 1px dashed rgba(255, 255, 255, 0.4);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.22);
}
/* Nothing outside the frame to dim when the export aspect matches the canvas or wider. */
#viewer-section .vp-frame.is-full { border: none; box-shadow: none; }

#viewer-section .vp-badge {
  position: absolute; left: 50%; top: 12px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; border-radius: 12px;
  background: rgba(24, 28, 33, 0.9); border: 1px solid rgba(255, 255, 255, 0.14);
  color: #e8eaed; font-size: 13px; font-weight: 600; z-index: 4;
}
#viewer-section .vp-badge-x {
  border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 8px;
  background: rgba(255, 255, 255, 0.1); color: #e8eaed;
  padding: 4px 8px; font-size: 12px; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
#viewer-section .vp-badge-x:hover { background: rgba(255, 255, 255, 0.2); }

/* Drop target. The canvas accepts dropped files; this tells you so. */
#viewer-section .vp-drop {
  position: absolute; inset: 10px; z-index: 5;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border: 3px dashed rgba(255, 255, 255, 0.7); border-radius: 16px;
  background: rgba(24, 28, 33, 0.72); color: #fff;
  pointer-events: none;                       /* must not eat the drop event itself */
  backdrop-filter: blur(2px);
}
#viewer-section .vp-drop-title { font-size: 17px; font-weight: 700; }
#viewer-section .vp-drop-sub { font-size: 13px; opacity: 0.8; }

/* ============================================================================
   PREVIEW CARD EXTRAS
   The card itself (pin, drag grip, resize corner, dock zone) is tool-cards.css.
   Mojavatar adds the undo/redo pair in the header, and lets the stage fill the
   floating pane's height when the pane is pinned and resized.
   ============================================================================ */
/* Undo/redo sit just left of the pin button, so the top-left corner stays free for the
   drag grip when the pane floats. */
#viewer-section .preview-history {
  position: absolute; right: 52px; top: 12px; z-index: 6;
  display: flex; gap: 6px;
}
#viewer-section .preview-history .vp-chip-history { display: flex; gap: 6px; }
#viewer-section .vp-tb-icon {
  width: 32px; height: 32px; padding: 0; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid #d1d5db; border-radius: 50%;
  background: #fff; color: #666; cursor: pointer;
}
#viewer-section .vp-tb-icon:hover:not(:disabled) { border-color: #3984a7; background: #f0f8ff; color: #3984a7; }
#viewer-section .vp-tb-icon:disabled { opacity: 0.5; cursor: not-allowed; }
/* Align the floating pane's drag grip with the header row (undo/redo/pin sit at top 12). */
#viewer-section .preview-drag-handle { top: 12px; left: 10px; }

/* A pinned pane carries an inline height (its docked height, or the user's resize):
   the stage stretches into it and the canvas follows, instead of the 16:9 preference. */
#viewer-section .preview-card.pinned .vp-stage { flex: 1 1 0; min-height: 100px; }
#viewer-section .preview-card.pinned .vp-canvas-col {
  height: 100%;
  aspect-ratio: auto;
  max-height: none;
}

/* ============================================================================
   ACTIONS CARD
   ============================================================================ */
/* The ? button rides the card corner, mirroring the tag generator's config gear
   (its .config-btn, verbatim; the shared sheet keeps .actions-card unpositioned). */
#viewer-section .actions-card { position: relative; }
#viewer-section .config-btn {
  padding: 6px;
  border: 2px solid #d1d5db;
  border-radius: 50%;
  background: #fff;
  cursor: pointer;
  transition: all 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: #666;
  position: absolute;
  right: 12px;
  top: 12px;
}
#viewer-section .config-btn:hover { background: #f0f8ff; border-color: #3984a7; color: #3984a7; }
#viewer-section .config-btn svg { width: 18px; height: 18px; display: block; }
/* The ? glyph is 320:512, and at the full 18px height it crowds the circle: one notch down. */
#viewer-section #helpBtn svg { width: 10px; height: 14px; }

/* The status line is ALWAYS in the layout (it ships with default text, and keeps its row
   even if a message is cleared), so posting a status never shoves the card taller. */
#viewer-section .tool-status { min-height: 20px; text-align: center; font-size: 13px; color: #666; }
/* Clear is destructive: a ghost-red hover on an otherwise quiet secondary button. */
#viewer-section #resetAllBtn:hover { border-color: #dc3545; color: #dc3545; background: #fdf3f4; }

/* ============================================================================
   SHARED FIELD EXTRAS (small vocabulary the shared sheet does not carry)
   ============================================================================ */
#viewer-section .vp-hint { margin: 0; text-align: center; font-size: 13px; color: #666; line-height: 1.5; }
#viewer-section .vp-label { font-size: 14px; font-weight: 500; color: #666; }
/* Group heads keep the page's centered-label language; the small action chip rides
   the right edge without pushing the label off-center. */
#viewer-section .vp-group-head { position: relative; display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 28px; margin-bottom: 8px; }
#viewer-section .vp-group-head label { margin: 0; }
#viewer-section .vp-group-head .vp-btn-text { position: absolute; right: 0; top: 50%; transform: translateY(-50%); }
/* Slider labels sit left, like the tag generator's. */
#viewer-section .input-group .slider-label { text-align: left; }
/* Small corner actions (Reset, Show all, Remove custom skin): the tag generator's
   gradient-reset chip, verbatim. */
#viewer-section .vp-btn-text {
  background: none; border: 1px solid #d1d5db; color: #888;
  font-size: 12px; padding: 3px 12px; border-radius: 12px; cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
#viewer-section .vp-btn-text:hover { color: #555; border-color: #999; }
#viewer-section .vp-btn-text:disabled { color: #c9ced4; border-color: #e5e7eb; cursor: default; }   /* nothing to remove */
#viewer-section .vp-readout { text-align: center; font-size: 13px; color: #666; font-variant-numeric: tabular-nums; }
/* Sliders sit tight against their value span without the wrapper gap. */
#viewer-section .slider-wrapper input[type="range"] { margin-right: 10px; }

/* ============================================================================
   SKIN CARD
   ============================================================================ */
/* Body type: Auto / Steve / Alex pills, every width. (A phone-only native dropdown used
   to swap in here; that was a Round 5 toolbar-width constraint, and once body type moved
   into the full-width Skin panel the pills fit everywhere and the unstyled select was the
   only thing the swap still delivered.) */
#viewer-section .mv-bodytype { display: flex; justify-content: center; }
#viewer-section .mv-bodytype #modelToggle { display: flex; width: 100%; gap: 4px; background: #eef2f4; padding: 4px; border-radius: 24px; }
#viewer-section .mv-bodytype .vp-seg-btn {
  flex: 1; min-width: 60px; min-height: 34px; padding: 0 14px;
  border: none; background: transparent; border-radius: 20px;
  font-size: 14px; font-weight: 600; color: #667; white-space: nowrap; cursor: pointer;
}
#viewer-section .mv-bodytype .vp-seg-btn.active { background: #fff; color: #3984a7; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); }
/* Auto has RESOLVED to Steve or Alex: mark the resolved pill with a quiet dot, so Auto
   is never making a silent choice on the user's behalf (the FAQ promises this). */
#viewer-section .mv-bodytype .toggle-btn.is-resolved:not(.active) { color: #24677f; position: relative; }
#viewer-section .mv-bodytype .toggle-btn.is-resolved:not(.active)::after {
  content: ""; position: absolute; left: 50%; bottom: 3px; transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%; background: #3984a7;
}
#viewer-section .username-row { display: flex; align-items: stretch; gap: 8px; }
#viewer-section .username-row #usernameInput { flex: 1; min-width: 0; width: auto; margin: 0; }
#viewer-section .username-row #loadSkinBtn { width: auto; flex: 0 0 auto; height: auto; padding: 0 22px; }

/* The upload area is the tag generator's icon drop zone, verbatim: dashed rest state,
   tinted hover, solid border while a file hovers over it. */
#viewer-section .icon-drop-zone {
  border: 2px dashed #d1d5db;
  border-radius: 18px;
  padding: 22px 20px;
  text-align: center;
  cursor: pointer;
  transition: all 0.2s ease;
  background: #fff;
  min-height: 90px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}
#viewer-section .icon-drop-zone:hover {
  border-color: #3984a7;
  background: #f0f8ff;
}
#viewer-section .icon-drop-zone.drag-over {
  border-color: #3984a7;
  background: #e6f3ff;
  border-style: solid;
}
#viewer-section .drop-zone-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
#viewer-section .drop-icon {
  width: 24px;
  height: 24px;
  color: #666;
}
#viewer-section .icon-drop-zone p {
  margin: 0;
  color: #666;
  font-size: 14px;
  font-weight: 500;
  word-break: break-word;
  text-align: center;
}

/* The loaded skin, as a nested white entry (the tag generator's color-entry pattern):
   name + download action in the header, its settings underneath. */
#viewer-section .skin-entry {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px; background: #fff; border: 1px solid #e9ecef; border-radius: 12px;
}
#viewer-section .skin-entry-header { display: flex; align-items: center; gap: 8px; }
#viewer-section .vp-skin-name {
  flex: 1; min-width: 0; font-size: 13px; font-weight: 600; color: #495057;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#viewer-section .skin-entry-btn {
  flex: 0 0 auto; width: 24px; height: 24px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid #d1d5db; border-radius: 50%; background: #fff; color: #6b7280; cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
#viewer-section .skin-entry-btn:hover:not(:disabled) { border-color: #3984a7; color: #3984a7; }
/* Remove is dimmed while the built-in skin is loaded (there is nothing to remove),
   and hovers RED when live: it discards, it does not download. */
#viewer-section .skin-entry-btn:disabled { opacity: 0.4; cursor: not-allowed; }
#viewer-section #removeSkinBtn:hover:not(:disabled) { border-color: #dc3545; color: #dc3545; }
/* The Part | Cube seg on the Selected-part header: a compact variant of the panel's
   Position/Rotation/Scale seg (same track, smaller pills). */
#transformPanel .vp-seg-sm { padding: 3px; gap: 4px; flex: 0 0 auto; }
#transformPanel .vp-seg-sm .vp-seg-btn { flex: 0 0 auto; min-height: 26px; padding: 0 12px; font-size: 12px; font-weight: 700; }

.skin-error {
  padding: 10px 16px;
  background: #fef2f2;
  border: 1px solid #fca5a5;
  border-radius: 12px;
  color: #991b1b;
  font-size: 13px;
  text-align: center;
}

/* ============================================================================
   POSE + CAMERA: white pill buttons in preset grids
   ============================================================================ */
#viewer-section .vp-preset-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
#viewer-section .vp-pose-grid { grid-template-columns: 1fr 1fr; }
#viewer-section .vp-btn {
  min-height: 40px; padding: 8px 10px;
  border: 2px solid #d1d5db; border-radius: 24px; background: #fff;
  color: #333; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: all 0.15s ease;
}
#viewer-section .vp-btn:hover { border-color: #3984a7; background: #f0f8ff; color: #3984a7; }
#viewer-section .vp-btn.active { border-color: #3984a7; background: #eaf3f7; color: #3984a7; }

/* ============================================================================
   BACKGROUND SWATCHES: circles, with the custom picker AS the seventh swatch
   ============================================================================ */
#viewer-section .bg-presets { display: grid; grid-template-columns: repeat(7, 1fr); gap: 8px; justify-items: center; }
#viewer-section .bg-preset,
#viewer-section .bg-custom {
  width: 100%; aspect-ratio: 1; max-width: 44px; min-width: 0; height: auto; padding: 0;
  border: 2px solid #d1d5db; border-radius: 50%; cursor: pointer;
}
#viewer-section .bg-preset:hover,
#viewer-section .bg-custom:hover { border-color: #3984a7; }
#viewer-section .bg-preset.active { border-color: #3984a7; box-shadow: 0 0 0 2px rgba(57, 132, 167, 0.3); }
#viewer-section .bg-custom {
  position: relative; overflow: hidden; background: #fff; color: #6b7280;
  display: flex; align-items: center; justify-content: center;
}
#viewer-section .bg-custom:hover { color: #3984a7; }
#viewer-section .bg-custom svg { width: 18px; height: 18px; pointer-events: none; }
/* The real color input covers the whole swatch, invisible, so the native picker opens on tap. */
#viewer-section .bg-custom input[type="color"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  padding: 0; margin: 0; border: none; opacity: 0; cursor: pointer;
}

/* The outline color is the tag generator's color-entry (swatch + hex), all shared CSS. */

/* Lights: the three nested slider groups sit tighter than card-level fields. */
#viewer-section #lightControls .input-group { margin-bottom: 8px; }
#viewer-section #lightControls .input-group:last-child { margin-bottom: 0; }

/* ============================================================================
   LAYERS ISLAND + OPTIONS ISLAND
   The minecraft-gui-generator's master-detail layout: setting groups and model
   parts as selectable rows on the left, one contextual options island on the
   right. Row/pill values match its Layers.module.css on the shared palette.
   ============================================================================ */
#viewer-section .mv-bottom-row {
  display: grid; grid-template-columns: 2fr 3fr; gap: 15px;   /* same gutter as .top-section */
  /* start, not stretch: a short options panel stretched to the list's height
     reads as a page of empty card. */
  align-items: start; margin-top: 15px;
}
/* gap stays the .tool-card 12px, the tag generator's card rhythm */
#viewer-section .mv-island { background: #f8f9fa; }
#viewer-section .mv-layers { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
#viewer-section .mv-layer-row {
  display: flex; align-items: center; gap: 10px;
  min-height: 44px; padding: 6px 10px 6px 14px;
  border: 2px solid #d1d5db; border-radius: 24px; background: #fff;
  color: #333; font-family: inherit; font-size: 15px; font-weight: 600; text-align: left;
  cursor: pointer; min-width: 0; transition: all 0.2s ease;
}
#viewer-section .mv-layer-row:hover { border-color: #3984a7; background: #f0f8ff; }
#viewer-section .mv-layer-row:focus-visible { outline: 2px solid #3984a7; outline-offset: 3px; }
/* Selected = the Cube chip's pressed look (solid fill, white text): unmissable, where the
   old pale #f0f8ff tint barely differed from the hover state. Hover keeps the tint, so
   hover and selected stay distinguishable; source order makes selected beat hover. */
#viewer-section .mv-layer-row.is-active,
#viewer-section .mv-layer-row.is-selected {
  border-color: #3984a7; background: #3984a7; color: #fff;
}
#viewer-section .mv-layer-icon { width: 20px; height: 20px; flex: 0 0 auto; }
#viewer-section .mv-layer-row > span {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The part list is a flat outliner (Blockbench-style), not a stack of buttons:
   quiet rows, hover tint, a left accent on the selected one, overlay layers indented. */
#viewer-section .mv-parts { display: flex; flex-direction: column; overscroll-behavior: contain; }
#viewer-section .vp-part {
  display: flex; align-items: center; gap: 10px;
  min-height: 38px; padding: 0 8px 0 10px; border-radius: 10px;
  cursor: pointer; min-width: 0; transition: background 0.15s ease;
}
#viewer-section .vp-part:hover { background: #f0f8ff; }
#viewer-section .vp-part.is-selected { background: #e8f2f7; box-shadow: inset 3px 0 0 #3984a7; }
#viewer-section .vp-part .vp-part-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 14px; font-weight: 600; color: #333;
}
#viewer-section .vp-part-sub { padding-left: 40px; min-height: 34px; }
#viewer-section .vp-part-sub .vp-part-name { font-size: 13px; font-weight: 500; color: #6b7280; }
#viewer-section .vp-part-sub .mv-layer-icon { width: 16px; height: 16px; color: #9aa4ac; }
#viewer-section .vp-part.is-selected .vp-part-name { color: #3984a7; }
/* A hidden part's name dims so the list mirrors the model. */
#viewer-section .vp-part:has(.vp-eye input:not(:checked)) .vp-part-name { color: #b6bec4; }

/* The eye is a REAL checkbox, only visually hidden: the JS delegates the change event
   from #visibility-section and reads .checked. Turn it into a button and the tool
   starts lying after undo. Styled as the GUI creator's round row action. */
#viewer-section .vp-eye {
  position: relative;
  width: 28px; height: 28px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  border-radius: 50%; background: rgba(0, 0, 0, 0.08); color: #374151;
  transition: background 0.15s ease, color 0.15s ease;
}
#viewer-section .vp-eye:hover { background: rgba(0, 0, 0, 0.16); color: #3984a7; }
/* The checkbox fills the whole circle, invisible, so the touch target is honest. */
#viewer-section .vp-eye input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer; z-index: 1;
}
#viewer-section .vp-eye svg { width: 15px; height: 15px; }
#viewer-section .vp-eye .eye-off { display: none; }
#viewer-section .vp-eye input:not(:checked) ~ .eye-on { display: none; }
#viewer-section .vp-eye input:not(:checked) ~ .eye-off { display: block; color: #9aa4ac; }
#viewer-section .vp-eye input:focus-visible ~ svg { outline: 2px solid #3984a7; outline-offset: 3px; border-radius: 4px; }
#viewer-section .mv-showall { align-self: center; }

/* The options panels: exactly one is shown, picked by the selected settings row.
   Controls sit in TINTED, borderless option groups on the island's #f8f9fa pane (the tag
   generator's two-tier depth); 16px between groups is its tool-panel rhythm. */
#viewer-section .mv-panel { display: flex; flex-direction: column; gap: 16px; }
#viewer-section .mv-panel .tool-card { border: none; }
#viewer-section .mv-panel-sub { display: flex; flex-direction: column; gap: 12px; }

/* ============================================================================
   SELECTED-PART PANEL (the transform editor, shown when a part is picked)
   Steppers and number pills come from .quantity-control in tool-cards.css; the
   rails add the color-coded axis chip.
   ============================================================================ */
#transformPanel .vp-seg { display: flex; gap: 6px; background: #eef2f4; padding: 4px; border-radius: 24px; }
#transformPanel .vp-seg-btn {
  flex: 1; min-height: 34px; border: none; border-radius: 20px; background: transparent;
  font-weight: 600; color: #667; font-size: 14px; cursor: pointer;
}
#transformPanel .vp-seg-btn.active { background: #fff; color: #3984a7; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); }
#viewer-section .vp-rails { display: flex; flex-direction: column; gap: 6px; }
/* The rails' own caption repeats the active segmented pill, so it stays out of sight
   (the markup keeps it for the JS's rail filtering). */
#viewer-section .vp-rails > .vp-label { display: none; }
#viewer-section .vp-rail input { font-variant-numeric: tabular-nums; font-weight: 600; color: #333; }
/* The rails have their own drag handler rather than the shared scrub, but they must still
   LOOK draggable: same cursor language as .scrubbable, text again once focused for typing. */
#viewer-section .vp-rail input { cursor: ew-resize; }
#viewer-section .vp-rail input:focus { cursor: text; }
/* The axis marker: a quiet colored letter (Blockbench-style), not a filled blob. The old
   solid 26x40 pills read as a fourth BUTTON on every row and their saturated fills fought
   the page's palette. The letter keeps the color mapping to the gizmo arrows, and the
   field it belongs to picks the same color up in its focus ring. */
#viewer-section .vp-axis {
  width: 16px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  color: var(--axis-color, #9ca3af);
  font-size: 13px; font-weight: 800;
}
#viewer-section .transform-field.tx { --axis-color: #e05555; }
#viewer-section .transform-field.ty { --axis-color: #4caf50; }
#viewer-section .transform-field.tz { --axis-color: #4a90d9; }
#viewer-section .transform-field input[type="number"]:focus { border-color: var(--axis-color); }

/* ============================================================================
   MODALS (export / help / confirm)
   NOT scoped under #viewer-section: they are reparented to <body> at init, so
   `inert` on the page background cannot inert them. Lifted from the glyph
   generator's modal system.
   ============================================================================ */
.vp-modal-overlay {
  position: fixed; inset: 0; z-index: 1100;   /* above the site navbar */
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(17, 24, 39, 0.55); overflow-y: auto;
}
.vp-modal-overlay[hidden] { display: none !important; }
.vp-modal {
  background: #fff; border-radius: 20px; width: 100%; max-width: 560px;
  max-height: calc(100dvh - 40px); overflow-y: auto; margin: auto;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.vp-modal-narrow { max-width: 460px; }
.vp-modal-head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 20px; background: #fff; border-bottom: 1px solid #eef0f2; border-radius: 20px 20px 0 0;
}
.vp-modal-head h3 { margin: 0; font-size: 20px; color: #2c3e50; }
.vp-modal-close {
  width: 34px; height: 34px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%;
  background: #f3f4f6; color: #6b7280; cursor: pointer;
  -webkit-appearance: none; appearance: none;
}
.vp-modal-close svg { display: block; width: 15px; height: 15px; }
.vp-modal-close:hover { background: #e5e7eb; color: #2c3e50; }
.vp-modal-body { padding: 22px 20px; display: flex; flex-direction: column; gap: 24px; }
.vp-modal-body p { margin: 0; color: #374151; line-height: 1.6; }

/* Fields inside a modal: the label sits ABOVE the control and both are centred. */
.vp-field-stack { display: flex; flex-direction: column; min-width: 0; }
.vp-field-stack > label {
  display: block; text-align: center; margin-bottom: 8px;
  font-weight: 600; color: #374151; font-size: 14px;
}
.vp-grid2 { display: grid; gap: 20px 16px; align-items: start; grid-template-columns: 1fr 1fr; }
.vp-grid2 > * { min-width: 0; }
.vp-modal-body .vp-select {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  border: 2px solid #d1d5db; border-radius: 12px;
  background: #fff; color: #2c3e50;
  font-family: inherit; font-size: 15px; font-weight: 600;
  text-align: left;
}
.vp-modal-body .vp-select:focus { outline: none; border-color: #3984a7; }
.vp-modal-body .vp-hint { display: block; text-align: center; font-size: 12px; color: #6b7280; margin: 8px 0 0; }

/* Sections inside a modal: a hairline, then a centred uppercase title. */
.vp-export-section { display: flex; flex-direction: column; gap: 12px; }
.vp-export-section + .vp-export-section {
  margin-top: 4px; padding-top: 22px; border-top: 1px solid #e5e7eb;
}
.vp-export-title {
  display: block; text-align: center;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
  color: #9ca3af; margin-bottom: 6px;
}
/* auto-fit, so 3 export tiles and 2 import tiles each fill their row evenly */
.vp-export-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.vp-export-tile {
  display: flex; flex-direction: column; gap: 3px; align-items: center; text-align: center;
  padding: 14px 10px; border: 2px solid #d1d5db; border-radius: 16px; background: #fff; cursor: pointer;
}
.vp-export-tile:hover { border-color: #3984a7; background: #f0f8ff; }
.vp-export-tile-title { font-size: 14px; font-weight: 700; color: #333; }
.vp-export-tile-sub { font-size: 12px; color: #666; }
.vp-confirm-actions { display: flex; gap: 12px; justify-content: center; }
.vp-confirm-actions button { margin: 0; flex: 1 1 140px; width: auto; }
/* The confirm's Reset is the destructive primary. */
#confirmOk { background: #dc3545; border-color: #dc3545; color: #fff; }
#confirmOk:hover { background: #bb2d3b; border-color: #bb2d3b; }
.vp-keys { display: grid; grid-template-columns: auto 1fr; gap: 8px 16px; margin: 0; }
.vp-keys dt { font-weight: 700; color: #2c3e50; font-size: 14px; }
.vp-keys dd { margin: 0; color: #374151; font-size: 14px; }

@media (max-width: 560px) {
  .vp-export-grid { grid-template-columns: 1fr 1fr; }
}

/* ============================================================================
   RESPONSIVE
   One block, at the end, so the display swaps cannot be undone by a later
   same-specificity desktop rule (that bug shipped once). The shared sheet
   already stacks .top-section and .tool-container at 710px.
   ============================================================================ */
/* The mojavatar-specific components follow the SAME scale ladder as the shared sheet:
   @710 controls drop to 36px (buttons 40), @480 card padding/radius 12, @360 padding/
   radius 10 and tighter gaps. Anything added here must pick its size from that ladder. */
@media (max-width: 710px) {
  /* Shorter than the old 4:5 portrait slab (the pinned pane carries this docked height),
     but tall enough that the figure reads: 16:9, ~195px on a 390 phone. */
  #viewer-section .vp-canvas-col { aspect-ratio: 16 / 9; max-height: 260px; }
  /* Layers stack above the options island, like the GUI creator. */
  #viewer-section .mv-bottom-row { grid-template-columns: 1fr; gap: 15px; }
  /* The SETTINGS list turns sideways: a horizontally scrollable chip strip costs one row
     (~40px) instead of six stacked rows (~280px) before the options island appears.
     The scrollbar is hidden; a chip cut off at the card edge is the scroll affordance,
     and showOptionsPanel() slides the lit chip into view. */
  #viewer-section .mv-layers {
    flex-direction: row;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  #viewer-section .mv-layers::-webkit-scrollbar { display: none; }
  #viewer-section .mv-layer-row { flex: 0 0 auto; min-height: 40px; font-size: 14px; padding: 4px 16px 4px 13px; }
  #viewer-section .icon-drop-zone { border-radius: 14px; }
  /* Pill buttons and the color swatch match the shared 36px control height
     (padding shrinks too: with border-box sizing it would floor the height at 41px). */
  #viewer-section .vp-btn { min-height: 36px; padding: 5px 10px; }
  /* Panel stacks track the card rhythm at this width. */
  #viewer-section .mv-panel { gap: 12px; }
  #viewer-section .mv-panel-sub { gap: 10px; }
}

@media (pointer: coarse) {
  #viewer-section .vp-part { min-height: 44px; }
  #viewer-section .vp-eye { width: 40px; height: 40px; }
}

@media (max-width: 480px) {
  #viewer-section .username-row { flex-direction: column; }
  /* In a column the row's flex axis is vertical: flex:1 would turn the input's height into
     a flex-basis of 0 and collapse it, so pin it back to its own height. */
  #viewer-section .username-row #usernameInput { flex: 0 0 auto; }
  #viewer-section .username-row #loadSkinBtn { width: 100%; height: 40px; }   /* shared button height here */
  #viewer-section .vp-preset-grid { gap: 8px; }
  #viewer-section .icon-drop-zone { border-radius: 12px; padding: 18px 16px; }
  /* The pin moves to the 8px corner here (shared); the chips and the ? button ride along. */
  #viewer-section .preview-history { top: 8px; right: 48px; }
  #viewer-section .config-btn { right: 8px; top: 8px; }
  /* Modals track the 12px card radius step. */
  .vp-modal { border-radius: 16px; }
  .vp-modal-head { padding: 14px 16px; border-radius: 16px 16px 0 0; }
  .vp-modal-body { padding: 18px 16px; gap: 20px; }
}

@media (max-width: 360px) {
  #viewer-section .mv-bottom-row { gap: 12px; margin-top: 12px; }
  #viewer-section .mv-panel { gap: 10px; }
  #viewer-section .mv-panel-sub { gap: 8px; }
  #viewer-section .mv-layer-row { min-height: 38px; padding: 4px 14px 4px 12px; }
  /* The pin drops to 28px at top 6 (shared): the chips and grip follow. */
  #viewer-section .preview-history { top: 6px; right: 40px; }
  #viewer-section .vp-tb-icon { width: 28px; height: 28px; }
  #viewer-section .config-btn { top: 6px; right: 6px; width: 28px; height: 28px; }
  #viewer-section .preview-drag-handle { top: 8px; left: 8px; }
  #viewer-section .icon-drop-zone { border-radius: 10px; padding: 16px 12px; min-height: 76px; }
  #viewer-section .skin-entry { padding: 10px; }
}

@media (max-width: 270px) {
  /* The pin drops to 24px at the 4px corner (shared): the ? and the chips keep tracking it. */
  #viewer-section .config-btn { right: 4px; top: 4px; width: 24px; height: 24px; }
  #viewer-section .preview-history { top: 4px; right: 34px; }
  #viewer-section .vp-tb-icon { width: 24px; height: 24px; }
}
