/* ===========================================================================
   ANTELIC — motion.

   Templates ship browser defaults. This file exists so that every transition
   and every animation on the site names one of five curves and one of five
   durations, and so that all of them can be read in one place.

   THE HARD RULE THIS FILE IS BUILT AROUND: nothing here may be load-bearing.
   Every word is in the DOM at full opacity before a line of JavaScript runs,
   and every rule below is either (a) a transition, which needs a starting
   state that is already correct, or (b) a keyframe animation with
   `animation-fill-mode: backwards`, which applies NOTHING if the animation
   never runs. There is no static hidden state anywhere in this file. Turn off
   JavaScript, turn off animation, load it in a browser from 2019: you get the
   whole page.

   Only two properties are ever animated — `transform` and `opacity` — with
   two deliberate exceptions, both of them state changes rather than entrances:
   button hover recolours, and the reference lens contracts its band. Both are
   the app's own behaviour, and both are correct at rest with no animation
   having run.
   =========================================================================== */

:root {
  /* --- easing ---------------------------------------------------------
     Four curves, and each one has a job. They are not interchangeable. */
  --ease-out-standard: cubic-bezier(0.16, 1, 0.3, 1);        /* hover, press, UI state */
  --ease-out-soft:     cubic-bezier(0.25, 0.46, 0.45, 0.94); /* content reveal, page */
  --ease-in-out-soft:  cubic-bezier(0.65, 0, 0.35, 1);       /* drawer open/close */
  /* the app's own curve, lifted from RangeBar.tsx. One-world continuity
     outranks token uniformity, so this one is NOT retuned to match the
     three above — the lens on this page moves the way the lens in the
     product moves, to the millisecond. */
  --ease-lens:         cubic-bezier(0.4, 0, 0.2, 1);

  /* --- duration -------------------------------------------------------- */
  --dur-micro:  150ms;   /* press feedback */
  --dur-ui:     250ms;   /* hover, drawer, focus */
  --dur-reveal: 700ms;   /* scroll-triggered block reveal */
  --dur-page:   450ms;   /* cross-document view transition */
  --dur-lens:   340ms;   /* the band contraction — app-identical */
  --dur-word:   560ms;   /* one word of the home H1 */
}

/* --------------------------------------------------------- scroll reveal ---
   The mechanism is a rAF scroll sweep, not an IntersectionObserver, and that
   is a deliberate deviation: an observer misses blocks the reader jumps PAST
   — the End key, an anchor, a restored scroll position — and leaves them
   permanently blank on a page already scrolled by. A sweep re-checks
   everything above the fold on every frame it runs, so a block can only be
   hidden while it is still below the fold. Five pages of anchor links make
   that more important, not less.

   The offset is 24px rather than 14: enough that the block visibly shears
   against the left margin rule as it settles, not so much that it reads as a
   slide-in. */
.js .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity var(--dur-reveal) var(--ease-out-soft),
    transform var(--dur-reveal) var(--ease-out-soft);
  transition-delay: var(--d, 0ms);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* ------------------------------------------------------ the one hero word ---
   Spent once, on the home H1, and nowhere else on the site. Two layers: the
   wrapper is a static mask, the inner span rises through it. 40ms per word
   across seven words is a 240ms span — well inside the 600ms ceiling, and far
   short of the per-letter granularity that reads as a gimmick.

   THE H1 IS THE LCP ELEMENT, so this is written to cost nothing if it does
   not run: there is no hidden base state. `both` fill applies the start frame
   only while the animation is pending, and the mask's negative side insets
   mean the resting clip never touches a glyph. */
@media (prefers-reduced-motion: no-preference) {
  @supports (clip-path: inset(0)) {
    .hw {
      display: inline-block;
      clip-path: inset(0);
      padding: 0.22em 0.1em;
      margin: -0.22em -0.1em;
    }
    .hw > span {
      display: inline-block;
      animation: hero-word var(--dur-word) var(--ease-out-soft) both;
      animation-delay: calc(var(--i, 0) * 40ms);
    }
    @keyframes hero-word {
      from { transform: translateY(125%); }
    }
  }
}

/* ------------------------------------------------------------ branch drift ---
   The one piece of decorative scroll-linked motion on the site, and it costs
   zero bytes of JavaScript: a scroll-driven timeline runs on the compositor,
   and a browser that doesn't know the syntax renders a static branch, which
   is the correct picture anyway.

   Transform only. The 0.10 opacity is fenced by a contrast argument — ink on
   branch-darkened wall measures about 8.9:1 — so it is never animated. And
   never on the hero branch: nothing on the first screen moves on load. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .branch--drift {
      animation: branch-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    .branch--drift.branch--flip {
      animation-name: branch-drift-flip;
    }
    @keyframes branch-drift {
      from { transform: translateY(-14px); }
      to   { transform: translateY(14px); }
    }
    @keyframes branch-drift-flip {
      from { transform: translateY(-14px) scaleX(-1); }
      to   { transform: translateY(14px) scaleX(-1); }
    }
  }
}

/* -------------------------------------------------------- page transitions ---
   V2 is five documents, so a navigation is now a real event. The browser
   default 250ms cross-fade reads snappy and utilitarian; 450ms reads
   editorial, which is the register of the room. Progressive enhancement only
   — an unsupporting browser gets an ordinary navigation and no broken UI. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: var(--dur-page);
    animation-timing-function: var(--ease-out-soft);
  }
}

/* ------------------------------------------------------ micro-interactions ---
   Press is a transform, not a corner: it does not touch the rectangular
   fence. It is on the controls a finger lands on and nothing else. */
.btn,
.lens-cell,
.drawer-tab,
.faq summary {
  transition:
    background-color var(--dur-ui) var(--ease-out-standard),
    color var(--dur-ui) var(--ease-out-standard),
    border-color var(--dur-ui) var(--ease-out-standard),
    transform var(--dur-micro) var(--ease-out-standard);
}
.btn:active,
.drawer-tab:active,
.faq summary:active { transform: scale(0.97); }

/* the drawer's own bars, and the disclosure marks: a bidirectional state, so
   a symmetric curve */
.drawer-bars i,
.faq-mark i {
  transition: transform var(--dur-ui) var(--ease-in-out-soft),
              opacity var(--dur-ui) var(--ease-in-out-soft);
}

/* the lens: the app's curve, the app's duration, not this file's */
.lens-cell { transition-duration: var(--dur-lens); transition-timing-function: var(--ease-lens); }
.track-band {
  transition:
    left var(--dur-lens) var(--ease-lens),
    width var(--dur-lens) var(--ease-lens),
    background-color var(--dur-lens) var(--ease-lens);
}

/* The primary call to action is an anchor into a section of the same page, so
   it should travel rather than cut. CSS only — no script is involved in the
   one control that matters. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* ------------------------------------------------------- the reduced state ---
   A designed state, not a kill switch. A reader who has asked for less motion
   gets the complete page, every word of it, immediately — never a stripped
   one. Nothing is hidden, the lens still contracts (instantly), the drawer
   still opens, the branches still hang. */
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.001ms !important;
  }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation-duration: 0.001ms !important; }
}

@media print {
  .js .reveal { opacity: 1 !important; transform: none !important; }
  .hw { clip-path: none; }
  .hw > span { animation: none; }
}
