/* Content is always visible. Motion must never gate rendering behind
   JavaScript or IntersectionObserver callbacks. */
[data-reveal],
body.has-reveal [data-reveal],
body.has-reveal [data-reveal].is-in {
    opacity: 1;
    transform: none;
}

.btn,
.nav-links a,
.footer-col a,
[data-magnetic] {
    transition:
        color 160ms ease,
        background-color 160ms ease,
        border-color 160ms ease,
        transform 160ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .btn,
    .nav-links a,
    .footer-col a,
    [data-magnetic] {
        transition: none;
    }
}

/* ===== BEGIN restored-from-HEAD (redesign drop recovery) ===== */
/* Rules that existed before the redesign and that the app still depends on:
   every class targeted here had NO styling at all afterwards, so the markup
   was rendering unstyled. Appended so they win on equal specificity.
   Delete everything between the BEGIN/END markers to undo the restoration. */
/* ------------------------------------------------------------------
   Scroll reveals.
   No-JS / pre-init: visible by default (graceful degradation).
   Once <body class="has-reveal"> is added by site.js, items collapse
   to hidden until they enter the viewport and gain .is-in.
------------------------------------------------------------------ */

[data-reveal] {
opacity: 1;
transform: none;
transition:
opacity var(--dur-slow) var(--ease-out-soft),
transform var(--dur-slow) var(--ease-out-soft);
transition-delay: var(--reveal-delay, 0ms);
}
/* ------------------------------------------------------------------
   Hero word split (filled in by site.js wrapping each word in a span).
------------------------------------------------------------------ */

.hero-headline {
display: block;
}
.hero-headline .word {
display: inline-block;
overflow: hidden;
line-height: 0.9;
padding-bottom: 0.08em;
margin-right: 0.18em;
}
.hero-headline .word > span {
display: inline-block;
will-change: transform;
}
body.has-reveal .hero-headline .word > span {
transform: translateY(110%);
}
body.has-reveal .hero-headline.is-in .word > span {
transform: translateY(0);
transition: transform 900ms var(--ease-out-soft);
transition-delay: calc(var(--word-i, 0) * 60ms);
}
/* ------------------------------------------------------------------
   Pinned product showcase.
   Section structure (site.js drives the timeline):
     .showcase
       .showcase-frame  (sticky-ish; pinned by ScrollTrigger)
         .showcase-screen [data-showcase-screen="live|movies|osd"]
       .showcase-captions
         .showcase-caption [data-showcase-caption="live|movies|osd"]
------------------------------------------------------------------ */

.showcase {
position: relative;
padding: clamp(48px, 8vw, 96px) 0;
}
/* ------------------------------------------------------------------
   Setup rail — vertical gradient line that fills as user scrolls.
   --rail-fill set by site.js (0..1).
------------------------------------------------------------------ */

.setup-rail {
position: relative;
--rail-fill: 0;
}
/* ------------------------------------------------------------------
   Magnetic button — translation applied by site.js via custom props.
------------------------------------------------------------------ */

[data-magnetic] {
transform: translate3d(var(--mx, 0), var(--my, 0), 0);
transition: transform 240ms var(--ease-out-soft);
}
/* ===== END restored-from-HEAD ===== */
