/* ===========================================================================
   ARCADIA — THE MOTION SYSTEM + DESKTOP RESPONSIVE   (sub-gate 12.g)
   Owner: P12-F6.  Companion file: motion.js.  Loads AFTER style.css.

   ---------------------------------------------------------------------------
   DESIGN AUTHORITY
     1. docs/design/UI_BRIEF.md §VISUAL AUTHORITY SET A3 — THE ANIMATION CANON.
        The exploration sheet draws `dot -> line -> curve -> Arcadia` and A3
        states in terms: this is the canonical form of TWO things the brief
        describes separately — §LOADING STATES' calibration sequence and
        §MOTION SYSTEM's "underline animating from fragmented signals into one
        continuous curve".  "They are ONE sequence, implemented once and
        reused — not two animations."
        This file therefore contains exactly THREE canon keyframe blocks —
        `a-canon-point`, `a-canon-curve`, `a-canon-mark` — and they are the
        ONLY definition of the sequence anywhere in the app.  Both hosts (the
        Arcadia lockup underline, and the calibration loader) wear the same
        three classes and differ only in TOKEN VALUES.  A test asserts each
        canon keyframe name is defined exactly once across arcadia/web/static.
     2. docs/design/UI_BRIEF.md §NUMBER ANIMATION — 400-700ms, subtle easing,
        "an instrument recalibrating, not somebody hitting three cherries".
     3. docs/design/UI_BRIEF.md §MOTION SYSTEM — thin line sweeps, data ticks
        resolving into a solid path, numbers snapping into alignment, progress
        bars calibrating, status dots pulsing ONCE after refresh.
        "Fast and restrained.  No bouncing cards, no elastic effects, no
        confetti."
     4. docs/design/UI_BRIEF.md §RESPONSIVE BEHAVIOR — see §7 below.
     5. docs/design/reference-preview.html — the ratified target.  It is a
        STATIC sheet: it depicts no motion and no viewport above a phone, so
        it constrains the geometry this file animates (the underline path is
        copied from it, unchanged) and nothing else.

   ---------------------------------------------------------------------------
   THE CASINO BAN, RESTATED AS RULES THIS FILE OBEYS
     §VISUAL IDEA bans "flashing odds"; §MOTION SYSTEM bans bouncing, elastic
     and confetti; §NUMBER ANIMATION bans slot-machine behaviour.  Concretely,
     and each of these is enforced by a test in
     tests/web/test_motion_and_responsive.py:
       - no `infinite`, and no iteration count above 1, anywhere.  Every
         animation in this file plays once and stops.  Nothing on this screen
         is ever in perpetual motion, because a forecast is not a ticker.
       - no keyframe animates `color`, `background`, `background-color`,
         `visibility` or `content` — that is what flashing is made of.  Status
         colour is set once, from state, and then holds still.
       - no easing overshoots.  Every curve here is a decelerate; none has a
         control point outside 0..1 on y, so nothing bounces or springs.
       - NO NUMBER IS EVER ANIMATED THROUGH A VALUE IT DOES NOT HOLD.  This is
         structural, not stylistic: the roll-in animates the REAL glyphs of the
         real value into place (see §4), and the bars animate `transform`, so
         no width, no text and no attribute is ever mid-flight at a figure a
         screenshot could report as real.

   ---------------------------------------------------------------------------
   FILL-MODE DISCIPLINE (this is what makes reduced-motion provable)
     Every animation here uses `backwards` fill and NEVER `forwards`/`both`.
     Consequence: outside its active duration an element renders in its
     NATURAL, un-animated state — the final state.  So `animation: none` alone
     collapses every one of these to exactly what the page shows without JS
     and without CSS animation.  §8's reduced-motion block is therefore
     complete by construction; it does not have to restate a single final
     value, and cannot drift out of sync with one.

   No build step.  No @import.  No external URL.  No CDN.  No `!important`.
   =========================================================================== */


/* ---------------------------------------------------------------------------
   1. MOTION TOKENS
   `--dur-number`, `--dur-ui` and `--ease-instrument` already exist in
   style.css; they are read here with fallbacks so this file is correct even
   if loaded alone.  Nothing below re-declares them.
   --------------------------------------------------------------------------- */
:root {
  /* -- Easings.  Both are pure decelerates: y never leaves 0..1, so no
        overshoot, no bounce, no elastic.  (UI_BRIEF §MOTION SYSTEM.) -------- */
  --ease-canon: cubic-bezier(0.22, 0.61, 0.36, 1);   /* = --ease-instrument */
  --ease-settle: cubic-bezier(0.16, 0.84, 0.44, 1);

  /* -- THE CANON, timed once.  `dot -> line -> curve -> Arcadia`.
        Phase 1+2 (dot, then line) are one timeline per point, because a point
        that appears and then slides onto the trajectory is one gesture, not
        two; the alignment happens at 38%-62% of that timeline.
        Derivation of the delays, so they can be re-derived after any edit:
          align ends at  0.62 * 620ms                        = 384ms
          last point is  8 * 40ms later (9 points, 0-indexed) = 320ms
          curve starts   384 + 320 = 704ms, rounded up to     = 720ms
          mark starts    720 + 360                            = 1080ms
          sequence ends  1080 + 240                           = 1320ms          */
  --dur-canon-point: 620ms;      /* phase 1+2: dot appears, then aligns      */
  --stagger-canon: 40ms;         /* the wave that resolves ticks into a path */
  --delay-canon-curve: 720ms;
  --dur-canon-curve: 360ms;      /* phase 3: the trajectory strokes in       */
  --delay-canon-mark: 1080ms;
  --dur-canon-mark: 240ms;       /* phase 4: Arcadia / the result resolves   */
  --dur-canon: 1320ms;           /* the whole sequence, end to end           */

  /* -- Number roll-in.  UI_BRIEF §NUMBER ANIMATION: 400-700ms.
        motion.js caps the stagger index at 4, so the widest number a screen
        can hold still lands inside the band:  480 + 4*45 = 660ms.            */
  --dur-roll: 480ms;
  --stagger-roll: 45ms;
  --roll-rise: 0.3em;            /* how far a glyph travels into alignment   */

  /* -- Bars calibrating (§MOTION SYSTEM "progress bars calibrating").
        Inside the number band too: a bar IS a number here.                   */
  --dur-calibrate: 560ms;
  --delay-calibrate: 120ms;

  /* -- Status pulse.  ONE pulse after refresh, never a heartbeat.            */
  --dur-pulse: 620ms;
  --delay-pulse: 240ms;
  --pulse-ring: 7px;
}


/* ===========================================================================
   2. THE CANON — `dot -> line -> curve -> Arcadia`
   UI_BRIEF §A3.  DEFINED ONCE, HERE.  Three keyframe blocks, three classes.
   Nothing else in the app may define this sequence; both hosts adopt these
   classes (motion.js), and a host customises the sequence ONLY by overriding
   the tokens in §1 — never by declaring another keyframe.
   ===========================================================================

   Read the phases against the brief:
     §LOADING STATES  "points appear -> align into a trajectory -> trajectory
                       becomes the Arcadia underline -> result resolves"
     §MOTION SYSTEM   "the Arcadia underline may animate from fragmented
                       signals into one continuous curve — many signals, one
                       verdict"
     §A1              "the underline is the probability object, not
                       decoration" — so the thing that resolves is the
                       trajectory itself, which is why phase 3 draws the
                       SAME path the wordmark's underline is made of. */

/* PHASE 1 (dot) + PHASE 2 (line).  A fragmented signal appears where it was
   observed, holds, then aligns onto the trajectory.  `--a-dx` / `--a-dy` are
   its observed offset, written by motion.js from one geometry table; at 62%
   the offset is gone and the point sits on the curve. */
@keyframes a-canon-point {
  0%   { opacity: 0; transform: translate(var(--a-dx, 0px), var(--a-dy, 0px)) scale(0.35); }
  18%  { opacity: 1; transform: translate(var(--a-dx, 0px), var(--a-dy, 0px)) scale(1); }
  38%  { opacity: 1; transform: translate(var(--a-dx, 0px), var(--a-dy, 0px)) scale(1); }
  62%  { opacity: 1; transform: none; }
  100% { opacity: 1; transform: none; }
}

/* PHASE 3 (curve).  The aligned points become one continuous path.  Drawn
   with a normalised dash: motion.js sets pathLength="100" on the path, so the
   dash arithmetic is independent of the geometry and stays correct if the
   underline is ever re-drawn. */
@keyframes a-canon-curve {
  from { stroke-dasharray: 100; stroke-dashoffset: 100; }
  to   { stroke-dasharray: 100; stroke-dashoffset: 0; }
}

/* PHASE 4 (Arcadia).  What the trajectory was for: the wordmark on the
   lockup, the named pipeline stage in the loader.  A settle, not an entrance. */
@keyframes a-canon-mark {
  from { opacity: 0; transform: translateY(0.14em); }
  to   { opacity: 1; transform: none; }
}

/* --- The three canon classes.  Single-class selectors ONLY.  Every
   `animation:` declaration in this file is on a single class, so §8's
   reduced-motion rules (same specificity, later in the file) always win. --- */

.a-canon-point {
  transform-box: fill-box;
  transform-origin: center;
  animation: a-canon-point var(--dur-canon-point) var(--ease-canon)
             calc(var(--a-i, 0) * var(--stagger-canon)) backwards;
}

.a-canon-curve {
  animation: a-canon-curve var(--dur-canon-curve) var(--ease-canon)
             var(--delay-canon-curve) backwards;
}

.a-canon-mark {
  animation: a-canon-mark var(--dur-canon-mark) var(--ease-settle)
             calc(var(--delay-canon-mark) + var(--a-i, 0) * var(--stagger-canon))
             backwards;
}


/* ---------------------------------------------------------------------------
   2a. CANON HOST — the Arcadia lockup underline.
   The wordmark's SVG already exists in base.html; motion.js adopts it rather
   than drawing a second one, so the animated underline and the printed
   underline are the same object.  The lockup carries only 4 points, so its
   phases start earlier — TOKENS ONLY, no second sequence.
     align ends 384ms + 3*40ms = 504ms -> curve 520ms -> mark 880ms -> 1120ms

   The class goes on the LOCKUP, not on the SVG: phase 4 for this host is the
   script lettering, which is the swash's sibling.  Put the override on the
   SVG and the two halves of one sequence run on two different clocks — the
   underline on the lockup's, the wordmark on the loader's.
   --------------------------------------------------------------------------- */
.a-canon--lockup {
  --delay-canon-curve: 520ms;
  --delay-canon-mark: 880ms;
  --dur-canon: 1120ms;
}


/* ---------------------------------------------------------------------------
   2b. CANON HOST — the calibration loader (§LOADING STATES).
   "No generic spinners."  motion.js draws this figure from the SAME trajectory
   path data as the wordmark underline, in a taller viewBox so the fragmented
   signals have room to be off-trajectory before they align.  That is what
   makes the brief's third beat literal rather than metaphorical: the
   trajectory does not merely resemble the Arcadia underline, it IS it.
   --------------------------------------------------------------------------- */
.a-canon--calibration {
  display: block;
  width: 100%;
  max-width: 17rem;
  height: auto;
  margin: var(--sp-2, 8px) 0 var(--sp-3, 12px);
  color: var(--accent-primary, #C8A66A);
  overflow: visible;
}
.a-canon--calibration .a-canon-curve {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.2;
  stroke-linecap: round;
}
.a-canon--calibration .a-canon-point { fill: currentColor; }

/* The stage list is phase 4 for this host: the named pipeline stages resolve
   in order once the trajectory is drawn, so the copy reads as the calculation
   completing rather than as a list that was always there. */
.a-loading-stages .a-canon-mark { display: block; }


/* ---------------------------------------------------------------------------
   3. LINE SWEEPS — the hairline rules under section labels.
   §MOTION SYSTEM's "thin line sweeps", and the cheapest possible one: the
   rule is already a gradient in style.css, so it is drawn rather than moved.
   Reuses the canon's curve timing token so the whole screen resolves on one
   clock.
   --------------------------------------------------------------------------- */
@keyframes a-sweep {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.a-rule {
  animation: a-sweep var(--dur-canon-curve) var(--ease-canon) backwards;
}


/* ---------------------------------------------------------------------------
   4. NUMBER ROLL-IN — §NUMBER ANIMATION, 400-700ms.
   ---------------------------------------------------------------------------
   THE RULE THAT SHAPES THIS ENTIRE SECTION: a number must never animate to,
   or through, a value it does not hold.

   So this is NOT a counter.  motion.js splits the number that is already in
   the DOM into its own glyphs and staggers them into alignment; it never
   parses a number, never computes an intermediate, never writes a digit that
   was not already there.  (motion.js contains no parseInt/parseFloat/Number()/
   toFixed/Math.round and no timer — tests assert all of that.)  At every
   frame, and at rest, the element's textContent is byte-identical to the
   payload value.  "Numbers snapping into alignment" (§MOTION SYSTEM) is the
   brief's own description of exactly this, and it is the only reading of
   §NUMBER ANIMATION that survives the screenshot test.
   --------------------------------------------------------------------------- */
@keyframes a-roll-in {
  from { opacity: 0; transform: translateY(var(--roll-rise)); }
  to   { opacity: 1; transform: none; }
}

.a-roll-glyph {
  display: inline-block;
  animation: a-roll-in var(--dur-roll) var(--ease-settle)
             calc(var(--a-i, 0) * var(--stagger-roll)) backwards;
}

/* The container's own entrance (style.css `a-settle`) would double up on the
   glyph roll, so an enhanced number hands its motion to its glyphs.  Single
   class, later file: this wins on load order at equal specificity. */
.a-num-roll { animation: none; }


/* ---------------------------------------------------------------------------
   5. BARS CALIBRATING — §MOTION SYSTEM "progress bars calibrating".
   Animates `transform`, never `width`: the inline width written by the
   template IS the payload value and is never touched, so the bar cannot be
   caught mid-flight reporting a figure the model did not produce.
   --------------------------------------------------------------------------- */
@keyframes a-calibrate {
  from { transform: scaleX(0); }
  to   { transform: none; }
}

.a-sig-fill,
.a-bar i {
  transform-origin: left center;
  animation: a-calibrate var(--dur-calibrate) var(--ease-canon)
             var(--delay-calibrate) backwards;
}

/* NOT animated: `.a-score-rail`'s fill.  The rail is a tier LADDER, not a
   progress bar — a score that has reached a boundary has reached it, and
   growing that fill would read as the score still arriving.  §MOTION SYSTEM
   asks for bars calibrating, not for every gold rectangle to move. */


/* ---------------------------------------------------------------------------
   6. STATUS PULSE — §MOTION SYSTEM "status dots pulsing ONCE after refresh",
   §DATA STATES "recently updated: small gold/green pulse + timestamp".
   ---------------------------------------------------------------------------
   Once.  A ring expands out of the dot and fades; the dot itself does not
   move, does not change colour and does not repeat.  Iteration count is 1 and
   there is no `infinite` in this file.

   A pulse is a CLAIM that something just refreshed, so it is spent carefully:
     - the SHELL STATUS dot only.  motion.js marks it with `a-pulse-dot`, from
       the two places the shell puts it (`.a-crumb`, `.a-statusline`).  The
       per-row confidence chips on the Top Plays board deliberately do NOT
       pulse: a tier is a standing property of a game, not a refresh, and four
       dots pulsing down a ranked list is the sportsbook tell this brief bans.
     - the CHANGE INDICATORS, which the template only emits when a value
       actually moved between editions (`PICK CHANGED`, the +/- delta tags).
   OPEN HOOK: a true "recently updated" pulse wants `data-updated` on the chip
   — see the 12.g report.
   --------------------------------------------------------------------------- */
@keyframes a-pulse-once {
  from { box-shadow: 0 0 0 0 currentColor; }
  to   { box-shadow: 0 0 0 var(--pulse-ring) transparent; }
}

.a-pulse-dot,
.a-tag--flip,
.a-tag--up,
.a-tag--down {
  animation: a-pulse-once var(--dur-pulse) var(--ease-canon)
             var(--delay-pulse) backwards;
}


/* ---------------------------------------------------------------------------
   7. RESPONSIVE — TABLET AND DESKTOP SPLIT PANELS
   ---------------------------------------------------------------------------
   WHICH AUTHORITY GOVERNS HERE, AND WHY.
     docs/design/reference-preview.html is the ratified build target, but it is
     MOBILE-ONLY: five 340px phone frames on a sheet, and nothing else.  It
     depicts no tablet and no desktop layout, so it cannot be the authority for
     one — the same way §A9 records that it depicts no loading, stale, held or
     empty state and hands those to the brief.
     UI_BRIEF §RESPONSIVE BEHAVIOR therefore governs this section, in full:
       "Works on iPhone, Android, tablet, desktop web.  On tablet/desktop:
        preserve the large-number hierarchy, use split panels, keep the primary
        recommendation visible, place supporting analytics beside it, don't
        stretch mobile cards into enormous horizontal slabs."
     Read as four testable constraints, and each rule below serves one:
       (a) large-number hierarchy PRESERVED — nothing in this section changes a
           type size.  The hero stays the hero at every width.
       (b) SPLIT PANELS — verdict beside analytics, not above them.
       (c) PRIMARY RECOMMENDATION STAYS VISIBLE — the verdict panel is sticky,
           so scrolling the analytics never scrolls the conclusion away.
       (d) NO HORIZONTAL SLABS — every column is width-capped; a card gets a
           NEIGHBOUR at desktop width, never a longer body.

   OWNERSHIP NOTE: style.css §19 already carries the desktop rules for Home and
   Top Plays (its `min-width: 62rem` block, owner E4).  This section does NOT
   restate them — it adds the tablet tier and the three screens §19 leaves at
   one column (Game detail, Routes, Model).  The one overlap is the container
   max-width at the tablet tier, which §19 does not set.  Flagged in the 12.g
   report so the two layers stay one system.
   --------------------------------------------------------------------------- */

/* --- TABLET (48rem - 62rem).  The measure opens up.
   BOUNDED ON PURPOSE, and this is the one rule in the file that must be:
   motion.css loads AFTER style.css, so an unbounded `min-width: 48rem` cap
   here would still be in force at 1400px and would silently OVERRIDE style.css
   §19's own 62rem desktop cap — the shell would grow to 44rem and stop, and
   the split panels below would lay out inside a column two thirds the width
   they were designed for.  It is only visible in a browser at desktop width,
   which is exactly the kind of cross-file cascade defect that ships.
   Ownership stays clean too: below 62rem this file sets the cap, at and above
   it style.css does, and neither overwrites the other. */
@media (min-width: 48rem) and (max-width: 61.999rem) {
  .a-topbar,
  .a-main,
  .a-nav-list,
  .a-footer { max-width: 44rem; }
}

/* --- The card screens go two-up from tablet width and stay that way.  A phone
   card stretched to 768px is exactly the "enormous horizontal slab" the brief
   bans, so the cards pair instead. */
@media (min-width: 48rem) {
  .a-screen--routes {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--sp-4, 16px);
    align-items: start;
  }
  .a-screen--routes > .a-screen-head,
  .a-screen--routes > .a-empty,
  .a-screen--routes > .a-screen-foot { grid-column: 1 / -1; }

  .a-stats { column-gap: var(--sp-5, 24px); }
}

/* --- DESKTOP (>= 62rem / 992px).  The split panels.  Same breakpoint as
   style.css §19 on purpose: the two files must change layout on the same line,
   or the app has two desktop layouts in a 2px band. */
@media (min-width: 62rem) {

  /* (b)+(c) GAME DETAIL — the instrument.  The verdict (score, tier, win
     probability) is the primary recommendation; it takes the left panel and
     stays put.  "Why", signals, risks, data quality and history are the
     supporting analytics and flow down the right.
     Placement is by role, not by ordinal: the header, the loader and the foot
     span both columns, the verdict is pinned to column 1 row 2, and everything
     else auto-flows into column 2 — so a screen that renders four sections and
     a screen that renders nine both lay out correctly. */
  .a-screen--game {
    display: grid;
    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
    column-gap: var(--sp-7, 48px);
    align-content: start;
  }
  .a-screen--game > * { grid-column: 2; min-width: 0; }
  .a-screen--game > .a-detail-head,
  .a-screen--game > .a-loading,
  .a-screen--game > .a-screen-foot { grid-column: 1 / -1; }
  .a-screen--game > .a-verdict {
    grid-column: 1;
    grid-row: 2;
    align-self: start;
    position: sticky;
    top: var(--sp-4, 16px);
  }

  /* (b) MODEL — nine disclosure blocks in one column is a scroll, not a
     dashboard.  The headline figures and the head/foot stay full width; the
     disclosure blocks pair up beside each other. */
  .a-screen--model {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--sp-7, 48px);
    align-items: start;
  }
  .a-screen--model > * { min-width: 0; }
  .a-screen--model > .a-screen-head,
  .a-screen--model > .a-stats,
  .a-screen--model > .a-screen-foot { grid-column: 1 / -1; }

  /* (d) ROUTES — already two-up from the tablet tier; at desktop it simply
     gains the wider gutter.  It does not go three-up: a route card is a
     verdict, and three abreast reads as a grid of options to shop between. */
  .a-screen--routes { column-gap: var(--sp-5, 24px); }
}

/* --- WIDE (>= 90rem).  Nothing new happens.  The shell is already capped by
   style.css §19 at 62rem, and this rule exists to say that the cap is
   deliberate: past this width Arcadia adds whitespace, not columns.  A third
   column would be the "cluttered dashboard" §VISUAL IDEA bans. */
@media (min-width: 90rem) {
  .a-screen--game { column-gap: var(--sp-8, 64px); }
}


/* ---------------------------------------------------------------------------
   8. REDUCED MOTION — every animation collapses to its final state.
   ---------------------------------------------------------------------------
   ACCESSIBILITY BEATS THE REFERENCE (operator standing rule), so this block is
   not a courtesy tier: it is the required rendering for anyone who asks for it.

   It is complete BY CONSTRUCTION, and this is the whole reason for the
   fill-mode discipline declared at the top of the file.  Every animation above
   uses `backwards` and none uses `forwards`/`both`, so outside its active
   duration each element already renders in its natural, final state.
   Removing the animation therefore leaves precisely the finished frame — the
   drawn underline, the aligned points, the settled wordmark, the full-width
   bars, the un-ringed dot, the number in place.  No final value is restated
   here, so no final value can drift out of sync with the animation above it.

   Every selector below is a single class, matching the single-class selectors
   that declare the animations; being later in the same file, these win.
   motion.js adds a second, independent layer: under reduced motion it does not
   split numbers into glyphs at all, so the number nodes are never touched.
   style.css §20 adds a third (a universal 1ms clamp).  Any one of the three is
   sufficient; all three are present.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .a-canon-point,
  .a-canon-curve,
  .a-canon-mark,
  .a-rule,
  .a-roll-glyph,
  .a-num-roll,
  .a-sig-fill,
  .a-bar i,
  .a-pulse-dot,
  .a-tag--flip,
  .a-tag--up,
  .a-tag--down {
    animation: none;
  }
}
