/* Motion and scrolling for the Tunet site (package B). Values come from the motion tokens in system.css.
   Rules: text that carries meaning never animates on load; keyframes animate only transform, translate,
   scale, rotate and opacity (documented scroll-bound exceptions: the .ink-sweep heading animates
   background-position, and the section pill track below 1024px animates its --fl/--fr mask lengths); nothing loops forever except the busy bar; every effect has a complete static
   state without JavaScript and under prefers-reduced-motion (block at the end of this file).
   The landing page keeps its pre-refresh motion (static/css/home.css), so the hover lift, arrow nudges,
   reveal animation and tab fade below skip anything inside .home-classic. */

/* ---------- Global keyframes (the only global keyframes on the site) ---------- */
@keyframes rise { from { opacity: 0; translate: 0 var(--rise-entrance); } }
@keyframes fade { from { opacity: 0; } }
@keyframes reveal { from { opacity: 0; translate: 0 var(--rise-reveal); } }
@keyframes ping { from { transform: scale(1); opacity: 0.55; } to { transform: scale(2.6); opacity: 0; } }
@keyframes busy { from { translate: -100% 0; } to { translate: 250% 0; } }
@keyframes draw-x { from { transform: scaleX(0); } }
@keyframes nav-progress { to { transform: scaleX(1); } }
@keyframes ink-sweep { to { background-position: 0 0; } }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; translate: 0 6px; } }
/* Footer wordmark (P2): settles as the footer scrolls in. */
@keyframes wordmark-settle { from { translate: 0 30%; opacity: 0.4; } }
/* Section pill edge fades below 1024px (system.css runs it on scroll(self inline) and registers --fl/--fr as
   <length>): no left fade at the start, both mid-track, no right fade at the end. Scroll-bound state, not
   motion, so it is deliberately not in the reduced-motion list below. */
@keyframes pill-edges { 0% { --fl: 0px; --fr: 2.5rem; } 8% { --fl: 1.5rem; } 92% { --fr: 2.5rem; } 100% { --fl: 1.5rem; --fr: 0px; } }

/* ---------- Anchors and scrolling ---------- */
html { scroll-padding-top: var(--anchor-offset); scroll-behavior: auto; }
html:has(main [data-section-nav]:not([data-section-nav="desktop"], [data-section-nav="toc"])) { --anchor-offset: 9.5rem; }
@media (min-width: 1024px) {
    html:has(main [data-section-nav="desktop"]) { --anchor-offset: 9.5rem; }
}
/* Smooth only after load, so a cold deep link lands at once. */
@media (prefers-reduced-motion: no-preference) {
    html.is-ready { scroll-behavior: smooth; }
}
/* Anchored sections skip most of their top padding (--space-section), so their first text lands 16-40px under
   the chrome: content starts .75rem below the anchor line; a leading section index (whose text sits .875rem
   under its rule) starts .875rem higher, so its rule sits just under the chrome and its text in the same band. */
main :is(.interior-section, .sa-section, .section-pad)[id] { scroll-margin-top: calc(0.75rem - var(--space-section)); }
main :is(.interior-section, .sa-section, .section-pad)[id]:has(> .section-index:first-child, > * > .section-index:first-child) { scroll-margin-top: calc(-0.125rem - var(--space-section)); }
/* Section pill links sit in the sticky pill at --subnav-top, inside the anchor offset; this margin tells focus
   scrolling they are already in view, so tabbing through the pill never moves the page. */
main .section-nav a { scroll-margin-top: calc(var(--subnav-top) - var(--anchor-offset)); }

/* ---------- View transitions: the nav stays put, the page cross-fades, the Safety AZ title morphs ---------- */
@media (prefers-reduced-motion: no-preference) {
    @view-transition { navigation: auto; }
    .site-nav { view-transition-name: site-nav; }
    /* The box hugs the text, so the morph scales one title instead of cross-fading two differently sized boxes.
       base.html drops the name when the title is off screen on either page, so it never flies in from below the fold. */
    .vt-safety-az-title { view-transition-name: vt-safety-az-title; width: fit-content; }
    ::view-transition-group(site-nav),
    ::view-transition-new(site-nav) { animation: none; }
    ::view-transition-old(site-nav) { display: none; }
    ::view-transition-old(root) { animation: 120ms var(--ease-in) both vt-out; }
    ::view-transition-new(root) { animation: 240ms var(--ease-out) 60ms both vt-in; }
    ::view-transition-group(vt-safety-az-title) { animation-duration: 360ms; animation-timing-function: var(--ease-out); }
    /* A title on one side only leaves and arrives with the page. */
    ::view-transition-old(vt-safety-az-title):only-child { animation: 120ms var(--ease-in) both vt-out; }
    ::view-transition-new(vt-safety-az-title):only-child { animation: 240ms var(--ease-out) 60ms both vt-in; }
    /* The pinned nav is captured without its backdrop blur, so the outgoing page would read through it: it is near
       opaque for the transition, and eases back to its resting alpha afterwards. */
    :root:active-view-transition .site-nav-panel { background-color: rgba(12, 12, 11, 0.96); transition: none; }
    /* The cookie banner stays pinned through the cross-fade instead of dimming with the page. */
    #cookie-banner { view-transition-name: cookie-banner; }
    ::view-transition-group(cookie-banner),
    ::view-transition-new(cookie-banner) { animation: none; }
    ::view-transition-old(cookie-banner) { display: none; }
}

/* ---------- Heroes: one static accent aurora ---------- */
main :is(.interior-hero, .hub-hero) { isolation: isolate; overflow-x: clip; } /* the aurora bleeds vertically, never sideways */
main :is(.interior-hero, .hub-hero)::before {
    content: '';
    position: absolute;
    inset: -30% -10% -20%;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(38% 50% at 78% 28%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 72%),
        radial-gradient(55% 60% at 50% 0%, rgba(255, 255, 255, 0.035), transparent 70%);
    opacity: 1;
}

/* Section hairlines that fade at the edges. */
main .interior-section[class*="border-t"],
main .hub-hero,
main .interior-hero {
    border-top-color: transparent;
    border-bottom-color: transparent;
    border-image: linear-gradient(90deg, transparent, var(--line-3) 50%, transparent) 1;
}

/* ---------- Hero entrance (catalogue 1-4): only secondary parts move; breadcrumb, badge, eyebrow, h1, tagline and lead are static ---------- */
main :is(.interior-hero, .hub-hero, .enquiry-intro) :is(.hero-actions, .hub-actions) {
    animation: rise var(--dur-entrance) var(--ease-out) both;
}
main :is(.interior-hero, .hub-hero, .enquiry-intro) :is(.hero-meta, .sa-facts, .paper-meta) {
    animation: rise var(--dur-entrance) var(--ease-out) 60ms both;
}
main :is(.hub-route-map, .record-figure, .package-hero-run) {
    animation: fade 600ms var(--ease-out) 120ms both;
}

/* ---------- Scroll reveal (5): effects.js adds .js-reveal only to blocks below the first screen ---------- */
html.js .js-reveal:not(.is-in) { opacity: 0; translate: 0 var(--rise-reveal); }
.js-reveal.is-in:not(.home-classic *) { animation: reveal var(--dur-reveal) var(--ease-out) var(--reveal-delay, 0ms) backwards; }

/* ---------- Scroll-driven decoration (6-8): CSS scroll timelines, no JavaScript ---------- */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        /* Section index rules below the first screen (effects.js marks them [data-draw]) draw in as the chapter
           enters; the index paragraph is the timeline subject. Rules already on screen at load stay drawn. */
        .section-index[data-draw] { view-timeline: --section-index block; }
        .section-index[data-draw]::before {
            animation: draw-x linear both;
            animation-timeline: --section-index;
            animation-range: entry 0% cover 20%;
        }

        /* The ink statement fills from ink-4 to ink-1 as it crosses the viewport. */
        main .ink-sweep:is(h1, h2, h3, p) {
            background: linear-gradient(90deg, var(--ink-1) 50%, var(--ink-4) 50%) 100% 0 / 200% 100% no-repeat;
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            animation: ink-sweep linear both;
            animation-timeline: view();
            animation-range: entry 25% cover 55%;
            /* The text clip stops at the box; at line-height 1.08 the last line's descenders hang below it. The padding
               gives them room and the margin gives it back (1rem is .section-title's bottom margin in system.css). */
            padding-bottom: 0.15em;
            margin-bottom: calc(1rem - 0.15em);
        }

        /* Footer wordmark (P2): rises into its band as the footer arrives. The footer clips it (overflow: clip). */
        .footer-wordmark {
            animation: wordmark-settle linear both;
            animation-timeline: view();
            animation-range: entry 0% entry 90%;
        }
    }
}
@media (forced-colors: active) {
    main .ink-sweep:is(h1, h2, h3, p) { background: none; color: CanvasText; }
}

/* Reading progress: scroll-driven where supported; effects.js sets --progress elsewhere. It reports position,
   so it also runs under reduced motion. */
@supports (animation-timeline: scroll()) {
    .nav-progress {
        transform: scaleX(0);
        animation: nav-progress linear both;
        animation-timeline: scroll(root block);
    }
}

/* ---------- Link cards (10): only cards that are links light an accent edge that follows the pointer ---------- */
main [data-spot="link"] {
    position: relative;
    transition: translate var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-standard);
}
main [data-spot="link"]::before {
    content: '';
    position: absolute;
    inset: -1px;
    padding: 1px;
    border-radius: inherit;
    pointer-events: none;
    z-index: 2;
    background: radial-gradient(14rem circle at var(--mx, 50%) var(--my, 0%), var(--accent), transparent 70%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: 0;
    transition: opacity var(--dur-base) var(--ease-out);
}
/* Cards that clip their content draw the edge just inside the border instead. */
main [data-spot="link"][data-spot-clip]::before { inset: 0; }
main [data-spot="link"]:is(:hover, :focus-visible, :has(:focus-visible))::before { opacity: 1; }
main [data-spot="link"]:not(.home-classic *):is(:hover, :focus-visible, :has(:focus-visible)) { border-color: color-mix(in srgb, var(--accent) 40%, var(--line-2)); }
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
    main [data-spot="link"]:not(.home-classic *):hover { translate: 0 var(--lift-card); }
}

/* ---------- Arrows lean into the action (12) ---------- */
:is(.text-link, .tunet-primary, .tunet-secondary, .hub-secondary, .hero-secondary-link, .hub-route, .micro-product-link, .pillar-card-action) > :is(span[aria-hidden="true"], svg) {
    transition: translate var(--dur-fast) var(--ease-out);
}
@media (prefers-reduced-motion: no-preference) {
    :is(.text-link, .tunet-primary, .tunet-secondary, .hub-secondary, .hero-secondary-link, .hub-route):not(.home-classic *):is(:hover, :focus-visible) > :is(span[aria-hidden="true"], svg),
    main [data-spot="link"]:not(.home-classic *):is(:hover, :has(:focus-visible)) :is(.text-link, .micro-product-link, .pillar-card-action) > span[aria-hidden="true"] { translate: 3px 0; }
    a[href^="#"]:not(.home-classic *):is(.text-link, .tunet-primary, .tunet-secondary, .hub-secondary, .hero-secondary-link, .hub-route):is(:hover, :focus-visible) > span[aria-hidden="true"] { translate: 0 2px; }
    a[target="_blank"]:not(.home-classic *):is(.text-link, .tunet-primary, .tunet-secondary, .hub-secondary, .hero-secondary-link, .hub-route):is(:hover, :focus-visible) > span[aria-hidden="true"] { translate: 2px -2px; }
    a.footer-top[href]:is(:hover, :focus-visible) > span[aria-hidden="true"] { translate: 0 -2px; }
}

/* ---------- Status dots (13): two pings, then rest (4.8 s) ---------- */
main :is(.hero-badge-dot, .hub-eyebrow > span) { position: relative; }
@media (prefers-reduced-motion: no-preference) {
    main :is(.hero-badge-dot, .hub-eyebrow > span)::after {
        content: '';
        position: absolute;
        inset: 0;
        border-radius: 50%;
        background: inherit;
        animation: ping 2.4s cubic-bezier(0, 0, 0.2, 1) 2 both;
    }
}

/* ---------- Code tabs (19): the newly selected panel fades in ---------- */
.code-tabs-panel:not([hidden]):not(.home-classic *) { animation: fade var(--dur-fast) var(--ease-out); }

/* ---------- Forms (20-21): a busy bar while a request is in flight, the result settles in ---------- */
form.htmx-request button[type="submit"] { position: relative; overflow: hidden; }
form.htmx-request button[type="submit"]::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    width: 40%;
    height: 2px;
    background: var(--brand-ink);
    animation: busy 1.1s linear infinite;
    pointer-events: none;
}
[data-form-result] > * { animation: reveal 240ms var(--ease-out) both; }

/* ---------- Reduced motion: everything above has a complete static state ---------- */
@media (prefers-reduced-motion: reduce) {
    main :is(.interior-hero, .hub-hero, .enquiry-intro) :is(.hero-actions, .hub-actions, .hero-meta, .sa-facts, .paper-meta),
    main :is(.hub-route-map, .record-figure, .package-hero-run),
    .js-reveal,
    .js-reveal.is-in,
    .section-index::before,
    .ink-sweep,
    .footer-wordmark,
    .code-tabs-panel:not([hidden]),
    [data-form-result] > * { animation: none; transition-duration: 0s; }
    main [data-spot="link"],
    main [data-spot="link"]::before,
    :is(.text-link, .tunet-primary, .tunet-secondary, .hub-secondary, .hero-secondary-link, .hub-route, .micro-product-link, .pillar-card-action) > :is(span[aria-hidden="true"], svg) { transition-duration: 0s; }
    html.js .js-reveal,
    html.js .js-reveal:not(.is-in) { opacity: 1; translate: none; }
    main .ink-sweep:is(h1, h2, h3, p) { background: none; color: var(--ink-1); }
    form.htmx-request button[type="submit"]::after { animation: none; width: 100%; opacity: 0.5; }
    html { scroll-behavior: auto; }
}

/* ---------- Print: every block prints, in solid ink ---------- */
@media print {
    /* Printed without backgrounds, the aurora (which bleeds below the hero) blanks out the top of the next section. */
    main :is(.interior-hero, .hub-hero)::before { display: none; }
    html.js .js-reveal:not(.is-in) { opacity: 1; translate: none; }
    .js-reveal.is-in,
    [data-form-result] > *,
    .footer-wordmark { animation: none; }
    main .ink-sweep:is(h1, h2, h3, p) { animation: none; background: none; color: var(--ink-1); }
}
