/* Motion for the public site (#65; redesigned 2026-10-16).
 *
 * One rule governs this file: the markup's default state is the FINAL state.
 * Nothing here hides content. The "before" states live under `.motion`, a
 * class app/static/js/motion.js puts on <html> only when the visitor has an
 * IntersectionObserver and has not asked for reduced motion. So with
 * JavaScript off, with reduced motion on, and in print, every page shows its
 * finished self: the hero picture already shows its updated stock, the diagrams are
 * already drawn, the Copilot answer is already there.
 *
 * Tokens first -- durations, curves and distances -- so the whole site moves
 * with one feel. Only transform and opacity (plus a clip or a dash for a
 * wipe or a drawn line) are animated.
 *
 * The feel, since the 2026-10-16 redesign:
 * - things arrive on a spring (a real one where the browser has `linear()`),
 *   so they land with weight instead of sliding to a stop;
 * - a picture's loop ends as deliberately as it starts: its pieces leave in
 *   the order they came, and placeholders sweep back over them, instead of
 *   everything blinking out together;
 * - "still being done" layers are wiped off along their reading direction,
 *   never cross-faded over the finished piece (that left a ghost of both);
 * - a click is a dip, a spring back and a halo, and the pointer travels on
 *   an arc, the way a hand moves a mouse.
 */

:root {
  --m-fast: 180ms;
  --m-base: 420ms;
  --m-slow: 760ms;
  --m-slower: 1000ms;
  --m-loop: 7s;
  --m-scene: 8s;   /* one turn of a channel-page picture */

  /* Out: things leaving or reacting to a pointer. Enter: things arriving --
     a long tail that settles rather than stops. In: things leaving a
     picture at the end of its turn. In-out: something travelling from one
     place to another. Snap: a wipe, quick to start and soft to finish. */
  --m-ease-out: cubic-bezier(.22, 1, .36, 1);
  --m-ease-enter: cubic-bezier(.16, 1, .3, 1);
  --m-ease-in: cubic-bezier(.55, 0, .75, .25);
  --m-ease-inout: cubic-bezier(.65, 0, .35, 1);
  --m-ease-snap: cubic-bezier(.7, 0, .2, 1);
  /* Springs. These are the fallbacks; a browser that has `linear()` gets a
     real damped spring below. Spring: lands with a 5 % overshoot. Pop: a
     livelier one for a pressed button and an "approved" badge. */
  --m-spring: cubic-bezier(.34, 1.3, .64, 1);
  --m-spring-pop: cubic-bezier(.34, 1.56, .64, 1);
  --m-ease-spring: var(--m-spring-pop);

  --m-rise: 22px;   /* how far a revealed block travels */
  --m-nudge: 4px;   /* hover lift, arrow nudge */
  --m-stagger: 80ms;
  --m-stagger-tight: 45ms;  /* a row of small tiles or chips */
  --m-stagger-line: 110ms;  /* the lines of a headline */
  --m-late: 260ms;          /* a picture arrives after the words beside it */
}

/* A damped spring sampled into `linear()` (stiffness 170, damping 18 and
   120 / 14), for browsers that have it. */
@supports (animation-timing-function: linear(0, 1)) {
  :root {
    --m-spring: linear(0, .025, .09, .181, .285, .395, .503, .605, .697, .778, .847, .904, .949, .985, 1.011, 1.029, 1.041, 1.047, 1.05, 1.049, 1.046, 1.042, 1.037, 1.032, 1.026, 1.021, 1.016, 1.012, 1.008, 1.005, 1.003, 1.001, 1, .999, .998, .998, .999, 1);
    --m-spring-pop: linear(0, .027, .098, .196, .309, .428, .544, .652, .749, .833, .903, .959, 1.002, 1.033, 1.055, 1.067, 1.073, 1.073, 1.07, 1.063, 1.056, 1.047, 1.038, 1.03, 1.022, 1.016, 1.01, 1.005, 1.002, .999, .997, .996, .995, .995, .996, .997, .998, 1);
  }
}

/* ---------------------------------------------------------------- reveal */
/* The existing .reveal (inline in base.html) still drives block entrances.
   These add the parts that need more than a fade-up. */

.motion [data-seq] > [data-step] {
  opacity: 0;
  transform: translateY(var(--m-rise)) scale(.97);
  transition: opacity var(--m-slow) var(--m-ease-enter), transform var(--m-slower) var(--m-spring);
  transition-delay: calc(var(--step, 0) * var(--m-stagger));
}
.motion [data-seq="tight"] > [data-step] { --m-stagger: var(--m-stagger-tight); }
.motion [data-seq].m-in > [data-step] { opacity: 1; transform: none; }

/* A line drawn between the steps of a list grows down as they arrive. */
.motion [data-seq] .m-grow { transform: scaleY(0); transform-origin: 50% 0; transition: transform var(--m-slower) var(--m-ease-enter); transition-delay: calc(var(--step, 0) * var(--m-stagger) + var(--m-late)); }
.motion [data-seq].m-in .m-grow { transform: none; }

/* A picture ([data-enter]) arrives as one object: it is tipped back a few
   degrees and a little low, and swings up into place as if set down on the
   page; "late" waits for the words beside it. On a wide screen it then
   drifts with the scroll (`.m-float`, a scroll-driven animation where the
   browser has one; nothing happens elsewhere), so the page has depth
   without a scroll listener. */
.motion [data-enter] {
  opacity: 0;
  transform: perspective(1400px) translateY(calc(var(--m-rise) * 2)) rotateX(9deg) scale(.95);
  transform-origin: 50% 100%;
  transition: opacity var(--m-slow) var(--m-ease-enter), transform var(--m-slower) var(--m-ease-enter);
}
.motion [data-enter="late"] { transition-delay: var(--m-late); }
.motion [data-enter].m-in { opacity: 1; transform: none; }
@supports (animation-timeline: view()) {
  .motion .m-float { animation: m-float linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
}
@keyframes m-float {
  from { transform: translateY(4%); }
  to   { transform: translateY(-4%); }
}

/* The live dot in the hero's badge breathes a soft halo instead of pinging. */
.motion .m-live > span:first-child { animation: m-halo 2.8s ease-out infinite; }

/* A headline that arrives line by line from behind its own baseline, each
   line swinging up from a slight tilt. The padding keeps descenders
   (g, j, į) inside the clip on tight leading. */
.motion [data-lines] > span { display: block; overflow: hidden; padding-bottom: .14em; margin-bottom: -.14em; }
.motion [data-lines] > span > span {
  display: block;
  opacity: 0;
  transform: translateY(110%) rotate(2.5deg);
  transform-origin: 0 100%;
  transition: transform var(--m-slower) var(--m-ease-enter), opacity var(--m-slow) var(--m-ease-out);
  transition-delay: calc(var(--step, 0) * var(--m-stagger-line));
}
.motion [data-lines].m-in > span > span { opacity: 1; transform: none; }

/* --------------------------------------------------------------- surfaces */

/* Aurora behind the hero grid. Two blurred blobs drifting; cheap because
   only transform moves and the element is composited on its own layer. */
.m-aurora { position: absolute; inset: -20% -10% auto; height: 120%; pointer-events: none; overflow: hidden; }
.m-aurora::before,
.m-aurora::after {
  content: ""; position: absolute; width: 46rem; height: 46rem; border-radius: 50%;
  filter: blur(80px); opacity: .5;
}
.m-aurora::before { background: radial-gradient(circle, rgba(14,165,233,.5), transparent 65%); top: -18rem; right: -10rem; }
.m-aurora::after  { background: radial-gradient(circle, rgba(0,95,184,.45), transparent 65%); top: 4rem; left: -16rem; }
.motion .m-aurora::before { animation: m-drift-a 28s ease-in-out infinite; }
.motion .m-aurora::after  { animation: m-drift-b 34s ease-in-out infinite; }

@keyframes m-drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(-4rem, 3rem, 0) scale(1.12); }
}
@keyframes m-drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.05); }
  50%      { transform: translate3d(3rem, -2rem, 0) scale(.95); }
}

/* The gradient on the hero's key words moves, slowly. */
.motion .gradient-text { background-size: 220% 100%; animation: m-gradient 14s ease-in-out infinite; }
@keyframes m-gradient {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}

/* --------------------------------------------------------------- pointer */

/* Tilt: motion.js writes --m-rx/--m-ry from the pointer. Desktop only, and
   the transition makes the return home eased rather than snapped. */
.motion [data-tilt] {
  transform: perspective(1100px) rotateX(var(--m-rx, 0deg)) rotateY(var(--m-ry, 0deg));
  transition: transform var(--m-base) var(--m-ease-out);
  transform-style: preserve-3d;
}

/* Spotlight border: a soft highlight following the pointer across a card.
   Used on the dark AI band only, where it reads as depth rather than noise. */
[data-spotlight] { position: relative; isolation: isolate; }
.motion [data-spotlight]::before {
  content: ""; position: absolute; inset: -1px; border-radius: inherit; pointer-events: none; z-index: -1;
  background: radial-gradient(18rem circle at var(--m-x, 50%) var(--m-y, 0%), rgba(125,211,252,.35), transparent 60%);
  opacity: 0; transition: opacity var(--m-base) var(--m-ease-out);
}
.motion [data-spotlight]:hover::before,
.motion [data-spotlight]:focus-within::before { opacity: 1; }

/* Magnetic primary CTA: motion.js offsets it a few pixels toward the cursor,
   and it springs home when the pointer leaves. */
.motion [data-magnetic] {
  transform: translate3d(var(--m-mx, 0), var(--m-my, 0), 0);
  transition: transform var(--m-slow) var(--m-spring);
}
.motion [data-magnetic]:active { transform: translate3d(var(--m-mx, 0), var(--m-my, 0), 0) scale(.96); transition-duration: var(--m-fast); }

/* ------------------------------------------------------------------ chrome */

/* The glass header tightens and gains a border once the page has scrolled. */
.m-header { transition: box-shadow var(--m-base) var(--m-ease-out), background-color var(--m-base) var(--m-ease-out); }
.m-header.m-stuck { box-shadow: 0 1px 0 rgba(2,32,71,.08), 0 8px 24px -16px rgba(2,32,71,.4); }

/* Reading progress under the sticky nav. Width would relayout, so it scales. */
.m-progress {
  position: absolute; left: 0; right: 0; bottom: -2px; height: 2px;
  background: linear-gradient(90deg, #005FB8, #38bdf8);
  transform: scaleX(var(--m-progress, 0)); transform-origin: 0 50%;
}

/* The nav's active pill slides between items instead of blinking. */
.m-navpill {
  position: absolute; z-index: -1; border-radius: .5rem; background: rgb(239 246 255);
  transition: transform var(--m-base) var(--m-ease-out), width var(--m-base) var(--m-ease-out);
}

/* Moving between pages: the old page eases out and up a touch, the new one
   settles in from just below. The header and logo are named in base.html,
   so they stay put. Only where motion.js starts a view transition. */
@keyframes m-page-out { to { opacity: 0; transform: translateY(-8px); } }
@keyframes m-page-in { from { opacity: 0; transform: translateY(12px); } }
.motion::view-transition-old(root) { animation: m-page-out var(--m-fast) var(--m-ease-in) both; }
.motion::view-transition-new(root) { animation: m-page-in var(--m-slow) var(--m-ease-enter) both; }

/* ------------------------------------------------------------------- cards */

/* A card lifts on a spring and its shadow deepens with it; pressing it
   settles it back. */
.m-card { transition: transform var(--m-slow) var(--m-spring), box-shadow var(--m-base) var(--m-ease-out), border-color var(--m-base) var(--m-ease-out); }
.motion .m-card:hover { transform: translateY(calc(var(--m-nudge) * -1.5)); box-shadow: 0 1px 2px rgba(2,32,71,.06), 0 18px 40px -18px rgba(2,32,71,.28); }
.motion .m-card:active { transform: translateY(0) scale(.985); transition-duration: var(--m-fast); }

/* The FAQ accordion opens with height and opacity together. */
.motion details[open] > :not(summary) { animation: m-open var(--m-base) var(--m-ease-out); }
@keyframes m-open {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* The highlighted plan is a white card with the brand outline the template
   gives it, nothing more. The travelling gradient border that used to sit
   here painted over the card's own background (a negative z-index child
   paints above its stacking context's background), so the whole card went
   blue -- removed 2026-10-01 (owner). */

/* --------------------------------------------- the living channel diagram */
/* A pulse travels each line in the direction that line actually carries, so
   the animation cannot contradict docs/claims-register.md: Amazon's only
   line runs inward. */

.m-wire { stroke-dasharray: var(--len, 200); stroke-dashoffset: 0; }
.motion .m-wire { stroke-dashoffset: var(--len, 200); transition: stroke-dashoffset var(--m-slower) var(--m-ease-inout); }
.motion .m-diagram.m-in .m-wire { stroke-dashoffset: 0; transition-delay: calc(var(--step, 0) * 90ms); }

/* Without motion the pulses sit at the start of their line and read as
   endpoints; with it, they travel. Either way they carry no information the
   text under the diagram does not also carry. */
.m-pulse { opacity: .5; }
.motion .m-diagram.m-in .m-pulse {
  opacity: 1;
  animation: m-travel 2.6s var(--m-ease-inout) infinite;
  animation-delay: calc(.6s + var(--step, 0) * .4s);
}
@keyframes m-travel {
  0%       { offset-distance: 0%; opacity: 0; }
  12%, 80% { opacity: 1; }
  100%     { offset-distance: 100%; opacity: 0; }
}

/* ---------------------------------------------------- the self-typing chat */
/* The animated copy is aria-hidden; the real text stays in the mockup's
   role="img" label, so a screen reader never hears it letter by letter. */

.m-type { display: inline-block; }
.motion .m-type { clip-path: inset(0 100% 0 0); animation: m-type 1.4s steps(28, end) forwards; animation-delay: var(--delay, 0s); }
@keyframes m-type { to { clip-path: inset(0 0 0 0); } }

@keyframes m-blink { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }

/* ------------------------------------------------------------- provisioning */
/* The signup success page mirrors the real steps rather than spinning. */

.motion [data-steps] li { opacity: .45; transition: opacity var(--m-base) var(--m-ease-out); }
.motion [data-steps] li.m-active { opacity: 1; }
.motion [data-steps] li.m-active .m-dot { animation: m-pulse-dot 1.6s ease-in-out infinite; }
@keyframes m-pulse-dot { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.35); } }

/* ------------------------------------------------------- channel scenes */
/* The pictures on a channel's own page (templates/public/_channel_scenes.html).
   Each [data-scene] loops only while motion.js marks it `.m-play` -- on
   screen, tab visible -- so a picture scrolled past costs nothing (the canvas
   prototype of the Pigu page crashed phones by running them all at once).
   Every keyframe ends where the markup already is. `.m-before` pieces -- an
   old price, a pending badge, ripples, the scanner beam -- and the travelling
   dots exist for the loop only: without motion they are not shown. One turn
   of every loop is --m-scene; the bridge hero's lanes (m-lane-*) take half a
   turn each, in the lane's own direction, a little out of step.

   The shape of one turn (2026-10-16): pieces arrive in order on a spring
   (`m-atN` at 8 % + 10 % per step), the picture holds, then from about
   87 % its pieces leave in the same order, a few hundred milliseconds
   apart, and the placeholders (`m-offN`) and old values (`m-goneN`,
   `m-swN`) sweep back over the finished ones -- so the turn closes as one
   gesture and the next one starts from a clean picture. Each keyframe stop
   names the curve to the next stop (`animation-timing-function` inside the
   keyframe), so one loop can spring in and ease out. */

[data-scene] .m-before,
[data-scene] .m-dot,
[data-scene] .m-ring { display: none; }
.motion [data-scene].m-play .m-before,
.motion [data-scene].m-play .m-dot,
.motion [data-scene].m-play .m-ring { display: block; }
/* A loop-only piece that lays out its own content (a placeholder centring its
   label, a skeleton row) keeps its own display instead of the block above. */
.motion [data-scene].m-play .m-before.flex { display: flex; }
.motion [data-scene].m-play .m-before.grid { display: grid; }

.m-mover { position: absolute; inset: 0; pointer-events: none; }
.m-dot { position: absolute; width: .75rem; height: .75rem; border-radius: 9999px; }
.m-dot-in { background: #059669; box-shadow: 0 0 0 4px rgba(5,150,105,.18), 0 0 14px 2px rgba(5,150,105,.35); }
.m-dot-out { background: #005FB8; box-shadow: 0 0 0 4px rgba(0,95,184,.18), 0 0 14px 2px rgba(0,95,184,.35); }
/* A travelling dot draws a short fading tail behind it, on the side it is
   coming from, so it reads as movement even in a still frame. */
.motion [data-scene].m-play .m-mover > .m-dot::after {
  content: ""; position: absolute; top: 50%; width: 2.25rem; height: 3px; margin-top: -1.5px; border-radius: 9999px;
  background: linear-gradient(90deg, transparent, currentColor); color: inherit; opacity: .45; pointer-events: none;
}
.motion [data-scene].m-play .m-dot-in::after { color: #059669; }
.motion [data-scene].m-play .m-dot-out::after { color: #005FB8; }
.motion [data-scene].m-play :is(.m-lane-in, .m-out-x) > .m-dot::after { right: 50%; }
.motion [data-scene].m-play :is(.m-lane-out, [class*="m-in-x"]) > .m-dot::after { left: 50%; transform: scaleX(-1); }
.motion [data-scene].m-play :is(.m-down1, .m-down2) > .m-dot::after { display: none; }
.m-ring { position: absolute; inset: -2px; border: 2px solid #005FB8; pointer-events: none; }
.m-fill { display: inline-block; transform-origin: 0 50%; }
.m-fill-y { transform-origin: 50% 0; }
.m-shrink { transform-origin: 0 50%; }
.m-turn, .m-orbit { transform-box: view-box; transform-origin: 50px 50px; }
.m-draw, .m-gauge { stroke-dasharray: 1; stroke-dashoffset: 0; }
/* The fanned deck: --fan is the finished spread (a row; on phones a 2x2
   grid), and the loop deals the cards into it. */
.m-fan { transform: var(--fan); }
.m-fan-1 { --fan: translate(-160%, 12%) rotate(-7deg); }
.m-fan-2 { --fan: translate(-54%, 0) rotate(-2deg); }
.m-fan-3 { --fan: translate(54%, 0) rotate(2deg); }
.m-fan-4 { --fan: translate(160%, 12%) rotate(7deg); }
@media (max-width: 639px) {
  .m-fan-1 { --fan: translate(-56%, 0) rotate(-3deg); }
  .m-fan-2 { --fan: translate(56%, 0) rotate(2deg); }
  .m-fan-3 { --fan: translate(-56%, 112%) rotate(2deg); }
  .m-fan-4 { --fan: translate(56%, 112%) rotate(-3deg); }
}

/* A pressed button: the dip and the spring back are the button's own
   transform; the halo is a copy of its background that grows and fades
   behind it. */
.motion [data-scene].m-play :is(.m-press, .m-press-late, .m-press7) { position: relative; }
.motion [data-scene].m-play :is(.m-press, .m-press-late, .m-press7)::after {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: inherit; opacity: 0; pointer-events: none;
}
.motion [data-scene].m-play :is(.m-press, .m-press-late, .m-press7) { isolation: isolate; }

.motion [data-scene].m-play .m-in-x1 { animation: m-in-x1 var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-in-x2 { animation: m-in-x2 var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-in-x3 { animation: m-in-x3 var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-in-x4 { animation: m-in-x4 var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-out-x { animation: m-out-x var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-at1 { animation: m-at1 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-at2 { animation: m-at2 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-at3 { animation: m-at3 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-at4 { animation: m-at4 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-at5 { animation: m-at5 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-at6 { animation: m-at6 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-at7 { animation: m-at7 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-at8 { animation: m-at8 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-gone2 { animation: m-gone2 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-gone4 { animation: m-gone4 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-gone6 { animation: m-gone6 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-w0 { animation: m-w0 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-w1 { animation: m-w1 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-w2 { animation: m-w2 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-w3 { animation: m-w3 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-press { animation: m-press var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-press::after { animation: m-press-ring var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-press-late { animation: m-press-late var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-press-late::after { animation: m-press-late-ring var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-pop { animation: m-pop var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-pop svg { animation: m-pop-tick var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-fill { animation: m-fill1 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-fill-y { animation: m-fill-y var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-ring { animation: m-ring var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-turn { animation: m-turn var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-orbit { animation: m-turn 40s linear infinite; }
.motion [data-scene].m-play .m-halo { animation: m-halo 2.4s var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-draw { animation: m-draw var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-gauge { animation: m-gauge var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-shrink { animation: m-shrink var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-scan { animation: m-scan var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-breathe { animation: m-blink 2.4s ease-in-out infinite; }
.motion [data-scene].m-play .m-ripple-1 { animation: m-ripple1 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-ripple-2 { animation: m-ripple2 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-ripple-3 { animation: m-ripple3 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-fan-1 { animation: m-fan var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-fan-2 { animation: m-fan var(--m-scene) linear .12s infinite backwards; }
.motion [data-scene].m-play .m-fan-3 { animation: m-fan var(--m-scene) linear .24s infinite backwards; }
.motion [data-scene].m-play .m-fan-4 { animation: m-fan var(--m-scene) linear .36s infinite backwards; }
.motion [data-scene].m-play .m-fill-2 { animation-name: m-fill2; }
.motion [data-scene].m-play .m-fill-3 { animation-name: m-fill3; }
.motion [data-scene].m-play .m-draw-2 { animation-name: m-draw2; }
.motion [data-scene].m-play .m-draw-3 { animation-name: m-draw3; }
.motion [data-scene].m-play .m-arc1 { animation-name: m-arc1; }
.motion [data-scene].m-play .m-arc2 { animation-name: m-arc2; }
.motion [data-scene].m-play .m-arc3 { animation-name: m-arc3; }
.motion [data-scene].m-play .m-arc4 { animation-name: m-arc4; }

/* Generated families: one keyframe per step, same shape, different moment. */
@keyframes m-at1 { 0%, 8% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 13%, 86.8% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 90.3%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-at2 { 0%, 18% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 23%, 87.6% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 91.1%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-at3 { 0%, 28% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 33%, 88.4% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 91.9%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-at4 { 0%, 38% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 43%, 89.2% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 92.7%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-at5 { 0%, 48% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 53%, 90% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 93.5%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-at6 { 0%, 58% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 63%, 90.8% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 94.3%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-at7 { 0%, 68% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 73%, 91.6% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 95.1%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-at8 { 0%, 78% { opacity: 0; transform: translateY(10px) scale(.97); animation-timing-function: var(--m-spring); } 83%, 92.4% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 95.9%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); } }
@keyframes m-off1 { 0%, 8% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 12%, 88.8% { opacity: 1; clip-path: inset(0 0 0 100%); } 88.81% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 92.8%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-off2 { 0%, 18% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 22%, 89.6% { opacity: 1; clip-path: inset(0 0 0 100%); } 89.61% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 93.6%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-off3 { 0%, 28% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 32%, 90.4% { opacity: 1; clip-path: inset(0 0 0 100%); } 90.41% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 94.4%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-off4 { 0%, 38% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 42%, 91.2% { opacity: 1; clip-path: inset(0 0 0 100%); } 91.21% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 95.2%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-off5 { 0%, 48% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 52%, 92% { opacity: 1; clip-path: inset(0 0 0 100%); } 92.01% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 96%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-off6 { 0%, 58% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 62%, 92.8% { opacity: 1; clip-path: inset(0 0 0 100%); } 92.81% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 96.8%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-off7 { 0%, 68% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 72%, 93.6% { opacity: 1; clip-path: inset(0 0 0 100%); } 93.61% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 97.6%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-off8 { 0%, 78% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 82%, 94.4% { opacity: 1; clip-path: inset(0 0 0 100%); } 94.41% { opacity: 1; clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-snap); } 98.4%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-gone2 { 0%, 16% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 21%, 89% { opacity: 0; clip-path: inset(0 0 100% 0); animation-timing-function: var(--m-ease-out); } 93%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-gone4 { 0%, 45% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 50%, 90.5% { opacity: 0; clip-path: inset(0 0 100% 0); animation-timing-function: var(--m-ease-out); } 94.5%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-gone6 { 0%, 54% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 59%, 92% { opacity: 0; clip-path: inset(0 0 100% 0); animation-timing-function: var(--m-ease-out); } 96%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-sw1 { 0%, 38% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 41%, 88.8% { opacity: 0; clip-path: inset(0 0 100% 0); } 92.3%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-sw2 { 0%, 41% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 44%, 89.6% { opacity: 0; clip-path: inset(0 0 100% 0); } 93.1%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-sw3 { 0%, 44% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 47%, 90.4% { opacity: 0; clip-path: inset(0 0 100% 0); } 93.9%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-sw4 { 0%, 47% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 50%, 91.2% { opacity: 0; clip-path: inset(0 0 100% 0); } 94.7%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-sw5 { 0%, 50% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 53%, 92% { opacity: 0; clip-path: inset(0 0 100% 0); } 95.5%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-sw6 { 0%, 53% { opacity: 1; clip-path: inset(0 0 0 0); animation-timing-function: var(--m-ease-snap); } 56%, 92.8% { opacity: 0; clip-path: inset(0 0 100% 0); } 96.3%, 100% { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes m-press { 0%, 27% { transform: none; animation-timing-function: var(--m-ease-out); } 28.5% { transform: scale(.92); animation-timing-function: var(--m-spring-pop); } 34%, 100% { transform: none; } }
@keyframes m-press-ring { 0%, 28.5% { opacity: 0; transform: scale(1); animation-timing-function: var(--m-ease-out); } 28.6% { opacity: .45; transform: scale(1); animation-timing-function: var(--m-ease-out); } 36%, 100% { opacity: 0; transform: scale(1.35, 1.9); } }
@keyframes m-press-late { 0%, 40% { transform: none; animation-timing-function: var(--m-ease-out); } 41.5% { transform: scale(.92); animation-timing-function: var(--m-spring-pop); } 47%, 100% { transform: none; } }
@keyframes m-press-late-ring { 0%, 41.5% { opacity: 0; transform: scale(1); animation-timing-function: var(--m-ease-out); } 41.6% { opacity: .45; transform: scale(1); animation-timing-function: var(--m-ease-out); } 49%, 100% { opacity: 0; transform: scale(1.35, 1.9); } }
@keyframes m-press7 { 0%, 66% { transform: none; animation-timing-function: var(--m-ease-out); } 67.5% { transform: scale(.92); animation-timing-function: var(--m-spring-pop); } 73%, 100% { transform: none; } }
@keyframes m-press7-ring { 0%, 67.5% { opacity: 0; transform: scale(1); animation-timing-function: var(--m-ease-out); } 67.6% { opacity: .45; transform: scale(1); animation-timing-function: var(--m-ease-out); } 75%, 100% { opacity: 0; transform: scale(1.35, 1.9); } }
@keyframes m-pop { 0%, 47% { opacity: 0; transform: scale(.7); animation-timing-function: var(--m-spring-pop); } 48.2% { opacity: 1; } 54%, 90% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 94%, 100% { opacity: 0; transform: scale(.94); } }
@keyframes m-pop-tick { 0%, 48.5% { clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-out); } 53%, 100% { clip-path: inset(0 0 0 0); } }
@keyframes m-pop7 { 0%, 69.5% { opacity: 0; transform: scale(.7); animation-timing-function: var(--m-spring-pop); } 70.7% { opacity: 1; } 76.5%, 90% { opacity: 1; transform: none; animation-timing-function: var(--m-ease-in); } 94%, 100% { opacity: 0; transform: scale(.94); } }
@keyframes m-pop7-tick { 0%, 71% { clip-path: inset(0 100% 0 0); animation-timing-function: var(--m-ease-out); } 75.5%, 100% { clip-path: inset(0 0 0 0); } }
@keyframes m-fill1 { 0%, 4% { transform: scaleX(0); animation-timing-function: var(--m-ease-enter); } 22%, 88% { transform: scaleX(1); animation-timing-function: var(--m-ease-in); } 93%, 100% { transform: scaleX(0); } }
@keyframes m-fill2 { 0%, 10% { transform: scaleX(0); animation-timing-function: var(--m-ease-enter); } 28%, 89% { transform: scaleX(1); animation-timing-function: var(--m-ease-in); } 94%, 100% { transform: scaleX(0); } }
@keyframes m-fill3 { 0%, 16% { transform: scaleX(0); animation-timing-function: var(--m-ease-enter); } 34%, 90% { transform: scaleX(1); animation-timing-function: var(--m-ease-in); } 95%, 100% { transform: scaleX(0); } }
@keyframes m-fill-y { 0%, 4% { transform: scaleY(0); animation-timing-function: var(--m-ease-enter); } 52%, 88% { transform: scaleY(1); animation-timing-function: var(--m-ease-in); } 94%, 100% { transform: scaleY(0); } }
@keyframes m-draw { 0%, 8% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 36%, 88% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 94%, 100% { stroke-dashoffset: -1; } }
@keyframes m-draw2 { 0%, 16% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 44%, 89% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 95%, 100% { stroke-dashoffset: -1; } }
@keyframes m-draw3 { 0%, 24% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 52%, 90% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 96%, 100% { stroke-dashoffset: -1; } }
@keyframes m-arc1 { 0%, 36% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 42%, 88% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 94%, 100% { stroke-dashoffset: -1; } }
@keyframes m-arc2 { 0%, 42% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 48%, 89% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 95%, 100% { stroke-dashoffset: -1; } }
@keyframes m-arc3 { 0%, 48% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 54%, 90% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 96%, 100% { stroke-dashoffset: -1; } }
@keyframes m-arc4 { 0%, 54% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 60%, 91% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 97%, 100% { stroke-dashoffset: -1; } }

/* Dots travel on an in-out curve: they pull away, cruise, and ease into the
   place they deliver to. */
@keyframes m-in-x1 { 0% { transform: translateX(0); opacity: 0; } 2% { opacity: 1; } 16% { transform: translateX(-100%); opacity: 1; } 18%, 100% { transform: translateX(-100%); opacity: 0; } }
@keyframes m-in-x2 { 0%, 10% { transform: translateX(0); opacity: 0; } 12% { opacity: 1; } 26% { transform: translateX(-100%); opacity: 1; } 28%, 100% { transform: translateX(-100%); opacity: 0; } }
@keyframes m-in-x3 { 0%, 20% { transform: translateX(0); opacity: 0; } 22% { opacity: 1; } 36% { transform: translateX(-100%); opacity: 1; } 38%, 100% { transform: translateX(-100%); opacity: 0; } }
@keyframes m-in-x4 { 0%, 30% { transform: translateX(0); opacity: 0; } 32% { opacity: 1; } 46% { transform: translateX(-100%); opacity: 1; } 48%, 100% { transform: translateX(-100%); opacity: 0; } }
@keyframes m-out-x { 0%, 55% { transform: translateX(-100%); opacity: 0; } 57% { opacity: 1; } 71% { transform: translateX(0); opacity: 1; } 73%, 100% { transform: translateX(0); opacity: 0; } }
/* One label at a time, each lifting into place. */
@keyframes m-w0 { 0%, 17% { opacity: 1; transform: none; } 21%, 100% { opacity: 0; transform: translateY(-4px); } }
@keyframes m-w1 { 0%, 18% { opacity: 0; transform: translateY(4px); } 22%, 28% { opacity: 1; transform: none; } 31%, 100% { opacity: 0; transform: translateY(-4px); } }
@keyframes m-w2 { 0%, 28% { opacity: 0; transform: translateY(4px); } 32%, 38% { opacity: 1; transform: none; } 41%, 100% { opacity: 0; transform: translateY(-4px); } }
@keyframes m-w3 { 0%, 38% { opacity: 0; transform: translateY(4px); } 42%, 48% { opacity: 1; transform: none; } 51%, 100% { opacity: 0; transform: translateY(-4px); } }
/* A ring that marks "this one": it closes in on its target and lets go. */
@keyframes m-ring { 0%, 70% { opacity: 0; transform: scale(1.08); } 74%, 84% { opacity: 1; transform: none; } 89%, 100% { opacity: 0; transform: scale(1.04); } }
@keyframes m-turn { to { transform: rotate(360deg); } }
@keyframes m-halo { 0% { transform: scale(.96); opacity: .7; } 70%, 100% { transform: scale(1.5); opacity: 0; } }
@keyframes m-gauge { 0%, 6% { stroke-dashoffset: 1; animation-timing-function: var(--m-ease-inout); } 30%, 50% { stroke-dashoffset: .2; animation-timing-function: var(--m-spring); } 60%, 89% { stroke-dashoffset: 0; animation-timing-function: var(--m-ease-in); } 96%, 100% { stroke-dashoffset: 1; } }
@keyframes m-shrink { 0%, 45% { transform: scaleX(1.078); animation-timing-function: var(--m-spring); } 54%, 89% { transform: scaleX(1); animation-timing-function: var(--m-ease-inout); } 96%, 100% { transform: scaleX(1.078); } }
/* The deck is dealt: each card flies out on a spring, a beat after the one
   before it (the delays on .m-fan-N), and is gathered back the same way. */
@keyframes m-fan { 0%, 8% { transform: translate(0, -38%) rotate(0deg) scale(.92); animation-timing-function: var(--m-spring); } 28%, 88% { transform: var(--fan); animation-timing-function: var(--m-ease-inout); } 96%, 100% { transform: translate(0, -38%) rotate(0deg) scale(.92); } }
@keyframes m-scan { 0%, 4% { transform: translateY(0); opacity: 0; } 7% { opacity: 1; } 32% { transform: translateY(100%); opacity: 1; } 35%, 100% { transform: translateY(100%); opacity: 0; } }
@keyframes m-ripple1 { 0%, 30% { transform: scale(.35); opacity: 0; } 32% { opacity: .7; } 62%, 100% { transform: scale(1.5); opacity: 0; } }
@keyframes m-ripple2 { 0%, 38% { transform: scale(.35); opacity: 0; } 40% { opacity: .7; } 70%, 100% { transform: scale(1.5); opacity: 0; } }
@keyframes m-ripple3 { 0%, 46% { transform: scale(.35); opacity: 0; } 48% { opacity: .7; } 78%, 100% { transform: scale(1.5); opacity: 0; } }

.motion [data-scene].m-play .m-lane-in { animation: m-lane-in calc(var(--m-scene) / 2) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-lane-out { animation: m-lane-out calc(var(--m-scene) / 2) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-lane-d2 { animation-delay: -1s; }
.motion [data-scene].m-play .m-lane-d3 { animation-delay: -2s; }
.motion [data-scene].m-play .m-lane-d4 { animation-delay: -3s; }
.motion [data-scene].m-play .m-trail-back { animation: m-trail-back var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-trail-long { animation: m-trail-long var(--m-scene) linear infinite; }

@keyframes m-lane-in { 0% { transform: translateX(0); opacity: 0; } 8% { opacity: 1; } 60% { transform: translateX(100%); opacity: 1; } 68%, 100% { transform: translateX(100%); opacity: 0; } }
@keyframes m-lane-out { 0% { transform: translateX(0); opacity: 0; } 8% { opacity: 1; } 60% { transform: translateX(-100%); opacity: 1; } 68%, 100% { transform: translateX(-100%); opacity: 0; } }
@keyframes m-trail-long { 0%, 4% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 56%, 100% { stroke-dashoffset: -1; } }
@keyframes m-trail-back { 0%, 44% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 60%, 100% { stroke-dashoffset: -1; } }

/* ---------------------------------------------------------- home scenes */
/* The home page's pictures (templates/public/_home_scenes.html) play by the
   same rules as the channel scenes above and reuse their pieces. What they
   add: a pulse that runs along an SVG line (a short dash walked along the
   path, so any curve works), the hero's four stock updates arriving one
   after another, cards that fall from a scatter into line, text that types
   itself (`m-typing`: its own name, because the page-wide `.motion .m-type`
   runs from page load, and a shared name would keep that clock when the
   picture starts playing), and the vault's two drops. */

.m-trail { stroke-dasharray: .14 2; }
.m-dial { transform-box: view-box; transform-origin: 20px 20px; }
/* --mess is where a card lies before it is tidied; the markup is the line. */
.m-tidy { transform: none; }
.m-mess-1 { --mess: translate(-3%, 8%) rotate(-5deg); }
.m-mess-2 { --mess: translate(5%, 4%) rotate(4deg); }
.m-mess-3 { --mess: translate(-6%, -6%) rotate(-3deg); }
.m-mess-4 { --mess: translate(4%, 10%) rotate(5deg); }
.m-mess-5 { --mess: translate(-3%, -4%) rotate(-4deg); }

.motion [data-scene].m-play .m-trail-in { animation: m-trail-in var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-trail-o1 { animation: m-trail-o1 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-trail-o2 { animation: m-trail-o2 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-trail-o3 { animation: m-trail-o3 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-trail-o4 { animation: m-trail-o4 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-trail-o5 { animation: m-trail-o5 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-trail-o6 { animation: m-trail-o6 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-sw1 { animation: m-sw1 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-sw2 { animation: m-sw2 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-sw3 { animation: m-sw3 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-sw4 { animation: m-sw4 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-sw5 { animation: m-sw5 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-sw6 { animation: m-sw6 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-ring1 { animation: m-ring1 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-ring2 { animation: m-ring2 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-tidy { animation: m-tidy var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-tidy:nth-child(2) { animation-delay: .06s; animation-fill-mode: backwards; }
.motion [data-scene].m-play .m-tidy:nth-child(3) { animation-delay: .12s; animation-fill-mode: backwards; }
.motion [data-scene].m-play .m-tidy:nth-child(4) { animation-delay: .18s; animation-fill-mode: backwards; }
.motion [data-scene].m-play .m-tidy:nth-child(5) { animation-delay: .24s; animation-fill-mode: backwards; }
.motion [data-scene].m-play .m-tidy:nth-child(6) { animation-delay: .3s; animation-fill-mode: backwards; }
.motion [data-scene].m-play .m-type { animation: m-typing var(--m-scene) steps(30, end) infinite; }
.motion [data-scene].m-play .m-press7 { animation: m-press7 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-press7::after { animation: m-press7-ring var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-pop7 { animation: m-pop7 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-pop7 svg { animation: m-pop7-tick var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-down1 { animation: m-down1 var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-down2 { animation: m-down2 var(--m-scene) var(--m-ease-inout) infinite; }
.motion [data-scene].m-play .m-dial { animation: m-dial var(--m-scene) linear infinite; }

@keyframes m-trail-in { 0%, 3% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 19%, 100% { stroke-dashoffset: -1; } }
@keyframes m-trail-o1 { 0%, 27% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 39%, 100% { stroke-dashoffset: -1; } }
@keyframes m-trail-o2 { 0%, 30% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 42%, 100% { stroke-dashoffset: -1; } }
@keyframes m-trail-o3 { 0%, 33% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 45%, 100% { stroke-dashoffset: -1; } }
@keyframes m-trail-o4 { 0%, 36% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 48%, 100% { stroke-dashoffset: -1; } }
@keyframes m-trail-o5 { 0%, 39% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 51%, 100% { stroke-dashoffset: -1; } }
@keyframes m-trail-o6 { 0%, 42% { stroke-dashoffset: .14; animation-timing-function: var(--m-ease-inout); } 54%, 100% { stroke-dashoffset: -1; } }
@keyframes m-ring1 { 0%, 2% { opacity: 0; transform: scale(1.12); } 6%, 18% { opacity: 1; transform: none; } 24%, 100% { opacity: 0; transform: scale(1.04); } }
@keyframes m-ring2 { 0%, 18% { opacity: 0; transform: scale(1.12); } 22%, 34% { opacity: 1; transform: none; } 42%, 100% { opacity: 0; transform: scale(1.04); } }
/* The scattered cards drop into line one after another on a spring, and
   drift back apart at the end of the turn. */
@keyframes m-tidy { 0%, 8% { transform: var(--mess); animation-timing-function: var(--m-spring); } 22%, 88% { transform: none; animation-timing-function: var(--m-ease-inout); } 97%, 100% { transform: var(--mess); } }
@keyframes m-typing { 0%, 4% { clip-path: inset(0 100% 0 0); } 22%, 89% { clip-path: inset(0 0 0 0); } 95%, 100% { clip-path: inset(0 100% 0 0); } }
@keyframes m-down1 { 0%, 4% { transform: translateY(0); opacity: 0; } 6% { opacity: 1; } 20% { transform: translateY(100%); opacity: 1; } 22%, 100% { transform: translateY(100%); opacity: 0; } }
@keyframes m-down2 { 0%, 52% { transform: translateY(0); opacity: 0; } 54% { opacity: 1; } 70% { transform: translateY(100%); opacity: 1; } 72%, 100% { transform: translateY(100%); opacity: 0; } }
/* The vault's dial turns a quarter and clicks into place. */
@keyframes m-dial { 0%, 70% { transform: rotate(0deg); animation-timing-function: var(--m-spring-pop); } 80%, 100% { transform: rotate(90deg); } }

/* ------------------------------------------------------------ DI scenes */
/* The pictures on /di (templates/public/_ai_scenes.html) reuse the pieces
   above and run on a longer turn (the frame sets --m-scene: 10s), because
   each one tells a whole exchange. What they add:
   - `m-offN`, the mirror of `m-atN`: a "still being done" layer drawn over
     the finished piece (a skeleton, a spinner, "tikrinama") is wiped off at
     the moment the piece would arrive, so a picture never waits empty;
   - a person's pointer (`m-cursor7`) that travels to a button on an arc
     (its sideways and its downward movement run on different curves, the
     svg inside carries the second), presses it together with `m-press7`
     and lingers, just before the approved state pops in;
   - a "writing" placeholder with thinking dots (`m-think`, `m-d2`/`m-d3`),
     a spinner, and a skeleton shimmer while a draft is being written;
   - an action the agent does not have: the agent bumps into it (`m-bumpN`),
     the row shakes its head and flashes red, one after another, so "cannot"
     reads as a refusal rather than a greyed-out button. */

.m-shake1, .m-shake2, .m-shake3 { transform: none; }
.m-spinner { transform-origin: 50% 50%; }
.m-shimmer { background-image: linear-gradient(100deg, rgb(var(--c-slate-100)) 30%, rgb(var(--c-slate-200)) 45%, rgb(var(--c-slate-100)) 60%); background-size: 400% 100%; }
.motion [data-scene].m-play .m-off1 { animation: m-off1 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-off2 { animation: m-off2 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-off3 { animation: m-off3 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-off4 { animation: m-off4 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-off5 { animation: m-off5 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-off6 { animation: m-off6 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-off7 { animation: m-off7 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-off8 { animation: m-off8 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-cursor7 { animation: m-cursor7 var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-cursor7 > svg { animation: m-cursor7-y var(--m-scene) linear infinite; }
.motion [data-scene].m-play .m-think { animation: m-think var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-spinner { animation: m-turn .8s linear infinite; }
.motion [data-scene].m-play .m-shimmer { animation: m-shimmer 1.6s linear infinite; }
.motion [data-scene].m-play .m-d2 { animation-delay: .2s; }
.motion [data-scene].m-play .m-d3 { animation-delay: .4s; }
.motion [data-scene].m-play .m-shake1 { animation: m-shake1 var(--m-scene) ease infinite; }
.motion [data-scene].m-play .m-shake2 { animation: m-shake2 var(--m-scene) ease infinite; }
.motion [data-scene].m-play .m-shake3 { animation: m-shake3 var(--m-scene) ease infinite; }
.motion [data-scene].m-play .m-deny1 { animation: m-deny1 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-deny2 { animation: m-deny2 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-deny3 { animation: m-deny3 var(--m-scene) var(--m-ease-out) infinite; }
.motion [data-scene].m-play .m-bump1 { animation: m-bump1 var(--m-scene) ease-out infinite; }
.motion [data-scene].m-play .m-bump2 { animation: m-bump2 var(--m-scene) ease-out infinite; }
.motion [data-scene].m-play .m-bump3 { animation: m-bump3 var(--m-scene) ease-out infinite; }

/* The pointer: sideways on a long ease-out, down on an in-out curve (the
   svg), so the two together trace an arc; a press, a spring back, a moment
   on the button, then it drifts off as it fades. */
@keyframes m-cursor7 {
  0%, 50% { opacity: 0; transform: translateX(72px); animation-timing-function: var(--m-ease-out); }
  53% { opacity: 1; }
  64.5% { opacity: 1; transform: translateX(0); animation-timing-function: var(--m-ease-out); }
  66.5% { transform: translateX(0) scale(.82); animation-timing-function: var(--m-spring-pop); }
  71%, 82% { opacity: 1; transform: translateX(0) scale(1); animation-timing-function: var(--m-ease-inout); }
  90%, 100% { opacity: 0; transform: translateX(18px); }
}
@keyframes m-cursor7-y {
  0%, 50% { transform: translateY(52px); animation-timing-function: var(--m-ease-inout); }
  64.5%, 82% { transform: translateY(0); animation-timing-function: var(--m-ease-inout); }
  90%, 100% { transform: translateY(14px); }
}
@keyframes m-think { 0%, 20% { opacity: 0; transform: translateY(4px); } 23%, 37% { opacity: 1; transform: none; } 41%, 100% { opacity: 0; transform: translateY(-4px); } }
@keyframes m-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
@keyframes m-shake1 { 0%, 28% { transform: none; } 30% { transform: translateX(-5px) rotate(-.6deg); } 32% { transform: translateX(4px) rotate(.5deg); } 34% { transform: translateX(-3px); } 36% { transform: translateX(1.5px); } 38%, 100% { transform: none; } }
@keyframes m-shake2 { 0%, 43% { transform: none; } 45% { transform: translateX(-5px) rotate(-.6deg); } 47% { transform: translateX(4px) rotate(.5deg); } 49% { transform: translateX(-3px); } 51% { transform: translateX(1.5px); } 53%, 100% { transform: none; } }
@keyframes m-shake3 { 0%, 58% { transform: none; } 60% { transform: translateX(-5px) rotate(-.6deg); } 62% { transform: translateX(4px) rotate(.5deg); } 64% { transform: translateX(-3px); } 66% { transform: translateX(1.5px); } 68%, 100% { transform: none; } }
@keyframes m-deny1 { 0%, 27% { opacity: 0; } 29%, 40% { opacity: 1; } 46%, 100% { opacity: 0; } }
@keyframes m-deny2 { 0%, 42% { opacity: 0; } 44%, 55% { opacity: 1; } 61%, 100% { opacity: 0; } }
@keyframes m-deny3 { 0%, 57% { opacity: 0; } 59%, 70% { opacity: 1; } 76%, 100% { opacity: 0; } }
@keyframes m-bump1 { 0%, 19% { opacity: 0; transform: translateX(-14px); } 22% { opacity: 1; transform: translateX(-14px); } 28% { opacity: 1; transform: translateX(4px); } 31% { transform: translateX(0); } 37% { opacity: 1; transform: translateX(0); } 41%, 100% { opacity: 0; transform: translateX(0); } }
@keyframes m-bump2 { 0%, 34% { opacity: 0; transform: translateX(-14px); } 37% { opacity: 1; transform: translateX(-14px); } 43% { opacity: 1; transform: translateX(4px); } 46% { transform: translateX(0); } 52% { opacity: 1; transform: translateX(0); } 56%, 100% { opacity: 0; transform: translateX(0); } }
@keyframes m-bump3 { 0%, 49% { opacity: 0; transform: translateX(-14px); } 52% { opacity: 1; transform: translateX(-14px); } 58% { opacity: 1; transform: translateX(4px); } 61% { transform: translateX(0); } 67% { opacity: 1; transform: translateX(0); } 71%, 100% { opacity: 0; transform: translateX(0); } }

/* ------------------------------------------------------------------ escape */
/* Belt and braces: even if `.motion` were set by mistake, these two wipe the
   file out for anyone who asked for less movement, and for print. */

@media (prefers-reduced-motion: reduce) {
  .motion *, .motion *::before, .motion *::after {
    animation: none !important;
    transition: none !important;
    transform: none !important;
    opacity: 1 !important;
    clip-path: none !important;
    stroke-dashoffset: 0 !important;
  }
}

@media print {
  .motion *, .motion *::before, .motion *::after {
    animation: none !important;
    transition: none !important;
    transform: none !important;
    opacity: 1 !important;
    clip-path: none !important;
  }
  [data-scene] .m-before, [data-scene] .m-dot, [data-scene] .m-ring { display: none !important; }
}

/* Phones get the ideas, not the machinery: no pinned columns, no pointer
   effects, no parallax. */
@media (max-width: 1023px) {
  .motion [data-tilt] { transform: none; }
  .motion [data-magnetic] { transform: none; }
  .motion .m-float { animation: none; }
  .m-pinned { position: static !important; }
}
