/* ============================================================
   SYSTEM FEEL (Package 3) — the app's motion vocabulary, ported
   from the design lab onto the LIVE surfaces. Theme-driven (System
   tokens only) + reduced-motion safe (end states always shown).
   Shared easings: --e-out settle · --e-pop arrive · --e-io travel.
   ============================================================ */
:root {
  /* THE MOTION SYSTEM'S TOKENS (Claude Design, Motion System, plans/32's return): three curves, arrive / leave durations in ms.
     Arrive = things arriving (front-loaded); Leave = things leaving (~70% of arrive); Move = moving or changing in place. */
  --e-arrive: cubic-bezier(.16, .84, .30, 1);
  --e-leave:  cubic-bezier(.50, 0, .75, .25);
  --e-move:   cubic-bezier(.60, 0, .20, 1);
  --d-micro: 120ms;  --d-micro-out: 90ms;
  --d-comp: 200ms;   --d-comp-out: 140ms;
  --d-screen: 280ms; --d-screen-out: 200ms;
  --d-sheet: 340ms;  --d-sheet-out: 240ms;
  --e-out: cubic-bezier(.16, .84, .30, 1);   /* RETIRED (Motion System, M7): equals Arrive */
  --e-pop: cubic-bezier(.16, .84, .30, 1);   /* RETIRED (Motion System): the overshoots are removed; it equals Arrive */
  --e-io:  cubic-bezier(.60, 0, .20, 1);    /* RETIRED (Motion System, M7): equals Move */
}

/* REDUCED MOTION (the system setting; Motion System "Rules"): no movement, scale or rotation, effects off, loops still. Every keyframe
   animation jumps to its end (its fill keeps it there: an entrance ends shown, an exit ends gone, and animationend still fires);
   transitions change only opacity and colour, 150 on Arrive. A scope with its own rule (the guide: none at all) keeps it. */
@media (prefers-reduced-motion: reduce) and (scripting: none) {
  *, *::before, *::after {
    animation-duration: 1ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important;
    transition-property: opacity, color, background-color, border-color, fill, stroke !important;
    transition-duration: 150ms !important; transition-delay: 0s !important; transition-timing-function: var(--e-arrive) !important;
    scroll-behavior: auto !important;
  }
}

/* C1 · a swap in place (main tabs, Day's views, Workshop's tabs, the Goals row): the new content rises y 6 → 0 with its opacity, 200
   Arrive, 60 into the old content's 120 Leave (SystemMotion.ghost). By `translate`, filled backwards only: a translate left on a page
   after it lands would make the page the box every position:fixed inside it is placed in. */
@keyframes c1-in { from { opacity: 0; translate: 0 6px; } to { opacity: 1; translate: 0 0; } }

/* C2 · into a sub-screen (Settings) and back: the sub-screen comes in x 24 → 0 with its opacity, 280 Arrive, while the page under leaves
   x 0 → −12 (SystemMotion.ghost 'under', 200 Leave, same start); back, the sub-screen leaves x 0 → 24 (ghost 'back') and the page
   under returns x −12 → 0, 280 Arrive. Filled backwards, as C1. A back swipe (the phone) needs the sideways pan for itself. */
/* D2 · a filter row hiding (SystemMotion.Row): it lifts y 0 → −10 with its opacity, 200 Leave; showing, it comes down y −10 → 0, 240 Arrive */
@keyframes d2-row-out { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -10px; } }
@keyframes d2-row-in { from { opacity: 0; translate: 0 -10px; } to { opacity: 1; translate: 0 0; } }
@keyframes c2-in { from { opacity: 0; translate: 24px 0; } to { opacity: 1; translate: 0 0; } }
@keyframes c2-back-in { from { opacity: .4; scale: .985; } to { opacity: 1; scale: 1; } }   /* round two C2: the page under comes forward from where it receded */
@keyframes d3-edge { 0% { transform: scaleX(0); opacity: 1; animation-timing-function: cubic-bezier(.16,.84,.30,1); } 27.3% { transform: scaleX(1); opacity: 1; } 72.7% { transform: scaleX(1); opacity: 1; animation-timing-function: cubic-bezier(.50,0,.75,.25); } 100% { transform: scaleX(1); opacity: 0; } }
[data-back-swipe], [data-back-swipe] [data-nobar] { touch-action: pan-y; }

/* E3 · a dialog (Motion System): scale .97 → 1 with its opacity, 240 Arrive; it closes in 160 Leave. No overshoot. Its scrim fades in 200
   Arrive and out 160 Leave. By `scale`, so a dialog centred by its own transform stays centred. */
@keyframes e3-in { from { opacity: 0; scale: .97; } to { opacity: 1; scale: 1; } }
@keyframes e3-out { from { opacity: 1; scale: 1; } to { opacity: 0; scale: .97; } }

/* E1 · a sheet from the bottom (phone): its scrim rgba(2,5,10,.62), flat (no blur), in 240 Arrive, out 200 Leave; the sheet y 102% → 0
   with its opacity, 340 Arrive, out 240 Leave. By `translate`, so a drag (a transform) adds to it. */
@keyframes scrim-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes scrim-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes e1-in { from { opacity: 0; translate: 0 102%; } to { opacity: 1; translate: 0 0; } }
@keyframes e1-out { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 102%; } }

/* I · the Encourage button's outline (his ruling 2026-10-04, 4: Design's proposal): opacity 1 → .45 → 1 over 2800 Move, three cycles
   after it appears, then still at full — its resting look is the button's own outline. */
@keyframes enc-outline { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* I · a spinner shows only once a wait passes 400 ms (never on a fast load), then turns every 900 ms. */
@keyframes spin-wait { to { opacity: 1; } }

/* E1 · the strip a sheet is dragged by (SystemMotion.sheetDrag): the finger moves the sheet there, never the page. */
[data-sheet-drag] { touch-action: none; }

/* E2 · a panel from the side (wide screen): x 28 → 0 with its opacity, 280 Arrive; out 200 Leave. No scrim: the page beside it stays live. */
@keyframes e2-in { from { opacity: 0; translate: 28px 0; } to { opacity: 1; translate: 0 0; } }
@keyframes e2-out { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 28px 0; } }

/* E4 · a menu that opens under its row: y −4 → 0 and scaleY .96 → 1 from its top edge, with its opacity, 180 Arrive. */
@keyframes e4-in { from { opacity: 0; translate: 0 -4px; scale: 1 .96; } to { opacity: 1; translate: 0 0; scale: 1; } }

/* D3 · a row's content opening / closing (Motion System): in, y −6 → 0 with its opacity (200 Arrive at +40, the rows below glide by
   FLIP, SystemMotion.flipAfter); out, its opacity (120 Leave), then it leaves the layout. No max-height. */
@keyframes d3-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@keyframes d3-out { from { opacity: 1; } to { opacity: 0; } }

/* G3 · a list re-filtered (Motion System): what leaves fades (d3-out, 120 Leave), then what stays glides (280 Move, SystemMotion.flip);
   what joins fades in (200 Arrive at +80) while the rest glide. */
@keyframes g3-in { from { opacity: 0; } to { opacity: 1; } }

/* G2 · a chip or row added / removed (Motion System): added, its neighbours glide (200 Move), then it scales in .9 → 1 with its opacity
   (200 Arrive); removed, it fades to .9 (120 Leave), then its neighbours glide. */
@keyframes g2-in { from { opacity: 0; transform: scale(.9); } to { opacity: 1; transform: none; } }
@keyframes g2-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.9); } }

/* A4 · step to step (Motion System; SystemMotion.Steps): out x 0 → −16 with its opacity (140 Leave), in x 16 → 0 (240 Arrive); --a4d is
   −1 going back, which mirrors both. */
@keyframes a4-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(calc(-16px * var(--a4d, 1))); } }
@keyframes a4-in { from { opacity: 0; transform: translateX(calc(16px * var(--a4d, 1))); } to { opacity: 1; transform: none; } }

/* E5 · a toast (Motion System; SystemMotion.Toast): in y 12 → 0 with its opacity (240 Arrive), out y → 12 (160 Leave; 120 when a new one
   replaces it). By `translate`, so the caller's own transform (its centring) stays. */
@keyframes e5-in { from { opacity: 0; translate: 0 12px; } to { opacity: 1; translate: 0 0; } }
@keyframes e5-out { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 12px; } }

/* E6 · the System's notice: the dim 240 Arrive; the flash (Ignite's glow) 160 Arrive up then 580 Leave down; three rays drawn from the centre
   (scaleX 0 → 1, 420 Arrive at +40) then fading 620; the panel scale .94 → 1 with its opacity, 360 Arrive at +200; out 280 Leave, in place.
   H1 · a level / rank up (times from the dim): the disc with the old level (Ignite's core, 320 Arrive at 120) and its glow (480 Arrive at
   180), the ring drawn (S6 Trace, 600 at 200), the old level up 8 px (140 Leave at 600), the new one .9 → 1 (360 Arrive at 700), three
   pulse rings (S5, 700 Arrive at 700 / 880 / 1060), the words y 6 → 0 (280 Arrive at 1000). No flash to white, no overshoot. */
/* D1 · a tab chip chosen: its echo, an outline .6 → 0 and 1 → 1.08, 320 Arrive, once (the fill and border: in 200 Arrive, out 140 Leave) */
@keyframes d1-echo { from { opacity: .6; transform: none; } to { opacity: 0; transform: scale(1.08); } }
@keyframes e6-dim { from { opacity: 0; } to { opacity: 1; } }
@keyframes e6-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes e6-glow { 0% { opacity: 0; animation-timing-function: cubic-bezier(.16,.84,.30,1); } 21.6% { opacity: 1; animation-timing-function: cubic-bezier(.50,0,.75,.25); } 100% { opacity: 0; } }
@keyframes e6-ray { 0% { transform: scaleX(0); opacity: 1; animation-timing-function: cubic-bezier(.16,.84,.30,1); } 40.4% { transform: scaleX(1); opacity: 1; animation-timing-function: cubic-bezier(.50,0,.75,.25); } 100% { transform: scaleX(1); opacity: 0; } }

@keyframes h1-glow { from { opacity: 0; transform: scale(.7); } to { opacity: 1; transform: none; } }
@keyframes h1-trace { from { stroke-dashoffset: 377; } to { stroke-dashoffset: 0; } }
@keyframes h1-old { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-8px); } }
@keyframes h1-new { from { opacity: 0; transform: scale(.9); } to { opacity: 1; transform: none; } }
/* H4 · a row earned (Day): it lights — Ignite's 320 Arrive — and settles over 300 Leave; the EXP it gained comes in over 120 (Arrive),
   then rises y 0 → −14 as it fades over 760 on Move. The pulse from its mark is S5. */
@keyframes h4-row { 0% { opacity: 0; animation-timing-function: cubic-bezier(.16,.84,.30,1); } 51.6% { opacity: 1; animation-timing-function: cubic-bezier(.50,0,.75,.25); } 100% { opacity: 0; } }
@keyframes h4-val { 0% { opacity: 0; translate: 0 0; animation-timing-function: cubic-bezier(.16,.84,.30,1); } 13.6% { opacity: 1; translate: 0 0; animation-timing-function: cubic-bezier(.60,0,.20,1); } 100% { opacity: 0; translate: 0 -14px; } }
@keyframes s5-pulse { from { opacity: .8; transform: scale(.5); } to { opacity: 0; transform: scale(1.9); } }
@keyframes h1-words { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* numbers hold their width through a skin swap (no layout ragdoll) */
.mono, .stp-v, .exp, .tn-count b { font-variant-numeric: tabular-nums; }

/* ---------- EXP travel (the payoff bar) ---------- */
.expbar > i { transition: transform 900ms var(--e-move); }   /* D4: EXP 900 Move */

/* ---------- BUTTON PRESS BLOOM — radial from the pointer ---------- */
/* curated CTAs only (self-contained buttons that can clip a bloom) */
.btn, .bigbtn, .train-btn, .prog-accept, .link-go, .dwin-done, .start-use, .cforge-go, .attr-accept, .lib-add {
  position: relative; overflow: hidden;
}
.btn::after, .bigbtn::after, .train-btn::after, .prog-accept::after, .link-go::after, .dwin-done::after, .start-use::after, .cforge-go::after, .attr-accept::after, .lib-add::after {
  content: ''; position: absolute; left: var(--rx, 50%); top: var(--ry, 50%);
  width: 8px; height: 8px; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 55%, transparent), transparent 70%);
  transform: translate(-50%, -50%) scale(0); opacity: 0;
}
.btn.bloom::after, .bigbtn.bloom::after, .train-btn.bloom::after, .prog-accept.bloom::after, .link-go.bloom::after, .dwin-done.bloom::after, .start-use.bloom::after, .cforge-go.bloom::after, .attr-accept.bloom::after, .lib-add.bloom::after {
  animation: fx-bloom .5s var(--e-out);
}
@keyframes fx-bloom { 0% { transform: translate(-50%, -50%) scale(0); opacity: .8; } 100% { transform: translate(-50%, -50%) scale(34); opacity: 0; } }

/* ---------- QUEST CHECK-OFF — box pop · strike draw · good sweep ---------- */
/* desktop rows */
.quest { position: relative; overflow: hidden; }
.quest .box { transition: color .25s var(--e-out),background-color .25s var(--e-out),border-color .25s var(--e-out),opacity .25s var(--e-out),transform .25s var(--e-out); }
.quest .nm { position: relative; }
.quest .nm::after { content: ''; position: absolute; left: 0; top: 54%; width: 100%; height: 1px; background: var(--dim); transform: scaleX(0); transform-origin: 0 50%; transition: transform 420ms var(--e-arrive); }   /* S6 Trace: drawn by scaleX, never a width */
.quest.done .nm { text-decoration: none; color: var(--dim); }
.quest.done .nm::after { transform: none; }
.quest .sweep { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 20%, transparent), transparent); transform: translateX(-100%); }
.quest.done .sweep { animation: fx-sweep .6s var(--e-io); }
@keyframes fx-sweep { to { transform: translateX(100%); } }

/* ---------- SURFACE MATERIALISE ----------
   INTENTIONALLY NOT applied to persistent panels. `.enter` is on nearly every
   panel in the app, so animating it re-fired on every navigation / re-render
   and read as flashing / "clip lag" (owner-reported). Arriving WINDOWS and
   modals already have their own one-time entrance (st-pop / attr-up), which is
   where a surface genuinely "materialises". Panels stay put. Sweep-style
   motion is reserved for USER-ACTION feedback only (the quest check-off). */

/* ---------- reduced motion — end states, no travel ----------
   Fires on the OS preference OR the in-app Motion=Reduced setting
   (:root[data-motion="reduced"], set by the shell from settings.motion). */
@media (prefers-reduced-motion: reduce) and (scripting: none) {
  .expbar > i { transition: none !important; }
  .quest .nm::after { transition: none !important; }
  .quest.done .sweep, .btn.bloom::after, .bigbtn.bloom::after, .train-btn.bloom::after, .prog-accept.bloom::after, .link-go.bloom::after, .dwin-done.bloom::after, .start-use.bloom::after, .cforge-go.bloom::after, .attr-accept.bloom::after, .lib-add.bloom::after { animation: none !important; }
}
:root[data-motion="reduced"] .expbar > i { transition: none !important; }
:root[data-motion="reduced"] .quest .nm::after { transition: none !important; }
:root[data-motion="reduced"] .quest.done .sweep, :root[data-motion="reduced"] .btn.bloom::after, :root[data-motion="reduced"] .bigbtn.bloom::after, :root[data-motion="reduced"] .train-btn.bloom::after, :root[data-motion="reduced"] .prog-accept.bloom::after, :root[data-motion="reduced"] .link-go.bloom::after, :root[data-motion="reduced"] .dwin-done.bloom::after, :root[data-motion="reduced"] .start-use.bloom::after, :root[data-motion="reduced"] .cforge-go.bloom::after, :root[data-motion="reduced"] .attr-accept.bloom::after, :root[data-motion="reduced"] .lib-add.bloom::after { animation: none !important; }
