/* ==========================================================================
   AlumDeck motion layer - motion.css
   --------------------------------------------------------------------------
   Pairs with motion.js. Both files are served from the same origin, so the
   site CSP (default-src 'self', no 'unsafe-inline', no 'unsafe-eval') is
   satisfied without a single inline style block or inline script.

   Rules of the house:
     - Only transform, opacity and stroke-dashoffset are animated on scroll.
     - Nothing animates width, height, top or left. Ever.
     - Every animation plays once. Nothing loops.
     - prefers-reduced-motion switches motion off AND forces the finished
       state, so a reduced-motion visitor never lands on a blank page.

   Tunable tokens live on :root and are all prefixed --motion- so they will
   not collide with the site's own design tokens.
   ========================================================================== */

:root {
  /* Ease-out curve. Fast at the start, settles gently, never overshoots. */
  --motion-ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* Reveal: distance travelled and time taken. Keep inside 150ms - 400ms. */
  --motion-shift: 14px;
  --motion-reveal-ms: 320ms;

  /* Gap between one staggered card and the next. */
  --motion-stagger-step: 60ms;

  /* Diagram draw-in. */
  --motion-draw-ms: 900ms;

  /* Card lift on hover and on keyboard focus. */
  --motion-lift-ms: 180ms;
  --motion-lift-shift: -3px;
  --motion-lift-shadow: 0 10px 24px rgba(18, 24, 31, 0.14);
  --motion-focus-ring: #2E4B7A;
}

/* ==========================================================================
   1. Reveal on scroll
   --------------------------------------------------------------------------
   Elements marked data-reveal start slightly displaced and transparent.
   motion.js adds .is-revealed when the element scrolls into view and then
   stops observing it, so each element settles exactly once.

   Only opacity and transform are touched, so the whole thing runs on the
   compositor and never triggers layout.
   ========================================================================== */

[data-reveal] {
  opacity: 0;
  transform: translateY(var(--motion-shift));
  transition:
    opacity var(--motion-reveal-ms) var(--motion-ease-out),
    transform var(--motion-reveal-ms) var(--motion-ease-out);
  /* Stagger. --i is set by motion.js per group member, default 0. */
  transition-delay: calc(var(--i, 0) * var(--motion-stagger-step));
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* Direction variants. Pick one per element, for example data-reveal="left".
   A bare data-reveal rises from below. */
[data-reveal="down"]  { transform: translateY(calc(var(--motion-shift) * -1)); }
[data-reveal="left"]  { transform: translateX(var(--motion-shift)); }
[data-reveal="right"] { transform: translateX(calc(var(--motion-shift) * -1)); }
[data-reveal="fade"]  { transform: none; }

/* The settle rule above already clears every variant, but state it again at
   equal specificity so variant selectors can never win the cascade. */
[data-reveal="down"].is-revealed,
[data-reveal="left"].is-revealed,
[data-reveal="right"].is-revealed,
[data-reveal="fade"].is-revealed {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   2. Stagger
   --------------------------------------------------------------------------
   Put data-reveal-group on the wrapper and data-reveal on each child. The
   script numbers the children by setting the --i custom property on each one,
   so there is no hardcoded nth-child ladder to maintain and a group can hold
   any number of cards.

   The script caps --i, so a long list never ends up with a multi-second tail.
   ========================================================================== */

[data-reveal-group] {
  /* Nothing needed here. The wrapper is only an anchor for the observer and
     a scope for numbering. Declared so the contract is visible in the CSS. */
}

/* ==========================================================================
   3. Diagram draw-in
   --------------------------------------------------------------------------
   For inline SVG only. The diagrams are inlined into the markup rather than
   loaded through <img>, so page CSS reaches their shapes.

   Every drawable shape carries class="draw" and pathLength="1". Normalising
   the geometry to a length of 1 means one dash pattern works for every shape
   regardless of its real perimeter: dasharray 1 is a single dash covering the
   whole outline, and dashoffset slides it from hidden (1) to drawn (0).

   The .draw class appears on rect, circle, line, ellipse and path in the
   AlumDeck diagram set, so the selector is deliberately element-agnostic.
   ========================================================================== */

[data-draw] .draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--motion-draw-ms) var(--motion-ease-out);
}

[data-draw].is-drawn .draw {
  stroke-dashoffset: 0;
}

/* ==========================================================================
   4. Card lift, hover and keyboard
   --------------------------------------------------------------------------
   A keyboard user gets exactly the same affordance as a mouse user, plus a
   real focus ring. Hover and focus-visible are written as separate rules on
   purpose: if they shared one selector list, a browser that did not
   understand :focus-visible would discard the hover rule along with it.

   box-shadow is the one non-compositor property in this file. It is confined
   to hover and focus on a small number of cards, never to scroll.
   ========================================================================== */

[data-lift] {
  transition:
    transform var(--motion-lift-ms) var(--motion-ease-out),
    box-shadow var(--motion-lift-ms) var(--motion-ease-out);
}

[data-lift]:hover {
  transform: translateY(var(--motion-lift-shift));
  box-shadow: var(--motion-lift-shadow);
}

[data-lift]:focus-visible {
  transform: translateY(var(--motion-lift-shift));
  box-shadow: var(--motion-lift-shadow);
  outline: 2px solid var(--motion-focus-ring);
  outline-offset: 3px;
}

/* When the card is a plain container wrapping a link or button, lift it as
   the inner control takes focus. Kept in its own rule because browsers
   without :has() must not lose the two rules above. */
[data-lift]:has(:focus-visible) {
  transform: translateY(var(--motion-lift-shift));
  box-shadow: var(--motion-lift-shadow);
}

/* ==========================================================================
   5. Counters
   --------------------------------------------------------------------------
   The count itself is done in JS. All the CSS has to do is stop the glyph
   widths changing as the digits cycle, which otherwise makes the surrounding
   layout twitch for the whole 900ms.
   ========================================================================== */

[data-count] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ==========================================================================
   6. Reduced motion
   --------------------------------------------------------------------------
   Two blocks. The first is the global guard, verbatim. The second undoes the
   starting states above, because switching a transition off does not move an
   element to the end of it: without this, every data-reveal element would sit
   at opacity 0 forever and every diagram would stay undrawn. That is the
   failure mode that matters, so it is handled explicitly rather than assumed.

   motion.js also bails out early under the same query and writes the finished
   classes, so the end state is guaranteed by CSS and by JS independently.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }

@media (prefers-reduced-motion: reduce) {

  /* Every reveal element is fully visible and sits where it belongs. */
  [data-reveal],
  [data-reveal="down"],
  [data-reveal="left"],
  [data-reveal="right"],
  [data-reveal="fade"] {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Every diagram stroke is fully drawn. dasharray is cleared as well as the
     offset so the outline is a plain solid stroke, not a dash that happens to
     line up. */
  .draw,
  [data-draw] .draw {
    stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important;
  }

  /* The lift becomes an instant state change rather than a glide. The focus
     ring is untouched: it is an outline, it never animated, and a keyboard
     user needs it regardless of motion preference. */
  [data-lift]:hover,
  [data-lift]:focus-visible {
    transform: none !important;
  }
}
