/* ============================================================================
   CetusEd public site — MOTION FOUNDATION
   Ledger items P0-5 (view transitions) · P0-6 (.reveal) · P0-7 (reduced motion)
   Spec: Addendum A Part D
   ----------------------------------------------------------------------------
   THE BUDGET. Nothing animates that is not in this table. Every addition to it
   makes the site less calm, and calm was the central request (Addendum A Part I
   item 6).

     Section reveal      view() scroll     scroll-driven   linear
     Hover, interactive  pointer           200ms           ease-out
     Accordion expand    click             240ms           ease-in-out
     Nav shrink          scroll past 80px  300ms           ease-out
     Page transition     navigation        450ms           ease-in-out
     Threshold dismiss   click/key/scroll  900ms           cubic-bezier(.16,1,.3,1)
     Diagram sequences   pinned scroll     scroll-driven   linear

   TWO RULES THAT ARE NOT STYLE PREFERENCES
   ----------------------------------------------------------------------------
   ⛔ THINGS ARRIVE; THINGS DO NOT LEAVE (Addendum A Part D). Content that fades
   out as it scrolls past cannot be re-read, breaks find-in-page, and if opacity:0
   is used as pseudo-hiding it REMAINS IN THE ACCESSIBILITY TREE while invisible —
   worse than either state. The one exception is a pinned section, where the
   reader's context is held by the plate that stays put.

   ⛔ NO SCROLL HIJACKING. The reference site uses Locomotive Scroll; we do not
   follow it. Smooth-scroll libraries break native scroll physics, browser
   find-in-page, keyboard paging and screen-reader navigation. The readers here
   are district IT staff and special-education directors on managed Windows
   machines who use Ctrl+F to find a specific WAC citation. That trade is bad for
   this audience specifically.

   ⚠ CSP NOTE (ledger C4). _headers sets script-src 'self' on every marketing
   path and lint-site-content-integrity RULE 4 fails the build on an inline
   <script>. Tier 1 and Tier 2 below are PURE CSS and need no script at all,
   which is the main reason they are the default mechanism rather than a JS
   observer. Tier 3 needs a vendored library — see the note at the bottom.
   ========================================================================= */

/* ══ TIER 1 — SCROLL REVEALS: native CSS, no JavaScript ═══════════════════════
   CSS scroll-driven animations are the mechanism for everything arriving into
   view. Support is roughly 84–90% (Chrome/Edge 115+, Safari 18+; Firefox still
   behind a flag as of mid-2026), so this is a PROGRESSIVE ENHANCEMENT.

   ⛔ THE CLASSIC FAILURE MODE, and the reason for the @supports structure below:
   elements are hidden by default, the animation never runs in an unsupporting
   browser, and the page appears BLANK. So the default state here is VISIBLE, and
   the hidden start state is applied ONLY inside @supports. A browser that cannot
   animate simply shows the content. */

.reveal { /* default state: fully visible. Do not add opacity:0 here. */ }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 15% entry 55%;
      /* animation-duration is meaningless when scroll is the clock — omitted.
         animation-fill-mode: both (the `both` above) is REQUIRED or elements
         snap back to their start state on scroll-up. */
    }
    /* Stagger without per-element classes: each child enters on its own range. */
    .reveal-stagger > * {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 10% entry 50%;
    }

    /* ── THE STRUCTURAL REVEAL SET ─────────────────────────────────────────
       ⚠ MEASURED: with .reveal applied by hand, FOUR elements animated on the
       whole home page. Four is not a motion system, it is a decoration on two
       headings — and the founder's read of "static" was correct.

       So arrival is applied STRUCTURALLY, to the elements every page is built
       from, rather than opted into per element. A page cannot forget it, and a
       new page gets it for nothing. Every rule below is inside @supports and
       inside no-preference, so an unsupporting browser and a reduced-motion
       reader both see fully-visible content — the classic failure here is
       elements hidden by default with the animation never running. */
    .sect > .main-col > *,
    .sect > .margin-col,
    .sect > .plate,
    main > .photo {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 8% entry 46%;
    }

    /* Rows and cards arrive in sequence rather than as a block. The range walks
       forward per child so the eye is led down the group instead of everything
       landing at once — the one place a stagger earns its keep. */
    .deflist > div:nth-child(1) { animation: rise linear both; animation-timeline: view(); animation-range: entry 6%  entry 40%; }
    .deflist > div:nth-child(2) { animation: rise linear both; animation-timeline: view(); animation-range: entry 10% entry 44%; }
    .deflist > div:nth-child(3) { animation: rise linear both; animation-timeline: view(); animation-range: entry 14% entry 48%; }
    .deflist > div:nth-child(n+4) { animation: rise linear both; animation-timeline: view(); animation-range: entry 18% entry 52%; }
    .cards > .card { animation: rise linear both; animation-timeline: view(); animation-range: entry 8% entry 48%; }

    /* Plate internals arrive AFTER their plate, so a diagram assembles rather
       than appearing. Short ranges: these are already on screen by then. */
    .matrix-row, .dr-clock, .tb-box, .chain-node, .jr-note, .tax-row, .cat-dom {
      animation: rise-s linear both;
      animation-timeline: view();
      animation-range: entry 2% entry 30%;
    }

    /* The waterline draws rather than fades — it is the site's one structural
       rule and it should arrive the way a rule is drawn. */
    .waterline { transform-origin: left center; animation: draw-x linear both;
                 animation-timeline: view(); animation-range: entry 5% entry 45%; }
  }
}
/* A shorter, quieter arrival for elements INSIDE a plate: the plate has already
   moved, so its contents should settle rather than travel. */
@keyframes rise-s { from { opacity: 0; translate: 0 .5rem; } to { opacity: 1; translate: 0 0; } }

@keyframes rise {
  from { opacity: 0; translate: 0 1.5rem; }
  to   { opacity: 1; translate: 0 0; }
}

/* ⛔ ANIMATE ONLY transform/translate AND opacity. These run on the compositor
   thread and stay as smooth as the scroll itself. Never animate width, height or
   margin — use scaleX/scaleY. And do NOT add will-change preemptively; the
   browser promotes layers on its own and a standing will-change costs memory on
   exactly the 4GB Chromebooks this site must hold 60fps on. */

/* A drawn rule — used by the deadline ruler and the record hairline. scaleX,
   never width. */
@keyframes draw-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .draw-in { transform-origin: left center; animation: draw-x linear both;
               animation-timeline: view(); animation-range: entry 20% entry 70%; }
  }
}

/* ══ TIER 2 — PAGE-TO-PAGE: cross-document view transitions ══════════════════
   One at-rule, on both the outgoing and incoming page. This is what makes an
   eleven-page site feel like ONE CONTINUOUS SURFACE rather than a set of
   documents. Chromium 126+ and Safari 18.2+; Firefox behind a flag —
   non-supporting browsers navigate normally and nothing breaks. */

@view-transition { navigation: auto; }

/* Elements that PERSIST across a navigation rather than blinking.
   ⛔ HARD FAILURE TO AVOID: two visible elements sharing a view-transition-name
   throws and skips the transition ENTIRELY. Names must be unique per page — so
   these four are singletons by construction, and .waterline is the one that
   enforces "exactly one horizon per viewport" from tokens.css. */
.vt-mark      { view-transition-name: cetus-mark; }      /* the whale mark in the nav */
.vt-spine     { view-transition-name: cetus-spine; }     /* the § section spine */
.vt-waterline { view-transition-name: cetus-waterline; } /* the record hairline / horizon */
.vt-navmark   { view-transition-name: cetus-navmark; }   /* the active-page indicator */

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(*) { animation-duration: var(--t-page); animation-timing-function: var(--e-inout); }
}

/* Direction carries meaning: `forwards` going deeper (Home → /record → a paper),
   `back` returning. On a site with a role router this is genuinely useful rather
   than decorative. Set the type in the link handler; branch on it here. */
@media (prefers-reduced-motion: no-preference) {
  :active-view-transition-type(forwards) ::view-transition-new(root) {
    animation: vt-in-fwd var(--t-page) var(--e-inout) both;
  }
  :active-view-transition-type(back) ::view-transition-new(root) {
    animation: vt-in-back var(--t-page) var(--e-inout) both;
  }
}
@keyframes vt-in-fwd  { from { opacity: 0; translate: 0 1.25rem; } to { opacity: 1; translate: 0 0; } }
@keyframes vt-in-back { from { opacity: 0; translate: 0 -1rem; }   to { opacity: 1; translate: 0 0; } }

/* ══ INTERACTION ═════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: no-preference) {
  a, button, summary, .card, .nav-link {
    transition: color var(--t-hover) var(--e-out),
                border-color var(--t-hover) var(--e-out),
                text-decoration-color var(--t-hover) var(--e-out),
                background-color var(--t-hover) var(--e-out);
  }
  /* <details>/<summary> is the accordion base — keyboard-native and works with
     no JS, which the CSP makes a requirement rather than a nicety.
     ⛔ Never nest accordions. Never open more than one by default. Open NONE on
     mobile (Addendum A Part F). */
  details > *:not(summary) { animation: acc-open var(--t-accordion) var(--e-inout) both; }
  .site-nav { transition: padding var(--t-nav) var(--e-out), background-color var(--t-nav) var(--e-out); }
}
@keyframes acc-open { from { opacity: 0; translate: 0 -0.35rem; } to { opacity: 1; translate: 0 0; } }

/* ══ TIER 3 — SEQUENCED CHOREOGRAPHY ═════════════════════════════════════════
   EXACTLY TWO places, both pinned sections where the plate holds still and its
   annotations advance:
     1. The deadline ruler (§5.3) — bars draw in order, the calendar shading
        fills, the overrun becomes visible. THE ORDER IS THE ARGUMENT.
     2. The integration flow (§5.6) — arrows draw one at a time so the reader can
        follow which direction each channel moves.
   Nothing else gets sequenced choreography.

   ⚠ TWO CONSTRAINTS BEFORE BUILDING THESE (ledger P3-2, P3-3):
     · GSAP from a CDN is BLOCKED by script-src 'self'. It must be vendored into
       site/assets/ and git-tracked (ledger C4 second-order, C6). Evaluate first
       whether scroll-driven CSS + a pinned sticky container can express the
       sequence without the library — for two linear sequences it very likely can,
       and that would remove the dependency entirely.
     · Addendum A Part I item 4: pinned sections are the most fragile thing in the
       plan, hardest at 360px and 3440px, and where scroll behaviour most often
       breaks. BUILD THEM LAST and be willing to ship the static plate instead. */

.pin-wrap  { position: relative; }
.pin-plate { position: sticky; top: 12vh; }
@media (max-width: 767px) { .pin-plate { position: static; } }  /* no pinning on narrow */

/* ══ P0-7 — THE GLOBAL REDUCED-MOTION GATE ═══════════════════════════════════
   Ships BEFORE any motion does. Under reduced motion every state resolves
   INSTANTLY to its end state, and the threshold does not appear at all
   (Addendum C Part D — and explicitly do NOT ship a "static version" of it; a
   still seascape with an Enter button is the vanity splash screen the research
   warns against).

   This block is last so it wins on specificity ties, and it is deliberately
   blunt: a motion rule added later that forgets its own guard is still caught. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
    animation-timeline: auto !important;
  }
  @view-transition { navigation: none; }
  .reveal, .reveal-stagger > *, .draw-in { opacity: 1 !important; translate: none !important; transform: none !important; }
  /* The canvas panels fade in and out on scroll. With animation collapsed to
     1ms they would settle wherever the reset left them, so they are forced
     visible — a reader with reduced motion gets every panel in normal flow,
     which is longer but complete. */
  /* ⛔ AND THE PANEL'S OWN GEOMETRY, NOT ONLY ITS CHILDREN'S. The directional
     arrivals translate and scale the INNER; before those existed this reset
     only had to neutralise the children. reveal.js also returns early under
     reduced motion so `reveal-ready` is never set and the hidden state never
     applies — this is the second of the two, kept because a rule that depends
     on a script remembering is not a guarantee. */
  .canvas-panel-in {
    opacity: 1 !important; animation: none !important;
    translate: none !important; scale: none !important; transform: none !important;
  }
  /* transform now carries the rotateX entry as well as the old translate, so
     the reset has to name it — which it already did. Kept explicit because the
     port changed WHICH property does the moving. */
  .canvas-panel-in > * {
    opacity: 1 !important; translate: none !important; animation: none !important;
    transform: none !important;
  }
  /* split.js returns before shredding anything under reduced motion, so these
     selectors should match nothing. Kept as the second of two independent
     stops: if a future edit makes the splitter run regardless, the lines are
     still visible rather than a blank page. */
  .sp-line-in { opacity: 1 !important; translate: none !important; transition: none !important; }
  .pin-plate { position: static; }
  [data-threshold] { display: none !important; }

  /* ── THE ONE CARVE-OUT, AND IT IS AN AFFORDANCE RATHER THAN AN EFFECT ────
     The blunt reset above froze the constellation's glows, and on the landing
     page those glows ARE the interface: the plate carries no labels and no
     figure lines, so ten still points on a field of thousands of real stars is
     a page with ten invisible controls. Nothing announced it — the founder's
     machine has Windows' animation toggle off and the map simply looked inert.

     What the setting exists to prevent is MOVEMENT — scaling, parallax, travel
     across the screen. So the scale is held at 1 and only the brightness
     breathes, slowly, on the same per-star rhythm. The affordance survives and
     the movement does not.

     ⛔ This is the ONLY exception in this block, and it is scoped to a single
     class. The reset stays blunt on purpose: a motion rule added later that
     forgets its own guard must still be caught. */
  .sm-glow {
    animation-name: star-breathe-still !important;
    animation-duration: var(--pe, 9s) !important;
    animation-iteration-count: infinite !important;
    transform: none !important;
  }
}
