/* ExecFlow motion bundle — generated by scripts/build-motion.mjs.
   Do not hand edit; edit the modules in site-blocks/fx and rebuild.
   Source: C:\Development\demos\managed-agents\site-blocks\fx
   Built: 2026-09-21 */

/* == fx-core@1.0.0 ==
 * Shared tokens + utilities for site-blocks effects.
 * Brand-neutral: override the custom properties per site.
 * All hidden-until-reveal styles in every module MUST be scoped under
 * `html.pf-motion:not(.fx-reduced)` so no-JS and reduced-motion users see everything.
 */
:root {
  --fx-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --fx-ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
  --fx-accent: #3b82f6;            /* brand accent (glow, live dots) */
  --fx-accent-2: #ff6b2b;          /* secondary accent (stamps, highlights) */
  --fx-ink: #0f172a;               /* dark surface for inverted sections */
  --fx-glow: 0 0 60px rgba(59, 130, 246, 0.25);
  --fx-hairline: rgba(15, 23, 42, 0.12);
  --fx-hairline-inverse: rgba(248, 250, 252, 0.16);
  --fx-dur: 0.7s;
}

/* 1px hairline grid backdrop (put on a position:relative parent's ::before or a child layer) */
.fx-hairline-grid {
  pointer-events: none;
  background-image:
    repeating-linear-gradient(0deg, var(--fx-hairline) 0 1px, transparent 1px 96px),
    repeating-linear-gradient(90deg, var(--fx-hairline) 0 1px, transparent 1px 96px);
  opacity: 0.35;
}

/* glass surface */
.fx-glass {
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border: 1px solid var(--fx-hairline);
  box-shadow: 0 8px 32px rgba(15, 23, 42, 0.08);
}
.fx-glass--dark {
  background: rgba(15, 23, 42, 0.66);
  border-color: var(--fx-hairline-inverse);
  box-shadow: 0 8px 32px rgba(2, 6, 23, 0.4);
}

/* soft brand glow layer */
.fx-glow {
  pointer-events: none;
  box-shadow: var(--fx-glow);
}

/* screen-reader only (for a11y text alternatives inside animated widgets) */
.fx-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* == fx-scroll-reveal@1.0.0 == */
/* Scroll-triggered entrance reveal. Pair with fx-scroll-reveal.js + fx-core.
 * Snippet: <div data-fx="fx-scroll-reveal" data-fx-variant="rise" data-fx-stagger="0.08">...</div>
 * Variants via data-fx-variant: fade | rise (default) | mask (clip-path wipe upward).
 * All hidden-until-reveal styles are motion-gated: no-JS and reduced-motion users see everything.
 */

/* Variant knobs (custom props inherit down to staggered children) */
[data-fx~="fx-scroll-reveal"][data-fx-variant="fade"] {
  --fx-sr-o: 0;
  --fx-sr-t: none;
}
[data-fx~="fx-scroll-reveal"][data-fx-variant="mask"] {
  --fx-sr-o: 1;
  --fx-sr-t: none;
  --fx-sr-c: inset(100% 0 0 0);
}

/* Animated unit = the element itself, or its DIRECT children when data-fx-stagger is set */
html.pf-motion:not(.fx-reduced) [data-fx~="fx-scroll-reveal"]:not([data-fx-stagger]),
html.pf-motion:not(.fx-reduced) [data-fx~="fx-scroll-reveal"][data-fx-stagger] > * {
  transition:
    opacity var(--fx-dur, 0.7s) var(--fx-ease-out),
    transform var(--fx-dur, 0.7s) var(--fx-ease-out),
    clip-path var(--fx-dur, 0.7s) var(--fx-ease-out);
  transition-delay: calc(var(--fx-sr-delay, 0s) + var(--fx-i, 0) * var(--fx-sr-stagger, 0s));
}

/* Hidden (pre-intersect) state; defaults = rise (opacity + translateY 24px) */
html.pf-motion:not(.fx-reduced) [data-fx~="fx-scroll-reveal"]:not([data-fx-stagger]):not(.fx-in),
html.pf-motion:not(.fx-reduced) [data-fx~="fx-scroll-reveal"][data-fx-stagger]:not(.fx-in) > * {
  opacity: var(--fx-sr-o, 0);
  transform: var(--fx-sr-t, translateY(24px));
  clip-path: var(--fx-sr-c, none);
}

/* mask needs an interpolable visible clip ("none" does not transition) */
html.pf-motion:not(.fx-reduced) [data-fx~="fx-scroll-reveal"][data-fx-variant="mask"]:not([data-fx-stagger]).fx-in,
html.pf-motion:not(.fx-reduced) [data-fx~="fx-scroll-reveal"][data-fx-variant="mask"][data-fx-stagger].fx-in > * {
  clip-path: inset(0 0 0 0);
}

/* belt-and-suspenders: still all motion when the OS asks for it */
@media (prefers-reduced-motion: reduce) {
  [data-fx~="fx-scroll-reveal"],
  [data-fx~="fx-scroll-reveal"] > * {
    transition: none;
  }
}

/* == fx-split-headline@1.0.0 == */
/* Per-word masked headline reveal. JS wraps words in .fx-word > .fx-word-inner;
 * inner spans rise from translateY(110%) once the root gains .fx-in (IntersectionObserver).
 * Snippet: <h2 data-fx="fx-split-headline">Everything you need</h2>
 * Delay per word: calc(--fx-i * --fx-sh-stagger); duration via --fx-sh-dur. Transform-only. */

[data-fx~="fx-split-headline"] .fx-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* keep descenders (g, y, p) inside the mask without shifting layout */
  padding-bottom: 0.09em;
  margin-bottom: -0.09em;
}

[data-fx~="fx-split-headline"] .fx-word-inner {
  display: inline-block;
  transform: translateY(0);
}

/* hidden-until-reveal state: ONLY on motion-capable JS pages (no-JS / reduced see full text) */
html.pf-motion:not(.fx-reduced) [data-fx~="fx-split-headline"] .fx-word-inner {
  transform: translateY(110%);
  transition: transform var(--fx-sh-dur, 0.8s) var(--fx-ease-out);
  transition-delay: calc(var(--fx-i, 0) * var(--fx-sh-stagger, 0.045s));
  will-change: transform;
}

html.pf-motion:not(.fx-reduced) [data-fx~="fx-split-headline"].fx-in .fx-word-inner {
  transform: translateY(0);
}

/* belt-and-suspenders: OS-level reduced motion stills everything to the final state */
@media (prefers-reduced-motion: reduce) {
  [data-fx~="fx-split-headline"] .fx-word-inner {
    transform: none !important;
    transition: none !important;
  }
}

/* == fx-magnetic@1.0.0 == */
/* Magnetic hover pull for CTAs. JS drives all motion; CSS only preps the element.
 * Usage: <a class="cta" data-fx="fx-magnetic"><span class="fx-magnetic-inner">Get started</span></a>
 * Nothing is hidden pre-reveal: no-JS and reduced-motion users get the plain, static CTA.
 */
[data-fx~="fx-magnetic"] {
  display: inline-block;
}
[data-fx~="fx-magnetic"] .fx-magnetic-inner {
  display: inline-block;
}

/* will-change hint only where the effect can actually run */
@media (hover: hover) and (pointer: fine) {
  html.pf-motion:not(.fx-reduced) [data-fx~="fx-magnetic"],
  html.pf-motion:not(.fx-reduced) [data-fx~="fx-magnetic"] .fx-magnetic-inner {
    will-change: transform;
  }
}

/* == fx-accordion@1.0.0 == */
/* Accessible accordion, grid-rows (0fr -> 1fr) collapse. Pair with fx-accordion.js + fx-core.
 * Snippet: <div data-fx="fx-accordion"><button>Q1</button><div>A1</div><button>Q2</button><div>A2</div></div>
 * Opts: data-fx-single (default true). Panels stay OPEN without JS (closed state is
 * gated under html.pf-motion). Reduced motion: toggles still work, just instantly.
 */

[data-fx~="fx-accordion"] .fx-acc-panel {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 0.32s var(--fx-ease-out);
}
[data-fx~="fx-accordion"] .fx-acc-inner {
  min-height: 0;
  overflow: hidden;
  transition: opacity 0.26s var(--fx-ease-out), visibility 0s;
}

/* closed state exists only once JS booted (html.pf-motion): no-JS users see every panel */
html.pf-motion [data-fx~="fx-accordion"] .fx-acc-panel:not(.fx-open) {
  grid-template-rows: 0fr;
}
html.pf-motion [data-fx~="fx-accordion"] .fx-acc-panel:not(.fx-open) > .fx-acc-inner {
  opacity: 0;
  visibility: hidden; /* drops collapsed content from tab order + AT after the tween */
  transition: opacity 0.26s var(--fx-ease-out), visibility 0s linear 0.32s;
}

/* trigger affordance: chevron drawn with currentColor, rotates when open */
[data-fx~="fx-accordion"] .fx-acc-trigger {
  cursor: pointer;
}
[data-fx~="fx-accordion"] .fx-acc-trigger::after {
  content: "";
  display: inline-block;
  width: 0.45em;
  height: 0.45em;
  margin-left: 0.55em;
  vertical-align: 0.12em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.32s var(--fx-ease-out);
}
[data-fx~="fx-accordion"] .fx-acc-trigger[aria-expanded="true"]::after {
  border-color: var(--fx-accent);
  transform: rotate(225deg);
}

/* reduced motion: keep the toggle, kill the tween (instant open/close) */
@media (prefers-reduced-motion: reduce) {
  [data-fx~="fx-accordion"] .fx-acc-panel,
  [data-fx~="fx-accordion"] .fx-acc-inner,
  [data-fx~="fx-accordion"] .fx-acc-trigger::after {
    transition-duration: 0s !important;
    transition-delay: 0s !important;
  }
}

/* == fx-theme-invert@1.0.0 == */
/* Minimal dark skin for .fx-invert sections; page CSS does the rest and styles its
 * fixed header off html.fx-theme-dark. Enhancement only: the class arrives via JS,
 * so also give the section its own static dark background for no-JS visitors.
 * <section class="proof-band" data-fx="fx-theme-invert">...</section> */

[data-fx~="fx-theme-invert"].fx-invert {
  background: var(--fx-ink);
  color: rgba(248, 250, 252, 0.92);
  --fx-hairline: var(--fx-hairline-inverse);
}

/* == fx-typing@1.0.0 == */
/* Layout + caret for the fx-typing typewriter module.
 * The typed span preserves the newlines the JS inserts between lines (pre-wrap).
 * Caret is a blinking accent bar drawn via ::after on .fx-typing-caret.
 * No content is hidden by CSS: no-JS users see the element's fallback text as-is.
 */
[data-fx~="fx-typing"] .fx-typing-text {
  white-space: pre-wrap;
}

[data-fx~="fx-typing"] .fx-typing-text.fx-typing-caret::after {
  content: "";
  display: inline-block;
  width: 0.55ch;
  height: 1em;
  margin-left: 0.08em;
  vertical-align: -0.12em;
  background: var(--fx-accent);
  animation: fx-typing-blink 1s steps(1, end) infinite;
}

@keyframes fx-typing-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* Reduced motion: final state is the plain full text — no caret, no blink. */
@media (prefers-reduced-motion: reduce) {
  [data-fx~="fx-typing"] .fx-typing-text.fx-typing-caret::after {
    animation: none;
    content: none;
  }
}
html.fx-reduced [data-fx~="fx-typing"] .fx-typing-text.fx-typing-caret::after {
  animation: none;
  content: none;
}

/* == fx-status-pills@1.0.0 == */
/* Transitions for the timer-driven pill state classes; the page owns pill layout/skin.
 * Pills are hidden pre-reveal ONLY under html.pf-motion:not(.fx-reduced), so no-JS
 * and reduced-motion users always see every pill.
 * <div data-fx="fx-status-pills" data-fx-interval="2.4"><span class="annotation-pill fx-pill">Call auto-booked</span>...</div> */

html.pf-motion:not(.fx-reduced) [data-fx~="fx-status-pills"] .fx-pill {
  opacity: 0;
  transform: translateY(8px) scale(0.96);
  transition:
    opacity 0.45s var(--fx-ease-out),
    transform 0.45s var(--fx-ease-out);
}

html.pf-motion:not(.fx-reduced) [data-fx~="fx-status-pills"] .fx-pill.fx-pill-on {
  opacity: 1;
  transform: none;
}

/* Belt-and-braces: OS-level reduced motion always sees the final state, stilled. */
@media (prefers-reduced-motion: reduce) {
  [data-fx~="fx-status-pills"] .fx-pill {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* == fx-progress-line@1.0.0 == */
/* Scroll-drawn progress hairline for step sequences. Needs fx-core.
 * Page places the rail inside a position:relative steps wrapper; JS appends the fill.
 * no-JS: no fill exists — the static hairline rail alone reads as an intentional wayfinding line.
 * Snippet: <div class="steps-wrap"><div class="fx-rail" data-fx="fx-progress-line"></div><div data-fx-step>Step 1</div>...</div> */

/* the rail: static hairline, present with or without JS */
[data-fx~="fx-progress-line"] {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 2px;
  background: var(--fx-hairline);
  pointer-events: none;
}

/* the JS-appended fill: drawn top-down via scaleY, transform-only */
[data-fx~="fx-progress-line"] .fx-progress-fill {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: var(--fx-accent);
  transform-origin: 50% 0;
  transform: scaleY(0);
}

html.pf-motion:not(.fx-reduced) [data-fx~="fx-progress-line"] .fx-progress-fill {
  will-change: transform;
}

/* default step treatment: dimmed until the fill passes. Motion-only scope, so
 * no-JS and reduced-motion users always see every step at full strength. */
html.pf-motion:not(.fx-reduced) [data-fx~="fx-progress-line"] ~ [data-fx-step] {
  opacity: 0.45;
  transition: opacity 0.45s var(--fx-ease-out);
}
html.pf-motion:not(.fx-reduced) [data-fx~="fx-progress-line"] ~ [data-fx-step].fx-on {
  opacity: 1;
}

/* hard stop if the preference flips mid-session; the fill's inline scaleY is left
 * alone on purpose — under RM it tracks scroll directly (honest wayfinding). */
@media (prefers-reduced-motion: reduce) {
  [data-fx~="fx-progress-line"] ~ [data-fx-step] {
    opacity: 1 !important;
    transition: none !important;
  }
  [data-fx~="fx-progress-line"] .fx-progress-fill {
    transition: none !important;
    will-change: auto;
  }
}

/* == fx-scrub-scale@1.0.0 == */
/* Scroll-scrubbed scale/opacity entrance for large frames (screenshots, video).
 * Usage: <div class="browser-frame" data-fx="fx-scrub-scale" data-fx-from="0.96" data-fx-opacity-from="0.55">...</div>
 * The CSS default IS the final state: no-JS and reduced-motion users always see the
 * frame at full scale/opacity. The scrubbed start state is applied only by JS. */

/* compositor hint only while motion is active; never hides or transforms content */
html.pf-motion:not(.fx-reduced) [data-fx~="fx-scrub-scale"] {
  will-change: transform, opacity;
  transform-origin: 50% 60%;
}

/* hard stop: if the preference flips mid-session, any inline scrub state is overridden */
@media (prefers-reduced-motion: reduce) {
  [data-fx~="fx-scrub-scale"] {
    transform: none !important;
    opacity: 1 !important;
    will-change: auto;
  }
}

