/*
 * motion.css: the opt-in motion components of the round 4 motion system.
 * Linked (after site.css) on the pages that have adopted it; the round 4 sample is the home
 * page and the metronome. Rolling it out = add the <link> (see README "Motion"). The tokens it
 * uses (--dur-*, --ease-*, --dist-*, --stagger*) live in site.css :root.
 * Rules: only transform/translate/scale and opacity are animated (plus colour fades on press);
 * nothing here hides content unless motion.js has loaded and motion is on; the reduced-motion
 * block at the end (and site.css's global rules) switch all of it off for Calm motion and for
 * the device's reduce-motion setting.
 * The CSP forbids <style> blocks and style="" attributes: all styling lives in files.
 */

/* ------------------------------------------------------------ page transitions */
/* Cross-document View Transitions: both pages must opt in, so only pages linking this file
   cross-fade. Browsers without support simply navigate as before. */
@view-transition { navigation: auto; }
.header-inner { view-transition-name: site-header; }
::view-transition-group(site-header) { animation-duration: 0s; }
::view-transition-old(root) { animation: vt-out var(--dur-2) var(--ease-in) both; }
::view-transition-new(root) { animation: vt-in var(--dur-4) var(--ease-emph) both; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; translate: 0 var(--dist-2); } }

/* ------------------------------------------------------------ scroll reveal (motion.js) */
:root.mo-reveal [data-reveal]:not(.is-revealed) { opacity: 0; translate: 0 var(--dist-3); }
:root.mo-reveal [data-reveal="fade"]:not(.is-revealed) { translate: none; }
:root.mo-reveal [data-reveal="scale"]:not(.is-revealed) { translate: none; scale: .97; }
:root.mo-reveal [data-reveal].revealing {
  transition: opacity var(--dur-4) var(--ease-out), translate var(--dur-4) var(--ease-emph), scale var(--dur-4) var(--ease-emph);
  transition-delay: calc(min(var(--reveal-i, 0), 8) * var(--stagger));
}

/* ------------------------------------------------------------ entrances (CSS only, first paint) */
/* Headline split into words in the markup: the real sentence is in a .visually-hidden span for
   screen readers, the animated words are aria-hidden, so it is read once and never word by word. */
.split .w { display: inline-block; animation: word-in var(--dur-5) var(--ease-emph) backwards; }
.split .w:nth-child(2) { animation-delay: calc(1 * var(--stagger-word)); }
.split .w:nth-child(3) { animation-delay: calc(2 * var(--stagger-word)); }
.split .w:nth-child(4) { animation-delay: calc(3 * var(--stagger-word)); }
.split .w:nth-child(5) { animation-delay: calc(4 * var(--stagger-word)); }
.split .w:nth-child(6) { animation-delay: calc(5 * var(--stagger-word)); }
.split .w:nth-child(7) { animation-delay: calc(6 * var(--stagger-word)); }
.split .w:nth-child(8) { animation-delay: calc(7 * var(--stagger-word)); }
.split .w:nth-child(9) { animation-delay: calc(8 * var(--stagger-word)); }
.split .w:nth-child(n+10) { animation-delay: calc(9 * var(--stagger-word)); }
@keyframes word-in { from { opacity: 0; transform: translateY(.38em); } }

/* data-enter="1".."4": content that arrives just after the headline, in order. */
[data-enter] { animation: enter-up var(--dur-4) var(--ease-emph) backwards; }
[data-enter="1"] { animation-delay: 120ms; }
[data-enter="2"] { animation-delay: 300ms; }
[data-enter="3"] { animation-delay: 420ms; }
[data-enter="4"] { animation-delay: 540ms; }
@keyframes enter-up { from { opacity: 0; translate: 0 var(--dist-2); } }

/* ------------------------------------------------------------ press and hover micro-interactions */
/* Pressable buttons: quick press, springy release; primary buttons can also lean toward the
   pointer (data-magnetic, motion.js sets --mx/--my). */
.button, button:not([class]), button.primary, button.secondary, button.accent, .round-btn {
  translate: var(--mx, 0px) var(--my, 0px);
  transition: transform var(--dur-3) var(--ease-spring), box-shadow var(--dur-3) var(--ease-spring),
    background-color var(--dur-2) var(--ease-out), translate var(--dur-3) var(--ease-spring), filter var(--dur-2);
}
.button:active, button:not([class]):active, button.primary:active, button.secondary:active, button.accent:active, .round-btn:active {
  transition-duration: 50ms, 50ms, var(--dur-2), var(--dur-3), var(--dur-2);
}
.is-magnet { transition-duration: var(--dur-3), var(--dur-3), var(--dur-2), 90ms, var(--dur-2); transition-timing-function: var(--ease-spring), var(--ease-spring), var(--ease-out), linear, ease; }

/* Chips and note buttons: a small springy squeeze on press, colour fades on select. */
.chip span, .seg span, .note-chip {
  transition: background-color var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out), scale var(--dur-3) var(--ease-spring);
}
.chip input:active + span, .seg input:active + span, .note-chip:active { scale: .93; transition-duration: var(--dur-2), var(--dur-2), var(--dur-2), 60ms; }
.switch input::after { transition: transform var(--dur-3) var(--ease-spring), background-color var(--dur-2); }

/* Cards: lift with a deeper shadow (light theme) or a brighter edge (dark theme). */
.card { transition: translate var(--dur-3) var(--ease-spring), box-shadow var(--dur-3) var(--ease-out), border-color var(--dur-2) var(--ease-out); }
.card:hover, .tool-card:hover { transform: none; }
.tool-card:hover, .tool-card:focus-within, .card.lift:hover, .card.lift:focus-within,
.tool-tile:hover, .tool-tile:focus-within, .soon-card:hover, .soon-card:focus-within {
  translate: 0 -4px; border-color: var(--line-strong);
  box-shadow: inset 0 1px 0 var(--hi), 0 2px 4px rgb(58 40 15 / .05), 0 18px 40px -14px rgb(58 40 15 / .24);
}
/* Round 6: the lifted card keeps its saffron string (the bottom edge) */
.tool-tile:hover, .tool-tile:focus-within, .soon-card:hover, .soon-card:focus-within, .card.lift:hover, .card.lift:focus-within { border-bottom-color: var(--saffron); }
:root[data-theme="dark"] :is(.tool-card, .card.lift, .tool-tile, .soon-card):is(:hover, :focus-within) { box-shadow: 0 0 0 1px var(--line-strong), 0 18px 40px -18px rgb(0 0 0 / .7); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.tool-card, .card.lift, .tool-tile, .soon-card):is(:hover, :focus-within) { box-shadow: 0 0 0 1px var(--line-strong), 0 18px 40px -18px rgb(0 0 0 / .7); }
}

/* ------------------------------------------------------------ round 6: the stage (home hero) */
/* Sound ribbons: each ribbon is one period of a string's first three harmonics, tiled by an SVG pattern and slid
   left (or right) by exactly one period, so the loop has no seam. Only the <svg> box moves (transform on its own
   composited layer): no repaint per frame. Very slow (8 to 12 px a second) and limited to a few minutes; paused
   while the hero is off-screen (motion.js adds .is-offscreen to [data-offscreen-pause]); still under Calm motion. */
.ribbon-1 { will-change: transform; animation: ribbon-a 80s linear 3 both; }
.ribbon-2 { will-change: transform; animation: ribbon-b 110s linear 2 both; }
.ribbon-3 { will-change: transform; animation: ribbon-c 64s linear 4 both; }
@keyframes ribbon-a { from { transform: translateX(0); } to { transform: translateX(-720px); } }
@keyframes ribbon-b { from { transform: translateX(-900px); } to { transform: translateX(0); } }
@keyframes ribbon-c { from { transform: translateX(0); } to { transform: translateX(-600px); } }
/* The stage light breathes: opacity only, from full (the still state, the brightest point, which is the one the
   contrast measurements use) down to 72% and back, ten-second breaths, for about two minutes. */
.home-hero::after { will-change: opacity; animation: stage-breathe 10s var(--ease-in-out) 12 alternate both; }
@keyframes stage-breathe { from { opacity: 1; } to { opacity: .72; } }
.is-offscreen .ribbon, .is-offscreen.home-hero::after { animation-play-state: paused; }

/* Pause / play button (WCAG 2.2.2 Pause, Stop, Hide). motion.js puts .is-user-paused on the hero when the visitor presses
   "Pause the moving lines": the ribbons and the stage light then hold where they are (like the off-screen pause above,
   so they are never reset) until "Play the moving lines". The row keeps the button's height from the first paint, so the
   page does not move when motion.js shows the button; the off switch below removes the row where the lines are off. */
.is-user-paused .ribbon, .is-user-paused.home-hero::after { animation-play-state: paused; }
.ribbon-row { display: flex; align-items: center; margin-top: var(--s-4); min-height: 44px; }
.ribbon-toggle { min-height: 44px; margin-bottom: 0; padding: 6px 18px 6px 14px; font-size: .9375rem; min-width: 15.25em; } /* both labels fit: the button keeps one width when the label swaps */
.rt-icon { width: 1em; height: 1em; flex: none; fill: currentColor; }
.ribbon-toggle[data-state="paused"] .rt-pause, .ribbon-toggle:not([data-state="paused"]) .rt-play { display: none; }

/* ------------------------------------------------------------ number roll (motion.js roll()) */
/* Each digit sits in a fixed-width cell (tabular by construction, whatever the font); the old
   digit leaves through ::after while the new one arrives. */
[data-roll] { display: inline-flex; justify-content: center; }
.dg { position: relative; display: inline-block; width: var(--dg-w, .62em); text-align: center; }
.dg-in { display: inline-block; }
.dg::after { content: attr(data-prev); position: absolute; left: 0; right: 0; top: 0; opacity: 0; pointer-events: none; }

/* ------------------------------------------------------------ off switch */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .split .w, [data-enter] { animation: none !important; }
  .ribbon, .home-hero::after { animation: none !important; will-change: auto; }
  .ribbon-row { display: none; }
  .card:hover, .tool-card:hover, .tool-card:focus-within, .card.lift:hover, .card.lift:focus-within, .tool-tile:is(:hover, :focus-within), .soon-card:is(:hover, :focus-within) { translate: none; }
  .button, button, .round-btn { translate: none !important; }
}
:root[data-motion="reduce"]::view-transition-group(*), :root[data-motion="reduce"]::view-transition-old(*), :root[data-motion="reduce"]::view-transition-new(*) { animation: none !important; }
:root[data-motion="reduce"] .split .w, :root[data-motion="reduce"] [data-enter] { animation: none !important; }
:root[data-motion="reduce"] .ribbon, :root[data-motion="reduce"] .home-hero::after { animation: none !important; will-change: auto; }
:root[data-motion="reduce"] .ribbon-row { display: none; }
:root[data-motion="reduce"] .card:hover, :root[data-motion="reduce"] .tool-card:hover, :root[data-motion="reduce"] .tool-card:focus-within,
:root[data-motion="reduce"] .card.lift:hover, :root[data-motion="reduce"] .card.lift:focus-within,
:root[data-motion="reduce"] .tool-tile:is(:hover, :focus-within), :root[data-motion="reduce"] .soon-card:is(:hover, :focus-within) { translate: none; }
:root[data-motion="reduce"] .button, :root[data-motion="reduce"] button, :root[data-motion="reduce"] .round-btn { translate: none !important; }
/* Forced colours (Windows contrast themes): site.css hides the ribbons' layer there (.hero-sky), so nothing moves and there is
   nothing to pause: the button row goes too. */
@media (forced-colors: active) {
  .ribbon-row { display: none; }
}
@media print {
  [data-reveal] { opacity: 1 !important; translate: none !important; scale: none !important; }
  .split .w, [data-enter] { animation: none !important; }
  .ribbon-row { display: none; }
}
