/* ========================================================================
   fx.css: the B2See motion, skeleton and celebration layer (the CSS half of
   window.FX; fx.js is the other half). Shared by the Electron renderer and
   the web console (web/build-web.js copies and cache-busts it).

   Rules this file lives by:
   - Only transform, opacity and filter animate, plus three allowed
     exceptions: SVG stroke-dash draws, the tab pill's width (the pill is out
     of flow) and one-shot box-shadow rings (fxPulse). Nothing here changes
     layout while it moves.
   - Nothing is infinite: there is no live dot and no breathing ring (the
     .fx-live dot and its fxLiveRing were removed, founder review 09-28).
     Skeleton shimmer stops after 8 sweeps (about 11s) and only exists while
     something loads.
   - No confetti, no particles, no canvas. A celebration is one soft ring on
     its element (FX.pulse in fx.js).
   - Every animation gives up under prefers-reduced-motion (index.html's
     global kill switch already makes them .001ms; the closing block here
     removes the rest). Failsafes (fxFailsafe) are delays, not motion, and
     deliberately survive reduced motion so nothing stays hidden.
   - Colours come from the existing tokens (--ink, --paper-*, --green-br,
     --gold ...) so both themes follow. Surfaces that are dark in BOTH themes
     (topbar, countdown, reserve bar) and cards on var(--ink) (which turn
     cream in dark) get their own skeleton tokens below.
   - The one place !important appears outside the reduced-motion block is
     the .fx-tabs-ready neutralisation (the Impressions range buttons carry an
     ID selector).

   Linked from index.html's <head> after the head <style> blocks, so ties with
   those base rules go to this file; the later body <style> blocks (SDK +
   Analytics, phone layer) still win ties. Surface stylesheets (fx-shell.css,
   fx-chance.css, fx-imp.css) load right after this one.
   ======================================================================== */

/* ---------- 1. tokens (same timing in both themes) ---------- */
:root{
  --fx-d1:120ms;   /* micro: press, hover colour, icon nudge */
  --fx-d2:200ms;   /* swaps, tooltips, toggles, exits */
  --fx-d3:320ms;   /* card/row entrance, modal card, toast in */
  --fx-d4:600ms;   /* bar grow, ring fill, sheen */
  --fx-d5:900ms;   /* line draw, count-up, celebration ring */
  --fx-ease:cubic-bezier(.16,.84,.28,1);     /* house ease-out (riseIn, .barfill) */
  --fx-soft:cubic-bezier(.2,.7,.2,1);        /* hover / lift */
  --fx-spring:cubic-bezier(.2,.9,.2,1.2);    /* small overshoot: pop, pill arrive */
  --fx-in:cubic-bezier(.5,0,.75,0);          /* exits only */
  --fx-stagger:40ms;
  --fx-rise:10px;
  --fx-lift:-2px;

  --fx-glow-green:0 0 0 4px rgba(43,167,106,.16),0 10px 26px -12px rgba(43,167,106,.65);
  --fx-glow-gold:0 0 0 4px rgba(201,146,31,.16),0 10px 26px -12px rgba(201,146,31,.6);
  /* one-shot pulse ring colours (fxPulse) */
  --fx-pc-green:rgba(43,167,106,.55);
  --fx-pc-gold:rgba(201,146,31,.55);
  --fx-pc-coral:rgba(207,67,38,.5);
  /* gold that is legible as 10px text on paper (plain --gold is ~2.6:1 in light) */
  --fx-gold-ink:#8A6212;

  /* skeleton ground + moving sheen. Explicit tokens rather than currentColor: text skeletons make the
     host's text transparent, so a currentColor mix would vanish with it. */
  --fx-sk-base:rgba(23,21,15,.07);
  --fx-sk-sheen:rgba(255,255,255,.55);
  --fx-sheen-c:rgba(255,255,255,.14);

  /* confirm dialog (FX.confirm, section 11): the dimmed page, the card's lift, the destructive button's hover */
  --fx-cf-scrim:rgba(23,21,15,.40);
  --fx-cf-shadow:0 2px 6px rgba(23,21,15,.08),0 30px 60px -24px rgba(23,21,15,.45);
  --fx-cf-danger-hover:#B83A1F;
}
:root[data-theme="dark"]{
  --fx-glow-green:0 0 0 4px rgba(55,184,118,.18),0 10px 26px -12px rgba(55,184,118,.6);
  --fx-glow-gold:0 0 0 4px rgba(217,168,60,.18),0 10px 26px -12px rgba(217,168,60,.55);
  --fx-pc-green:rgba(55,184,118,.55);
  --fx-pc-gold:rgba(217,168,60,.55);
  --fx-pc-coral:rgba(224,106,78,.5);
  --fx-gold-ink:#D9A83C;
  --fx-sk-base:rgba(237,232,218,.07);
  --fx-sk-sheen:rgba(255,255,255,.07);
  --fx-cf-scrim:rgba(5,4,2,.62);
  --fx-cf-shadow:0 2px 6px rgba(0,0,0,.4),0 30px 60px -24px rgba(0,0,0,.85);
  --fx-cf-danger-hover:#EA7C62;
}
/* cards on var(--ink): near-black in light, CREAM in dark, so their skeleton tokens flip the other way */
.fx-sk-inv,.rankbig,.lbhead,.ian-hero{--fx-sk-base:rgba(251,248,240,.12);--fx-sk-sheen:rgba(255,255,255,.10)}
:root[data-theme="dark"] :is(.fx-sk-inv,.rankbig,.lbhead,.ian-hero){
  --fx-sk-base:rgba(23,21,15,.10);--fx-sk-sheen:rgba(255,255,255,.45);--fx-sheen-c:rgba(255,255,255,.5)}
/* chrome that is dark in BOTH themes (--topbar-bg, the countdown and reserve bar gradients) */
.fx-sk-chrome,.topbar,.countdown,.reserve-bar{--fx-sk-base:rgba(251,248,240,.12);--fx-sk-sheen:rgba(255,255,255,.10)}
/* the sign-in gate follows the theme (its card is paper-2 in light, near-black in dark) */
#signin{--fx-sk-base:rgba(23,21,15,.07);--fx-sk-sheen:rgba(255,255,255,.55)}
:root[data-theme="dark"] #signin{--fx-sk-base:rgba(237,232,218,.07);--fx-sk-sheen:rgba(255,255,255,.07)}

/* ---------- 2. keyframes (all fx-prefixed) ---------- */
@keyframes fxRise{from{opacity:0;transform:translateY(var(--fx-rise))}}
@keyframes fxFade{from{opacity:0}}
@keyframes fxPop{from{opacity:0;transform:scale(.92)}}
@keyframes fxInFwd{from{opacity:0;transform:translateX(14px)}to{opacity:1;transform:none}}
@keyframes fxInBack{from{opacity:0;transform:translateX(-14px)}to{opacity:1;transform:none}}
@keyframes fxOut{to{opacity:0;transform:translateY(8px) scale(.98)}}
@keyframes fxFadeOut{to{opacity:0}}
@keyframes fxShimmer{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
@keyframes fxSkBreath{from{opacity:.4}to{opacity:.75}}
@keyframes fxDraw{to{stroke-dashoffset:0}}
@keyframes fxGrowX{from{transform:scaleX(0)}}
@keyframes fxGrowY{from{transform:scaleY(0)}}
@keyframes fxDeltaUp{
  0%{opacity:0;transform:translateY(4px) scale(.9)}
  16%{opacity:1;transform:translateY(0) scale(1)}
  70%{opacity:1}
  100%{opacity:0;transform:translateY(-14px)}
}
@keyframes fxRowUp{from{background-color:var(--green-soft)}}
@keyframes fxRowDown{from{background-color:var(--coral-soft)}}
/* the drag-over ring on dropzones; FX.pulse draws the same ring through Web Animations (fx.js) */
@keyframes fxPulse{
  0%{box-shadow:var(--fx-own,0 0 #0000),0 0 0 0 var(--fx-pc,var(--fx-pc-green))}
  70%{box-shadow:var(--fx-own,0 0 #0000),0 0 0 11px transparent}
  100%{box-shadow:var(--fx-own,0 0 #0000),0 0 0 0 transparent}
}
@keyframes fxCheck{to{stroke-dashoffset:0}}
@keyframes fxShake{0%,100%{transform:none}25%{transform:translateX(-4px)}50%{transform:translateX(4px)}75%{transform:translateX(-3px)}}
@keyframes fxSheen{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
@keyframes fxTick{from{opacity:.35;transform:translateY(35%)}}
@keyframes fxModalIn{from{opacity:0;transform:translateY(8px) scale(.96)}}
@keyframes fxBackdropIn{from{opacity:0}}
/* not motion: a delayed "show it anyway" so a missed JS step can never leave content hidden */
@keyframes fxFailsafe{to{opacity:1;visibility:visible}}
@keyframes fxFailsafeHide{to{visibility:hidden}}

/* ---------- 3. entrances ---------- */
/* FX.reveal adds one of these with --fx-i (and --fx-step) inline and removes it on animationend.
   Fill is BACKWARDS only: a held forwards frame would pin transform and kill the card hover lift. */
.fx-in-rise{animation:fxRise var(--fx-d3) var(--fx-ease) calc(var(--fx-i,0) * var(--fx-step,40ms)) backwards}
.fx-in-fade{animation:fxFade var(--fx-d3) var(--fx-soft) calc(var(--fx-i,0) * var(--fx-step,40ms)) backwards}
.fx-in-pop{animation:fxPop var(--fx-d3) var(--fx-spring) calc(var(--fx-i,0) * var(--fx-step,40ms)) backwards}

/* Static blocks inside a panel rise in sequence each time the panel opens (panels toggle display, so the
   animation replays exactly like riseIn). nth-child gives the order without JS; FX.stagger sets the same
   --fx-i explicitly. Capped at 6 steps. */
[data-fx-stagger] > *{animation:fxRise var(--fx-d3) var(--fx-ease) calc(var(--fx-i,0) * 50ms) backwards}
[data-fx-stagger] > :nth-child(2){--fx-i:1}
[data-fx-stagger] > :nth-child(3){--fx-i:2}
[data-fx-stagger] > :nth-child(4){--fx-i:3}
[data-fx-stagger] > :nth-child(5){--fx-i:4}
[data-fx-stagger] > :nth-child(6){--fx-i:5}
[data-fx-stagger] > :nth-child(n+7){--fx-i:6}

/* Direction-aware panels: FX.tabs sets data-fx-dir on its dirTarget when the active index grows or
   shrinks. The ID in each selector out-ranks `.panel.on{animation:riseIn}` without !important. */
#chance[data-fx-dir="fwd"] > .panel.on{animation:fxInFwd .38s var(--fx-ease) both}
#chance[data-fx-dir="back"] > .panel.on{animation:fxInBack .38s var(--fx-ease) both}
#impressions[data-fx-dir="fwd"] .iview.on{animation:fxInFwd .38s var(--fx-ease) both}
#impressions[data-fx-dir="back"] .iview.on{animation:fxInBack .38s var(--fx-ease) both}

/* ---------- 4. tabs: sliding indicator (FX.tabs / data-fx-tabs) ---------- */
.fx-pos-rel{position:relative}
.fx-tabind{position:absolute;left:0;top:0;pointer-events:none;z-index:0;opacity:0;
  transition:transform var(--fx-d3) var(--fx-spring),width var(--fx-d3) var(--fx-spring),
    height var(--fx-d3) var(--fx-spring),opacity var(--fx-d2) ease}
.fx-tabind.fx-placed{opacity:1}
.fx-tabs-noanim > .fx-tabind{transition:none}
/* underline: a 100px bar scaled to the tab's width (transform only); sits on the strip's bottom border */
.fx-tabink{top:auto;bottom:-1px;height:2px;width:100px;transform-origin:0 50%;background:var(--ink);border-radius:2px}
/* pill: the one width animation allowed (absolutely positioned, contained, out of flow). Pills and thumbs
   live inside a bordered track, so they take a softer overshoot than --fx-spring: a full 1.2 overshoot
   pokes past the track's edge on the end items. */
.fx-tabpill,.fx-tabthumb{transition-timing-function:cubic-bezier(.2,.9,.25,1.04)}
.fx-tabpill{background:var(--ink);contain:layout paint;border-radius:8px}
/* thumb: the paper segmented-control knob */
.fx-tabthumb{background:var(--paper-2);border-radius:7px;contain:layout paint;
  box-shadow:0 1px 2px rgba(23,21,15,.10),0 0 0 1px var(--line-2)}
.fx-tabs-ready > :not(.fx-tabind){position:relative;z-index:1}
.fx-tabs-ready > button{transition:color var(--fx-d2) var(--fx-soft),border-color var(--fx-d2) var(--fx-soft)}
/* Neutralise the active item's own ground only once the indicator is placed, so no-JS and pre-init
   render exactly as before. */
.fx-tabs-underline.fx-tabs-ready > .on,
.fx-tabs-underline.fx-tabs-ready > [aria-pressed="true"]{border-bottom-color:transparent!important}
:is(.fx-tabs-pill,.fx-tabs-thumb).fx-tabs-ready > :is(.on,[aria-pressed="true"],[aria-selected="true"]){
  background:transparent!important;border-color:transparent!important;box-shadow:none!important}
/* viz chips each carry their own paper ground; clear the inactive ones so the pill is visible as it travels */
.vizTabs.fx-tabs-ready .vtab{background:transparent}

/* ---------- 5. micro-interactions ---------- */
/* press dip */
:is(.btn,.mini-btn,.shipbtn,.ian-watch,.ian-tbtn,.isdk-preset,.platbtn,.segbtn,.subtab,.vtab,.tab,.prod,
  .acctbtn,.rdy[data-rdy],.sv-head .x):active:not(:disabled):not(.is-disabled){
  transform:translateY(1px) scale(.98);transition-duration:90ms}
/* primary button: glow + one sheen pass on hover. position/overflow on .btn-go only; the focus outline
   is outside the border box, so its own overflow never clips it. */
.btn-go{position:relative;overflow:hidden}
.btn-go::after{content:"";position:absolute;inset:0;pointer-events:none;transform:translateX(-100%);
  background:linear-gradient(100deg,transparent 30%,rgba(255,255,255,.38) 50%,transparent 70%)}
@media (hover:hover){
  .btn-go:not(:disabled):not(.is-disabled):hover{box-shadow:var(--fx-glow-green)}
  .btn-go:not(:disabled):not(.is-disabled):hover::after{animation:fxSheen var(--fx-d4) var(--fx-soft) 1}
  /* the card lift (index.html .panel .card:hover) extended to the list cards that are not .card */
  :is(.surv,.sh-row,.ian-tile,.metric,.vid,.paycard){
    transition:transform .22s var(--fx-soft),box-shadow .22s ease,border-color .2s ease}
  :is(.surv,.sh-row,.ian-tile,.metric,.vid,.paycard):hover{
    transform:translateY(var(--fx-lift));box-shadow:var(--shadow-lg);border-color:var(--line-2)}
}
/* segmented type toggle: the active ground eases instead of snapping */
#typeSeg .segbtn{transition:background-color var(--fx-d2) var(--fx-soft),color var(--fx-d2) var(--fx-soft),
  border-color var(--fx-d2) var(--fx-soft)}

/* tilt (FX.tilt / data-fx-tilt). The transform exists ONLY while the pointer is over the card
   (.fx-tilting): a resting perspective() would make every tilt card a permanent stacking context and
   containing block. On leave the card eases back to its own transform. The .panel .card copy out-ranks
   index.html's `.panel .card:hover{transform}` and adds the same lift through --fx-ty. */
.fx-tilt{transition:transform var(--fx-d3) var(--fx-soft),box-shadow .22s ease,border-color .2s ease}
.fx-tilt.fx-tilting,.panel .card.fx-tilt.fx-tilting{
  transform:perspective(900px) rotateX(var(--fx-rx,0deg)) rotateY(var(--fx-ry,0deg)) translateY(var(--fx-ty,0px));
  transition:transform 90ms linear,box-shadow .22s ease,border-color .2s ease}
.panel .card.fx-tilt.fx-tilting{--fx-ty:var(--fx-lift)}
.fx-glare{position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:3;opacity:0;
  transition:opacity var(--fx-d2) ease;
  background:radial-gradient(360px circle at var(--fx-gx,50%) var(--fx-gy,50%),#fff,transparent 70%)}
.fx-tilting > .fx-glare{opacity:.07}

/* ---------- 6. skeletons ---------- */
/* primitives. `.fx-sk` alone is a block; the shape classes include it, so either spelling works. */
:is(.fx-sk,.fx-sk-line,.fx-sk-num,.fx-sk-pill,.fx-sk-circle){
  position:relative;overflow:hidden;background:var(--fx-sk-base);border-radius:8px}
:is(.fx-sk,.fx-sk-line,.fx-sk-num,.fx-sk-pill,.fx-sk-circle,.fx-sk-cover)::after{
  content:"";position:absolute;inset:0;pointer-events:none;transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,var(--fx-sk-sheen),transparent);
  animation:fxShimmer 1.4s var(--fx-soft) 8}
.fx-sk-line{display:block;height:.78em;width:var(--w,100%);border-radius:6px;margin:.35em 0}
.fx-sk-num{display:inline-block;height:1em;width:var(--w,5ch);vertical-align:-.1em;border-radius:6px}
.fx-sk-pill{display:inline-block;height:var(--h,28px);width:var(--w,120px);border-radius:999px;vertical-align:middle}
.fx-sk-circle{display:inline-block;width:var(--s,34px);height:var(--s,34px);border-radius:50%;vertical-align:middle;flex:none}

/* the cover FX injects into an armed host (text / block / busy modes). It is the host's LAST child,
   aria-hidden, and never takes pointer events. */
.fx-sk-cover{position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:inherit;overflow:hidden;
  background:var(--fx-sk-base)}
/* block mode with data-fx-sk-tpl: the cover holds the template's own .fx-sk shapes instead of one slab */
.fx-sk-cover.fx-sk-cover-tpl{background:none;padding:inherit;display:flex;flex-direction:column;
  justify-content:center;gap:.5em}
.fx-sk-cover.fx-sk-cover-tpl::after{content:none}

/* text: keeps its own text for sizing (so nothing reflows when it clears) and paints it transparent */
.fx-sk-text{-webkit-text-fill-color:transparent;color:transparent;text-shadow:none}
.fx-sk-text > :not(.fx-sk-cover){visibility:hidden}
.fx-sk-text > .fx-sk-cover{border-radius:6px;top:.1em;bottom:.1em}
:where(.fx-sk-text){width:fit-content;min-width:var(--w,1.5em)}
.fx-sk-ib{display:inline-block}
/* block: content kept in place but hidden under one shimmer slab */
.fx-sk-block{-webkit-text-fill-color:transparent;color:transparent}
.fx-sk-block > :not(.fx-sk-cover){visibility:hidden}
/* busy: content stays visible and usable at 55%, a sheen passes over it */
.fx-sk-busy > :not(.fx-sk-cover){opacity:.55}
.fx-sk-busy > .fx-sk-cover{background:none}
/* replaced elements (select, input, img) cannot hold a cover: they breathe instead */
.fx-sk-solid{-webkit-text-fill-color:transparent;color:transparent;animation:fxSkBreath 1.4s ease-in-out 8 alternate}
/* list clones are real-class skeleton cards; keep them inert */
.fx-sk-clone{pointer-events:none;user-select:none}
.fx-sk-hide{display:none!important}
/* cleared: the real content fades in. fx.js runs that fade (like FX.pulse, FX.swap and FX.shake) through
   Web Animations rather than a class: a class animation would replace the host's own animation-name (a
   [data-fx-stagger] child, an ID-level entrance) and replay that entrance when the class is removed. */

/* L1 session restore: #signin carries .fx-restoring in markup. The switch, the card title, the four views,
   the status line and the card footer are hidden (visibility, so the card keeps the login view's height)
   and a .fx-restore-sk stack is laid over them, each bone on the row that replaces it and the caption in
   the footer's band. fx.js removes the class after 6s; the CSS failsafe does the same even if no script
   runs, so the login form can never stay hidden. */
.fx-restore-sk{display:none}
#signin.fx-restoring .card{position:relative}
#signin.fx-restoring :is(.si-seg,.si-head,.si-view,#siMsg,.si-card-foot){visibility:hidden;animation:fxFailsafe 1ms linear 6s forwards}
#signin.fx-restoring .fx-restore-sk{display:flex;flex-direction:column;align-items:flex-start;position:absolute;
  left:24px;right:24px;top:22px;bottom:0;animation:fxFailsafeHide 1ms linear 6s forwards}
.fx-restore-sk .fx-sk{flex:none;display:block;width:100%;border-radius:9px}
.fx-restore-sk .si-sk-seg{height:50px;border-radius:11px}
.fx-restore-sk .si-sk-h{height:24px;width:52%;margin-top:23px;border-radius:7px}
.fx-restore-sk .si-sk-p{height:11px;width:84%;margin-top:13px;border-radius:5px}
.fx-restore-sk .si-sk-p2{width:48%;margin-top:8px}
.fx-restore-sk .si-sk-l{height:10px;width:30%;margin-top:25px;border-radius:4px}
.fx-restore-sk .si-sk-f{height:44px;margin-top:9px}
.fx-restore-sk .si-sk-f + .si-sk-l{margin-top:17px}
.fx-restore-sk .si-sk-l + .si-sk-f + .si-sk-l + .si-sk-f{margin-top:10px}
.fx-restore-sk .si-sk-btn{height:46px;margin-top:18px;border-radius:10px}
.fx-restore-sk .fx-restore-cap{align-self:stretch;margin-top:auto;padding:16px 0 18px;text-align:center;font-size:10.5px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink-soft);font-weight:700}

/* ---------- 7. numbers ---------- */
/* first-sight count-up waiting for its first intersection; shown anyway 2.5s after display */
.fx-count-pending{opacity:0;animation:fxFailsafe 1ms linear 2.5s forwards}
.fx-ib{display:inline-block}           /* an inline figure gets a box while FX.swap moves it */
/* "+N" chip: position:fixed at the figure's right edge, set by FX.delta */
.fx-delta{position:fixed;z-index:9000;pointer-events:none;white-space:nowrap;
  font:700 11px/1 'JetBrains Mono',ui-monospace,monospace;letter-spacing:.02em;
  color:var(--green);background:var(--green-soft);padding:4px 7px;border-radius:999px;
  box-shadow:0 0 0 1px rgba(43,167,106,.28),0 6px 16px -8px rgba(23,21,15,.35);
  animation:fxDeltaUp 1.1s var(--fx-ease) forwards}
/* countdown digits (A wraps each changed unit): roll in, 1Hz */
.fx-tick{display:inline-block;animation:fxTick 180ms var(--fx-ease)}

/* ---------- 8. drawing ---------- */
/* FX.draw sets pathLength=100 on the element, so 100 is always the full stroke whatever its real length */
.fx-draw{stroke-dasharray:100;stroke-dashoffset:100;
  animation:fxDraw var(--fx-dur,900ms) var(--fx-ease) var(--fx-delay,0ms) forwards}
.fx-grow-x{transform-origin:0 50%;animation:fxGrowX var(--fx-d4) var(--fx-ease) calc(var(--fx-i,0) * var(--fx-step,25ms)) backwards}
.fx-grow-y{transform-origin:50% 100%;animation:fxGrowY var(--fx-d4) var(--fx-ease) calc(var(--fx-i,0) * var(--fx-step,25ms)) backwards}
/* SVG marks scale from their own box, not the canvas origin */
svg .fx-grow-x,svg .fx-grow-y{transform-box:fill-box}
.fx-ring{display:inline-block;vertical-align:middle;flex:none;overflow:visible}
.fx-ring .fx-ring-trk{fill:none;stroke:var(--line)}
.fx-ring .fx-ring-arc{fill:none;stroke:var(--green-br);stroke-linecap:round;
  transition:stroke-dasharray var(--fx-d4) var(--fx-ease)}
.fx-ring.fx-gold .fx-ring-arc{stroke:var(--gold)}

/* ---------- 9. lists ---------- */
/* FLIP rows: one tint flash back to the row's own ground (single pass, well under 3 flashes a second) */
.fx-row-up{animation:fxRowUp 900ms var(--fx-soft)}
.fx-row-down{animation:fxRowDown 900ms var(--fx-soft)}

/* ---------- 10. attention ---------- */
/* FX.pulse(el, tone) and FX.shake(el) are Web Animations in fx.js (no class): the ring colour comes from
   --fx-pc-green / -gold / -coral above, and the element's own shadow stays under the ring */
.fx-check{display:inline-flex;width:1.1em;height:1.1em;margin-left:.35em;vertical-align:-.18em;
  color:var(--green-br);animation:fxPop var(--fx-d3) var(--fx-spring) backwards}
.fx-check svg{width:100%;height:100%;overflow:visible}
.fx-check path{fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;
  stroke-dasharray:1;stroke-dashoffset:1;animation:fxCheck 420ms var(--fx-ease) 120ms forwards}
/* exits (FX.closeAnimated). A full-screen overlay marked data-fx-modal fades; its card drops. */
.fx-out{animation:fxOut var(--fx-d2) var(--fx-in) forwards;pointer-events:none}
[data-fx-modal].fx-out{animation:fxFadeOut var(--fx-d2) var(--fx-in) forwards}
[data-fx-modal].fx-out > *{animation:fxOut var(--fx-d2) var(--fx-in) forwards}
.fx-modal-in{animation:fxModalIn 280ms var(--fx-spring) backwards}
.fx-backdrop-in{animation:fxBackdropIn 180ms ease backwards}
/* one-shot diagonal light band across a dark hero card (FX.sheen / data-fx-sheen) */
.fx-sheen{position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:inherit;overflow:hidden}
.fx-sheen::after{content:"";position:absolute;inset:0;transform:translateX(-100%);
  background:linear-gradient(105deg,transparent 32%,var(--fx-sheen-c) 50%,transparent 68%);
  animation:fxSheen var(--fx-d4) var(--fx-soft) 1 forwards}
/* glows */
.fx-glow-green{box-shadow:var(--fx-glow-green)}
.fx-glow-gold{box-shadow:var(--fx-glow-gold)}
/* reduced-motion stand-in for a celebration: a static ring for 1.2s, nothing moves */
.fx-glow-static{outline:3px solid var(--green-br);outline-offset:2px}
.fx-glow-static-gold{outline:3px solid var(--gold);outline-offset:2px}
/* milestone chip: mono caps in a gold ring */
.fx-chip{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;
  font:700 10px/1 'JetBrains Mono',ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;
  padding:4px 8px;border-radius:999px;color:var(--fx-gold-ink);background:rgba(201,146,31,.10);
  box-shadow:inset 0 0 0 1px rgba(201,146,31,.45)}
.fx-chip.fx-green{color:var(--green);background:var(--green-soft);box-shadow:inset 0 0 0 1px rgba(43,167,106,.35)}
.fx-chip.fx-pop{animation:fxPop var(--fx-d3) var(--fx-spring) backwards}
/* visually hidden, still announced (FX's polite live region) */
.fx-sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ---------- 11. confirm dialog (FX.confirm) ---------- */
/* The console's own confirm, in place of the system box. The card speaks the sign-in card's language: paper-2,
   a hairline border, 14px corners, a tracked micro-label, a Fraunces title, and the paper-3 foot band (here it
   holds the two buttons). It sits above the sign-in gate and the paper grain. Motion is calm and one-shot: the
   scrim fades in, the card fades up from 97%; on close both fade (fx.js removes the node on fxCfScrimOut). The
   confirm button is the console's green .btn-go, or coral when the action is destructive (tone "danger"): white
   text on light coral (4.7:1), dark text on dark-theme coral (the lighter coral cannot carry white). */
.fx-cf{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;
  padding:max(20px,env(safe-area-inset-top)) 16px max(20px,env(safe-area-inset-bottom));overflow-y:auto;
  overscroll-behavior:contain;background:var(--fx-cf-scrim);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
  font-family:'Hanken Grotesk',system-ui,sans-serif;color:var(--ink);animation:fxFade 180ms var(--fx-soft) backwards}
.fx-cf-card{position:relative;width:100%;max-width:416px;margin:auto;background:var(--paper-2);border:1px solid var(--line);
  border-radius:14px;box-shadow:var(--fx-cf-shadow);overflow:hidden;outline:none;
  animation:fxCfIn 260ms var(--fx-ease) 30ms backwards}
.fx-cf-main{padding:22px 24px 20px}
.fx-cf-kicker{display:flex;align-items:center;gap:12px;margin-bottom:12px;font-size:10px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ink-soft)}
.fx-cf-kicker::after{content:"";height:1px;width:40px;background:var(--line-2)}
.fx-cf-title{margin:0;font-family:'Fraunces',Georgia,serif;font-weight:600;font-size:25px;line-height:1.12;letter-spacing:-.025em;
  color:var(--ink);text-wrap:balance}
.fx-cf-body{margin:8px 0 0;font-size:13.5px;line-height:1.55;color:var(--ink-soft);text-wrap:balance}
.fx-cf-actions{display:flex;justify-content:flex-end;gap:10px;padding:14px 24px 16px;background:var(--paper-3);
  border-top:1px solid var(--line)}
.fx-cf-btn{appearance:none;margin:0;font:inherit;font-size:13.5px;font-weight:700;line-height:1.3;padding:10px 18px;
  border-radius:10px;border:1px solid transparent;cursor:pointer;
  transition:background-color var(--fx-d2) var(--fx-soft),border-color var(--fx-d2) var(--fx-soft),
    box-shadow var(--fx-d2) var(--fx-soft),opacity var(--fx-d2) var(--fx-soft),transform 90ms ease}
.fx-cf-cancel{background:var(--paper-2);border-color:var(--line-2);color:var(--ink)}
.fx-cf-ok{background:var(--green-br);color:#072414}
.fx-cf[data-tone="danger"] .fx-cf-ok{background:var(--coral);color:#fff}
:root[data-theme="dark"] .fx-cf[data-tone="danger"] .fx-cf-ok{color:#230C06}
@media (hover:hover){
  .fx-cf-cancel:not([aria-disabled="true"]):hover{background:var(--paper);border-color:var(--ink-soft)}
  .fx-cf-ok:not([aria-disabled="true"]):hover{box-shadow:var(--fx-glow-green)}
  .fx-cf[data-tone="danger"] .fx-cf-ok:not([aria-disabled="true"]):hover{background:var(--fx-cf-danger-hover);
    box-shadow:0 10px 24px -12px rgba(207,67,38,.7)}
}
.fx-cf-btn:not([aria-disabled="true"]):active{transform:translateY(1px) scale(.98)}
/* out-ranks the app-wide button:focus-visible (its 8px radius would not match these 10px corners). The ring is
   the deeper --green: the bright --green-br is only 2.7:1 on the light paper-3 band, under the 3:1 a focus
   indicator needs (--green is 6:1 there, and 5.2:1 on the dark band). */
.fx-cf-btn:focus-visible{outline:2px solid var(--green);outline-offset:2px;border-radius:10px}
.fx-cf[data-tone="danger"] .fx-cf-ok:focus-visible{outline-color:var(--coral)}
/* busy (a confirm with busyLabel): both buttons stay focusable but answer nothing until the page goes */
.fx-cf-btn[aria-disabled="true"]{cursor:default}
.fx-cf-busy .fx-cf-cancel{opacity:.5}
.fx-cf-busy .fx-cf-ok{cursor:progress}
.fx-cf.fx-cf-out{animation:fxCfScrimOut 170ms var(--fx-in) forwards;pointer-events:none}
.fx-cf.fx-cf-out .fx-cf-card{animation:fxCfOut 170ms var(--fx-in) forwards}
@keyframes fxCfIn{from{opacity:0;transform:translateY(6px) scale(.97)}}
@keyframes fxCfOut{to{opacity:0;transform:translateY(4px) scale(.98)}}
@keyframes fxCfScrimOut{to{opacity:0}}
/* phones: the two buttons share the foot band evenly (the phone layer already makes them 48px tall) */
@media (max-width:480px){
  .fx-cf-main{padding:20px 20px 18px}
  .fx-cf-title{font-size:23px}
  .fx-cf-actions{display:grid;grid-template-columns:1fr 1fr;padding:12px 16px 14px}
  .fx-cf-btn{width:100%;padding:12px 14px}
}

/* ---------- 12. reduced motion ---------- */
/* index.html's kill switch already makes every animation and transition .001ms. This block removes what
   would still be SEEN (a shimmer band, a drawn-in stroke frozen at 0, a tilt, a moving ring).
   fxFailsafe rules are NOT listed: they are delays, and must still fire. */
@media (prefers-reduced-motion: reduce){
  .fx-cf,.fx-cf-card,.fx-cf.fx-cf-out,.fx-cf.fx-cf-out .fx-cf-card{animation:none}
  .fx-cf-btn{transition:none}
  .fx-cf-btn:not([aria-disabled="true"]):active{transform:none}
  .fx-in-rise,.fx-in-fade,.fx-in-pop,[data-fx-stagger] > *,.fx-draw,.fx-grow-x,.fx-grow-y,
  .fx-row-up,.fx-row-down,.fx-check,.fx-check path,.fx-out,[data-fx-modal].fx-out > *,
  .fx-modal-in,.fx-backdrop-in,.fx-chip.fx-pop,.fx-sk-solid,.fx-tick,.fx-delta,
  #chance[data-fx-dir] > .panel.on,#impressions[data-fx-dir] .iview.on{animation:none}
  .fx-draw{stroke-dasharray:none;stroke-dashoffset:0}
  .fx-check path{stroke-dashoffset:0}
  .fx-sk-solid{opacity:.55}
  :is(.fx-sk,.fx-sk-line,.fx-sk-num,.fx-sk-pill,.fx-sk-circle,.fx-sk-cover)::after,
  .fx-sheen,.fx-glare,.btn-go::after{display:none}
  .fx-tabind,.fx-ring .fx-ring-arc{transition:none}
  .fx-tilt.fx-tilting,.panel .card.fx-tilt.fx-tilting{transform:none}
  :is(.surv,.sh-row,.ian-tile,.metric,.vid,.paycard):hover{transform:none}
}
