/*
 * IVW-012 motion stylesheet. Every keyframe and transition in the
 * catalogue at shared/MOTION.md lives here; see that file for the
 * per-entry trigger, moving elements, keyframe table and reduced-motion
 * state this implements. Every duration, delay and easing curve below is
 * a var(--...) from shared/styles/tokens.css (section 1 of MOTION.md);
 * this file declares no bespoke timing value of its own outside the two
 * composite periods immediately below, both built from token values with
 * calc(). This file restyles no component beyond the motion this ticket
 * adds; layout, colour and spacing stay in shared/styles/base.css
 * (IVW-011).
 *
 * CSP note (D-006): every animation here is class- or attribute-driven.
 * shared/scripts/motion.js never writes a style attribute, never injects
 * a <style> element, and never calls insertRule; it only toggles the
 * classes and data attributes this file already defines rules for.
 */

/* Two composite periods, both built from tokens.css duration tokens with
 * calc() at their point of use (not declared as a new custom property
 * here, so every var(--...) in this file still resolves inside
 * tokens.css): a slow-rotation glow's full turn is
 * `calc(var(--duration-slower) * 10)`, well past the ui-design skill's
 * "slow rotation" bar; a pulse glow's full brighten-and-settle cycle is
 * `calc(var(--duration-slower) * 3)`. See shared/MOTION.md section 1.
 */

/* -----------------------------------------------------------------------
 * Reveal family shared mechanics (2.1, 2.2, 2.7, 2.8 in MOTION.md).
 *
 * Default, no-script state: every reveal-family element is fully visible
 * with no transform. shared/scripts/motion.js adds `is-motion-armed` at
 * init, which is what makes the pre-reveal hidden state below apply; if
 * script never runs, this default is what a visitor sees, always.
 * --------------------------------------------------------------------- */

[data-motion="hero-reveal"].is-motion-armed [data-motion-item] {
  opacity: 0;
  transform: translateY(1.5em) scale(0.97);
}

[data-motion="hero-reveal"].is-motion-armed.is-revealed [data-motion-item] {
  animation: iv-hero-reveal var(--duration-slow) var(--ease-standard) both;
}

[data-motion="hero-reveal"].is-motion-armed.is-revealed [data-motion-item="1"] {
  animation-delay: 0ms;
}

[data-motion="hero-reveal"].is-motion-armed.is-revealed [data-motion-item="2"] {
  animation-delay: var(--duration-fast);
}

[data-motion="hero-reveal"].is-motion-armed.is-revealed [data-motion-item="3"] {
  animation-delay: calc(var(--duration-fast) * 2);
}

[data-motion="hero-reveal"].is-motion-armed.is-revealed [data-motion-item="4"] {
  animation-delay: calc(var(--duration-fast) * 3);
}

@keyframes iv-hero-reveal {
  0% {
    transform: translateY(1.5em) scale(0.97);
    opacity: 0;
  }
  55% {
    transform: translateY(-0.22em) scale(1.008);
    opacity: 1;
  }
  78% {
    transform: translateY(0.08em) scale(0.997);
    opacity: 1;
  }
  100% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}

[data-motion="section-reveal"].is-motion-armed {
  opacity: 0;
  transform: translateY(12%) scale(0.98);
}

[data-motion="section-reveal"].is-motion-armed.is-revealed {
  animation: iv-section-reveal var(--duration-base) var(--ease-standard) both;
}

@keyframes iv-section-reveal {
  0% {
    transform: translateY(12%) scale(0.98);
    opacity: 0;
  }
  60% {
    transform: translateY(-3%) scale(1.006);
    opacity: 1;
  }
  82% {
    transform: translateY(1%) scale(0.998);
    opacity: 1;
  }
  100% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}

[data-motion="cta-emphasis"].is-motion-armed {
  opacity: 0;
  transform: scale(0.9);
}

[data-motion="cta-emphasis"].is-motion-armed.is-revealed {
  animation: iv-cta-emphasis var(--duration-slow) var(--ease-standard)
    calc(var(--duration-fast) * 2) both;
}

@keyframes iv-cta-emphasis {
  0% {
    transform: scale(0.9);
    opacity: 0;
  }
  52% {
    transform: scale(1.05);
    opacity: 1;
  }
  76% {
    transform: scale(0.98);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

/* Diagnostic compile pulse (2.7): the block's own text stays visible
 * throughout; only the caret's fade-in and the block's own glow flash
 * are armed and revealed. */
[data-motion="diagnostic-pulse"].is-motion-armed .diagnostic-block__caret {
  opacity: 0;
}

[data-motion="diagnostic-pulse"].is-motion-armed.is-revealed {
  animation: iv-diagnostic-pulse var(--duration-slow) var(--ease-standard)
    var(--duration-fast) both;
}

[data-motion="diagnostic-pulse"].is-motion-armed.is-revealed .diagnostic-block__caret {
  animation: iv-diagnostic-caret-in var(--duration-slow) var(--ease-standard)
    var(--duration-fast) both;
}

@keyframes iv-diagnostic-pulse {
  0% {
    box-shadow: 0 0 0 rgba(var(--glow-color-rgb), 0);
  }
  30% {
    box-shadow: 0 0 var(--glow-spread) rgba(var(--glow-color-rgb), 0.55);
  }
  48% {
    box-shadow: 0 0 var(--glow-spread) rgba(var(--glow-color-rgb), 0.22);
  }
  66% {
    box-shadow: 0 0 var(--glow-spread) rgba(var(--glow-color-rgb), 0.41);
  }
  100% {
    box-shadow: 0 0 0 rgba(var(--glow-color-rgb), 0);
  }
}

@keyframes iv-diagnostic-caret-in {
  0% {
    opacity: 0;
  }
  48% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

/* Scroll-driven, script-free baseline for the reveal family: a browser
 * that supports CSS scroll-driven animations plays every reveal with no
 * script at all. Suppressed automatically wherever `is-motion-armed` has
 * not been added (script did not run, or reduced motion cleared it in
 * the block below), since the animation targets the same selectors the
 * script-driven rules above use. */
@supports (animation-timeline: view()) {
  [data-motion="hero-reveal"] [data-motion-item] {
    animation: iv-hero-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 35%;
  }

  [data-motion="section-reveal"] {
    animation: iv-section-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 35%;
  }

  [data-motion="cta-emphasis"] {
    animation: iv-cta-emphasis linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 35%;
  }

  [data-motion="diagnostic-pulse"] {
    animation: iv-diagnostic-pulse linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 35%;
  }

  [data-motion="diagnostic-pulse"] .diagnostic-block__caret {
    animation: iv-diagnostic-caret-in linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 35%;
  }
}

/* -----------------------------------------------------------------------
 * 2.3 Nav panel open and close. Entirely CSS: an animation, paused by
 * default, is set running by the same checkbox state base.css's own
 * panel slide already reads. Scoped to the phone range where the panel
 * toggle exists at all (base.css switches to the inline desktop layout
 * at 48rem and up; see base.css's own breakpoint comment).
 * --------------------------------------------------------------------- */

@media not all and (min-width: 48rem) {
  .nav__panel .nav__item {
    animation: iv-nav-item-in var(--duration-base) var(--ease-standard) both;
    animation-play-state: paused;
  }

  .nav__toggle-checkbox:checked ~ .nav__panel .nav__item {
    animation-play-state: running;
  }

  .nav__panel .nav__item:nth-child(1) { animation-delay: 0ms; }
  .nav__panel .nav__item:nth-child(2) { animation-delay: var(--duration-fast); }
  .nav__panel .nav__item:nth-child(3) { animation-delay: calc(var(--duration-fast) * 2); }
  .nav__panel .nav__item:nth-child(4) { animation-delay: calc(var(--duration-fast) * 3); }
  .nav__panel .nav__item:nth-child(5) { animation-delay: calc(var(--duration-fast) * 4); }
  .nav__panel .nav__item:nth-child(6) { animation-delay: calc(var(--duration-fast) * 5); }
}

@keyframes iv-nav-item-in {
  0% {
    transform: translateX(15%);
    opacity: 0;
  }
  65% {
    transform: translateX(-3%);
    opacity: 1;
  }
  85% {
    transform: translateX(1%);
    opacity: 1;
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

/* -----------------------------------------------------------------------
 * 2.4 Hover and focus glow: pulse variant and slow-rotation variant.
 * Entirely CSS; the host must be `position: relative` (every component
 * this attaches to in base.css already establishes a stacking context or
 * is safely made one here) so the pseudo-element glow ring can position
 * behind it without affecting layout.
 * --------------------------------------------------------------------- */

.is-glow-pulse,
.is-glow-rotate {
  position: relative;
  isolation: isolate;
}

.is-glow-pulse::before,
.is-glow-rotate::before {
  content: "";
  position: absolute;
  inset: calc(var(--glow-spread) * -1);
  border-radius: inherit;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
}

.is-glow-pulse:hover::before,
.is-glow-pulse:focus-visible::before {
  background: radial-gradient(
    circle,
    rgba(var(--glow-color-rgb), 0.55) 0%,
    rgba(var(--glow-color-rgb), 0) 70%
  );
  animation: iv-glow-pulse calc(var(--duration-slower) * 3) var(--ease-standard) infinite;
}

.is-glow-rotate:hover::before,
.is-glow-rotate:focus-visible::before {
  background: conic-gradient(
    from 0deg,
    rgba(var(--glow-color-rgb), 0) 0%,
    rgba(var(--glow-color-rgb), 0.55) 50%,
    rgba(var(--glow-color-rgb), 0) 100%
  );
  animation: iv-glow-rotate calc(var(--duration-slower) * 10) linear infinite;
}

@keyframes iv-glow-pulse {
  0% {
    transform: scale(1);
    opacity: 0.35;
  }
  20% {
    transform: scale(1.06);
    opacity: 0.9;
  }
  45% {
    transform: scale(0.99);
    opacity: 0.5;
  }
  60% {
    transform: scale(1.02);
    opacity: 0.65;
  }
  100% {
    transform: scale(1);
    opacity: 0.35;
  }
}

@keyframes iv-glow-rotate {
  0% {
    transform: rotate(0deg);
    opacity: 0.55;
  }
  100% {
    transform: rotate(360deg);
    opacity: 0.55;
  }
}

/* Nav CTA exception (IVW-019, founder review E-007): the nav's
 * "Talk to us" control is outlined and static in base.css, and its hover
 * is a fixed shadow only. Whatever glow class a page adds to .nav__cta,
 * the pseudo-element ring below is removed, so the control never spins
 * and never paints a ring the phone panel could clip. The catalogue
 * entry 2.4 above is otherwise unchanged for every other host. */
.nav__cta[class*="is-glow-"]::before {
  display: none;
  animation: none;
}

/* -----------------------------------------------------------------------
 * 2.5 Token-rotation storyboard.
 * --------------------------------------------------------------------- */

.token-rotation__frames {
  position: relative;
}

.token-rotation__frame {
  grid-area: 1 / 1;
}

.token-rotation__frame[hidden] {
  display: none;
}

.token-rotation[data-frame-active] .token-rotation__frames {
  display: grid;
}

.token-rotation__frame.is-frame-entering {
  animation: iv-frame-in var(--duration-slower) var(--ease-standard) both;
}

.token-rotation__frame.is-frame-leaving {
  animation: iv-frame-in var(--duration-slower) var(--ease-standard) reverse both;
}

@keyframes iv-frame-in {
  0% {
    transform: translateY(6%) scale(0.985);
    opacity: 0;
  }
  60% {
    transform: translateY(-1.5%) scale(1.004);
    opacity: 1;
  }
  82% {
    transform: translateY(0.5%) scale(0.999);
    opacity: 1;
  }
  100% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}

.token-rotation__dot[aria-current="true"] {
  background: var(--brand);
}

.token-rotation__play[aria-pressed="true"] {
  box-shadow: var(--glow-static);
}

/* Optional, script-free scroll enhancement: only while script has not
 * taken control (data-motion-scroll="js" is absent) and the browser
 * supports scroll-driven animation. The tall wrapper and sticky viewport
 * classes are only meaningful under this @supports block; elsewhere they
 * have no visual effect because animation-timeline never applies. */
.token-rotation--scroll .token-rotation__viewport {
  position: relative;
}

.token-rotation--scroll .token-rotation__frame {
  position: absolute;
  inset: 0;
}

@supports (animation-timeline: view()) {
  .token-rotation--scroll:not([data-motion-scroll="js"]) {
    height: 400vh;
  }

  .token-rotation--scroll:not([data-motion-scroll="js"]) .token-rotation__viewport {
    position: sticky;
    top: 0;
    height: 100vh;
  }

  /* The markup ships frames 2 to 4 with the `hidden` attribute so the
   * no-script, no-scroll-support default (plain HTML) shows only frame
   * 1, statically. A browser that reaches this @supports block un-hides
   * every frame so the scroll-linked layout below can show all four,
   * still with no script involved. */
  .token-rotation--scroll:not([data-motion-scroll="js"]) .token-rotation__frame[hidden] {
    display: block;
  }

  .token-rotation--scroll:not([data-motion-scroll="js"]) .token-rotation__frame {
    animation: iv-frame-in linear both;
    animation-timeline: view();
  }

  .token-rotation--scroll:not([data-motion-scroll="js"]) .token-rotation__frame[data-frame="1"] {
    animation-range: entry 0% 25%;
  }

  .token-rotation--scroll:not([data-motion-scroll="js"]) .token-rotation__frame[data-frame="2"] {
    animation-range: 25% 50%;
  }

  .token-rotation--scroll:not([data-motion-scroll="js"]) .token-rotation__frame[data-frame="3"] {
    animation-range: 50% 75%;
  }

  .token-rotation--scroll:not([data-motion-scroll="js"]) .token-rotation__frame[data-frame="4"] {
    animation-range: 75% 100%;
  }
}

/* -----------------------------------------------------------------------
 * 2.6 Set-operation diagram.
 * --------------------------------------------------------------------- */

/* Baseline appearance: the set diagram is new to this ticket (not one of
 * the fourteen shared/COMPONENTS.md components), so its non-motion look
 * lives here alongside its keyframes rather than in a second file. */
.set-diagram {
  width: 100%;
  height: 100%;
}

.set-diagram__circle-a,
.set-diagram__circle-b {
  fill: none;
  stroke-width: 2;
  transform-box: fill-box;
  transform-origin: center;
}

.set-diagram__circle-a {
  stroke: var(--brand);
}

.set-diagram__circle-b {
  stroke: var(--proof);
}

.set-diagram__intersection {
  fill: var(--brand-hot);
  opacity: 0.35;
  transform-box: fill-box;
  transform-origin: center;
}

.set-diagram__error {
  fill: var(--threat);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
}

.set-diagram.is-intersecting .set-diagram__circle-a {
  animation: iv-set-intersect-a var(--duration-slower) var(--ease-standard) both;
}

.set-diagram.is-intersecting .set-diagram__circle-b {
  animation: iv-set-intersect-b var(--duration-slower) var(--ease-standard) both;
}

@keyframes iv-set-intersect-a {
  0% {
    transform: translateX(8%);
    opacity: 0.6;
  }
  58% {
    transform: translateX(-0.96%);
    opacity: 1;
  }
  80% {
    transform: translateX(0.32%);
    opacity: 1;
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes iv-set-intersect-b {
  0% {
    transform: translateX(-8%);
    opacity: 0.6;
  }
  58% {
    transform: translateX(0.96%);
    opacity: 1;
  }
  80% {
    transform: translateX(-0.32%);
    opacity: 1;
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

.set-diagram.is-empty .set-diagram__intersection {
  animation: iv-set-shrink var(--duration-slow) var(--ease-in) both;
}

@keyframes iv-set-shrink {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  100% {
    transform: scale(0);
    opacity: 0;
  }
}

.set-diagram.is-error .set-diagram__circle-a,
.set-diagram.is-error .set-diagram__circle-b {
  stroke: var(--threat);
}

.set-diagram.is-error .set-diagram__error {
  animation: iv-set-error var(--duration-slow) var(--ease-standard) both;
}

@keyframes iv-set-error {
  0% {
    transform: scale(0.9);
    opacity: 0;
  }
  65% {
    transform: scale(1.03);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

/* -----------------------------------------------------------------------
 * Reduced motion: every entry above lands in its final, static state,
 * with no animation running at all (not merely a zero-duration one, so
 * the scroll-driven and infinite-loop rules above, which do not depend
 * on the duration tokens tokens.css already zeroes, are explicitly
 * disabled here too).
 * --------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  /* Reveal family: final state is fully visible, no transform, whether
   * or not script added `is-motion-armed` (it never gets a chance to
   * behind this media query, since shared/scripts/motion.js checks
   * matchMedia at init and skips arming entirely; these overrides are a
   * second, CSS-only guarantee against the @supports scroll-driven path
   * above, which does not depend on the armed class at all). */
  [data-motion="hero-reveal"] [data-motion-item],
  [data-motion="section-reveal"],
  [data-motion="cta-emphasis"],
  [data-motion="diagnostic-pulse"],
  [data-motion="diagnostic-pulse"] .diagnostic-block__caret,
  .nav__panel .nav__item {
    animation: none !important;
    animation-timeline: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  [data-motion="diagnostic-pulse"] {
    box-shadow: none !important;
  }

  .is-glow-pulse::before,
  .is-glow-rotate::before {
    animation: none !important;
    opacity: 0 !important;
  }

  /* Storyboard: frame changes are instant `hidden`-attribute swaps (see
   * shared/scripts/motion.js), so no cross-fade animation ever starts. */
  .token-rotation__frame {
    animation: none !important;
    animation-timeline: none !important;
    transform: none !important;
  }

  /* Re-hide frames 2 to 4 in the no-script, scroll-capable case: the
   * @supports (animation-timeline: view()) block above un-hides every
   * `[hidden]` frame so it can scroll-drive them, but that un-hide must
   * not survive into the reduced-motion static state when script has
   * not taken over (no script means data-motion-scroll="js" is never
   * set). Without this rule, a visitor with script disabled, on a
   * browser supporting animation-timeline, with reduced motion set,
   * would see all four absolutely positioned frames rendered at once. */
  .token-rotation--scroll .token-rotation__frame[hidden] {
    display: none !important;
  }

  .token-rotation--scroll {
    height: auto !important;
  }

  .token-rotation--scroll .token-rotation__viewport {
    position: static !important;
    height: auto !important;
  }

  /* Set diagram: each state still lands, instantly, in the same order
   * script always applies (intersecting, then empty, then error); only
   * the animated transition between them is removed. */
  .set-diagram__circle-a,
  .set-diagram__circle-b {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  .set-diagram__intersection {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  .set-diagram.is-empty .set-diagram__intersection {
    transform: scale(0) !important;
    opacity: 0 !important;
  }

  .set-diagram__error {
    animation: none !important;
    transform: scale(1) !important;
    opacity: 0 !important;
  }

  .set-diagram.is-error .set-diagram__error {
    opacity: 1 !important;
  }
}
