/* ScrollVars: interaction-state presets (use with toggles(), the Popover
 * API, or any class you flip yourself. Including sv-live by hand, which
 * replays the whole entrance system on demand). */

/* Pop: entry/exit for popovers, dialogs and display-toggled panels via the
   modern discrete-transition trio (@starting-style + allow-discrete).
   Works with [popover] / <dialog> out of the box, or with the `sv-open`
   class that toggles() flips. */
.sv-pop {
  --sv-pop-distance: 0.75rem;
  --sv-pop-duration: 350ms;
  transition:
    opacity var(--sv-pop-duration) var(--sv-ease, ease),
    translate var(--sv-pop-duration) var(--sv-ease, ease),
    display var(--sv-pop-duration) allow-discrete,
    overlay var(--sv-pop-duration) allow-discrete;
}
[popover].sv-pop:not(:popover-open) {
  opacity: 0;
  translate: 0 var(--sv-pop-distance);
}
/* only where <dialog> is implemented: the type selector matches the unknown
   element too, and hiding THAT would break the documented static fallback
   panel (nothing else hides it there, no UA display:none) */
@supports selector(dialog:modal) {
  dialog.sv-pop:not([open]) {
    opacity: 0;
    translate: 0 var(--sv-pop-distance);
  }
}
.sv-pop:not([popover]):not(dialog):not(.sv-open) {
  display: none;
  opacity: 0;
  translate: 0 var(--sv-pop-distance);
}
@starting-style {
  [popover].sv-pop:popover-open,
  dialog.sv-pop[open],
  .sv-pop.sv-open {
    opacity: 0;
    translate: 0 var(--sv-pop-distance);
  }
}

/* Rotating words: a clipped column of words; set --sv-word (integer index)
   to slide one row per word. Old word exits up, the next rises from below.
   Discrete value + transition = smooth, the house idiom. */
.sv-words {
  display: inline-flex;
  flex-direction: column;
  /* baseline (the default) on an inline flex container exports the FIRST
     word's baseline. So the rotator aligns with surrounding text at any
     host line-height. (bottom only lined up when the host was ~1.15 too.) */
  vertical-align: baseline;
  height: 1.15em;
  overflow: hidden;
}
.sv-words > * {
  display: block;
  height: 1.15em;
  line-height: 1.15;
  translate: 0 calc(var(--sv-word, 0) * -1.15em);
  transition: translate var(--sv-duration, 650ms)
    var(--sv-ease, cubic-bezier(0.28, 0.84, 0.42, 1));
}

/* Acts: a time-driven MASTER CLOCK in pure CSS. The same slice idiom the
   scroll scenes use, but the clock is a registered custom property in
   transition. Trigger with a class: sv-open (toggles/click) or sv-live
   (scroll-triggered timed choreography. Zero JS). Define acts as slices:

     .stage  { --a1: clamp(0, var(--sv-act), 1);
               --a2: clamp(0, calc(var(--sv-act) - 1), 1); }
     .title  { opacity: var(--a1); translate: 0 calc((1 - var(--a1)) * 2rem); }
     .cards  { scale: calc(0.9 + var(--a2) * 0.1); }

   Knobs: --sv-acts-count (how many acts, default 3) and --sv-acts-duration
   (whole timeline, default 2s). Retiming is relative: change one number
   and every act rescales. Reversible and interruptible by nature: remove
   the class mid-flight and the transition RETARGETS instead of restarting.
   Needs @property (Chrome 85 / FF 128 / Safari 16.4); older browsers snap
   straight to the finished state. Content never hidden.
   --sv-acts-settle is internal, set only by toggles() during its boot
   settle (src/core/toggles.ts), which also holds an element's own inline
   transition-duration LONGHAND for the same settle. What remains unguarded:
   an author RULE, not an inline style, that overrides transition-duration
   or the whole transition shorthand on .sv-acts at higher specificity than
   this one still owns the settle timing instead of it. */
@property --sv-act {
  syntax: '<number>';
  initial-value: 0;
  inherits: true;
}
.sv-acts {
  --sv-act: 0;
  transition: --sv-act var(--sv-acts-settle, var(--sv-acts-duration, 2s)) linear;
}
/* sv-ui (set by toggles(), src/core/toggles.ts) marks each toggle TARGET
   (the trigger itself when there is no separate target): without the
   :not(.sv-ui) here, a click-only widget on a page whose scroll driver
   never boots would be frozen at the finished state forever instead of
   starting at act zero and animating on click. A widget the click driver
   does not control (plain scroll reveal, no toggle) is not marked, so it
   keeps the no-JS/no-boot fallback below. */
html:not(.sv-on) .sv-acts:not(.sv-ui) {
  --sv-act: var(--sv-acts-count, 3); /* no JS at all: the finished state, not act zero */
}
.sv-acts.sv-open {
  --sv-act: var(--sv-acts-count, 3);
}
/* Driven from the inherited --sv-live flag, like the entrance presets: the
   nearest tracker (not just any live ancestor) decides whether this acts
   clock is live, so a nested non-live tracker keeps its own clock at 0.
   :not(.sv-open) excludes an opened widget: without it this rule (0,3,0)
   outranks .sv-acts.sv-open (0,2,0) above, so an opened widget sitting
   inside (or itself a) non-live tracker would compute --sv-act: 0 instead
   of staying at the finished value. */
.sv-on :is(.sv, [data-sv]) .sv-acts:not(.sv-open),
.sv-on :is(.sv, [data-sv]).sv-acts:not(.sv-open) {
  --sv-act: calc(var(--sv-live) * var(--sv-acts-count, 3));
}

@media (prefers-reduced-motion: reduce) {
  .sv-pop,
  .sv-words > *,
  .sv-acts {
    transition-duration: 0.01ms;
  }
}

/* The same under the page's own switch, html[data-sv-motion="reduce"]
   (core/motion.ts): a test keeps every rule in step with the block above. */
:where([data-sv-motion="reduce"]) .sv-pop,
:where([data-sv-motion="reduce"]) .sv-words > *,
:where([data-sv-motion="reduce"]) .sv-acts {
  transition-duration: 0.01ms;
}
