/* ════════════════════════════════════════════════════════════════════
   EMTRACE MOVEMENT MODE — STATIC IMAGE STAGE.

   The dial + brass lever are painted directly from the approved
   photorealistic mockup (images/vault/dial-resting.png) as a
   background image inside .vmm-stage. A second image
   (images/vault/dial-motion.png) is layered on top with opacity 0
   and cross-fades in while .vault-movement-mode carries .is-cranking
   (during a step transition). The brass lever itself is BAKED INTO
   the image; .vmm-lever-hit is a transparent <button> positioned
   over the visible lever that captures pointer / touch / keyboard
   events.

   The active capsule is the unambiguous hero of the screen: a tall
   vertical brass-bordered HTML card that sits IN FRONT of the dial,
   covering the central oculus. The dial frames the capsule like a
   Roman picture frame. The brass crank is the persistent, quiet
   affordance — pull it DOWN to summon the next voice; pull it UP to
   return to the previous voice. ↓ / ↑ arrow keys, "next" / "last"
   voice commands, and (where supported) touch-drag all funnel into
   the same animation.

   Stepping = the capsule TILTS BACK and SINKS into the central
   oculus (translateY + translateZ + rotateX + scale + opacity) as
   the dial rotates one detent. A warm bloom PointLight inside the
   oculus ramps 0 → peak → 0 across the descent + ascent window,
   reading as warm light glowing UP from the well. The next capsule
   then rises from the oculus into its resting position. Total
   choreography ≈ 650ms with stone-weight easing — fast enough to
   feel modern, weighted enough to feel like a real mechanism.

   What was here before (and is now intentionally gone):
     • god-rays SVG + screen blend + blur(28px)
     • glowing white-gold "chasm" rift core + cinematic radial gradient
     • jagged crack SVG with stroke-dashoffset draw + gold-glow filter
     • marble bump-map duplicate with mix-blend-mode overlay
     • dark void backdrop + vmmVoidPulse
     • full-viewport "marble halves" tearing apart at ±9° rotateX
     • dust mote canvas + jagged stone-debris polygon fragments
     • origin-aware ripple expanding to scale(80)
     • white/gold bloom radial flash scaling to 1.7
     • screen shake (rupture jitter + slam jolt)
     • all WebAudio synthesized FX (chasm thud, stone crack, brass seal,
       entry chord, idle whisper, breach swell)
     • mic-orb halo expand-to-2.4, mic-confirm scale-to-1.55, filament fire
     • card breath ring (vmmCardBreath) infinite pulse
     • card sheen sliding shimmer (vmmCardSheen)
     • all overshoot springs (cubic-bezier(0.34, 1.56, 0.64, 1))
     • Marble Membrane tessellated displacement plane (vmmAbsorb / vmmBurst)
     • horizontal flick gesture (replaced with vertical crank-drag)

   What stays: the carriage shell (marble surface + warm tint), the
   header (back + breadcrumb), the centered card with brass border +
   audio-driven photo reveal, and a static mic orb. Everything moves
   with one of three curves (--ease-real-out / --ease-real-inout /
   --ease-real-in) over 240ms / 480ms / 720ms — except the dial
   rotation, which uses a custom stone-weight cubic-bezier defined
   in JS so it can be applied to a Three.js animation.
   ════════════════════════════════════════════════════════════════════ */

body.vmm-active { overflow: hidden; }

/* ────────────────────────────────────────────────────────────────────
   Mode container.
   ──────────────────────────────────────────────────────────────────── */
.vault-movement-mode {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: stretch;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--dur-cross) var(--ease-real-inout),
    visibility 0s linear var(--dur-cross);
}

.vault-movement-mode.is-open {
  pointer-events: auto;
  opacity: 1;
  visibility: visible;
  transition:
    opacity var(--dur-cross) var(--ease-real-inout),
    visibility 0s linear 0s;
}

.vault-movement-mode.is-closing {
  opacity: 0;
  pointer-events: none;
}

/* ────────────────────────────────────────────────────────────────────
   Faint warm tint over the marble — atmosphere only, never opaque.
   ──────────────────────────────────────────────────────────────────── */
.vmm-tint {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 80% 60% at 50% 60%, rgba(255, 235, 180, 0.06), transparent 70%),
    linear-gradient(180deg, rgba(255, 245, 215, 0.03) 0%, rgba(120, 80, 30, 0.03) 100%);
  opacity: 0;
  transition: opacity var(--dur-cross) var(--ease-real-out);
  z-index: 1;
}
.vault-movement-mode.is-open .vmm-tint { opacity: 1; }

/* ────────────────────────────────────────────────────────────────────
   Header — Trace brand ribbon (_outer-pulse parity_). Cosmetics for
   .trace-brand-* live in css/trace-brand-top-nav.css; only layout hooks
   and empty-state typography remain here.
   ──────────────────────────────────────────────────────────────────── */
.vmm-header {
  position: relative;
  flex: 0 0 auto;
  z-index: 10;
}

/* When breadcrumb is empty before first bind, preserve bar height silently */
.vmm-breadcrumb:empty {
  visibility: hidden;
  min-height: 2rem;
}

.vmm-header .trace-brand-home-btn:focus-visible {
  outline: 2px solid rgba(90, 55, 30, 0.45);
  outline-offset: 3px;
  border-radius: 10px;
}

/* ────────────────────────────────────────────────────────────────────
   Stage — the approved photorealistic mockup is the entire visual.
   .vmm-stage-image--rest paints dial-resting.png; --motion paints
   dial-motion.png with opacity 0 and fades in only while a step is
   in flight (.vault-movement-mode.is-cranking).
   ──────────────────────────────────────────────────────────────────── */
.vmm-stage {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 6;
  overflow: hidden;
  /* Marble fallback in the rare case the dial PNG fails to load. */
  background: url('../images/vault-marble-light.jpg') center / cover no-repeat;
  background-color: #1a120a;
}

.vmm-stage-image {
  position: absolute;
  inset: 0;
  background-position: center center;
  background-size: cover;
  background-repeat: no-repeat;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  z-index: 0;
}

.vmm-stage-image--rest {
  background-image: url('../images/vault/dial-resting.png');
  opacity: 1;
}

.vmm-stage-image--motion {
  background-image: url('../images/vault/dial-motion.png');
  opacity: 0;
  transition: opacity 180ms var(--ease-real-out);
  z-index: 1;
  /* The motion PNG already shows the lever in its pulled position;
     adding a tiny scale + rotate tween while .is-cranking is set
     amplifies the "lever moved" reading so users on phones can't
     miss it even if the two PNGs are visually similar at a glance. */
  transform: scale(1) rotate(0deg);
  transform-origin: 75% 60%;
}

.vault-movement-mode.is-cranking .vmm-stage-image--motion {
  opacity: 1;
  animation: vmmLeverPump 700ms var(--ease-real-inout) forwards;
}

/* Pump cycle: the motion image scales-and-rotates audibly to sell
   the lever throw, then settles back. The rotation pivots from the
   lever's screw-down point on the dial rim (~75% horizontal,
   60% vertical of the image), so the perceived motion is the lever
   arm sweeping a wide arc, not the whole dial spinning. The angle
   was tuned UP from a too-subtle 2.6° to 7° so the throw is
   unmistakable from across the room (matches a real machinist's
   detent lever). */
@keyframes vmmLeverPump {
  0%   { transform: scale(1.00) rotate(0deg); }
  18%  { transform: scale(1.02) rotate(5deg); }
  45%  { transform: scale(1.03) rotate(7deg); }
  72%  { transform: scale(1.02) rotate(5deg); }
  100% { transform: scale(1.00) rotate(0deg); }
}

/* Soft warm bloom over the oculus while a capsule is descending /
   ascending. Pure CSS — replaces the WebGL PointLight ramp the
   Three.js engine used. */
.vmm-stage::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 38%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle,
    rgba(255, 220, 150, 0.42) 0%,
    rgba(255, 200, 110, 0.22) 30%,
    rgba(255, 200, 110, 0.00) 65%);
  pointer-events: none;
  opacity: 0;
  transition: opacity 360ms var(--ease-real-out);
  z-index: 2;
}
.vmm-stage.is-blooming::after {
  opacity: 1;
}

/* ────────────────────────────────────────────────────────────────────
   Brass-lever hit zone — INVISIBLE button positioned over the brass
   crank baked into the dial-resting.png. Captures pointer / touch /
   keyboard events to drive the step animation. The crank itself is
   in the image; this element is fully transparent.

   Default position assumes the lever sits at roughly the 3 o'clock
   side of the dial. The CSS variables --vmm-lever-x / --vmm-lever-y
   / --vmm-lever-w / --vmm-lever-h let the page operator nudge the
   hit zone to align with the actual mockup without touching the JS.
   ──────────────────────────────────────────────────────────────────── */
.vmm-stage {
  --vmm-lever-x: 78%;     /* horizontal center of the lever in the image */
  --vmm-lever-y: 60%;     /* vertical center of the lever */
  --vmm-lever-w: 14%;     /* hit zone width */
  --vmm-lever-h: 28%;     /* hit zone height */
  --vmm-lever-pull: 0px;  /* live drag offset, set by JS */
}

.vmm-lever-hit {
  position: absolute;
  left: var(--vmm-lever-x);
  top: var(--vmm-lever-y);
  width: var(--vmm-lever-w);
  height: var(--vmm-lever-h);
  transform: translate(-50%, calc(-50% + var(--vmm-lever-pull)));
  background: transparent;
  border: 0;
  margin: 0;
  padding: 0;
  cursor: grab;
  z-index: 5;
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  appearance: none;
  -webkit-appearance: none;
  /* Transition snaps back when JS resets the pull var. */
  transition: transform 220ms var(--ease-real-out);
}
.vmm-lever-hit:focus-visible {
  outline: 2px solid rgba(255, 230, 170, 0.85);
  outline-offset: 4px;
  border-radius: 12px;
}
.vmm-lever-hit.is-pulling {
  cursor: grabbing;
  transition: none; /* follow the pointer 1:1 while dragging */
}

/* While a step is animating the lever throw also gets a visible
   nudge so the affordance reads as "you just pulled it" even from
   across the room. The nudge follows the same curve as the motion
   image's pump (vmmLeverPump): a sharp throw down + slight rotate,
   then a clean snap back to neutral. */
.vault-movement-mode.is-cranking .vmm-lever-hit {
  animation: vmmLeverNudge 700ms var(--ease-real-inout) forwards;
}
@keyframes vmmLeverNudge {
  0%   { transform: translate(-50%, calc(-50% + var(--vmm-lever-pull))) rotate(0deg); }
  18%  { transform: translate(-50%, calc(-50% + 22px)) rotate(5deg); }
  45%  { transform: translate(-50%, calc(-50% + 30px)) rotate(7deg); }
  72%  { transform: translate(-50%, calc(-50% + 18px)) rotate(4deg); }
  100% { transform: translate(-50%, -50%) rotate(0deg); }
}

/* The card slot floats in the UPPER-CENTER of the canvas, ABOVE the
   dial which renders in the lower half. The card emerges from the
   dial's oculus on entry and rises into this resting position — the
   slot's center is at 44% from the top so the card sits clearly in
   the upper portion of the screen with the dial visibly framing it
   from below.
   The card's tilt-and-sink animation uses translateZ in 3D space —
   perspective on the slot makes the descent read as a real fall INTO
   the well. */
.vmm-card-slot {
  position: absolute;
  top: 44%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  place-items: center;
  /* Card sized so it dominates the upper half but never extends past
     the bottom of the viewport — leaves at least 30% of canvas
     height below the card for the dial to be fully visible. */
  width:  min(28vw, 42vh);
  height: min(72vh, 48vw);
  min-width: 220px;
  min-height: 320px;
  overflow: visible;
  z-index: 6;
  /* Strong 3D perspective so the descending capsule's translateZ +
     rotateX read as a real physical fall INTO the oculus, not a
     2D shrink. 1200px = aggressive but architecturally believable. */
  perspective: 1200px;
  perspective-origin: 50% 50%;
  transform-style: preserve-3d;
  pointer-events: none;
}
.vmm-card-slot > .vmm-card {
  grid-column: 1;
  grid-row: 1;
  pointer-events: auto;
}

/* ────────────────────────────────────────────────────────────────────
   The single card. We use the SAME .vault-tile markup the homepage
   wall paints (cream plaque base + dark exhibit-chamber top + photo
   bg + brass divider + meta strip) so Movement Mode shows a
   recognizably identical capsule, just scaled up. The .vmm-card
   ancestor class adds Movement-Mode-only behaviour (the tile is the
   centerpiece, not one of many in a grid; descent/ascent animations;
   playback overlay).
   ──────────────────────────────────────────────────────────────────── */
.vmm-card-slot > .vmm-card.vault-tile {
  position: relative;
  width:  100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  cursor: pointer;
  /* Bigger tile shadow than the wall version so it reads as the only
     object in the room, not one of many. */
  box-shadow:
    inset 0 0 0 1px rgba(212, 175, 55, 0.55),
    inset 0 1px 0 rgba(255, 235, 180, 0.45),
    inset 0 -1px 0 rgba(120, 80, 30, 0.55),
    0 28px 56px rgba(20, 12, 4, 0.65),
    0 12px 24px rgba(20, 12, 4, 0.40);
  will-change: transform, opacity, clip-path;
}

/* AGDVR: photo stays sealed until --reveal reaches 1 on audio ended.
   Overrides wall .has-revealed (which would snap blur(0) on the same
   .vault-tile markup). Progress during playback does NOT ease reveal.
   Sealed = visible photo under heavy blur (wall-tile idiom), NOT a
   brown opacity mask — that read as an empty slab in production. */
.vmm-card-slot > .vmm-card.vault-tile .vault-tile__bg {
  position: relative;
  --agdvr-reveal: var(--reveal, 0);
  filter: blur(calc((1 - var(--agdvr-reveal)) * 22px))
          saturate(calc(0.88 + var(--agdvr-reveal) * 0.12))
          brightness(calc(0.94 + var(--agdvr-reveal) * 0.06));
  transform: scale(calc(1.12 - var(--agdvr-reveal) * 0.08));
  opacity: 1;
  transition:
    filter 520ms var(--ease-real-out, ease-out),
    transform 520ms var(--ease-real-out, ease-out);
}

.vmm-card-slot > .vmm-card.vault-tile.has-revealed .vault-tile__bg {
  filter: blur(calc((1 - var(--agdvr-reveal)) * 22px))
          saturate(calc(0.88 + var(--agdvr-reveal) * 0.12))
          brightness(calc(0.94 + var(--agdvr-reveal) * 0.06));
  transform: scale(calc(1.12 - var(--agdvr-reveal) * 0.08));
  opacity: 1;
}

/* The wall renders a large cream meta plaque covering ~28% of the
   tile bottom. In Movement Mode we keep the same proportion so the
   sender + time strip is unmistakable as the homepage idiom. */
.vmm-card-slot > .vmm-card.vault-tile .vault-tile__meta {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: clamp(10px, 1.6vh, 18px) clamp(12px, 1.8vw, 22px);
}
.vmm-card-slot > .vmm-card.vault-tile .vault-tile__sender {
  font-size: clamp(1.05rem, 1.4vw, 1.4rem);
}
.vmm-card-slot > .vmm-card.vault-tile .vault-tile__time {
  font-size: clamp(0.72rem, 0.8vw, 0.82rem);
}

/* ────────────────────────────────────────────────────────────────────
   Movement-Mode-only playback overlay — the wall tile doesn't need
   a play button (clicking the tile opens it), but in Movement Mode
   the capsule is already open, so we surface a play / pause button,
   live waveform, and MM:SS timer in the lower-third of the tile,
   ABOVE the cream meta plaque. Layout:

      ┌────────────────────────┐
      │                        │
      │      photo bg          │
      │      (the tile)        │
      │                        │
      │     ◄── waveform       │
      │       ▶  play          │  ← .vmm-card__playback overlay
      │       00:00 / 00:20    │
      │  ──── brass divider ── │
      │  Sender                │  ← .vault-tile__meta
      │  2 hours ago        ▶  │
      └────────────────────────┘
   ──────────────────────────────────────────────────────────────────── */
.vmm-card__playback {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(56px, 7vh, 86px);
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(6px, 1vh, 12px);
  padding: 0 clamp(16px, 2vw, 28px);
  z-index: 3;
  pointer-events: none; /* the buttons inside opt back in */
}
.vmm-card__playback[aria-hidden="true"] { display: none; }

.vmm-card__waveform {
  width: 100%;
  max-width: 90%;
  height: clamp(36px, 6vh, 56px);
  display: block;
  background: transparent;
}

.vmm-card__play {
  width: clamp(52px, 6.6vh, 70px);
  height: clamp(52px, 6.6vh, 70px);
  border-radius: 50%;
  background: radial-gradient(circle at 30% 28%,
    rgba(255, 230, 170, 0.96) 0%,
    rgba(232, 190, 110, 0.94) 45%,
    rgba(180, 130, 30, 0.92) 100%);
  border: 1.5px solid rgba(255, 230, 170, 0.85);
  color: rgba(40, 24, 6, 0.94);
  box-shadow:
    0 1px 0 rgba(255, 245, 215, 0.65) inset,
    0 -2px 4px rgba(120, 80, 30, 0.55) inset,
    0 8px 22px rgba(40, 25, 5, 0.55),
    0 2px 6px rgba(40, 25, 5, 0.35);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition:
    transform var(--dur-touch, 120ms) var(--ease-real-out, ease-out),
    box-shadow var(--dur-touch, 120ms) var(--ease-real-out, ease-out);
  pointer-events: auto;
  -webkit-tap-highlight-color: transparent;
  appearance: none;
  -webkit-appearance: none;
}
.vmm-card__play:hover {
  background: radial-gradient(circle at 30% 28%,
    rgba(255, 240, 195, 1) 0%,
    rgba(240, 200, 120, 1) 45%,
    rgba(190, 140, 35, 0.96) 100%);
  box-shadow:
    0 1px 0 rgba(255, 245, 215, 0.78) inset,
    0 -2px 4px rgba(120, 80, 30, 0.55) inset,
    0 10px 26px rgba(40, 25, 5, 0.62),
    0 3px 8px rgba(40, 25, 5, 0.48);
}
.vmm-card__play:focus-visible {
  outline: 2px solid rgba(255, 230, 170, 0.85);
  outline-offset: 3px;
}
.vmm-card__play-icon { width: 38%; height: 38%; }

.vmm-card .vmm-card__play-icon--play  { display: none; }
.vmm-card.is-paused .vmm-card__play-icon--pause { display: none; }
.vmm-card.is-paused .vmm-card__play-icon--play  { display: inline-block; }

.vmm-card__timer {
  font-family: var(--font-body, system-ui, sans-serif);
  font-variant-numeric: tabular-nums;
  font-size: clamp(0.78rem, 0.95vw, 0.92rem);
  letter-spacing: 0.06em;
  color: rgba(255, 230, 170, 0.92);
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
}

/* Audio-driven brass-glow on the tile border while playing. Capped
   modulation so the voice WARMS the frame, never strobes. */
.vmm-card-slot > .vmm-card.vault-tile.is-resting {
  box-shadow:
    inset 0 0 0 1px rgba(212, 175, 55, calc(0.55 + var(--audio-glow, 0) * 0.30)),
    inset 0 1px 0 rgba(255, 235, 180, 0.45),
    inset 0 -1px 0 rgba(120, 80, 30, 0.55),
    0 28px 56px rgba(20, 12, 4, 0.65),
    0 12px 24px rgba(20, 12, 4, 0.40),
    0 0 calc(10px + var(--audio-glow, 0) * 24px)
        rgba(212, 175, 55, calc(0.18 + var(--audio-glow, 0) * 0.18));
  transition: box-shadow 90ms linear;
}

/* ────────────────────────────────────────────────────────────────────
   Card descent / ascent — EMERGE FROM (and SINK INTO) the dial's
   oculus. The next capsule visibly grows OUT of the dial's central
   well via a clip-path circle that expands from a tiny seed at the
   oculus origin (50% horizontal, ~55% vertical of the card — which
   spatially lands right on the dial's central well behind the card)
   to a full circle that covers the card. Combined with a vertical
   rise + scale-up, the effect is a capsule rising from inside the
   vault. The descending card reverses the same trajectory: it
   shrinks and clips down INTO the oculus and disappears.

   --vmm-oculus-x / --vmm-oculus-y can be nudged on .vmm-card-slot
   to align the emergence seed with the actual oculus position in
   the dial-resting.png mockup if the dial isn't perfectly centered.
   ──────────────────────────────────────────────────────────────────── */
.vmm-card-slot {
  --vmm-oculus-x: 50%;
  --vmm-oculus-y: 55%;
}

.vmm-card.is-descending {
  animation: vmmDescendOculus 360ms var(--ease-real-in) forwards;
  pointer-events: none;
  z-index: 5;
  transform-origin: var(--vmm-oculus-x) var(--vmm-oculus-y);
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform, opacity, clip-path;
}
.vmm-card.is-ascending {
  animation: vmmAscendOculus 460ms var(--ease-real-out) forwards;
  pointer-events: none;
  z-index: 6;
  transform-origin: var(--vmm-oculus-x) var(--vmm-oculus-y);
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform, opacity, clip-path;
}

@keyframes vmmDescendOculus {
  0% {
    transform: translate3d(0, 0, 0) rotateX(0deg) scale(1);
    clip-path: circle(150% at var(--vmm-oculus-x) var(--vmm-oculus-y));
    opacity: 1;
    filter: brightness(1);
  }
  60% {
    opacity: 0.6;
    clip-path: circle(35% at var(--vmm-oculus-x) var(--vmm-oculus-y));
  }
  100% {
    transform: translate3d(0, 14%, -200px) rotateX(-32deg) scale(0.10);
    clip-path: circle(0% at var(--vmm-oculus-x) var(--vmm-oculus-y));
    opacity: 0;
    filter: brightness(0.4);
  }
}

@keyframes vmmAscendOculus {
  0% {
    /* Start as a vanishingly small seed at the oculus origin, deep
       and tilted back as if pushed up from inside the well. */
    transform: translate3d(0, 14%, -200px) rotateX(-32deg) scale(0.10);
    clip-path: circle(0% at var(--vmm-oculus-x) var(--vmm-oculus-y));
    opacity: 0;
    filter: brightness(0.45);
  }
  30% {
    /* The capsule has cleared the oculus lip — clip opens enough to
       read as a luminous seed expanding outward from inside. */
    opacity: 0.92;
    clip-path: circle(28% at var(--vmm-oculus-x) var(--vmm-oculus-y));
  }
  70% {
    /* Most of the card is now visible; tilt and depth ease toward
       resting pose. The clip continues expanding past the corners. */
    opacity: 1;
    clip-path: circle(95% at var(--vmm-oculus-x) var(--vmm-oculus-y));
  }
  100% {
    transform: translate3d(0, 0, 0) rotateX(0deg) scale(1);
    clip-path: circle(150% at var(--vmm-oculus-x) var(--vmm-oculus-y));
    opacity: 1;
    filter: brightness(1);
  }
}

/* Reduced-motion: skip the 3D tilt-and-sink, the cross-fade to the
   motion image, and the bloom. Cards simply opacity-cross over 240ms
   — same metaphor (one voice replaces the previous), zero vestibular
   load. The dial-resting.png stays visible throughout. */
@media (prefers-reduced-motion: reduce) {
  .vmm-stage-image--motion { display: none; }
  .vmm-stage::after { display: none; }
  .vmm-lever-hit { transition: none !important; }
  .vmm-card.is-descending,
  .vmm-card.is-ascending {
    animation: none !important;
    transition: opacity 240ms linear;
    transform: none !important;
    filter: none !important;
  }
  .vmm-card.is-descending { opacity: 0; }
  .vmm-card.is-ascending  { opacity: 1; }
  /* AGDVR a11y parity with Listening Room: no vestibular blur animation */
  .vmm-card-slot > .vmm-card.vault-tile.is-agdvr-complete .vault-tile__bg {
    filter: none;
    opacity: 1;
    transform: none;
  }
}

/* ────────────────────────────────────────────────────────────────────
   Crank tooltip — quiet directional hint.

   Positioned by JS over the on-screen brass crank location. Appears
   on first hover/touch, fades after 1.5s, and stops appearing after
   the user has used the dial 3 times (tracked in localStorage as
   `vault.movement.dialUses`). The tooltip is purely instructional;
   the crank shape is the primary affordance.
   ──────────────────────────────────────────────────────────────────── */
.vmm-crank-tooltip {
  position: absolute;
  z-index: 7;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 9px 14px;
  background: linear-gradient(180deg, rgba(247, 240, 224, 0.96), rgba(232, 220, 188, 0.92));
  color: #3a2a1a;
  border: 1px solid rgba(180, 130, 30, 0.55);
  border-radius: 10px;
  font-family: var(--font-body);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.65) inset,
    0 6px 14px rgba(80, 50, 20, 0.22);
  opacity: 0;
  transform: translate(0, 4px);
  transition:
    opacity 180ms var(--ease-real-out),
    transform 180ms var(--ease-real-out);
  white-space: nowrap;
}

.vmm-crank-tooltip[hidden] { display: none; }

.vmm-crank-tooltip.is-visible {
  opacity: 1;
  transform: translate(0, 0);
}

.vmm-crank-tooltip__row {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.vmm-crank-tooltip__arrow {
  font-family: var(--font-body);
  font-size: 0.92rem;
  color: #8a5b1f;
  font-weight: 600;
  width: 12px;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .vmm-crank-tooltip {
    transition: opacity 80ms linear;
    transform: none !important;
  }
}

/* ────────────────────────────────────────────────────────────────────
   PULL ▼ affordance — the persistent badge next to the brass knob.

   Different from the verbose hover tooltip: this is the FIRST-USE
   teach-moment that the brass lever is the primary input. It's
   positioned by the JS via the same projected-knob coordinates as
   the hover tooltip, fades in once the dial scene paints, and is
   hidden permanently after the user's first successful detent step
   (and never reappears on subsequent sessions once dialUses ≥ 3).
   ──────────────────────────────────────────────────────────────────── */
.vmm-pull-hint {
  position: absolute;
  z-index: 7;
  pointer-events: none;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 4px 10px 5px;
  background: linear-gradient(180deg, rgba(247, 240, 224, 0.94), rgba(232, 220, 188, 0.88));
  color: #5a3f1a;
  border: 1px solid rgba(180, 130, 30, 0.55);
  border-radius: 8px;
  font-family: var(--font-body);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.6);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.7) inset,
    0 4px 10px rgba(80, 50, 20, 0.22);
  opacity: 0;
  transform: translateY(-2px);
  transition:
    opacity 280ms var(--ease-real-out),
    transform 280ms var(--ease-real-out);
  user-select: none;
}

.vmm-pull-hint[hidden] { display: none; }

.vmm-pull-hint.is-visible {
  opacity: 1;
  transform: translateY(0);
  /* Subtle 2.4s breath only on the badge so the eye is drawn to it
     without breaking the no-strobe motion law. Single direction
     fade — no oscillation, no scale. */
  animation: vmmPullHintBreath 2400ms var(--ease-real-inout) infinite alternate;
}

@keyframes vmmPullHintBreath {
  0%   { opacity: 1; }
  100% { opacity: 0.75; }
}

.vmm-pull-hint__label {
  font-size: 0.62rem;
  letter-spacing: 0.22em;
  color: #6a4a1a;
  line-height: 1;
}

.vmm-pull-hint__arrow {
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: #8a5b1f;
  line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
  .vmm-pull-hint {
    transition: opacity 80ms linear;
    transform: none !important;
  }
  .vmm-pull-hint.is-visible {
    animation: none !important;
    opacity: 1 !important;
  }
}

/* ────────────────────────────────────────────────────────────────────
   MIC ORB — static brass disc.

   Listening: lit. Idle / paused: dimmed. NO halo expanding to scale 2.4,
   NO core scaling to 1.55 on speech-confirm, NO filament firing 50vh
   into the sky. The orb is a fixed-size brass cabochon that brightens
   when listening. That's it.
   ──────────────────────────────────────────────────────────────────── */
.vmm-mic-orb {
  position: absolute;
  left: 50%;
  bottom: 56px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  z-index: 6;
  opacity: 0;
  transition: opacity var(--dur-touch) var(--ease-real-out);
}

.vault-movement-mode.is-open .vmm-mic-orb {
  opacity: 1;
  pointer-events: auto;
  cursor: pointer;
}

.vmm-mic-orb:focus-visible {
  outline: 2px solid rgba(180, 130, 30, 0.85);
  outline-offset: 4px;
}

.vmm-mic-orb.is-ptt-active .vmm-mic-orb__core {
  box-shadow:
    0 0 22px rgba(255, 190, 90, 0.95),
    0 0 0 8px rgba(247, 230, 200, 0.75);
}

.vmm-mic-orb.is-ptt-active .vmm-mic-orb__label {
  border-color: rgba(200, 140, 40, 0.65);
}

.vmm-mic-orb__core {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%,
    rgba(255, 250, 230, 1) 0%,
    rgba(255, 215, 130, 0.95) 35%,
    rgba(212, 175, 55, 0.65) 70%,
    rgba(120, 80, 30, 0.45) 100%);
  box-shadow:
    0 0 12px rgba(255, 215, 130, 0.55),
    0 0 0 6px rgba(247, 240, 224, 0.5);
  transition:
    box-shadow var(--dur-touch) var(--ease-real-out),
    opacity var(--dur-touch) var(--ease-real-out);
}

.vmm-mic-orb.is-listening .vmm-mic-orb__core {
  box-shadow:
    0 0 18px rgba(255, 215, 130, 0.85),
    0 0 0 6px rgba(247, 240, 224, 0.65);
}

.vmm-mic-orb.is-paused .vmm-mic-orb__core {
  opacity: 0.55;
  box-shadow:
    0 0 8px rgba(212, 175, 55, 0.35),
    0 0 0 6px rgba(247, 240, 224, 0.4);
}

.vmm-mic-orb__label {
  font-family: var(--font-body);
  font-size: 0.74rem;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: #5a3f1a;
  background: rgba(247, 240, 224, 0.78);
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid rgba(180, 130, 30, 0.32);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.55) inset;
  user-select: none;
}

/* ────────────────────────────────────────────────────────────────────
   Inline notice — mic-blocked / browser-not-supported banner.
   ──────────────────────────────────────────────────────────────────── */
.vmm-notice {
  position: absolute;
  top: 80px;
  left: 50%;
  transform: translateX(-50%);
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(247, 240, 224, 0.94);
  color: #3a2a1a;
  border: 1px solid rgba(180, 130, 30, 0.55);
  font-family: var(--font-body);
  font-size: 0.82rem;
  z-index: 8;
  max-width: min(80%, 560px);
  text-align: center;
  box-shadow: 0 6px 18px rgba(60, 40, 10, 0.18);
}

.vmm-notice[hidden] { display: none; }

/* ────────────────────────────────────────────────────────────────────
   Onboarding overlay — first-time, dismissable.
   ──────────────────────────────────────────────────────────────────── */
.vmm-onboard {
  position: absolute;
  inset: 0;
  background: rgba(40, 24, 6, 0.5);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: vmmOnboardFadeIn var(--dur-cross) var(--ease-real-out);
}

.vmm-onboard[hidden] { display: none; }

@keyframes vmmOnboardFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .vmm-onboard { animation: none !important; }
}

.vmm-onboard__card {
  background: linear-gradient(180deg, rgba(255, 250, 230, 0.99), rgba(232, 220, 188, 0.97));
  border: 1px solid rgba(180, 130, 30, 0.55);
  border-radius: 18px;
  padding: 28px 30px;
  max-width: 480px;
  width: min(85%, 480px);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.7) inset,
    0 24px 48px rgba(40, 24, 6, 0.45);
  color: #2a2018;
}

.vmm-onboard__title {
  font-family: var(--font-display, 'Cormorant Garamond', serif);
  font-size: 1.6rem;
  font-style: italic;
  margin: 0 0 16px;
}

.vmm-onboard__steps {
  margin: 0 0 18px;
  padding-left: 20px;
  font-family: var(--font-body);
  font-size: 0.92rem;
  line-height: 1.7;
  color: #3a2a1a;
}

.vmm-onboard__steps strong {
  color: #8a5b1f;
  font-weight: 600;
}

.vmm-onboard__alt {
  font-family: var(--font-body);
  font-size: 0.82rem;
  color: rgba(60, 45, 25, 0.72);
  font-style: italic;
  margin-bottom: 18px;
}

.vmm-onboard__got-it {
  display: block;
  width: 100%;
  padding: 11px 16px;
  border-radius: 999px;
  border: 1px solid rgba(120, 80, 30, 0.55);
  background: linear-gradient(180deg, #d4af37, #b8893a);
  color: #2a1c08;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.5) inset,
    0 6px 12px rgba(60, 40, 10, 0.25);
  transition: box-shadow var(--dur-touch) var(--ease-real-out);
}

.vmm-onboard__got-it:hover,
.vmm-onboard__got-it:focus-visible {
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.6) inset,
    0 10px 18px rgba(60, 40, 10, 0.32);
  outline: none;
}

/* ────────────────────────────────────────────────────────────────────
   Mobile fallback — the min(34.2vw, 51.3vh) calc handles most phones
   automatically. This media query just lifts the mic orb above the
   soft-keyboard safe area and tightens the tooltip on narrow screens.
   ──────────────────────────────────────────────────────────────────── */
@media (max-width: 640px) {
  .vmm-mic-orb { bottom: 36px; }
  .vmm-crank-tooltip {
    font-size: 0.74rem;
    padding: 7px 11px;
  }
}

/* ── Capsule Stage FLIP (wall tile → stage) ───────────────────────── */
.vault-movement-mode.is-flying {
  transform-origin: var(--flip-origin-x, 50%) var(--flip-origin-y, 50%);
  animation: vmmFlyIn var(--dur-cross, 480ms) var(--ease-real-inout, cubic-bezier(0.4, 0, 0.2, 1)) both;
}

.vault-movement-mode.is-folding {
  transform-origin: var(--flip-origin-x, 50%) var(--flip-origin-y, 50%);
  animation: vmmFoldOut var(--dur-cross, 480ms) var(--ease-real-inout, cubic-bezier(0.4, 0, 0.2, 1)) both;
}

@keyframes vmmFlyIn {
  0% {
    opacity: 0;
    transform: translate3d(var(--flip-x, 0), var(--flip-y, 0), 0)
               scale(var(--flip-sx, 0.2), var(--flip-sy, 0.2));
  }
  60% { opacity: 1; }
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1, 1);
  }
}

@keyframes vmmFoldOut {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1, 1);
  }
  100% {
    opacity: 0;
    transform: translate3d(var(--flip-x, 0), var(--flip-y, 0), 0)
               scale(var(--flip-sx, 0.2), var(--flip-sy, 0.2));
  }
}

/* ── Orbit index (semicircular ring around capsule — Plan Phase 5) ─ */
.vmm-orbit {
  position: absolute;
  top: 44%;
  left: 50%;
  width: min(46vw, 64vh);
  height: min(88vh, 64vw);
  min-width: 300px;
  min-height: 400px;
  transform: translate(-50%, -60%);
  pointer-events: none;
  z-index: 5;
  opacity: 0;
  transition: opacity 320ms ease;
}
.vault-movement-mode.is-open .vmm-orbit {
  opacity: 1;
}
.vmm-orbit__track {
  position: absolute;
  left: -4%;
  right: -4%;
  top: -2%;
  height: 22%;
  width: 108%;
  overflow: visible;
}
.vmm-orbit__seat {
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: rgba(212, 175, 55, 0.45);
  border: 1.5px solid rgba(255, 235, 180, 0.65);
  box-shadow: 0 0 8px rgba(212, 175, 55, 0.35);
  transform: translate(-50%, -50%);
  transition: transform 240ms ease, background 240ms ease, box-shadow 240ms ease;
}
.vmm-orbit__seat.is-active {
  background: rgba(255, 220, 140, 0.98);
  border-color: rgba(255, 245, 220, 0.95);
  box-shadow: 0 0 16px rgba(212, 175, 55, 0.75);
  transform: translate(-50%, -50%) scale(1.4);
}
.vmm-orbit__label {
  position: absolute;
  left: 50%;
  top: -1%;
  transform: translate(-50%, -100%);
  font-family: var(--font-body, system-ui, sans-serif);
  font-size: clamp(0.62rem, 0.75vw, 0.72rem);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 230, 170, 0.88);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.65);
  pointer-events: none;
}

/* AGDVR: no decorative waveform motion while idle — only during real playback */
.vmm-card.is-paused .vmm-card__waveform {
  display: none !important;
}

@media (prefers-reduced-motion: reduce) {
  .vault-movement-mode.is-flying,
  .vault-movement-mode.is-folding {
    animation: none !important;
    transform: none !important;
  }
}
