/* web-base motion: named animations as classes. Curves and durations come from tokens/base.css;
   a feel (data-motion on <html>: crisp | soft | lively, default soft) sets travel, scale and timing.
   Enters fill backwards only, so nothing holds a transform after it ends (hover lift still works).
   Animates only transform and opacity, except .expand (grid rows), the hero blur (.seq, .phrase) and the scroll-fade masks, see README. Pair with motion.js. */
:root, [data-motion="soft"] {
  --motion-distance: var(--space-2);      /* reveal / short slide travel */
  --motion-scale: 0.96;                   /* scale-in starts here */
  --motion-stagger: 50ms;                 /* delay between siblings */
  --motion-enter: var(--dur-base);
  --motion-exit: var(--dur-fast);
  --motion-reveal: var(--dur-reveal);
  --motion-count: 900ms;                  /* page-level reveal, allowed above 400ms */
  --motion-emphasis: var(--ease-out);     /* curve for things that pop: scale-in, press release */
  --motion-lift: calc(-1 * var(--space-1) / 2);
  --motion-press: 0.97;
  --motion-seq: 90ms;                     /* gap between steps of a page sequence (hero) */
  --motion-tilt: 6deg;                    /* scroll-settle starts tilted back this far */
  --motion-blur: 8px;                     /* hero entrances start this blurred */
}
[data-motion="crisp"] {
  --motion-distance: var(--space-1);
  --motion-scale: 0.98;
  --motion-stagger: 30ms;
  --motion-enter: var(--dur-fast);
  --motion-exit: var(--dur-fast);
  --motion-reveal: var(--dur-slow);
  --motion-count: 600ms;
  --motion-emphasis: var(--ease-out);
  --motion-lift: calc(-1 * var(--space-1) / 4);
  --motion-press: 0.98;
  --motion-seq: 60ms;
  --motion-tilt: 0deg;
  --motion-blur: 0px;
}
[data-motion="lively"] {
  --motion-distance: var(--space-4);
  --motion-scale: 0.9;
  --motion-stagger: 70ms;
  --motion-enter: var(--dur-slow);
  --motion-exit: var(--dur-fast);
  --motion-reveal: var(--dur-reveal);
  --motion-count: 1100ms;
  --motion-emphasis: var(--ease-spring);
  --motion-lift: calc(-1 * var(--space-1));
  --motion-press: 0.95;
  --motion-seq: 120ms;
  --motion-tilt: 12deg;
  --motion-blur: 12px;
}

/* ---- reveal: content entering the page ---- */
@keyframes wd-reveal { from { opacity: 0; transform: translateY(var(--motion-distance)); } }
.reveal,
.reveal-stagger > * { animation: wd-reveal var(--motion-reveal) var(--ease-out) backwards; }
.reveal-stagger > * { animation-delay: calc(var(--i, 0) * var(--motion-stagger)); }
:is(.reveal-stagger, [data-stagger]) > :nth-child(2) { --i: 1; }  :is(.reveal-stagger, [data-stagger]) > :nth-child(3) { --i: 2; }
:is(.reveal-stagger, [data-stagger]) > :nth-child(4) { --i: 3; }  :is(.reveal-stagger, [data-stagger]) > :nth-child(5) { --i: 4; }
:is(.reveal-stagger, [data-stagger]) > :nth-child(6) { --i: 5; }  :is(.reveal-stagger, [data-stagger]) > :nth-child(7) { --i: 6; }
:is(.reveal-stagger, [data-stagger]) > :nth-child(n+8) { --i: 7; } /* cap: long lists should not trickle in */
/* scroll-triggered: motion.js adds .reveal / .reveal-stagger when [data-reveal] enters the viewport */
.wd-motion [data-reveal]:not(.reveal):not(.reveal-stagger) { opacity: 0; }

/* ---- scale-in / scale-out: dialogs, menus, popovers. Set --origin to the anchor corner. ---- */
@keyframes wd-scale-in { from { opacity: 0; transform: scale(var(--motion-scale)); } }
@keyframes wd-scale-out { to { opacity: 0; transform: scale(var(--motion-scale)); } }
.scale-in { transform-origin: var(--origin, center); animation: wd-scale-in var(--motion-enter) var(--motion-emphasis) backwards; }
.scale-out { transform-origin: var(--origin, center); animation: wd-scale-out var(--motion-exit) var(--ease-in) both; }

/* ---- slide-in / slide-out: drawers and sheets (full travel), toasts (data-travel="short") ---- */
.slide-in, .slide-out { --sx: 100%; --sy: 0%; --so: 1; }
[data-from="left"]   { --sx: -100%; --sy: 0%; }
[data-from="top"]    { --sx: 0%; --sy: -100%; }
[data-from="bottom"] { --sx: 0%; --sy: 100%; }
[data-travel="short"] { --so: 0; }
[data-travel="short"][data-from="right"], [data-travel="short"]:not([data-from]) { --sx: calc(3 * var(--motion-distance)); }
[data-travel="short"][data-from="left"]   { --sx: calc(-3 * var(--motion-distance)); }
[data-travel="short"][data-from="top"]    { --sy: calc(-3 * var(--motion-distance)); }
[data-travel="short"][data-from="bottom"] { --sy: calc(3 * var(--motion-distance)); }
@keyframes wd-slide-in { from { opacity: var(--so); transform: translate(var(--sx), var(--sy)); } }
@keyframes wd-slide-out { to { opacity: var(--so); transform: translate(var(--sx), var(--sy)); } }
.slide-in { animation: wd-slide-in var(--motion-enter) var(--ease-out) backwards; }
.slide-out { animation: wd-slide-out var(--motion-exit) var(--ease-in) both; }

/* ---- lift: hover on cards. Shadow fades in on a pseudo-element, so only opacity animates. ---- */
.lift { position: relative; transition: transform var(--dur-base) var(--ease-in-out); }
.lift::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: var(--shadow-lg); opacity: 0; pointer-events: none; transition: opacity var(--dur-base) var(--ease-in-out); }
.lift.is-lifted, .lift:focus-within { transform: translateY(var(--motion-lift)); transition-timing-function: var(--ease-out); }
.lift.is-lifted::after, .lift:focus-within::after { opacity: 1; transition-timing-function: var(--ease-out); }
@media (hover: hover) {
  .lift:hover { transform: translateY(var(--motion-lift)); transition-timing-function: var(--ease-out); }
  .lift:hover::after { opacity: 1; transition-timing-function: var(--ease-out); }
}

/* ---- press: buttons and anything tappable. Down is ease-out, release uses the emphasis curve. ---- */
.press { transition: transform var(--dur-fast) var(--motion-emphasis); }
.press:active, .press.is-pressed { transform: scale(var(--motion-press)); transition-timing-function: var(--ease-out); }

/* ---- underline-grow: nav links (body links stay underlined at rest). Grows from the left, leaves to the right. ---- */
.underline-grow { position: relative; text-decoration: none; }
.underline-grow::after { content: ""; position: absolute; left: 0; right: 0; bottom: -0.2em; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: right; transition: transform var(--dur-base) var(--ease-in-out); }
.underline-grow:focus-visible::after, .underline-grow[aria-current]::after, .underline-grow.is-active::after { transform: scaleX(1); transform-origin: left; }
@media (hover: hover) { .underline-grow:hover::after { transform: scaleX(1); transform-origin: left; } }

/* ---- shimmer: skeletons while loading. Ambient loop, the one animation allowed to repeat. ---- */
@keyframes wd-shimmer { to { transform: translateX(100%); } }
.shimmer { position: relative; overflow: hidden; background: var(--paper-2); border-radius: var(--radius-sm); }
.shimmer::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--paper) 70%, transparent), transparent); animation: wd-shimmer 1400ms var(--ease-in-out) infinite; }

/* ---- expand: accordions and disclosure. Animates grid rows 0fr -> 1fr (the one layout exception). ---- */
.expand { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--motion-enter) var(--ease-in-out); }
.expand > * { min-height: 0; overflow: hidden; opacity: 0; transition: opacity var(--motion-exit) var(--ease-in); }
.expand.is-open { grid-template-rows: 1fr; }
.expand.is-open > * { opacity: 1; transition: opacity var(--motion-enter) var(--ease-out); }

/* ---- count: numbers climbing to their value, run by motion.js on [data-count] ---- */
[data-count] { font-variant-numeric: tabular-nums; }

/* ================= page level: the moments that make a landing page feel alive ================= */

/* ---- seq: one choreographed entrance (the hero). style="--seq: n" sets each element's step. ---- */
@keyframes wd-seq { from { opacity: 0; filter: blur(var(--motion-blur)); transform: translateY(var(--motion-distance)); } }
.seq { animation: wd-seq var(--motion-reveal) var(--ease-out) backwards; animation-delay: calc(var(--seq, 0) * var(--motion-seq)); }

/* ---- phrase: hero headline in short phrases (2 to 3 words), each fades in, un-blurs and rises (as Linear).
   <span class="phrase" style="--seq: 1">Hear every</span> <span class="phrase" style="--seq: 1.5">customer.</span>
   Only the hero headline. Never single words, never letters, never body text. ---- */
@keyframes wd-phrase { from { opacity: 0; filter: blur(var(--motion-blur)); transform: translateY(20%); } }
.phrase { display: inline-block; animation: wd-phrase calc(var(--motion-reveal) * 1.5) var(--ease-out) backwards; animation-delay: calc(var(--seq, 0) * var(--motion-seq)); }

/* ---- line-rise: headline lines rise out of a mask. <span class="line-rise"><span>Line</span></span> ---- */
@keyframes wd-line-rise { from { transform: translateY(105%); } }
.line-rise { display: block; overflow: clip; padding-bottom: 0.1em; margin-bottom: -0.1em; }
.line-rise > span { display: block; animation: wd-line-rise var(--motion-reveal) var(--ease-out) backwards; animation-delay: calc(var(--seq, 0) * var(--motion-seq)); }

/* ---- settle: scroll-linked. The hero shot starts tilted back and lies flat as the page scrolls.
   Put it on an inner wrapper so it doesn't fight the entrance. Browsers without scroll timelines show it flat. ---- */
@keyframes wd-settle { from { transform: perspective(1200px) rotateX(var(--motion-tilt)) scale(0.97); } }
@supports (animation-timeline: scroll()) {
  .settle { transform-origin: 50% 0; animation: wd-settle linear both; animation-timeline: scroll(root); animation-range: 0 50vh; }
}

/* ---- marquee: logos or quotes drifting sideways. motion.js duplicates the track; pauses on hover. ---- */
@keyframes wd-marquee { to { transform: translateX(-50%); } }
.marquee { overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); }
.marquee-track { display: flex; width: max-content; animation: wd-marquee var(--marquee-dur, 40s) linear infinite; }
.marquee-track > * { margin-inline-end: var(--marquee-gap, var(--space-12)); }
.marquee:hover .marquee-track, .marquee:focus-within .marquee-track { animation-play-state: paused; }

/* ---- showcase: steps beside a product panel. motion.js picks the active step; when its first child
   is sticky (pinned in a tall track), scroll progress drives it and fills each step's --p from 0 to 1.
   Steps are buttons: click jumps to that step. In the active panel, [data-stagger] children stagger in again. ---- */
.showcase-stage { display: grid; }
.showcase-panel { grid-area: 1 / 1; opacity: 0; transform: scale(var(--motion-scale)); visibility: hidden; transition: opacity var(--motion-exit) var(--ease-in), transform var(--motion-exit) var(--ease-in), visibility 0s linear var(--motion-exit); }
.showcase-panel.is-active { opacity: 1; transform: none; visibility: visible; transition: opacity var(--motion-enter) var(--ease-out), transform var(--motion-enter) var(--motion-emphasis); }
.showcase-panel.is-active [data-stagger] > * { animation: wd-reveal var(--motion-reveal) var(--ease-out) backwards; animation-delay: calc(var(--motion-enter) / 2 + var(--i, 0) * var(--motion-stagger)); }
.step-bar { position: relative; width: 2px; border-radius: var(--radius-pill); background: var(--line); overflow: hidden; }
@keyframes wd-step-fill { from { transform: scaleY(0); } to { transform: scaleY(1); } }
[data-showcase].is-auto [data-step].is-active .step-bar::after { animation: wd-step-fill var(--autoplay, 5000ms) linear both; }
.step-bar::after { content: ""; position: absolute; inset: 0; background: var(--accent); transform: scaleY(var(--p, 0)); transform-origin: top; }

/* ---- spotlight: a soft light follows the pointer across a tile (hover devices only, motion.js) ---- */
.spotlight { position: relative; isolation: isolate; overflow: hidden; }
.spotlight::before { content: ""; position: absolute; left: 0; top: 0; z-index: -1; width: 28rem; height: 28rem; margin: -14rem 0 0 -14rem; border-radius: 50%; background: radial-gradient(closest-side, var(--accent-soft), transparent); transform: translate(var(--mx, 50%), var(--my, 50%)); opacity: 0; pointer-events: none; transition: opacity var(--dur-slow) var(--ease-in-out); }
.spotlight.is-lit::before { opacity: 1; }

/* ---- slide-toggle: a thumb slides between options (pricing period, tabs). Set --seg-i and --seg-n. ---- */
.slide-toggle { position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; padding: var(--space-1); background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius-pill); }
.slide-toggle::before { content: ""; position: absolute; top: var(--space-1); bottom: var(--space-1); left: var(--space-1); width: calc((100% - 2 * var(--space-1)) / var(--seg-n, 2)); background: var(--paper); border-radius: inherit; box-shadow: var(--shadow-sm); transform: translateX(calc(var(--seg-i, 0) * 100%)); transition: transform var(--motion-enter) var(--motion-emphasis); }
.slide-toggle button { position: relative; min-height: var(--target-min); padding: 0 var(--space-5); background: none; border: 0; border-radius: inherit; color: var(--ink-2); font-weight: 500; cursor: pointer; }
.slide-toggle button:hover { color: var(--ink); }
.slide-toggle button[aria-pressed="true"] { color: var(--ink); }

/* ---- roll: a value swaps in place, old out the top, new in from below (wdRoll(el, text)) ---- */
@keyframes wd-roll-in { from { opacity: 0; transform: translateY(60%); } }
@keyframes wd-roll-out { to { opacity: 0; transform: translateY(-60%); } }
.roll { display: inline-grid; overflow: clip; vertical-align: bottom; }
.roll > * { grid-area: 1 / 1; }
.roll-in { animation: wd-roll-in var(--motion-enter) var(--motion-emphasis) backwards; }
.roll-out { animation: wd-roll-out var(--motion-exit) var(--ease-in) both; }

/* ---- caret: a text cursor blinking in a product mock's input (as Raycast). Illustrations only. ---- */
@keyframes wd-blink { 50% { opacity: 0; } }
.caret { display: inline-block; width: 1px; height: 1.15em; margin-left: 1px; vertical-align: text-bottom; background: currentColor; animation: wd-blink 1100ms steps(1) infinite; }

/* ---- scroll-fill: a sticky header gains its background once the page scrolls (motion.js) ---- */
.scroll-fill { isolation: isolate; }
.scroll-fill::before { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--paper); border-bottom: 1px solid var(--line); opacity: 0; transition: opacity var(--dur-base) var(--ease-in-out); }
.scroll-fill.is-scrolled::before { opacity: 1; }

/* ================= dashboards: motion that explains change (see motion/maps/dashboard.md) ================= */

/* ---- fade-in: data replacing a skeleton. Opacity only: in a dashboard nothing travels on load. ---- */
@keyframes wd-fade { from { opacity: 0; } }
.fade-in { animation: wd-fade var(--motion-enter) var(--ease-out) backwards; }

/* ---- flash: a value or row that just changed glows once, then fades so the eye can find it.
   wdFlash(el) restarts it. The fade is long on purpose (1.2s): it marks, it doesn't move. ---- */
@keyframes wd-flash { from { opacity: 1; } to { opacity: 0; } }
.flash { position: relative; isolation: isolate; }
.flash::after { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--accent-soft); opacity: 0; pointer-events: none; animation: wd-flash 1200ms var(--ease-out) both; }

/* ---- grow-y: bars drawing up from the baseline, first load only. Later changes transition --v instead. ---- */
@keyframes wd-grow-y { from { transform: scaleY(0); } }
.grow-y > * { transform-origin: bottom; animation: wd-grow-y var(--motion-reveal) var(--ease-out) backwards; animation-delay: calc(var(--i, 0) * 20ms); }

/* ---- live-dot: the one ambient loop a dashboard gets, beside "Live" ---- */
@keyframes wd-ping { from { transform: scale(1); opacity: 0.6; } to { transform: scale(2.6); opacity: 0; } }
.live-dot { position: relative; display: inline-block; width: var(--space-2); height: var(--space-2); border-radius: var(--radius-pill); background: var(--accent); }
.live-dot::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--accent); animation: wd-ping 1600ms var(--ease-out) infinite; }

/* ---- scroll-fade: a scroll container fades its edges only where there is more to scroll (as shadcn, Clerk).
   Scroll-linked; without scroll timelines the edges simply don't fade. ---- */
@property --fade-s { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --fade-e { syntax: "<length>"; inherits: false; initial-value: 0px; }
@keyframes wd-fade-s { from { --fade-s: 0px; } to { --fade-s: var(--space-8); } }
@keyframes wd-fade-e { from { --fade-e: var(--space-8); } to { --fade-e: 0px; } }
@supports (animation-timeline: scroll()) {
  .scroll-fade-x {
    mask-image: linear-gradient(to right, transparent, #000 var(--fade-s), #000 calc(100% - var(--fade-e)), transparent);
    animation: wd-fade-s linear both, wd-fade-e linear both;
    animation-timeline: scroll(self inline), scroll(self inline);
    animation-range: 0 var(--space-8), calc(100% - var(--space-8)) 100%;
  }
}


/* ================= docs: motion that orients (see motion/maps/docs.md) ================= */

/* ---- turn: a disclosure chevron turns as its control opens (sidebar groups, details).
   Put it inside the [aria-expanded] control. --turn sets the angle (90deg by default). ---- */
.turn { transition: transform var(--motion-enter) var(--ease-in-out); }
[aria-expanded="true"] .turn { transform: rotate(var(--turn, 90deg)); }

/* ---- spy: a thumb glides to the section being read (an "On this page" list, as Mintlify).
   wdSpy(nav) adds the thumb, marks the link with aria-current and moves it. Only transform animates:
   the thumb is 1px tall and scales to the link's height. ---- */
.spy { position: relative; }
.spy-thumb { position: absolute; left: 0; top: 0; width: 2px; height: 1px; background: var(--accent); border-radius: 0; transform-origin: 0 0; transform: translateY(var(--spy-y, 0px)) scaleY(var(--spy-h, 0)); transition: transform var(--motion-enter) var(--ease-in-out); pointer-events: none; }

/* ---- scroll-fade-y: scroll-fade-x for tall containers (a docs sidebar, as Next.js).
   Reuses --fade-s / --fade-e and their keyframes from scroll-fade. ---- */
@supports (animation-timeline: scroll()) {
  .scroll-fade-y {
    mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-s), #000 calc(100% - var(--fade-e)), transparent);
    animation: wd-fade-s linear both, wd-fade-e linear both;
    animation-timeline: scroll(self block), scroll(self block);
    animation-range: 0 var(--space-8), calc(100% - var(--space-8)) 100%;
  }
}

/* ---- smooth jumps: in-page links glide unless reduced motion is on (wdJump does the same from JS) ---- */
@media (prefers-reduced-motion: no-preference) {
  html:has(.spy) { scroll-behavior: smooth; }
}

/* ---- reduced motion: no travel, no scale, no loop. base.css already shortens every duration. ---- */
@media (prefers-reduced-motion: reduce) {
  :root, [data-motion] { --motion-distance: 0px; --motion-scale: 1; --motion-lift: 0px; --motion-press: 1; --motion-tilt: 0deg; --motion-blur: 0px; }
  .shimmer::after, .settle, .caret, .live-dot::after { animation: none; }
  .flash::after { animation-duration: 1200ms !important; } /* the marker stays: a fading tint carries meaning, not motion */
  .marquee-track { animation: none; flex-wrap: wrap; width: auto; justify-content: center; row-gap: var(--space-4); }
  .marquee-track > [aria-hidden="true"] { display: none; }
  .marquee { mask-image: none; }
}
