/* ═══════════════════════════════════════════════════════════════════════════
   THEME-MOTION-2027 · the CSS contract for static/js/theme-2027.js
   ---------------------------------------------------------------------------
   This is home-2027.css §20 MOTION lifted out of the homepage and made
   portable: the same variants, the same easing, the same 80ms stagger step —
   but keyed on `data-t27` instead of `data-h27`, unscoped so it works on any
   of the ~40 pages, and gated on `html.t27-ready` instead of `html.h27-ready`.

   Why a different attribute: home-2027.js observes every [data-h27] on the
   homepage. A second engine observing the same nodes would race it and arm the
   other's gate class, so this layer claims `data-t27` and the two can never
   fight. The class both engines ADD is the same `x27-in`, so the revealed state
   stays one shared vocabulary across ux-2027, home-2027 and this file.

   Page-author contract:
     <div data-t27="up">…</div>                     up · rise · left · right · mask · fade
     <div data-t27="rise" style="--h27-i:2">…</div>  stagger — index × 80ms
     <div data-t27="rise" data-t27-i="2">…</div>     same, written as an attribute

   ── The gate class ─────────────────────────────────────────────────────────
   Every hidden (pre-reveal) state below sits behind `html.t27-ready`, which
   theme-2027.js adds ONLY once its IntersectionObserver has been constructed.
   If the script is missing, blocked, throws, or the browser has no
   IntersectionObserver, the gate is never set and nothing is ever hidden — the
   page degrades to fully visible rather than to a blank column.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --t27-ease: cubic-bezier(.16, 1, .3, 1);
}

/* ══ HIDDEN (pre-reveal) STATES — armed by the JS gate class only ════════ */

html.t27-ready [data-t27]         { opacity: 0; will-change: opacity, transform; }
html.t27-ready [data-t27="up"]    { transform: translate3d(0, 26px, 0); }
html.t27-ready [data-t27="rise"]  { transform: translate3d(0, 42px, 0); }
html.t27-ready [data-t27="left"]  { transform: translate3d(-30px, 0, 0); }
html.t27-ready [data-t27="right"] { transform: translate3d(30px, 0, 0); }
html.t27-ready [data-t27="mask"]  { clip-path: inset(0 0 100% 0); transform: none; }
html.t27-ready [data-t27="fade"]  { transform: none; }

/* ══ THE TRANSITION ══════════════════════════════════════════════════════
   Ungated on purpose: it has to be in force before the gate class lands, or
   the first elements to reveal would snap rather than travel. --h27-i is the
   stagger index — kept under the h27 name so markup copied from the homepage
   carries its cascade across unchanged. */

[data-t27] {
  transition:
    opacity   780ms var(--t27-ease, cubic-bezier(.16, 1, .3, 1)),
    transform 880ms var(--t27-ease, cubic-bezier(.16, 1, .3, 1)),
    clip-path 980ms var(--t27-ease, cubic-bezier(.16, 1, .3, 1));
  transition-delay: calc(var(--h27-i, 0) * 80ms);
}

/* ══ REVEALED STATE ══════════════════════════════════════════════════════
   DOUBLED on purpose, and this is load-bearing. The per-variant rules above
   carry `html.t27-ready` (0,2,1), while a bare `[data-t27].x27-in` is only
   (0,2,0) — one notch short, so the hidden transform would keep winning and
   the element would sit at opacity 0 forever. Repeating the selector WITH the
   gate prefix (0,3,1) is what actually un-hides it. The `.h27`-scoped pair is
   the same guard again for pages that nest this markup inside the homepage
   wrapper, where home-2027.css contributes a further element of specificity. */

[data-t27].x27-in,
html.t27-ready [data-t27].x27-in,
.h27 [data-t27].x27-in,
html.t27-ready .h27 [data-t27].x27-in {
  opacity: 1;
  transform: none;
}

/* clip-path is the "mask" variant's business alone — it is the only one that
   travels from a clip. Left on the blanket rule it pinned every revealed
   element to its border box, which silently cropped anything that paints
   outside: glows, corner badges, and the descenders of display type set at
   line-height < 1. */
html.t27-ready [data-t27="mask"].x27-in,
html.t27-ready .h27 [data-t27="mask"].x27-in { clip-path: inset(0 0 0 0); }
html.t27-ready [data-t27="mask"].x27-in.x27-done,
html.t27-ready .h27 [data-t27="mask"].x27-in.x27-done { clip-path: none; }

/* ══ REDUCED MOTION ══════════════════════════════════════════════════════
   theme-2027.js already skips the observer entirely here and never sets the
   gate class, so this block is the belt to that braces: even if the class were
   somehow present, everything stays visible and nothing travels. */

@media (prefers-reduced-motion: reduce) {
  [data-t27],
  html.t27-ready [data-t27] {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    transition: none !important;
    transition-delay: 0s !important;
    will-change: auto !important;
  }
}
