/* =====================================================================
   VAULT INTERACTION STANDARD  (platform-wide, 2026-09-08)
   =====================================================================

   Two rules, applied everywhere, permanently:

       CHOICE  = STAYS LIT
       ACTION  = CLEARLY REACTS

   A control that represents a CHOICE stays visibly selected from the
   moment it is clicked/tapped until either (a) another option in the same
   group is chosen, or (b) the player moves to a page/step/game state
   where that choice is no longer relevant. A control that performs an
   ACTION never stays permanently lit -- it reacts (hover, press, success,
   disabled) and settles back.

   Loaded AFTER style.css on every page, and deliberately built as ONE
   shared layer rather than as per-control fixes, so a new control gets
   the standard by adding a class rather than by re-deriving the look.

   WHY THIS FILE EXISTS AT ALL (the real defect it fixes): style.css's
   depth/provider "selected" rule was `.depth-btn.on` (specificity 0,2,0),
   while the buttons themselves are `class="btn ghost depth-btn"` and
   `.btn.ghost` (also 0,2,0) is declared LATER in the same file. Equal
   specificity, later rule wins -- so `.btn.ghost`'s transparent
   background, muted text and grey border silently overrode the entire
   selected look, and the only surviving evidence of a selection was the
   tiny "checkmark" pseudo-element. Human testing on 2026-09-08 reported
   exactly that: "DEEP is selected but only a tiny tick appears."

   The selected-state rules below therefore spell out compound selectors
   that beat every base-button rule in style.css outright
   (`.btn.ghost.vault-choice.is-selected` = 0,4,0) instead of relying on
   file order. That verbosity is intentional: it cannot silently regress
   the next time a base-button rule is added or moved.

   The JS side is public/uiSelection.js (window.VaultUiSelection) --
   markSelected / clearSelected / lockSelected / flashAction. Class names
   are applied there, never inline styles, so the whole look lives here.
   ===================================================================== */

/* ---------------------------------------------------------------------
   1. CHOICE = STAYS LIT
   ------------------------------------------------------------------ */

.vault-choice {
  cursor: pointer;
  /* Desktop matters here: this mobile-shaped UI is tested with a mouse on
     a Windows laptop, where a control with no hover/press response reads
     as broken rather than as "waiting for a tap". */
  transition:
    background-color var(--vault-motion-fast, 140ms) var(--vault-ease, ease),
    border-color var(--vault-motion-fast, 140ms) var(--vault-ease, ease),
    color var(--vault-motion-fast, 140ms) var(--vault-ease, ease),
    transform var(--vault-motion-fast, 140ms) var(--vault-ease, ease),
    box-shadow var(--vault-motion-fast, 140ms) var(--vault-ease, ease);

  /* ZERO-SHIFT SELECTION. Measured on a real phone viewport (375x812,
     2026-09-09): a `.btn.ghost.vault-choice` was 54px tall at rest and
     59px once selected, so every tap nudged the whole rest of the page
     down 5px. Two separate causes, both fixed here rather than at the
     call site:
       +2px  the selected state goes to a 2px border, so the RESTING
             state is given one too (see the border-colour rules below --
             the weight is constant, only the colour changes);
       +3px  the "checkmark" pseudo-element is a symbol-font glyph whose
             taller metrics grew the line box, so the line box is pinned.
     Re-measured after the fix: 57.25px selected AND unselected. */
  line-height: var(--vault-leading-control, 1.25);
  border-width: 2px;
  border-style: solid;
}

/* The resting border of a choice control. #333654 measures 1.67:1
   against the page ground -- below WCAG 1.4.11's 3:1 for a boundary
   that is the only thing identifying a control -- which is a large part
   of why an unselected option read as "dead" rather than "tappable" in
   human testing. --vault-edge-control measures 3.37:1 on the page and
   3.08:1 inside a card. Written as
   compound selectors for the same reason the selected rules below are:
   `.btn.ghost` (0,2,0) would otherwise silently win. */
.btn.vault-choice,
.btn.ghost.vault-choice {
  border: 2px solid var(--vault-edge-control, #5b60a0);
}
.theatre-toggle-btn.vault-choice {
  border-color: var(--vault-edge-control, #5b60a0);
}

/* The lit state. Fill + border + contrasting text + a restrained lift, so
   the selection is legible across a room, in daylight, on a phone --
   never a tick alone. Gold is Vault's own selection colour, already used
   by the in-game answer cards, so one visual language covers setup
   choices and gameplay choices alike. */
.vault-choice.is-selected,
.btn.vault-choice.is-selected,
.btn.ghost.vault-choice.is-selected,
.btn.primary.vault-choice.is-selected {
  background: var(--vault-gold-ink, #3a2f0d);
  border: 2px solid var(--gold);
  color: var(--gold);
  font-weight: var(--vault-weight-strong, 700);
  transform: translateY(var(--vault-lift-select, -1px));
  box-shadow: var(--vault-elev-select, 0 3px 10px rgba(243, 201, 74, 0.28));
}

/* Not colour-only: a selected control also carries aria-pressed="true"
   (set by uiSelection.js) plus this weight/border change, so the state
   survives colour-blindness and greyscale. The tick is SECONDARY -- it
   sits alongside the fill, it is never the evidence on its own. */
.vault-choice.is-selected::before {
  content: "\2713\00a0";
  font-weight: 700;
  font-size: 0.9em;
  line-height: 1;
}

/* A choice that has been committed (LOCK, or any point past which it can
   no longer change). Stays lit -- deliberately stronger and calmer than
   the momentary selected fill, never invisible.

   2026-09-09: the text colour was --muted (#9a9bb0). That made a LOCKED
   choice read as *greyed out* -- weaker than the surrounding options --
   when spec section 9 requires the opposite: "LOCKED CHOICE: becomes
   visibly stronger". It is now a settled gold (8.76:1 on this fill, vs
   6.27:1 before) plus an inset gold ring, so lock reads as "committed,
   and still yours" rather than "disabled". The lift is kept so a locked
   choice still visibly outranks the neighbours that dim around it. */
.vault-choice.is-locked,
.btn.vault-choice.is-locked,
.btn.ghost.vault-choice.is-locked {
  background: var(--vault-gold-ink-deep, #211b0c);
  border: 2px solid var(--gold);
  color: var(--vault-gold-soft, #d9b64a);
  font-weight: var(--vault-weight-strong, 700);
  transform: translateY(var(--vault-lift-select, -1px));
  box-shadow: inset 0 0 0 1px var(--vault-gold-ring, rgba(243, 201, 74, 0.55));
  cursor: default;
}

/* Every selected/locked choice stays fully legible even when the whole
   group is disabled together on commit -- otherwise :disabled's blanket
   opacity would dim the one control the player most needs to still see. */
.vault-choice.is-selected:disabled,
.vault-choice.is-locked:disabled,
.vault-choice.is-selected[aria-disabled="true"],
.vault-choice.is-locked[aria-disabled="true"] {
  opacity: 1;
}

/* ---------------------------------------------------------------------
   2. ACTION = CLEARLY REACTS
   ------------------------------------------------------------------
   Applied to the shared button surfaces platform-wide rather than to a
   hand-maintained list of ids, so a newly added button reacts by default
   instead of appearing dead until someone remembers to style it. */

.btn,
.vault-action,
.option-grid button,
.game-list button,
.wager-preset-list button,
.theatre-toggle-btn,
.people-panel-row,
.rail-toggle,
.shell-back-btn,
.shell-wager-btn,
.shell-end-btn,
.side-panel-close-btn,
.session-chevron,
.shout-fab-btn,
.wager-live-badge {
  cursor: pointer;
  transition:
    filter var(--vault-motion-fast, 120ms) var(--vault-ease, ease),
    transform var(--vault-motion-instant, 90ms) var(--vault-ease, ease),
    box-shadow var(--vault-motion-fast, 120ms) var(--vault-ease, ease),
    background-color var(--vault-motion-fast, 140ms) var(--vault-ease, ease),
    border-color var(--vault-motion-fast, 140ms) var(--vault-ease, ease);
}

/* Interactive controls that are identified by their boundary rather than
   by a fill get the 3:1 control edge too, for the same measured reason
   as the choice controls above -- an answer card is a --vault-surface
   rectangle on the --vault-ground page, which is 1.09:1 on its own, so
   the border is doing all of the work. Deliberately NOT applied to the
   decorative chips (.predictor-chip, .wager-response-chip, .d-chip):
   those are status readouts, not things to press. */
.option-grid button,
.game-list button,
.wager-preset-list button,
.rail-toggle {
  border-color: var(--vault-edge-control, #5b60a0);
}

/* Hover (mouse/trackpad). Guarded by (hover: hover) so a touch device
   never gets a hover state stuck on after a tap. */
@media (hover: hover) {
  .btn:not(:disabled):hover,
  .vault-action:not(:disabled):hover,
  .option-grid button:not(:disabled):hover,
  .game-list button:not(:disabled):hover,
  .wager-preset-list button:not(:disabled):hover,
  .theatre-toggle-btn:not(:disabled):hover,
  .people-panel-row:hover,
  .rail-toggle:hover,
  .shell-back-btn:hover,
  .shell-wager-btn:hover,
  .shell-end-btn:hover,
  .side-panel-close-btn:hover,
  .session-chevron:hover,
  .shout-fab-btn:hover,
  .wager-live-badge:hover {
    filter: brightness(1.14);
  }
  /* An unselected CHOICE also warms its edge on hover, so a mouse user
     can tell a choice group apart from a row of actions before clicking
     anything -- brightness alone on a near-black control is close to
     invisible. */
  .vault-choice:not(:disabled):not(.is-selected):not(.is-locked):hover {
    border-color: var(--gold);
    color: var(--vault-text, #f5f4f2);
  }
}

/* Press/depress. :active covers mouse, trackpad and touch; .is-pressing
   is the same look driven from pointerdown by uiSelection.js, for the
   cases where a browser is slow to apply :active on touch. */
.btn:not(:disabled):active,
.btn.is-pressing:not(:disabled),
.vault-action:not(:disabled):active,
.vault-action.is-pressing:not(:disabled),
.option-grid button:not(:disabled):active,
.option-grid button.is-pressing:not(:disabled),
.game-list button:not(:disabled):active,
.game-list button.is-pressing:not(:disabled),
.wager-preset-list button:not(:disabled):active,
.wager-preset-list button.is-pressing:not(:disabled),
.theatre-toggle-btn:not(:disabled):active,
.theatre-toggle-btn.is-pressing:not(:disabled),
.people-panel-row:active,
.people-panel-row.is-pressing,
.rail-toggle:active,
.shell-back-btn:active,
.shell-wager-btn:active,
.shell-end-btn:active,
.side-panel-close-btn:active,
.session-chevron:active,
.shout-fab-btn:active,
.wager-live-badge:active {
  transform: translateY(1px) scale(0.985);
  filter: brightness(0.92);
}

/* A press must be felt as well as seen. On a phone the finger covers the
   control, so brightness alone is invisible under it -- the 1px sink and
   the darkened, tightened edge are what read as "that went down" once
   the finger lifts. Kept to a single frame, no bounce: this is a button,
   not a toy. */
.btn:not(:disabled):active,
.vault-action:not(:disabled):active,
.option-grid button:not(:disabled):active,
.game-list button:not(:disabled):active,
.wager-preset-list button:not(:disabled):active,
.theatre-toggle-btn:not(:disabled):active {
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.45);
}

/* Temporary success confirmation ("Copied", "Sent"). Held briefly by
   uiSelection.js's flashAction, then removed -- an ACTION must never end
   up permanently lit, which is precisely what distinguishes it from a
   choice. */
.vault-action.is-success,
.btn.vault-action.is-success,
.btn.ghost.vault-action.is-success,
.btn.primary.vault-action.is-success {
  background: #12301f;
  border: 2px solid var(--good);
  color: var(--good);
  font-weight: 700;
}

/* ---------------------------------------------------------------------
   3. Keyboard + accessibility
   ------------------------------------------------------------------ */

/* Visible keyboard focus everywhere, including on controls whose base
   rule sets `border: none` -- outline draws outside the box model, so it
   cannot be swallowed by a background/border declaration. */
.btn:focus-visible,
.vault-choice:focus-visible,
.vault-action:focus-visible,
.option-grid button:focus-visible,
.game-list button:focus-visible,
.wager-preset-list button:focus-visible,
.theatre-toggle-btn:focus-visible,
.people-panel-row:focus-visible,
.shell-back-btn:focus-visible,
.shell-wager-btn:focus-visible,
.shell-end-btn:focus-visible,
.side-panel-close-btn:focus-visible,
.session-chevron:focus-visible,
.shout-fab-btn:focus-visible,
.wager-live-badge:focus-visible,
.rail-toggle:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: var(--vault-focus-width, 3px) solid var(--vault-focus-color, var(--gold));
  outline-offset: var(--vault-focus-offset, 2px);
}

/* style.css's `.card input { outline: none }` is (0,1,1) -- EXACTLY the
   same specificity as the `input:focus-visible` above, which means the
   keyboard focus ring on every name/room-code/wager field was surviving
   only because this stylesheet happens to be linked second. That is the
   precise failure mode this whole file exists to prevent (see the header:
   `.depth-btn.on` vs `.btn.ghost`, equal specificity, later rule wins,
   selection invisible). Declared here at (0,2,1) so it out-ranks the
   `outline: none` outright and cannot be broken by re-ordering a link
   tag. */
.card input:focus-visible,
.card textarea:focus-visible,
.wager-card input:focus-visible,
.wager-card textarea:focus-visible {
  outline: var(--vault-focus-width, 3px) solid var(--vault-focus-color, var(--gold));
  outline-offset: var(--vault-focus-offset, 2px);
}

/* A focused control must not be hidden UNDER a neighbour. The outline is
   drawn 2px outside the box, which a following sibling's background can
   paint over. Deliberately scoped to IN-FLOW control classes: a blanket
   `:focus-visible { position: relative }` would re-position the fixed
   chevrons, the shout FAB and the wager badge into normal flow the
   moment they took focus, and they would visibly jump. */
.btn:focus-visible,
.option-grid button:focus-visible,
.game-list button:focus-visible,
.wager-preset-list button:focus-visible,
.theatre-toggle-btn:focus-visible,
.people-panel-row:focus-visible,
.rail-toggle:focus-visible {
  position: relative;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .vault-choice,
  .btn,
  .vault-action,
  .option-grid button,
  .game-list button,
  .wager-preset-list button,
  .theatre-toggle-btn,
  .people-panel-row,
  .rail-toggle,
  .session-chevron,
  .shout-fab-btn,
  .wager-live-badge {
    transition: none;
  }
  /* The state changes themselves stay -- only the movement goes, so a
     press is still felt as a brightness/colour change. */
  .btn:not(:disabled):active,
  .vault-action:not(:disabled):active,
  .option-grid button:not(:disabled):active,
  .game-list button:not(:disabled):active,
  .wager-preset-list button:not(:disabled):active,
  .theatre-toggle-btn:not(:disabled):active,
  .people-panel-row:active {
    transform: none;
  }
  .vault-choice.is-selected,
  .vault-choice.is-locked {
    transform: none;
  }
}

/* ---------------------------------------------------------------------
   5. TOUCH TARGETS
   ------------------------------------------------------------------
   Measured on a real 375x812 phone viewport, 2026-09-09. Every one of
   these was under the 44px minimum (WCAG 2.5.8 AA / Apple HIG 44pt):

       shellEndSessionBtn     84 x 22
       shellWagerBtn (dice)   39 x 27
       side-panel close       32 x 32
       theatre toggles       ~115 x 35
       rail-toggle            (same family)

   They are NOT simply grown. The shell header is one tight row on a
   narrow phone -- brand + room code + game name + dice + Games + End
   Session -- and inflating every control in it would force a redesign
   of a header that works. Instead each control keeps its drawn size and
   gains an invisible, centred hit area of at least 44x44 via a pseudo-
   element. The pseudo-element is part of the button's own box, so a tap
   inside it activates the button with no markup, no JS and no layout
   change whatsoever.

   Horizontal expansion is capped below each row's gap so neighbouring
   hit areas cannot overlap and steal each other's taps: .shell-header
   has gap:10px (so <=4px a side), .theatre-controls-row and
   .ai-provider-row have gap:8px (so <=3px a side).

   The theatre toggles ALSO grow for real (min-height), because they sit
   in their own row with room to spare and they are primary preference
   controls a player genuinely aims at, not header furniture. */
.shell-back-btn,
.shell-wager-btn,
.shell-end-btn,
.side-panel-close-btn,
.rail-toggle,
.btn.tiny,
.mix-bar .btn,
.checkin-row .btn {
  position: relative;
}
.shell-back-btn::after,
.shell-wager-btn::after,
.shell-end-btn::after,
.side-panel-close-btn::after,
.rail-toggle::after,
.btn.tiny::after,
.mix-bar .btn::after,
.checkin-row .btn::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-width: calc(100% + 8px);
  min-height: var(--vault-touch-min, 44px);
  /* Purely a hit area: never paints, never captures a pointer away from
     the button it belongs to (it IS the button, as far as hit-testing is
     concerned), and never affects layout. */
  border-radius: inherit;
}
/* The panel close/back buttons are square icon controls sitting alone in
   a panel header with nothing beside them, so they can take the full
   44px in BOTH axes without any risk of stealing a neighbour's taps.
   Measured 32x32 before this. */
.side-panel-close-btn::after {
  min-width: var(--vault-touch-min, 44px);
}
.theatre-toggle-btn {
  min-height: var(--vault-touch-min, 44px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ---------------------------------------------------------------------
   6. PLAYER PHOTOS ARE GAME UI, NOT IMAGES
   ------------------------------------------------------------------
   Spec section 9: "Player photos are game UI elements, not navigable
   images: no accidental anchors, draggable=false, no tap-to-open-raw-
   image, no save/share callout, accessibility preserved."

   Measured before this rule existed (2026-09-09, real page): every
   <img> in the app reported draggable=true, user-select:auto and no
   touch-callout suppression -- so a long-press on a friend's face on
   iOS raised the native Save Image / Share sheet in the middle of a
   round, and a slow drag on desktop peeled the photo off as a drag
   image. Both are the browser treating a game token as content.

   The `alt` attributes stay exactly as they are: this suppresses
   browser CHROME, never assistive technology. What still needs markup
   is `draggable="false"` (the attribute is the only fully reliable
   suppression in Firefox) -- see the handoff note. */
.player-row-photo,
.rail-photo,
.people-panel-photo,
.people-profile-photo,
.photo-preview-img,
.d-player-photo {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  /* A photo is never the tap target -- the chip or row around it is. */
  pointer-events: none;
  /* One cropping rule for all of them, so a portrait selfie and a
     landscape group shot both arrive as the same clean circle. */
  object-fit: cover;
  object-position: center;
  background: var(--vault-surface, #14162a);
  flex: 0 0 auto;
}

/* The QR codes are scanned, not saved or dragged either. They keep
   pointer-events so a desktop user can still right-click one
   deliberately if they want to. */
#qrImg,
.display-qr,
.checkin-qr-box img,
.d-qr {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* A photo needs its own edge or it dissolves into whatever it is sitting
   on -- a dark hoodie against a dark chip is not a recognisable face. */
.player-row-photo,
.rail-photo,
.people-panel-photo {
  box-shadow: 0 0 0 1px var(--vault-edge-control, #5b60a0);
}

/* ACTIVE PLAYER. The spotlighted player is who the round is ABOUT, so
   their photo carries a gold ring rather than the neutral edge -- the
   same "Vault is pointing at you" language as a selected choice, at
   photo scale. Applied through the row/chip the client already marks,
   so nothing new has to be tracked. */
.rail-chip.rail-active .rail-photo,
.people-panel-row.people-row-active .people-panel-photo {
  box-shadow: 0 0 0 2px var(--gold);
}


/* ===================================================================
   SPICE / DEPTH SLIDER THEATRE (2026-09-08, brief section 11)
   "The sliders themselves should feel like game controls, not
   configuration widgets."

   --vault-slider-fill      how far along the dial is, 0-100%
   --vault-slider-intensity 0-1, how hot/deep the current level is
   Both are set by public/sliderTheatre.js, which is also the single
   place that decides what a level SOUNDS like -- so the look and the
   sound of a level can never drift apart.

   Spice runs cool -> hot (gold into red). Depth runs shallow -> deep
   (pale into a saturated blue-violet). Deliberately different
   directions on the colour wheel, matching their deliberately
   different sonic vocabularies.
   =================================================================== */
.vault-slider {
  --vault-slider-fill: 0%;
  --vault-slider-intensity: 0;
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  /* Was 26px. Spice and Depth are GAME controls a player grabs mid-round,
     and 26px is well under the 44px touch minimum -- measured 303x26 on a
     375px phone. The visible track and thumb are unchanged; only the
     grabbable box grows, and the track stays vertically centred in it. */
  height: var(--vault-touch-min, 44px);
  background: transparent;
  cursor: pointer;
  touch-action: pan-y; /* let a vertical page scroll still work off the thumb */
}
.vault-slider:disabled { cursor: not-allowed; opacity: 0.55; }

/* --- the track, with a real visible fill ---
   8px -> 14px, and the thumb 22px -> 26px (2026-09-13). The grabbable box
   was already 44px, so the control was big enough to HIT while still looking
   like a hairline. The fill is what carries Spice and Depth -- the two
   settings the whole session turns on -- and at 8px that reading was a
   squint on a phone held at arm's length. The webkit thumb's margin-top goes
   -7px -> -6px to stay centred on the taller track: (14 - 26) / 2. */
.vault-slider::-webkit-slider-runnable-track {
  height: 14px;
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--vault-slider-lo) 0%,
    var(--vault-slider-hi) var(--vault-slider-fill),
    rgba(255, 255, 255, 0.13) var(--vault-slider-fill),
    rgba(255, 255, 255, 0.13) 100%
  );
}
.vault-slider::-moz-range-track {
  height: 14px;
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--vault-slider-lo) 0%,
    var(--vault-slider-hi) var(--vault-slider-fill),
    rgba(255, 255, 255, 0.13) var(--vault-slider-fill),
    rgba(255, 255, 255, 0.13) 100%
  );
}

/* --- the thumb: big enough to hit on a real phone --- */
.vault-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  margin-top: -6px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--vault-slider-hi);
  box-shadow: 0 0 calc(4px + 10px * var(--vault-slider-intensity)) var(--vault-slider-glow);
  transition: transform 90ms ease-out;
}
.vault-slider::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--vault-slider-hi);
  box-shadow: 0 0 calc(4px + 10px * var(--vault-slider-intensity)) var(--vault-slider-glow);
}
.vault-slider:active::-webkit-slider-thumb { transform: scale(1.18); }

/* --- per-dial palettes, now straight off the shared vocabulary ---
   SPICE runs cool -> hot (ember to flame); DEPTH runs shallow -> deep
   (surface water to cold deep water). Deliberately opposite directions
   on the colour wheel because the two dials are orthogonal (spec S4) and
   must never look like one dial with two names. */
.vault-slider-spice {
  --vault-slider-lo: var(--vault-spice-low, #f5c86a);
  --vault-slider-hi: var(--vault-spice-high, #ff4d3d);
  --vault-slider-glow: var(--vault-spice-glow, rgba(255, 77, 61, 0.75));
}
.vault-slider-depth {
  --vault-slider-lo: var(--vault-depth-low, #9fd0ff);
  --vault-slider-hi: var(--vault-depth-high, #6a5cff);
  --vault-slider-glow: var(--vault-depth-glow, rgba(106, 92, 255, 0.75));
}

/* --- the numeric read-out, sized to feel like a readout not a label --- */
.slider-value {
  display: inline-block;
  min-width: 2ch;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--gold);
}

/* --- one short pulse per step change --- */
@keyframes vaultSliderPulse {
  0%   { box-shadow: 0 0 0 0 var(--vault-slider-glow); }
  100% { box-shadow: 0 0 0 12px rgba(0, 0, 0, 0); }
}
.vault-slider-pulse::-webkit-slider-thumb { animation: vaultSliderPulse 260ms ease-out; }
.vault-slider-pulse::-moz-range-thumb { animation: vaultSliderPulse 260ms ease-out; }

/* --- level 10: the top of the dial is visibly, not just audibly, different --- */
.vault-slider-peak::-webkit-slider-thumb { border-width: 3px; box-shadow: 0 0 16px var(--vault-slider-glow), 0 0 30px var(--vault-slider-glow); }
.vault-slider-peak::-moz-range-thumb { border-width: 3px; box-shadow: 0 0 16px var(--vault-slider-glow), 0 0 30px var(--vault-slider-glow); }

/* Reduced motion: the fill, colour and glow all stay -- they carry the
   information. Only the repeating movement is removed. sliderTheatre.js also
   declines to add the pulse class at all in that case, so this is belt and
   braces for a preference that changes mid-session. */
@media (prefers-reduced-motion: reduce) {
  .vault-slider-pulse::-webkit-slider-thumb { animation: none; }
  .vault-slider-pulse::-moz-range-thumb { animation: none; }
  .vault-slider::-webkit-slider-thumb { transition: none; }
  .vault-slider:active::-webkit-slider-thumb { transform: none; }
}

/* Host approval of a shared screen (2026-09-10). The screen shows a short
   code; this is where the host accepts it. Sized for a code typed across a
   room, and the disconnect control is deliberately quiet -- it is a rare,
   deliberate action, not something to press by accident mid-round. */
.display-pair-row { display: flex; gap: var(--vault-space-2, 8px); align-items: stretch; margin: var(--vault-space-2, 8px) 0; }
.display-pair-input {
  flex: 1 1 auto; min-width: 0; min-height: var(--vault-touch-min, 44px);
  font-size: 1.25rem; letter-spacing: 0.18em; text-align: center; text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.display-pair-row .btn { flex: 0 0 auto; }
#displayPairNotice.ok { color: var(--vault-gold-soft, #e8c46a); }
.display-list { display: flex; flex-direction: column; gap: var(--vault-space-1, 4px); }
.display-row { display: flex; align-items: center; justify-content: space-between; gap: var(--vault-space-2, 8px); }
.display-row .btn { min-height: var(--vault-touch-min, 44px); padding-inline: var(--vault-space-3, 12px); }

/* ================= DEEP DIVE TAKEOVER (2026-09-10) =================
   Deep Dive used to be 13px grey text at 5% of the viewport, under a button,
   and absent from the shared display entirely. It is the moment Vault steps
   into the room, so it now behaves like one: the game recedes, the card comes
   forward, Vault speaks, and it returns cleanly.
   Shared by the phone and the TV -- only the scale differs. */
.deep-dive-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--vault-z-takeover, 900);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 5vw, 48px);
  background: color-mix(in srgb, var(--vault-ground, #0b0d17) 82%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity var(--vault-motion-medium, 220ms) var(--vault-ease, cubic-bezier(.2,.7,.3,1));
}
.deep-dive-overlay.is-open { opacity: 1; }
.deep-dive-overlay.hidden { display: none; }

.deep-dive-card {
  width: min(680px, 100%);
  max-height: 86vh;
  overflow-y: auto;
  padding: clamp(20px, 4vw, 36px);
  border-radius: var(--vault-radius-lg, 18px);
  background: var(--vault-surface, #151a2e);
  border: 1px solid color-mix(in srgb, var(--vault-gold, #e8c46a) 45%, transparent);
  box-shadow: 0 24px 80px rgba(0,0,0,.6), 0 0 0 1px rgba(0,0,0,.4);
  text-align: center;
  transform: translateY(14px) scale(.97);
  transition: transform var(--vault-motion-medium, 220ms) var(--vault-ease, cubic-bezier(.2,.7,.3,1));
}
.deep-dive-overlay.is-open .deep-dive-card { transform: translateY(0) scale(1); }

.deep-dive-eyebrow {
  font-size: clamp(11px, 1.4vw, 13px);
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--vault-gold, #e8c46a);
  margin-bottom: clamp(10px, 2vh, 18px);
}
/* The line itself: this is the thing the room reads, so it is sized to be
   read, not skimmed past. */
.deep-dive-text {
  font-size: clamp(19px, 2.6vh, 30px);
  line-height: 1.35;
  font-weight: 600;
  color: var(--vault-text, #f2f3f8);
  text-wrap: balance;
}
.deep-dive-dismiss {
  margin-top: clamp(16px, 3vh, 28px);
  min-height: var(--vault-touch-min, 44px);
  padding: 0 var(--vault-space-4, 16px);
  border-radius: var(--vault-radius-md, 12px);
  background: transparent;
  color: var(--vault-muted, #9a9bb0);
  border: 1px solid var(--vault-edge-control, #3a3f57);
  cursor: pointer;
}
.deep-dive-dismiss:hover { color: var(--vault-text, #f2f3f8); }

/* On a TV nobody taps anything: the card is bigger and the dismiss control
   is irrelevant, so it stops taking up room. */
body.vault-display .deep-dive-card { width: min(1100px, 92%); }
body.vault-display .deep-dive-text { font-size: clamp(30px, 5vh, 64px); }
body.vault-display .deep-dive-dismiss { display: none; }

/* Reduced motion: the takeover still happens, it just does not animate. */
@media (prefers-reduced-motion: reduce) {
  .deep-dive-overlay, .deep-dive-card { transition: none; }
  .deep-dive-card { transform: none; }
}
.deep-dive-overlay.no-motion, .deep-dive-overlay.no-motion .deep-dive-card { transition: none; transform: none; }

/* ===== PERSONAL-AI CONNECTION STATES (2026-09-10) =====
   Four states, visually distinct, so a player never has to guess whether
   Vault is waiting for them, waiting for their AI, or has given up. The
   spinner appears ONLY where something is genuinely happening without the
   player; on the manual route the label says what they still have to do. */
.ai-connect-connecting { color: var(--vault-text, #f2f3f8); }
.ai-connect-failed { color: var(--vault-verdict-no, #e4776b); }
.ai-connect-spinner {
  display: inline-block;
  width: 0.85em; height: 0.85em;
  margin-right: 0.5em;
  vertical-align: -0.08em;
  border: 2px solid color-mix(in srgb, var(--vault-gold, #e8c46a) 35%, transparent);
  border-top-color: var(--vault-gold, #e8c46a);
  border-radius: 50%;
  animation: vault-ai-spin 900ms linear infinite;
}
@keyframes vault-ai-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .ai-connect-spinner { animation: none; border-top-color: var(--vault-gold, #e8c46a); }
}
