/* LUSKI Arcade — shared design tokens for the game-UI component library
 * (nav bar, item-card, dialogue-box, quest-log). Every component in this
 * folder styles itself ONLY through these custom properties so the four
 * pieces read as one system instead of four separately-designed widgets.
 *
 * Extends the site's existing dark/ruby identity (--bg:#070707,
 * --accent:#F3037E from generate_pages.py's SHARED_CSS and garage/tokens.css)
 * rather than replacing it -- this library has to sit inside the same brand,
 * not next to it. The parchment/gold tokens are new: they're what makes an
 * item-card or dialogue box read as "game UI" instead of just another dark
 * card, and they're deliberately warm and light against the site's black
 * ground the same way a real inventory-screen tooltip pops off a dark HUD.
 */
:root{
  /* — inherited site identity, unchanged — */
  --arc-bg: #070707;
  --arc-ink: #f2eff2;
  --arc-ink-dim: rgba(242,239,242,.62);
  --arc-ink-faint: rgba(242,239,242,.38);
  --arc-accent: #F3037E;
  --arc-accent-dim: rgba(243,3,126,.16);
  --arc-line: rgba(242,239,242,.14);

  /* — new: parchment/gold, the "item card" surface — */
  --arc-parchment: #ece3cf;
  --arc-parchment-2: #e2d6b8;
  --arc-parchment-ink: #2b2213;
  --arc-parchment-ink-dim: rgba(43,34,19,.62);
  --arc-gold: #c8a24a;
  --arc-gold-strong: #9c7a2e;
  --arc-gold-line: rgba(156,122,46,.45);

  /* — status — */
  --arc-good: #3fbf87;
  --arc-live: #e8a33d;

  /* — chrome: the site's other established surface (generate_pages.py /
     generate_cars.py / sell-your-car's SHARED_CSS all define the identical
     --chrome-1/2/3 + --border-chrome-strong + --ink pair already; mirrored
     here byte-for-byte, not reinvented, so a component that needs to sit on
     brushed chrome instead of the dark HUD ground still styles itself only
     through tokens). Ink here is the dark pair those pages use for text on
     that light ground -- --arc-ink/-dim above are only legible on --arc-bg. */
  /* DEPRECATED IN V2 -- do not delete, do not build new things on this.
     The brushed-silver chrome surface was the pre-arcade site's identity;
     v2 is committing to flat --arc-bg (black) / --arc-ink (white) with
     --arc-accent reserved for actionable elements only (buttons, links,
     the current-nav underline) -- not decorative gradients. These three
     tokens plus --arc-border-chrome-strong / --arc-ink-solid* /
     --arc-chrome-nav-bg below stay defined, unchanged, because ~80 live
     pages still render on this surface (garage/, sell-your-car/, the
     model pages) and this pass is additive-only -- ripping chrome out
     from under them would break real pages for a design decision they
     haven't been migrated to yet. New v2 pages/components should reach
     for --arc-bg/--arc-ink instead of any --arc-chrome-*/--arc-ink-solid*
     token below; treat this whole block as read-only legacy support. */
  --arc-chrome-1: #0c0c0d;
  --arc-chrome-2: #131316;
  --arc-chrome-3: #1c1c1f;
  --arc-border-chrome-strong: rgba(255,255,255,0.18);
  --arc-ink-solid: #f5f5f7;
  --arc-ink-solid-dim: rgba(245,245,247,0.62);
  /* The exact nav gradient the site used before this component replaced it
     (generate_pages.py, generate_cars.py, sell-your-car/index.html all carry
     this literal value) -- copied whole rather than approximated through
     --arc-chrome-1/2/3, which are a separate, simpler 3-tone card palette. */
  --arc-chrome-nav-bg: linear-gradient(115deg,#f2f3f4 0%,#dcdedf 22%,#131316 45%,#cfd1d3 68%,#e7e9ea 100%);
  /* --arc-accent itself is ~3.6:1 on --arc-chrome-nav-bg -- fine for the
     current-tab underline (a graphical object, 3:1 floor) but not for the
     current-tab's own text (4.5:1 floor). Darkened ~15% toward black clears
     ~4.75:1 while still reading as the same ruby -- a literal, not
     color-mix(), so accessibility never silently regresses if a browser's
     color-mix support is missing. */
  --arc-accent-deep: #cf036b;

  /* v2: Clash Display is not a new font choice -- index.html already
     self-hosts it (/fonts/ClashDisplay-Semibold.woff2, family name
     literally "Clash Display", weight 600, see its @font-face + preload
     block) for the boot-overlay title. It's just never been wired into
     this token, so nav.css/item-card.css/quest-log.css headings (all of
     which already read --arc-font-display) were still rendering in
     Helvetica Neue on a page that had the better face loaded one scroll
     away. Only one weight exists on disk (Semibold/600) -- do not set
     font-weight below 600 against this stack expecting a lighter cut to
     exist. Safe to change for the other ~79 pages this file also loads
     on: none of them declare the @font-face, so "Clash Display" simply
     fails to resolve there and the browser falls through to the
     pre-existing Helvetica Neue fallback -- same render as before this
     line changed, not a regression. A v2 page that wants the real face
     just needs to copy index.html's @font-face block (this token can't
     load the font file itself, only name-check for it). */
  --arc-font-display: "Clash Display", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --arc-font-body: "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* HUD/label face -- nav links, slot labels, eyebrow text, anything
     wanting a wide condensed grotesque feel. Nothing self-hosted on disk
     fits that brief today (fonts/ has only ClashDisplay-Semibold.woff2 --
     checked before writing this), so this is a placeholder built from a
     face already loaded everywhere (system Helvetica Neue) rather than
     a made-up webfont reference that would 404. It leans on letter-
     spacing to fake the condensed/wide HUD feel since a font-family var
     can't carry tracking -- pair it with a wider letter-spacing
     (~.05em-.15em depending on size) wherever it's used, the way
     nav.css's own .arc-nav__link already hand-tunes letter-spacing today. */
  --arc-font-hud: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --arc-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* Same physical stack as --arc-mono above, exposed under the
     --arc-font-* naming family so v2 components can reach for
     "display/hud/mono" as one consistent set instead of mixing that
     naming scheme with the older bare --arc-mono name. Deliberately
     var()-indirected rather than a copy-pasted duplicate stack: item-
     card.css and quest-log.css already read --arc-mono directly for
     prices/km, and a second hand-copied stack would silently drift from
     it the next time either gets tuned. */
  --arc-font-mono: var(--arc-mono);

  --arc-radius: 3px;
  --arc-ease: cubic-bezier(.16,1,.3,1);
  /* the four Phase 1 components each hand-picked a hover/press duration
     independently (160ms / .15s+.2s / .18s+.12s) before this token existed --
     every one of them now points here so a future retune only touches one line */
  --arc-transition: 160ms;
  /* Page-to-page cross-fade only (see the @view-transition block at the end of
     this file). Deliberately NOT --arc-transition: that one is scoped to
     component hover/press by its own comment above, and a whole-viewport
     dissolve wants its own knob. Kept under ~250ms because during the fade the
     destination document is already live underneath two snapshots, so a click
     in that window lands on something the visitor cannot see yet. */
  --arc-dur-page: 180ms;

  /* the warm highlight used for glints/gradients on gold surfaces (item-card's
     glint and CTA gradient derive it via color-mix already; this token exists
     so any future component reaches for it instead of hand-deriving its own) */
  --arc-gold-highlight: color-mix(in srgb, var(--arc-gold) 40%, white);

  /* — v2: type scale — a real modular scale (1.25, "major third") instead of
     every component hand-picking its own font-size like the four Phase 1
     components did (dialogue-box: 15/14/14/12/11px, item-card: 8.5-24px,
     nav: 12/11px, quest-log: 12-15px -- none of them relate to each other by
     any ratio, they were each eyeballed independently). Anchored at 16px
     because that's the real base: grepped every body{}/html{} rule in the
     site's CSS (guide-page.css, model-page.css) plus index.html's inline
     styles and none of them set a font-size on <html> or <body> -- the site
     has been running on the unstyled browser default (16px) the whole time,
     so 16px is what's actually anchoring every existing px value on the
     page today, not a number invented for this scale. Expressed in rem
     (not px, unlike the rest of this codebase) specifically for this token
     set: a type scale is the one place where respecting a user's OS/browser
     text-size preference matters, and since nothing sets html{font-size},
     1rem === 16px sitewide today -- adopting these costs nothing visually
     until someone actually changes their browser's default size, which is
     exactly when they should. xs/sm sit below base for HUD chrome (labels,
     eyebrows); md and up are for headings.
       xs   0.64rem   (~10.2px)
       sm   0.8rem    (~12.8px)
       base 1rem      ( 16px, the anchor)
       md   1.25rem   ( 20px)
       lg   1.5625rem ( 25px)
       xl   1.9531rem (~31.3px)
       2xl  2.4414rem (~39.1px)
       3xl  3.0518rem (~48.8px)
       4xl  3.8147rem (~61px)
  */
  --arc-scale-xs: 0.64rem;
  --arc-scale-sm: 0.8rem;
  --arc-scale-base: 1rem;
  --arc-scale-md: 1.25rem;
  --arc-scale-lg: 1.5625rem;
  --arc-scale-xl: 1.9531rem;
  --arc-scale-2xl: 2.4414rem;
  --arc-scale-3xl: 3.0518rem;
  --arc-scale-4xl: 3.8147rem;

  /* — v2: per-item stagger delay, documented not defined —
     Not a static value, so there's nothing to declare here, but the pattern
     belongs at the token layer so a future component author finds it before
     re-inventing it (item-card grids / quest-log lists are the obvious first
     users -- a row of cards animating in with identical delay looks like a
     glitch, not an entrance). Usage:
       CSS : animation-delay: calc(60ms + var(--arc-stagger-i, 0) * 40ms);
       JS  : items.forEach((el, i) => el.style.setProperty('--arc-stagger-i', i));
     The `, 0` fallback in the calc() matters: if a component forgets to set
     the property (or JS hasn't run yet, e.g. the reduced-motion/no-JS case),
     var(--arc-stagger-i, 0) resolves to 0 and every item gets the same base
     60ms delay instead of animation-delay silently failing to a raw
     `calc(60ms + * 40ms)` parse error, which would happen with an undefined
     custom property and no fallback. Base delay (60ms) and per-item step
     (40ms) are for the calling component to choose -- deliberately not
     tokenized here, since a fast HUD list and a slow item-card reveal want
     different numbers, and forcing one shared constant on both would just
     get overridden inline anyway. */

  /* — v2: z-index scale —
     No --z-* tokens existed anywhere in the codebase before this (checked:
     grepped every --z- and z-index across every .css/.html file). What
     exists instead is an unwritten, undocumented ordering already followed
     by literal values scattered across index.html/model-page.css/cars/*:
     cart overlay+drawer at 200/201, the reserve-deposit overlay at 300,
     the toast at 999, and the boot sequence's full-page takeover at 100000
     with its custom cursor one above it at 100001 (the cursor has to clear
     its own overlay). nav.css's sticky .arc-nav is a literal 40. This scale
     doesn't invent new numbers -- it names the four bands the arcade layer
     actually needs and points them at the existing convention so a future
     component that migrates to the token doesn't jump the existing stacking
     order: hud matches nav.css's current literal, modal sits in the same
     200-300 band already used for overlays/drawers site-wide, toast matches
     the existing literal exactly, and cursor matches the boot-cursor's
     literal so a future arcade cursor stacks correctly even if it's ever on
     screen at the same time as the boot sequence. Ordering rationale:
     cursor > toast > modal > hud, i.e. a custom cursor always wins, a toast
     notification should still surface over an open modal (matches the
     existing site: toast=999 already outranks the 200/300 overlays), a
     modal sits above the HUD chrome it's interrupting, and the HUD sits
     above ordinary page content (implicit 0/auto). */
  --arc-z-hud: 40;
  --arc-z-modal: 300;
  --arc-z-toast: 999;
  --arc-z-cursor: 100001;

  /* — v2.2: the interaction system (see arcade/interact.css) —
     Before these existed every component hand-picked its own lift distance,
     press behaviour and focus offset: lifts at -2/-3/-4px, ten different
     scale(1.01)/scale(1.02) hover-grows, three press states in the whole
     codebase, and ruby focus rings at four different offsets. interact.css
     owns transform/box-shadow/outline for every interactive element and
     resolves all of it from here, so a retune is one line, not forty. */
  --arc-ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); /* the nav-link feel, promoted to the sitewide hover curve */
  --arc-ease-press: cubic-bezier(0.4, 0, 1, 1);         /* press wants to arrive fast and stop, not overshoot */
  --arc-dur-lift: 300ms;
  --arc-dur-press: 90ms;

  /* Three lift distances, not one: a 4px lift reads as playful on a card or a
     chip but as a wobble on a dense row of buttons, so buttons get half of it
     and dense rows get none. */
  --arc-lift-chip: -4px;
  --arc-lift-btn: -2px;
  --arc-lift-card: -4px;
  --arc-press-scale: 0.98;
  --arc-press-sink: 1px;
  --arc-disabled-opacity: 0.5;

  /* Focus stays ruby by Eric's explicit call (Aug 2026), rather than the blue
     garage/tokens.css uses. Ruby measures 3.21:1 on --arc-parchment and far
     more on --arc-bg, so it clears the 3:1 floor SC 1.4.11 sets for focus
     indicators on BOTH grounds -- no per-surface override is needed.
     The known and accepted cost: on the controls whose hover is also ruby,
     focused and hovered look alike. The two-tone ring below is what buys back
     most of that distinction -- the --arc-focus-ground gap is a shape change,
     not a colour change, so it still reads as "focused" at the same hue. */
  --arc-focus: var(--arc-accent);
  --arc-focus-ground: var(--arc-bg);
  --arc-focus-ring: 0 0 0 2px var(--arc-focus-ground), 0 0 0 4px var(--arc-focus);

  --arc-accent-glow: rgba(243, 3, 126, 0.28);
  --arc-shadow-card-hover: 0 20px 44px rgba(0, 0, 0, .45);
  --arc-glow-btn-hover: 0 0 0 3px var(--arc-accent-dim), 0 8px 20px -8px var(--arc-accent-glow);
}

/* Collapsing the durations at the token level means every component that
   reads them goes still at once, including ones written later that never
   thought about reduced motion. interact.css additionally zeroes transform
   itself, because a 1ms lift is still a lift. */
@media (prefers-reduced-motion: reduce){
  :root{
    --arc-transition: 1ms;
    --arc-dur-lift: 1ms;
    --arc-dur-press: 1ms;
  }
}

@media (prefers-reduced-motion: reduce){
  .arc-reduced-motion *, .arc-reduced-motion *::before, .arc-reduced-motion *::after{
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   CROSS-PAGE TRANSITIONS
   ───────────────────────────────────────────────────────────────────────────
   A 180ms cross-dissolve between same-origin pages, built on cross-document
   View Transitions. Pure CSS: no JavaScript, no new file, no new request.

   Why this and not the overlay that was here before: the previous system
   intercepted every link click, called preventDefault(), ran a black slide,
   and only THEN navigated -- roughly 440ms of dead time in which the browser
   had not even been told to fetch the next page. It was removed for feeling
   sluggish, and rebuilding it would repeat the mistake. Here the browser
   navigates immediately and the fade is composited from snapshots the UA
   takes for free, so the click-to-request gap is under one frame.

   The old page is deliberately HELD fully opaque rather than fading out. The
   UA default cross-fades both directions at once, which dips the midpoint
   luminance and reads as a black flash against our near-black ground. Holding
   the outgoing frame and floating the incoming one over it keeps the ground
   perfectly steady.

   Keyframes are namespaced arc-xdoc-* to stay clear of the live .arc-page-*
   pagination component in arcade/paginate.js, which owns arc-page names.

   Not opted in: the homepage (its own boot overlay already covers the screen
   on arrival, so a dissolve into it is invisible work) and /garage/, which
   loads garage/tokens.css instead of this file and routes itself.

   ── STATUS: CONTESTED. READ THIS BEFORE TRUSTING EITHER CLAIM ─────────────
   A production audit (Aug 2026) concluded this feature was dead: the outgoing
   page fired pageswap with a live e.viewTransition, the destination fired
   pagereveal with e.viewTransition === null, and Chrome logged "AbortError:
   Transition was skipped". It reported /cars/ -> /guides/ failing 6 out of 6,
   and claimed the dissolve only completed when the destination was /privacy/
   or /terms/.

   A later attempt to find the root cause could NOT reproduce that. Measured
   locally against the real pages and real paths, the same instrumentation
   showed the transition SUCCEEDING in 6 of 7 trials -- including the exact
   /cars/ -> /guides/ navigation called deterministic, including with a cold
   cache, and including with the service worker fully unregistered. The single
   failure occurred in the transitional moment just after unregistering the
   service worker, while navigator.serviceWorker.controller still reported
   true, which is a condition the test created and a real visitor never hits.

   The likeliest explanation for the disagreement is that the audit's own
   instrumentation perturbed what it measured: it used injected init scripts,
   repeated service-worker unregistration and response interception, all on the
   navigation path whose timing decides whether a transition survives.

   So: do NOT rely on the "it is broken" claim, and do NOT rely on "it is
   fixed" either. What is actually established is narrower -- the mechanism
   works, and it is at least sometimes skipped. Skipping is harmless: the
   result is a hard cut, which is exactly the behaviour that preceded this
   feature.

   Ruled out by A/B experiment across both investigations, each with a verified
   control -- do not spend time re-testing these: the nested reduced-motion
   @view-transition below; the service worker; Cache-Control: no-store;
   duplicate view-transition-name (a full computed-style scan finds none);
   any JS calling skipTransition/startViewTransition (zero hits); page byte
   size (/archives/ is SMALLER than /privacy/); document height (every page
   measures far under the max texture size, and /privacy/ at 2717px is TALLER
   than /guides/ at 2694px); the <html> element's attributes; whether the
   opt-in is inline or in an external stylesheet; file path vs directory index;
   and stripping the destination's scripts, stylesheets and entire <body>.

   If a real visitor ever reports a hard cut where a fade was expected, the
   remaining lead is render-blocking the destination with
   <link rel="expect" blocking="render">, which holds first paint until named
   content exists and is the documented remedy for a transition being skipped
   because the new document painted too early. It was deliberately NOT shipped
   here: it would add a render-blocking directive to 84 live commercial pages
   to fix a failure that could not be reproduced, and a wrong id in that
   directive blocks first paint entirely.
   ═══════════════════════════════════════════════════════════════════════════ */

@view-transition { navigation: auto; }

@keyframes arc-xdoc-hold { from, to { opacity: 1; } }
@keyframes arc-xdoc-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* The UA group animation defaults to 250ms; pin it so the group and its two
   children cannot disagree about how long the transition lasts. */
::view-transition-group(root) { animation-duration: var(--arc-dur-page); }

::view-transition-old(root) {
  animation: var(--arc-dur-page) linear both arc-xdoc-hold;
  /* The UA default is plus-lighter, which is correct for a symmetric
     cross-fade but ghosts badly here: the incoming layer is offset 6px, so
     additive blending double-exposes every edge during the rise. */
  mix-blend-mode: normal;
}
::view-transition-new(root) {
  animation: var(--arc-dur-page) var(--arc-ease) both arc-xdoc-in;
  mix-blend-mode: normal;
}

@media (prefers-reduced-motion: reduce) {
  /* Belt and braces. Turning the navigation off should be enough on its own,
     but a nested @view-transition is newer syntax than the pseudo-elements
     and an engine that drops the nested rule would otherwise keep animating.
     Killing the pseudos too means the worst case is a hard cut, never motion
     someone asked not to see. */
  @view-transition { navigation: none; }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
