/* ==========================================================================
   forge-motion.css — Apple motion v2 (family rules 11 and 12; Mark, 2026-11-03).
   Loaded last by every static marketing page and by the Next marketing layout.
   Every rule that moves anything sits inside prefers-reduced-motion:
   no-preference, so reduced motion means instant state changes and static
   content. Only transform and opacity animate (disclosures also animate their
   height, which rule 12 names). Nothing is tied to the scroll position.
   ========================================================================== */

/* ── Rule 11: one-time entrances (forge-motion.js sets the attributes) ─────
   The hidden start state exists only once forge-motion.js has run, so content
   is visible without JS, with reduced motion and in print. */
@media screen and (prefers-reduced-motion: no-preference) {
  html[data-motion-ready] { --reveal-y: 20px; }
  html[data-motion-ready] [data-reveal]:not([data-revealed]) { opacity: 0; transform: translateY(var(--reveal-y)); }
  html[data-motion-ready] [data-reveal][data-revealed] { opacity: 1; transform: none; }
  html[data-motion-ready] [data-reveal][data-revealed]:not([data-reveal-done]) {
    transition: opacity 700ms cubic-bezier(.25, .1, .25, 1) var(--reveal-delay, 0ms),
                transform 700ms cubic-bezier(.25, .1, .25, 1) var(--reveal-delay, 0ms);
  }
  html[data-motion-ready] [data-reveal][data-reveal-instant] { transition: none; }

  /* /thesis ARR chart: the bars grow once, with the block's entrance. Budget (Sol r8 R8-3): duration + total delay
     <= 800ms for every bar, so the block's own stagger is capped at 20ms and the bars add 0/40/80ms. Static under
     reduced motion (forge-polish-standalone.css). */
  html[data-motion-ready] :is([data-reveal]:not([data-revealed]) .chart, .chart[data-reveal]:not([data-revealed])) .gbar { transform: scaleY(0); }
  html[data-motion-ready] :is([data-revealed]:not([data-reveal-instant]) .chart, .chart[data-revealed]:not([data-reveal-instant])) .gbar {
    transform: none; transform-origin: bottom; transform-box: fill-box;
    transition: transform 700ms cubic-bezier(.25, .1, .25, 1) min(var(--reveal-delay, 0ms), 20ms);
  }
  html[data-motion-ready] :is([data-revealed]:not([data-reveal-instant]) .chart, .chart[data-revealed]:not([data-reveal-instant])) .gbar:nth-of-type(2) { transition-delay: calc(min(var(--reveal-delay, 0ms), 20ms) + 40ms); }
  html[data-motion-ready] :is([data-revealed]:not([data-reveal-instant]) .chart, .chart[data-revealed]:not([data-reveal-instant])) .gbar:nth-of-type(3) { transition-delay: calc(min(var(--reveal-delay, 0ms), 20ms) + 80ms); }
}
@media screen and (prefers-reduced-motion: no-preference) and (max-width: 760px) {
  html[data-motion-ready] { --reveal-y: 12px; }
}
/* Keyboard focus never waits for an entrance. */
[data-reveal]:focus-within { opacity: 1 !important; transform: none !important; }
@media print {
  [data-reveal], [data-reveal] .gbar { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* Sol r8 R8-2: paint-property transitions on the Next marketing routes (the shell's theme fade, the nav and footer
   links' colour fades, Tailwind's .transition on links, cards and headings) become instant; only transform-family
   properties and opacity may animate. Zero specificity, so the press rule above still sets its own timings. */
.pm-shell { transition: none !important; }
:where(.pm-shell) :where(a, button, h2, article, .transition, [class*="transition-"]):not(.pm-mobile-nav) {
  transition-property: transform, translate, scale, opacity !important;
}

/* Until load, nothing scrolls smoothly (some pages set html { scroll-behavior: smooth } themselves). */
html:not([data-smooth]) { scroll-behavior: auto !important; }

/* Reduced motion: every state change on these public pages is instant (the chapter rail, menus, hover tints
   and the pages' own transitions included). Loaded only by public pages; the Ask Sabina launcher, in its own
   shadow root, keeps its own reduced-motion rules. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0s !important; transition-delay: 0s !important;
    animation-duration: 0s !important; animation-delay: 0s !important; animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* ── Rule 12: interaction motion ────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  /* Anchor jumps scroll natively and smoothly, but only once the page has loaded (forge-motion.js sets
     data-smooth), so a deep link lands on its target at once instead of gliding there. */
  html[data-smooth] { scroll-behavior: smooth; }

  /* Press and hover (Sol r8 R8-4): every button, chip and card family on these pages. Press is the individual
     `scale` property and hover the `translate` property, so neither fights an entrance's `transform` (or the
     keyboard-focus override). Pointer-down: scale .97, 100ms ease-out; release: a critically damped 250ms.
     These transitions replace each control's own (Sol r8 R8-2), so state colours change at once and only
     transform-family properties and opacity animate. They wait while an entrance is still playing.
     Sol r9 R9-2 and the turn-13 sweep added: the consent bar's two buttons (styled here, never in the synced
     consent.js), the desktop chest trigger, every disclosure summary, the skip links, the chest and phone-menu rows
     and their "All six drawers" and Sign in links. */
  :is(.button, .btn, .step-btn, .ix-link, .deck-link, .top-right a, .theme-toggle, .themetog, .menu-toggle, .burger,
      .pm-menu-toggle, .tool-list > a, .reading-links > a, a.scp1, a.scp2, a.scp3,
      .pm-shell [aria-label="Toggle site theme"] button, .pm-shell main a.h-10, .pm-shell main a.block.border,
      .pm-shell main a.border[class*="forge-paper2"], .pm-shell main a[style*="padding"],
      .pm-shell main a[style*="border:1px solid"], .pm-shell main a.px-6, main button,
      [data-consent-bar] button, .nav-trigger, summary, a.skip, .cm-all, .np-all, .np-signin, .np-quick, .np-rest,
      :is(#mobile-nav, .mobile-nav, #navpanel, .pm-mobile-nav, #chestmenu, .chest-menu) a):not([data-reveal]:not([data-reveal-done])) {
    transition: scale 250ms cubic-bezier(.2, .8, .2, 1), translate 180ms cubic-bezier(.25, .1, .25, 1),
                opacity 150ms ease-out !important;
  }
  @media (hover: hover) {
    :is(.button, .btn, .step-btn, .ix-link, .deck-link, .pm-shell main a.h-10, .pm-shell main a[style*="padding"],
        .pm-shell main a[style*="border:1px solid"], .pm-shell main a.block.border, .pm-shell main a.px-6, a.scp1, a.scp2, a.scp3):hover {
      translate: 0 -1px;
    }
  }
  :is(.button, .btn, .step-btn, .ix-link, .deck-link, .top-right a, .theme-toggle, .themetog, .menu-toggle, .burger,
      .pm-menu-toggle, .tool-list > a, .reading-links > a, a.scp1, a.scp2, a.scp3,
      .pm-shell [aria-label="Toggle site theme"] button, .pm-shell main a.h-10, .pm-shell main a.block.border,
      .pm-shell main a.border[class*="forge-paper2"], .pm-shell main a[style*="padding"],
      .pm-shell main a[style*="border:1px solid"], .pm-shell main a.px-6, main button,
      [data-consent-bar] button, .nav-trigger, summary, a.skip, .cm-all, .np-all, .np-signin, .np-quick, .np-rest,
      :is(#mobile-nav, .mobile-nav, #navpanel, .pm-mobile-nav, #chestmenu, .chest-menu) a):active {
    scale: .97;
  }
  :is(.button, .btn, .step-btn, .ix-link, .deck-link, .top-right a, .theme-toggle, .themetog, .menu-toggle, .burger,
      .pm-menu-toggle, .tool-list > a, .reading-links > a, a.scp1, a.scp2, a.scp3,
      .pm-shell [aria-label="Toggle site theme"] button, .pm-shell main a.h-10, .pm-shell main a.block.border,
      .pm-shell main a.border[class*="forge-paper2"], .pm-shell main a[style*="padding"],
      .pm-shell main a[style*="border:1px solid"], .pm-shell main a.px-6, main button,
      [data-consent-bar] button, .nav-trigger, summary, a.skip, .cm-all, .np-all, .np-signin, .np-quick, .np-rest,
      :is(#mobile-nav, .mobile-nav, #navpanel, .pm-mobile-nav, #chestmenu, .chest-menu) a):not([data-reveal]:not([data-reveal-done])):active {
    transition: scale 100ms ease-out, translate 180ms cubic-bezier(.25, .1, .25, 1), opacity 150ms ease-out !important;
  }

  /* Text links: the underline grows from its start on hover (a scaleX pseudo,
     not a layout change). .text-link's ::after is its 44px hit area. */
  @media (hover: hover) {
    .text-link::before {
      content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
      background: currentColor; transform: scaleX(0); transform-origin: left center;
      transition: transform 200ms cubic-bezier(.25, .1, .25, 1); pointer-events: none;
    }
    .text-link:hover { text-decoration: none; }
    .text-link:hover::before { transform: scaleX(1); }
  }

  /* Desktop dropdown ("The chest"): opens from its trigger (top right) with
     scale .96 to 1 plus opacity, 220ms critically damped; closes the same way,
     faster (150ms). CSS transitions reverse from their current value, so
     reopening mid-close continues smoothly. */
  .nav-chest .chest-menu {
    transform-origin: top right; transform: scale(.96);
    transition: opacity 150ms ease-in, transform 150ms ease-in, visibility 0s linear 150ms;
  }
  .nav-chest .chest-menu.open {
    transform: none;
    transition: opacity 220ms cubic-bezier(.2, .8, .2, 1), transform 220ms cubic-bezier(.2, .8, .2, 1), visibility 0s;
  }

  /* Phone menus: slide down from the header's edge with a spring, over a
     dimming scrim (forge-motion.js; tap it or swipe up to dismiss), and leave
     along the same path. */
  .mobile-nav, .pm-mobile-nav {
    transition: opacity 180ms ease-in, transform 200ms ease-in, display 200ms allow-discrete;
  }
  .mobile-nav:not([hidden]), .pm-mobile-nav:not([hidden]) {
    transition: opacity 250ms ease-out, transform 350ms cubic-bezier(.32, .72, 0, 1), display 350ms allow-discrete;
  }
  .mobile-nav[hidden], .pm-mobile-nav[hidden] { opacity: 0; transform: translateY(-12px); }
  @starting-style {
    .mobile-nav:not([hidden]), .pm-mobile-nav:not([hidden]) { opacity: 0; transform: translateY(-12px); }
  }
}
/* The phone menus open over the page instead of pushing it down. */
@media (max-width: 800px) {
  .site-header .mobile-nav, .pm-header .pm-mobile-nav {
    position: absolute; left: 0; right: 0; top: 100%;
    max-height: calc(100dvh - 72px); overflow-y: auto; overscroll-behavior: contain;
    background: var(--ground, var(--forge-paper)); border-bottom: 1px solid var(--line, var(--pm-line));
  }
}
/* The ledger panel (≤900px) animated max-height, a layout property; it now
   slides (transform) and fades instead. */
@media (max-width: 900px) {
  #navpanel {
    max-height: calc(100dvh - 60px); overflow-y: auto; overscroll-behavior: contain;
    opacity: 0; transform: translateY(-12px); visibility: hidden;
    transition: opacity 180ms ease-in, transform 200ms ease-in, visibility 0s linear 200ms;
  }
  #navpanel.open {
    opacity: 1; transform: none; visibility: visible;
    transition: opacity 250ms ease-out, transform 350ms cubic-bezier(.32, .72, 0, 1), visibility 0s;
  }
}
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  #navpanel, #navpanel.open { transition: none; transform: none; }
}
/* The scrim sits under the header (the header and its menu stay above it). */
.fm-scrim {
  position: fixed; inset: 0; z-index: 35; background: rgb(11 10 7 / .32);
  opacity: 0; pointer-events: none; transition: opacity 250ms ease-out;
}
.fm-scrim[data-open] { opacity: 1; pointer-events: auto; }
@media (min-width: 901px) { .fm-scrim { display: none; } }
@media (prefers-reduced-motion: reduce) { .fm-scrim { transition: none; } }
@media (prefers-reduced-transparency: reduce) { .fm-scrim { background: rgb(11 10 7 / .55); } }

/* Disclosures: height plus opacity, 250ms, so content never jumps. */
@media (prefers-reduced-motion: no-preference) {
  :root { interpolate-size: allow-keywords; }
  details::details-content {
    height: 0; opacity: 0; overflow: clip;
    transition: height 250ms cubic-bezier(.25, .1, .25, 1), opacity 250ms ease-out, content-visibility 250ms allow-discrete;
  }
  details[open]::details-content { height: auto; opacity: 1; }
}

/* Images still loading fade in from a placeholder colour (300ms) when they arrive. */
@media (prefers-reduced-motion: no-preference) {
  img[data-img-fade] { background-color: var(--paper-2, var(--surface, var(--forge-paper2, transparent))); opacity: 0; transition: opacity 300ms ease-out; }
  img[data-img-fade][data-img-loaded] { opacity: 1; }
}
@media print { img[data-img-fade] { opacity: 1 !important; } }
