/* replay
   The chart instrument shared by The Move (forms/reaction.css) and The Poll
   (forms/poll.css) -- one path, cut and coloured at the previous close, with
   the close ruled across the plot, a leading dot, and a price/change row that
   updates without jiggling while it counts. Everything here is the `rp-`
   prefix `js/replay.js` draws with `js/viz.js`'s `lineChart(ns:"rp", ...)`;
   a form's own layout, its transport controls and its volume strip (Move
   only -- The Poll's stored series carries no volume) stay in that form's own
   stylesheet, loaded after this one (rule 14: this file owns only what is
   genuinely one thing drawn twice). */

/* --- the top row: ticker, market clock, transport ---------------------
   `chartTop()`'s own markup. The Move draws all three (its `.rdout.rp-out`
   wraps it, in the card's own header, css/shell.css's column); The Poll
   draws only the transport slot -- its `visual` carries no symbol and no
   per-bar time (see forms/poll.js), so `.rp-sym`/`.rp-clock` are simply
   never emitted rather than emitted empty. Moved here from
   forms/reaction.css at session 37, once The Poll started calling
   `chartTop` too -- one thing drawn by two forms belongs in the file both
   load, not in one form's own (rule 14). */
.rp-top{display:flex;align-items:center;gap:9px;min-height:20px}
.rp-sym{font-size:var(--fs-13);font-weight:700;letter-spacing:var(--ls-01);color:var(--muted)}
/* The market clock. Tabular and min-width'd because it is the one number on
   the card that changes every frame: proportional digits make "11:11 AM" and
   "10:00 AM" different widths and the whole row twitches for eight seconds. */
.rp-clock{font-size:var(--fs-13);font-weight:600;color:var(--dim);
  font-variant-numeric:tabular-nums;min-width:74px}

/* --- the transport ----------------------------------------------------
   Two circles, two corners, on every replaying card (session 37). First cut
   put both in the panel's top-right; the reviewer, on the real Simulator,
   asked for them apart -- "I liked the fast forward being in the bottom
   right. I'd like to standardize that" (skip), and the Poll's own again
   button "should be in the same location as ... the Move" (top-right,
   where it already was). `js/replay.js`'s `againMarkup()`/`skipMarkup()`
   draw them; `attachTransport(card, clock)` wires whichever it finds under
   `card`. Both share `.rp-tp`'s look and size so the two corners read as
   one control split in two, not two different buttons. Sized and placed
   once, here, rather than per form -- The Move used to own both circles'
   CSS in forms/reaction.css; it does not any more. */
.rp-tp{position:relative;appearance:none;-webkit-appearance:none;
  width:30px;height:30px;flex:none;border-radius:50%;
  border:1px solid var(--edge);background:transparent;color:var(--muted);
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  transition:color .16s ease-out,border-color .16s ease-out}
.rp-tp:hover,.rp-tp:focus-visible{color:var(--ink);border-color:var(--muted)}
.rp-tp svg{width:13px;height:13px;fill:currentColor;stroke:none;display:block}
/* Pseudo, not padding -- the same convention as shell.css's `.act::after`:
   a 44px tap target around a 30px circle costs the row nothing, because it
   is not part of layout. */
.rp-tp::after{content:"";position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%);width:44px;height:44px}
/* Top-right of the panel, in `chartTop`'s own row -- `margin-left:auto`
   pushes it to the row's end exactly the way the old single `.rp-ctl`
   circle did. */
.rp-tp-again{margin-left:auto}
/* Bottom-right of the chart, inside whatever `position:relative` box wraps
   the line (The Move's `.pathwrap`, The Poll's `.pl-plot`) -- the corner
   The Move's own three-chevron fast-forward sat in before this session's
   first cut moved it into the top row, restored here as the shared button
   both forms now carry in that same spot. `z-index:3` clears the line and
   the scrub layer under it. */
.rp-tp-skip{position:absolute;right:0;bottom:2px;z-index:3}
/* Nothing to drive until the state machine is wired, and nothing to drive
   ever if the payload failed the model -- both buttons read as disabled.
   Skip alone goes on to disable at `done`/`reduced`: there is nothing left
   to jump past once the day has already finished, animated or not. */
.card[data-rp="loading"] .rp-tp,
.card[data-rp="invalid"] .rp-tp{opacity:.35;pointer-events:none}
.card[data-rp="done"] .rp-tp-skip,
.card[data-rp="reduced"] .rp-tp-skip{opacity:.35;pointer-events:none}

/* --- the price, running --------------------------------------------- */
/* `.rdprice`/`.rdchg` (css/shell.css, `.card.solo .rdprice` etc.) already set
   this row's size and colour for every solo form's readout; what a REPLAYED
   number needs beyond that is tabular figures and a fixed min-height, so 79
   rewrites a second during the Move's session and five during the Poll's
   spark cannot jiggle the row they sit in. */
.rp-price{font-variant-numeric:tabular-nums;line-height:1;min-height:29px}
.rp-chg{font-variant-numeric:tabular-nums;line-height:1;min-height:14px;
  align-items:baseline}
.rp-chg em{font-style:normal;font-weight:400;color:var(--dim)}

/* --- the line --------------------------------------------------------
   Two copies of one path, clipped at the previous close: above it is green,
   below it is red, and the crossing is exact because it is a geometric cut
   rather than a colour chosen per segment. */
.rp-svg{width:100%;height:100%;overflow:visible;display:block;
  clip-path:inset(0 calc(100% - var(--rp-p,0%)) 0 0)}
.rp-line{fill:none;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;
  vector-effect:non-scaling-stroke}
.rp-line.up{stroke:var(--up)}
.rp-line.dn{stroke:var(--down)}

/* The previous close, ruled and stationary -- the only fixed thing on the
   plot, drawn before the first bar so "above or below yesterday" is readable
   from the first frame. */
.rp-ref{position:absolute;left:0;right:0;border-top:1px dashed var(--edge);
  transform:translateY(-50%);pointer-events:none}
.rp-ref span{position:absolute;left:0;top:5px;font-size:var(--fs-11);color:var(--dim);
  letter-spacing:0;text-transform:none;white-space:nowrap}
/* js/viz.js's `lineChart` takes the free side: a baseline close enough to the
   plot's own bottom edge that a label drawn 5px under the rule would spill
   past the box (2026-09-11-feed-s42-002, TSLA's prev close is the day's exact
   low) gets this class instead, and the label sits 5px clear on the other
   side of the rule -- same 5px, same distance, opposite direction. */
.rp-ref--above span{top:auto;bottom:5px}

/* The leading edge. A position, not a price -- the printed price snaps to
   real prints, this marks where the drawn line currently is. `--stance` is
   set live by each caller's own script through the same two classes the rest
   of the renderer uses, so the marker and the readout turn at the same
   instant and no colour literal is written twice. Hidden until a caller's
   `createReplayClock` has written a `data-rp` state onto the card -- The
   Poll passes `card` for this reason even though it draws no button the
   other states govern. */
.rp-dot{position:absolute;width:9px;height:9px;border-radius:50%;
  background:var(--stance);transform:translate(-50%,-50%);
  box-shadow:0 0 0 4px var(--stance-wash);opacity:0;
  transition:opacity .2s ease-out}
.card[data-rp="playing"] .rp-dot,
.card[data-rp="paused"] .rp-dot,
.card[data-rp="done"] .rp-dot,
.card[data-rp="reduced"] .rp-dot{opacity:1}

/* Reduced motion gets the finished day at once, no wipe. `createReplayClock`
   never starts a frame loop when `reduced` is set; this stops the
   transitions that would still fire. Each caller's own volume strip (Move
   only) carries the identical rule in its own stylesheet, since it is not
   drawn here. */
@media (prefers-reduced-motion:reduce){
  .rp-svg{clip-path:none}
  .rp-dot{transition:none}
}

/* --- the end pill -------------------------------------------------------
   Text at the last point's own height -- `lineChart`'s `o.endPill` option
   (js/viz.js). The Poll's move badge is the only caller today: a label on
   the line rather than a reading that rides the leading dot, placed once at
   markup time so it arrives with the plot instead of a second after it.
   Moved from forms/poll.css's `.pl-move`, unchanged. */
.rp-pill{position:absolute;right:0;transform:translateY(-50%);
  font-size:var(--fs-13);font-weight:700;letter-spacing:var(--ls-n01);color:var(--stance);
  background:var(--panel);padding:1px 5px;border-radius:var(--radius-xs)}

/* --- an event rule on the series -----------------------------------------
   A dashed vertical rule at a stored session, with a label that stays
   hidden until a caller's own walk marks it `.lit` (js/replay.js's
   `createWalk`, `o.marks`) -- a mark shown before the pen reaches it reads
   as something the chart already knew. `lineChart`'s `o.rules` option (a
   labelled point, `js/viz.js`) draws this; `.low` is that function's own
   free-side flip, decided once instead of at two call sites with the same
   0.35 cutoff (fo-002). Moved from forms/indexchange.css's `.ixc-ann`
   (written first) and forms/earnings.css's `.ern-rel` (copied from it,
   byte-identical) -- one declaration of a dashed rule and its opaque-backed
   label, not two. The opaque backing is the same erase-to-background move
   `lineChart`'s own ruled-baseline label reaches for when the path runs
   through it (js/viz.js). */
.rp-rule{position:absolute;top:0;bottom:0;width:0;z-index:2;
  opacity:0;transition:opacity .28s ease}
.rp-rule.lit{opacity:1}
.rp-rule i{position:absolute;top:0;bottom:0;left:0;
  border-left:1px dashed var(--muted)}
.rp-rule em{position:absolute;top:0;right:6px;
  font-style:normal;font-size:var(--fs-10);line-height:1.35;color:var(--muted);
  white-space:nowrap;letter-spacing:.02em;
  background:var(--ground);padding:1px 4px;border-radius:var(--radius-xs)}
/* `.low` -- the label takes the end of the plot the RULED BASELINE is not
   at, the same "step away from the thing you would otherwise sit on" move
   the baseline's own label makes one level up in js/viz.js, because this
   one is placed by x and the collision is in y. */
.rp-rule.low em{top:auto;bottom:0}
/* Reduced motion draws the finished chart with no wipe (`.rp-svg` above
   does the same), so the mark has to arrive with it rather than wait for a
   transition that never runs. */
@media (prefers-reduced-motion:reduce){
  .rp-rule{transition:none}
}

/* --- the date axis under the plot ----------------------------------------
   The window's own two ends, and optionally what sits between them --
   `lineChart`'s `o.axis` option (js/viz.js). A reader has to know what the
   line spans; only a window wider than one session needs to say so (The
   Earnings is the only caller today -- The Move and The Poll each draw one
   day and do not). Moved from forms/earnings.css's `.ern-axis`/
   `.ern-axis-day`. Drawn as part of `lineChart`'s own return, inside
   whichever box the caller's stylesheet insets for the shared transport
   (forms/earnings.css's `.ern-canvas`) -- so a full-width row here already
   lands its right edge where the line ends, with no extra reserve to name
   a second time. */
.rp-axis{display:flex;justify-content:space-between;align-items:baseline;
  gap:8px;margin-top:6px;font-size:var(--fs-10);line-height:1.2;color:var(--dim);
  letter-spacing:.04em;font-variant-numeric:tabular-nums}
.rp-axis-mid{text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}

/* --- the inspector -----------------------------------------------------
   js/inspect.js's `attachInspect` -- drag along a finished plot and the
   readout follows your finger; release and it returns to the settled
   values. Lifted from The Move's own `.scrub` (forms/reaction.js/.css, pre
   this session) so The Poll, The Index Change and The Earnings can carry it
   too (foundry fo-012: "you can't scroll through the graph on the non move
   cards"). Renamed `.rp-insp` on the move, so it cannot be confused with
   `.rp-scrub` below -- that is the dev-only SCRUBBER (a transport), this is
   the reader-facing INSPECTOR (a readout), and they coexist on the same
   card: the scrubber drives the clock, the inspector only ever reads it,
   and `attachInspect`'s own gate (`clock.state.done`) means the two are
   never live at once -- the scrubber can only move the clock before done,
   the inspector only reads once it is.

   Absolutely positioned over whatever box IS the chart's own coordinate
   space (The Move's `.pathwrap`, The Poll's `.pl-plot`, The Index Change's
   `.ixc-canvas`, The Earnings' `.ern-canvas`) -- that box is `position:
   relative` already, the same requirement `skipMarkup()`'s CSS makes of its
   own container. `pointer-events:none` throughout: the layer is drawn, never
   hit-tested, so it never steals the drag it is illustrating. */
.rp-insp{position:absolute;inset:0;pointer-events:none;opacity:0;
  transition:opacity .12s ease-out}
.rp-insp.on{opacity:1}
.rp-insp i{position:absolute;top:0;bottom:0;width:1px;background:var(--edge)}
.rp-insp b{position:absolute;width:11px;height:11px;border-radius:50%;
  background:var(--stance);transform:translate(-50%,-50%);
  box-shadow:0 0 0 4px var(--stance-wash)}
/* de-008 (session 43): a tap holds this on for ~1.5s (js/inspect.js's
   TAP_HOLD_MS) and then releases it the same way a drag's release always
   has. Same reduced-motion pattern as `.rp-dot`/`.rp-rule` above -- a plain
   show and hide, no fade in or out. */
@media (prefers-reduced-motion:reduce){
  .rp-insp{transition:none}
}

/* The inspector's own hit area. `touch-action:pan-y`, not `none` --
   PR #46's own gate run (formcheck, "an unanswered card does not trap the
   feed") caught this live: `drivePromptStack`'s gesture-trap check enumerates
   every descendant of a `prompt_first` card whose computed `touch-action` is
   `none` and fails any that is not one of its own named controls
   (`pr-range`/`pg-range`/`rv-range`/`pathwrap`) -- and `touch-action:none`
   here made `.pl-plot` (The Poll) exactly that: a second element trapping the
   vertical gesture, so an ordinary swipe could not leave the card.
   `pan-y` is the fix, not a narrower allowlist: it disables HORIZONTAL native
   panning only, so a horizontal drag still reaches `attachInspect`'s own
   pointer listeners uncontested (nothing here reads `touch-action` in JS; it
   is a browser-level gesture split, decided before any listener runs) while
   a vertical swipe pans the feed natively, same as everywhere else on the
   card. `none` would have been wrong even where the gate cannot see it --
   The Index Change and The Earnings are `media_first`, not `prompt_first`,
   so `drivePromptStack` never walks their cards at all (checked in
   contracts/forms.json), meaning this exact failure was never going to be
   caught on either of them by this check, this run or any run. The bug was
   real regardless of who could see it.
   Cursor stays gated to `done`/`reduced` -- inspection is a post-playback
   affordance, and a crosshair promising it mid-draw would invite a drag that
   fights the animation. Opt-in per form, rather than a form's own copy of
   the rule: The Poll, The Index Change and The Earnings each add this class
   to whichever element is `attachInspect`'s `hitEl`. The Move keeps its own
   pre-existing `.pathwrap` rule in forms/reaction.css (`touch-action:none`,
   unconditional, written before this class existed) rather than being
   rewired to it -- that rule is not walked by `drivePromptStack` either
   (`reaction` is also `media_first`), so nothing here proves it right; it is
   simply outside what this extraction touches. */
.rp-scan{touch-action:pan-y;cursor:default}
.card[data-rp="done"] .rp-scan,
.card[data-rp="reduced"] .rp-scan{cursor:crosshair}

/* --- the scrubber (dev-only, `?dev=1`) --------------------------------
   js/scrubber.js's `attachScrubber` -- one per replaying card, appended
   into whichever `position:relative` box already holds the skip button
   (or a form's own `__clock` mount). Never drawn without `body.devtools`
   on the document (js/scrubber.js checks that itself before touching the
   DOM at all), so every rule below is dead weight on a reader's own card,
   never a rect it has to fit -- scoped under that class anyway, the same
   discipline as any other rule that must never leak (AGENTS.md rule 14),
   so a devtools-off reader is provably unaffected by a stylesheet read,
   not merely by a class that happens not to be there.

   Bottom of the panel, overlay rather than reflow (rule 25): absolute,
   full width of whatever box it was mounted into, own background so it
   reads over a moving chart or a shrinking bar rather than fighting it for
   contrast. */
body.devtools .rp-scrub{
  position:absolute;left:0;right:0;bottom:0;z-index:9;
  display:flex;flex-direction:column;gap:2px;padding:6px 8px 5px;
  background:rgba(6,7,8,.82);backdrop-filter:blur(2px);
  border-top:1px solid var(--edge);
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
}
body.devtools .rp-scrub-range{
  -webkit-appearance:none;appearance:none;width:100%;height:14px;
  background:transparent;margin:0;cursor:pointer;
}
body.devtools .rp-scrub-range::-webkit-slider-runnable-track{
  height:3px;background:var(--edge);border-radius:var(--radius-xs);
}
body.devtools .rp-scrub-range::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:12px;height:12px;
  margin-top:-4.5px;border-radius:50%;background:var(--muted);
  border:1px solid var(--ink);
}
body.devtools .rp-scrub-row{display:flex;align-items:center;gap:8px}
body.devtools .rp-scrub-play,body.devtools .rp-scrub-rate{
  -webkit-appearance:none;appearance:none;flex:none;
  min-width:30px;height:22px;padding:0 6px;border-radius:var(--radius-xs);
  border:1px solid var(--edge);background:transparent;color:var(--muted);
  font-size:11px;line-height:1;cursor:pointer;
}
body.devtools .rp-scrub-play:hover,body.devtools .rp-scrub-rate:hover{
  color:var(--ink);border-color:var(--muted);
}
body.devtools .rp-scrub-read{
  font-size:var(--fs-11);color:var(--dim);font-variant-numeric:tabular-nums;
  letter-spacing:0;text-transform:none;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;
}
