/* ==========================================================================
   motion.css: three deliberate motions for aczen.live, and nothing else.
   1. The month timeline rail fills in brand blue as you scroll the steps.
   2. Ledger cells are "written in" left to right, like ink, once per table.
   3. Prices roll up when the billing toggle changes (answers a user action).
   Plus a hairline header lift after 8px of scroll.

   Safety contract (why the file is shaped like this):
   - Every animation sits inside prefers-reduced-motion: no-preference, so a
     reduced-motion visitor gets the final, fully drawn page.
   - Nothing is ever hidden unless motion.js has run (.motion-ready on <html>)
     or the browser natively supports scroll-driven animation, so a JS
     failure can never leave content invisible.
   - Only transform, opacity and clip-path animate (compositor-friendly);
     the header shadow is the single paint-only exception, over 8px of scroll.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Timeline rail: static base (applies with or without motion)
   -------------------------------------------------------------------------- */

/* Rail only exists once the step grid has a separate day column (styles.css: 760px) */
@media (min-width: 760px) {
  /* Rail geometry tokens; styles.css already gives .steps position: relative as the rail's containing block */
  .steps {
    --steps-rail-x: calc(7.5rem + 16px); /* centre of the 32px gap after the 7.5rem day column; override if styles.css changes the grid */
    --steps-rail-inset: 40px; /* matches .step padding-block, so the rail starts level with the first day marker */
  }

  /* Grey track and blue fill share geometry; the fill sits on top */
  .steps::before,
  .steps::after {
    content: ""; /* pseudo-elements render only with content set */
    position: absolute; /* drawn over the list without taking space in the grid */
    top: var(--steps-rail-inset); /* starts at the first marker, not at the section rule */
    bottom: var(--steps-rail-inset); /* ends at the last entry's content, not its closing rule */
    left: var(--steps-rail-x); /* runs down the gutter between day markers and step text */
    width: 2px; /* hairline weight that still reads next to 3rem numerals */
    margin-left: -1px; /* centres the 2px line on the rail x position */
    pointer-events: none; /* decoration must never swallow clicks on step content */
  }

  /* The track: the unfilled month still to come */
  .steps::before {
    background: var(--rule); /* same grey as the ledger rules, so it reads as part of the book */
  }

  /* The fill: how far through the month you have read */
  .steps::after {
    background: var(--brand); /* brand blue, not orange: progress is not a deadline */
    transform-origin: top; /* grows downward, in reading order */
    /* Default is fully drawn, so reduced-motion and no-JS visitors see the finished line */
  }
}


/* --------------------------------------------------------------------------
   Everything below animates, so it lives inside the reduced-motion guard
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {

  /* ------------------------------------------------------------------------
     1a. Rail, native route: CSS scroll-driven animation (Chromium today)
     ------------------------------------------------------------------------ */
  @supports (animation-timeline: view()) {
    /* Named timelines only matter where the rail is drawn */
    @media (min-width: 760px) {
      /* The list publishes a view timeline its ::after can bind to */
      .steps {
        view-timeline: --steps-tl block; /* named, because an anonymous view() on a pseudo-element is ambiguous across engines */
      }

      /* The fill scrubs with scroll through the list */
      .steps::after {
        animation: rail-fill linear both; /* linear, because scroll position itself is the easing */
        animation-timeline: --steps-tl; /* driven by the list crossing the viewport, not by time */
        animation-range: cover 40vh cover calc(100% - 60vh); /* keeps the line's tip near 60% of viewport height as you read */
      }

      /* Each step publishes its own timeline for its marker */
      .step {
        view-timeline: --step-tl block; /* same name on siblings is fine: each marker resolves its own parent */
      }

      /* Marker wakes up as the rail's tip passes it */
      .step-day {
        transform-origin: left bottom; /* scales from the numeral's baseline corner, so it doesn't drift into the rail */
        animation: step-day-reach linear both; /* "both" holds the dim state before and the lit state after */
        animation-timeline: --step-tl; /* tied to this step's position, matching the rail's 60% tip */
        animation-range: cover calc(40vh + 16px) cover calc(40vh + 96px); /* rail tip reaches the marker (step top + 40px padding) inside this window */
      }
    }

    /* Header lift: pure CSS, scrubbed over the first 8px of page scroll */
    .site-header {
      animation: header-lift linear both; /* both keeps the lifted state for the rest of the page */
      animation-timeline: scroll(root block); /* the document scroller, since the header is sticky to it */
      animation-range: 0px 8px; /* a tiny range makes it feel like a state change, not a fade */
    }
  }

  /* ------------------------------------------------------------------------
     1b. Rail, fallback route: motion.js sets --steps-progress and .is-reached
         (.motion-fallback is only added when animation-timeline is unsupported)
     ------------------------------------------------------------------------ */
  @media (min-width: 760px) {
    /* The fill follows the JS-computed 0..1 progress */
    .motion-fallback .steps::after {
      transform: scaleY(var(--steps-progress, 1)); /* defaults to full, so a stalled script shows a finished line */
      will-change: transform; /* promoted once, since it updates every scrolled frame while in view */
    }

    /* Markers rest dimmed until the rail reaches them */
    .motion-fallback .step-day {
      opacity: 0.45; /* quiet until reached, matching the native route's start state */
      transform: scale(0.92); /* slightly smaller, so "reached" reads as arriving */
      transform-origin: left bottom; /* same anchor as the native route */
      transition: opacity 320ms var(--ease-out, ease-out), transform 320ms var(--ease-out, ease-out); /* time-based here because JS flips a class, not a scrubbed value */
    }

    /* Reached markers come to full strength */
    .motion-fallback .step.is-reached .step-day {
      opacity: 1; /* full contrast once the rail has passed */
      transform: none; /* back to natural size */
    }
  }

  /* Header fallback: motion.js toggles .is-scrolled on a rAF-throttled passive listener */
  .motion-fallback .site-header {
    transition: box-shadow 200ms var(--ease-out, ease-out); /* short, because it confirms a state rather than performing */
  }

  /* Same end state as the header-lift keyframe */
  .motion-fallback .site-header.is-scrolled {
    box-shadow: 0 1px 0 var(--rule), 0 8px 18px -14px rgba(4, 33, 88, 0.45); /* hairline plus a brand-deep tinted shadow; shared with the keyframe below */
  }


  /* ------------------------------------------------------------------------
     2. Ledger rows written in (both routes; motion.js adds .is-inked once)
     ------------------------------------------------------------------------ */

  /* Row index feeds the stagger: 0 / 80 / 160ms */
  .motion-ready .ledger tbody tr:nth-child(2) {
    --row: 1; /* second entry starts one beat later */
  }

  /* Third row is two beats in */
  .motion-ready .ledger tbody tr:nth-child(3) {
    --row: 2; /* third entry */
  }

  /* Later columns start a touch later, so the pen visibly moves across the row */
  .motion-ready .ledger tbody td:nth-child(2) {
    --col: 1; /* second field */
  }

  /* Third column */
  .motion-ready .ledger tbody td:nth-child(3) {
    --col: 2; /* third field */
  }

  /* Fourth column (invoice and payment tables have four) */
  .motion-ready .ledger tbody td:nth-child(4) {
    --col: 3; /* fourth field */
  }

  /* Cells wait, clipped, until their table is inked; clipped on the cell because clip-path on <tr> is unreliable across engines */
  .motion-ready .ledger:not(.is-inked) tbody td {
    clip-path: inset(0 100% 0 0); /* fully clipped from the right: nothing written yet, but the row keeps its height */
  }

  /* Once inked, each cell wipes open left to right in row-then-column order */
  .motion-ready .ledger.is-inked tbody td {
    animation: ink-in 420ms var(--ease-out, ease-out) both; /* "both" keeps cells clipped during their delay */
    animation-delay: calc(var(--row, 0) * 80ms + var(--col, 0) * 45ms); /* 80ms per row as briefed, 45ms per field for the writing feel */
  }


  /* ------------------------------------------------------------------------
     3. Price roll (motion.js adds the class after main.js swaps the text)
     ------------------------------------------------------------------------ */

  /* Two class names with identical motion let JS restart the roll without forcing a reflow */
  .price .amount.is-rolling {
    /* No display change needed: .price is display:flex, so this span is a blockified flex item that transforms apply to */
    animation: price-roll-a 280ms var(--ease-out, ease-out); /* ~280ms: noticeable, but done before the eye re-reads the number */
  }

  /* Alternate name: switching animation-name restarts the animation */
  .price .amount.is-rolling-b {
    animation: price-roll-b 280ms var(--ease-out, ease-out); /* identical motion under a new name */
  }
}


/* --------------------------------------------------------------------------
   Keyframes (inert unless referenced inside the guard above)
   -------------------------------------------------------------------------- */

/* Rail fill: empty to full */
@keyframes rail-fill {
  /* Nothing read yet */
  from {
    transform: scaleY(0); /* collapsed to the top edge */
  }
  /* Whole month read */
  to {
    transform: scaleY(1); /* full rail */
  }
}

/* Marker arrival: dim and slightly small, then full */
@keyframes step-day-reach {
  /* Before the rail arrives */
  from {
    opacity: 0.45; /* recedes without disappearing, so the day is always legible */
    transform: scale(0.92); /* a small step, enough to feel like arrival */
  }
  /* Rail has passed */
  to {
    opacity: 1; /* full contrast */
    transform: none; /* natural size */
  }
}

/* Header lift: flat to a hairline plus soft shadow */
@keyframes header-lift {
  /* At the very top the header sits flush with the page */
  from {
    box-shadow: 0 1px 0 transparent, 0 8px 18px -14px rgba(4, 33, 88, 0); /* same shape as the end state so it interpolates cleanly */
  }
  /* After 8px, content is scrolling under it, so it separates */
  to {
    box-shadow: 0 1px 0 var(--rule), 0 8px 18px -14px rgba(4, 33, 88, 0.45); /* brand-deep tint instead of flat grey */
  }
}

/* Ink wipe: the cell is revealed from its left edge */
@keyframes ink-in {
  /* Unwritten */
  from {
    clip-path: inset(0 100% 0 0); /* right inset at 100% hides the whole cell */
  }
  /* Written */
  to {
    clip-path: inset(0 0 0 0); /* nothing clipped */
  }
}

/* Price roll, name A: new value rises into place */
@keyframes price-roll-a {
  /* Starts low and faint, as if rolled up from below */
  from {
    transform: translateY(40%); /* 40% of its own height: a roll, not a jump */
    opacity: 0.2; /* faint but present, so the number never blinks out */
  }
  /* Settles on the baseline */
  to {
    transform: none; /* back on the baseline with "/month" */
    opacity: 1; /* full strength */
  }
}

/* Price roll, name B: identical, exists only so the animation can restart */
@keyframes price-roll-b {
  /* Same start as A */
  from {
    transform: translateY(40%); /* same offset as A */
    opacity: 0.2; /* same starting opacity as A */
  }
  /* Same end as A */
  to {
    transform: none; /* baseline */
    opacity: 1; /* full strength */
  }
}
