/* Kapnova site extras: the layer we own on top of the designer's site.css.
   site.css, field.js and dec_data.js are copied verbatim from each prototype drop, so
   everything we add lives here and in site-extras.js: port fixes, the phone menu,
   scroll motion and the page fade. Nothing here hides content by default; only
   site-extras.js hides an element, and only one that starts below the first screen. */
:root { --km-ease: cubic-bezier(.2, .7, .2, 1); --km-step: 70ms; }

/* Port fix: overflow:hidden makes every band a scroll container, so the sticky platform
   rail, decisions panel and legal contents never stuck to the viewport. clip renders the
   same without becoming a scroller. */
.band { overflow: clip; }

/* Phone menu. site.css hides the nav links below 1020px; this opens the same five as a
   panel under the bar. Plain text toggle, no icon, per the design notes. */
.nav-menu { display: none; align-items: center; font: 500 0.92rem/1 var(--f-sans); color: var(--fg-d); background: transparent; border: 1px solid var(--line-d); border-radius: 3px; padding: 10px 14px; cursor: pointer; }
.nav-menu:hover { border-color: var(--soft-d); }
@media (max-width: 1020px) {
  .nav-menu { display: inline-flex; }
  .nav.open ul { display: grid; position: absolute; top: 100%; left: 0; right: 0; gap: 0; margin: 0; padding: 4px max(20px, env(safe-area-inset-right, 0px)) 20px max(20px, env(safe-area-inset-left, 0px)); background: var(--ink); border-bottom: 1px solid var(--line-d); }
  .nav.open ul a { display: block; padding: 14px 0; font-size: 1.05rem; color: var(--fg-d); border-top: 1px solid var(--line-d); }
  .nav.open ul li:first-child a { border-top: 0; }
}
@media (max-width: 480px) {
  .nav .in { gap: 10px; }
  .nav .btn, .nav-menu { padding: 9px 12px; font-size: 0.88rem; }
}

/* Page fade: cross-document view transitions (Chrome/Edge 126+, Safari 18.2+; other
   browsers navigate instantly). The nav keeps its own name so it stays still.
   The real opt-in is an inline <style> right after <meta charset> on every page:
   Chrome re-checks the opt-in whenever any stylesheet finishes, so when the Google
   Fonts sheet (or a cold fetch) lands before this file, the incoming page reads as
   opted out and the fade is dropped (measured 4/21 vs 21/21 with the inline tag).
   This copy is the backstop, and the 404's only opt-in. Keep it top-level: nested in
   @media Chrome reads it unreliably. Reduced motion keeps the swap, drops the fade. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .26s; animation-timing-function: cubic-bezier(.4, 0, .2, 1); }
.nav { view-transition-name: km-nav; }
::view-transition-group(km-nav), ::view-transition-new(km-nav) { animation: none; }
::view-transition-old(km-nav) { display: none; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* Landing entrance: the first screen assembles when someone arrives without the page
   fade (a fresh visit, a reload, an outside link, or a browser without view
   transitions). Arrivals through the fade skip it: each page's inline head script
   sets html.km-vt on pagereveal, before the first frame. Pure CSS with backwards
   fill, so every element ends exactly as authored and nothing waits on a script.
   km-land-lift moves `transform`, so it composes with km-sink's `translate`. */
@keyframes km-land { from { opacity: 0; translate: 0 18px; } }
@keyframes km-land-title { from { opacity: 0; translate: 0 .24em; filter: blur(10px); } }
@keyframes km-land-fade { from { opacity: 0; } }
@keyframes km-land-lift { from { opacity: 0; transform: translateY(36px); } }
@media (prefers-reduced-motion: no-preference) {
  html:not(.km-vt) .nav { animation: km-land-fade .7s ease backwards; }
  html:not(.km-vt) .hero #field { animation: km-land-fade 1.8s ease .1s backwards; }
  /* Hero text, top to bottom; the headline comes into focus. */
  html:not(.km-vt) :is(.hero > .in, .nav + header .head, .pf-hero .top, .ct-hero .wrap > div:first-child) > * { animation: km-land .9s var(--km-ease) .08s backwards; }
  html:not(.km-vt) :is(.hero > .in, .nav + header .head, .pf-hero .top, .ct-hero .wrap > div:first-child) > :nth-child(2) { animation-delay: .2s; }
  html:not(.km-vt) :is(.hero > .in, .nav + header .head, .pf-hero .top, .ct-hero .wrap > div:first-child) > :nth-child(3) { animation-delay: .34s; }
  html:not(.km-vt) :is(.hero > .in, .nav + header .head, .pf-hero .top, .ct-hero .wrap > div:first-child) > :nth-child(4) { animation-delay: .46s; }
  html:not(.km-vt) :is(.hero > .in, .nav + header .head, .pf-hero .top, .ct-hero .wrap > div:first-child) > :nth-child(n+5) { animation-delay: .58s; }
  html:not(.km-vt) :is(.hero > .in, .nav + header .head, .pf-hero .top, .ct-hero .wrap > div:first-child) > :is(h1, .hero-t) { animation-name: km-land-title; animation-duration: 1.15s; }
  /* The hero's visual follows the text in. */
  html:not(.km-vt) .ab-hero figure,
  html:not(.km-vt) .pf-hero .heroshot,
  html:not(.km-vt) .dc-hero .lib-field,
  html:not(.km-vt) .tr-hero .wrap > :not(.head),
  html:not(.km-vt) .pr-hero .lead-q,
  html:not(.km-vt) .jb-hero .wm,
  html:not(.km-vt) .lg-hero .summ,
  html:not(.km-vt) .ct-hero form.ct { animation: km-land-lift 1.1s var(--km-ease) .42s backwards; }
  /* Research is a paper whose margin notes are placed from layout: fade only, no movement. */
  html:not(.km-vt) .sheet :is(.toc, .col > .kicker, .col > .ptitle, .col > .byline, .col > .abstract) { animation: km-land-fade 1s ease .12s backwards; }
  html:not(.km-vt) .sheet .col > .ptitle { animation-delay: .22s; }
  html:not(.km-vt) .sheet .col > :is(.byline, .abstract) { animation-delay: .38s; }
}

/* Scroll reveal. Only site-extras.js adds these classes. Keyframes carry just a `from`
   frame with backwards fill, so each element lands on its own authored opacity and
   transform; individual translate/scale leave designer transforms (hover lifts) alone. */
.km-wait { opacity: 0 !important; }
.km-wait:focus-within { opacity: 1 !important; }
.km-wait:is([data-motion=wipe], [data-motion=open]) { opacity: 1 !important; clip-path: inset(0 100% 0 0); }
.km-wait[data-motion=open] { clip-path: inset(0 50% round 6px); }
.km-in { animation: km-up .7s var(--km-ease) calc(var(--km-i, 0) * var(--km-step)) backwards; }
.km-in[data-motion=rise] { animation-name: km-rise; animation-duration: .9s; }
.km-in[data-motion=rise] img { animation: km-settle .9s var(--km-ease) calc(var(--km-i, 0) * var(--km-step)) backwards; }
.km-in[data-motion=wipe] { animation-name: km-wipe; animation-duration: .9s; }
.km-in[data-motion=open] { animation-name: km-open; animation-duration: .9s; }
.km-in[data-motion=draw] { animation-name: km-fade; animation-duration: .5s; }
@keyframes km-up { from { opacity: 0; translate: 0 14px; } }
@keyframes km-rise { from { opacity: 0; translate: 0 28px; } }
@keyframes km-settle { from { scale: 1.04; } }
@keyframes km-wipe { from { clip-path: inset(0 100% 0 0); } }
@keyframes km-open { from { clip-path: inset(0 50% round 6px); } }
@keyframes km-fade { from { opacity: 0; } }

/* Scroll-linked, compositor-driven, no JS. The timeline properties come after the
   animation shorthand because the shorthand resets them. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 901px) {
    /* Hero imagery settles a little slower than the page; at rest it sits where it was drawn. */
    .pf-hero .heroshot, .ab-hero figure {
      animation: km-sink linear both;
      animation-timeline: scroll(root);
      animation-range: 0 85vh;
    }
    /* On a landing these also run the entrance; same specificity as that rule, later wins. */
    html:not(.km-vt) .pf-hero .heroshot,
    html:not(.km-vt) .ab-hero figure {
      animation: km-land-lift 1.1s var(--km-ease) .42s backwards, km-sink linear both;
      animation-timeline: auto, scroll(root);
      animation-range: normal, 0 85vh;
    }
    .ab-hero figure { --km-sink: 36px; }
  }
  /* Reading progress under the nav on the long documents. Neutral, since gold means scored
     and indigo means action. Invisible wherever the timeline cannot resolve. */
  html:has(.sheet, .ldoc) { timeline-scope: --km-read; }
  .sheet, .ldoc { view-timeline: --km-read block; }
  html:has(.sheet, .ldoc) .nav::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; pointer-events: none;
    background: var(--soft-d); transform-origin: 0 50%; scale: 0 1;
    animation: km-read linear both;
    animation-timeline: --km-read;
    animation-range: contain 0% contain 100%;
  }
}
@keyframes km-sink { to { translate: 0 var(--km-sink, 56px); } }
@keyframes km-read { to { scale: 1 1; } }

@media (prefers-reduced-motion: reduce), print {
  .km-wait { opacity: 1 !important; clip-path: none !important; }
  .km-in, .km-in img { animation: none !important; }
}
@media print { .nav::after { display: none; } }
