/* ═══════════════════════════════════════════════════════════════════════════
   HOME-MOTION-2027 · "M27" — the homepage motion layer
   ---------------------------------------------------------------------------
   home-2027.css §20 owns the *arrival* of each block: a container slides up,
   fades in, and stops. That is one gesture per act, and by Act 03 the page
   reads as a slide deck — every section enters exactly the same way, and the
   things inside those sections (the dashboard, the nine cells, the schedule,
   the map HUD) never move at all.

   This file adds the second gesture: once a block has landed, its *contents*
   resolve. Bars grow, rows deal in, the chart wipes, numbers count, icons
   settle. Nothing here loops in the reader's peripheral vision — every
   sequence runs once, on entry, and then holds still.

   HOW IT IS TRIGGERED
     home-motion-2027.js observes a fixed list of containers and puts `.m27-in`
     on each as it enters the viewport. Every hidden state below is gated
     behind `html.m27-ready`, which that script sets only after its observer is
     constructed — so if the JS is blocked, throws, or is simply never reached,
     nothing is ever hidden and the page renders exactly as it does today.
     This layer deliberately does NOT key off `.x27-in` (home-2027.js's class)
     so that a fault in either engine cannot blank content owned by the other.

   ORDER
     Loads after home-2027.css, and every rule carries the `.h27` scope.

     The gate is written `:where(html.m27-ready)` rather than `html.m27-ready`
     on purpose. `:where()` contributes nothing to specificity, so a hidden
     state weighs exactly the same as the plain selector it hides — which means
     the matching reveal below it (always one `.m27-in` heavier) wins without
     every rule in this file having to repeat the prefix. Written the obvious
     way, `html.m27-ready .h27 .h27-chip` (0,3,2) beats `.h27 .m27-in .h27-chip`
     (0,3,1) and the chip stays invisible forever.

     `:where()` also fails safe: a browser that does not understand it drops the
     whole hidden-state rule, which lands on the same fully-visible fallback as
     a browser that never runs the script.

   CONTENTS
     §1  tokens
     §2  ticker
     §3  act 01 · the problem split
     §4  act 02 · run & grow
     §5  the AI-layer stage
     §6  act 03 · the dashboard          ← the largest sequence
     §7  act 04 · nine modules
     §8  act 05 · the relay
     §9  integrations marquee
     §10 act 06 · AI cards
     §11 act 07 · the number wall
     §12 coverage map
     §13 act 10 · FAQ
     §14 scroll-driven depth (progressive enhancement)
     §15 pointer spotlight
     §16 reduced motion
   ═══════════════════════════════════════════════════════════════════════════ */


/* ══ §1 · TOKENS ════════════════════════════════════════════════════════════
   Two easings and a step. Everything in this file is built from them, so the
   whole page settles at one tempo rather than each section inventing its own.

   `--m27-i` is the stagger index every block below reads. It is set with
   :nth-child() per block rather than written by script, so the cascade is
   inspectable in devtools and does not depend on the JS having seen the
   markup — the script's only job is the `.m27-in` trigger. */

.h27 {
  /* Soft decelerate — long tail, no overshoot. Motion should look like weight
     settling, not like a spring. */
  --m27-ease: cubic-bezier(.22, .78, .28, 1);
  --m27-ease-out: cubic-bezier(.16, 1, .3, 1);
  --m27-step: 62ms;   /* stagger between siblings */
  --m27-dur: 720ms;
}


/* ══ §2 · CLINIC TICKER ═════════════════════════════════════════════════════
   The marquee already runs. What it lacked was an entrance — it was simply
   there at full strength the moment the hero scrolled off. Now the lane opens
   from the centre and the names fade up into it. */

:where(html.m27-ready) .h27 .h27-ticker { --m27-y: 0px; }
:where(html.m27-ready) .h27 .h27-ticker__track { opacity: 0; transform: translate3d(0, 8px, 0); }
.h27 .h27-ticker.m27-in .h27-ticker__track {
  opacity: 1; transform: none;
  transition: opacity 900ms var(--m27-ease-out), transform 900ms var(--m27-ease-out);
}

/* The coral dot on each name gets a single soft breath on entry rather than a
   permanent pulse — a loop this small, repeated 18 times across the lane, is
   the kind of thing that makes a page feel restless. */
.h27 .h27-ticker.m27-in .h27-ticker__item::before {
  animation: m27-breath 1400ms var(--m27-ease-out) both;
  animation-delay: calc(var(--m27-i, 0) * 40ms);
}
@keyframes m27-breath {
  0%   { transform: scale(.2); opacity: 0; }
  55%  { transform: scale(1.35); opacity: 1; }
  100% { transform: scale(1); opacity: .7; }
}

/* Hovering a name lifts it out of the run. */
.h27 .h27-ticker__item {
  transition: color 300ms var(--m27-ease), transform 420ms var(--m27-ease);
}
.h27 .h27-ticker__item:hover { transform: translateY(-2px); }


/* ══ §3 · ACT 01 · THE PROBLEM SPLIT ════════════════════════════════════════
   The argument here is 5-against-1, and it was being made by two static
   panels. Now each side assembles: the numeral lands first and hard, the
   sentence follows, then the chips deal out one at a time — five on the left,
   three on the right, so the imbalance is something you watch happen. */

/* The numeral. Scales down into place from slightly too large, which reads as
   arriving rather than fading. */
:where(html.m27-ready) .h27 .h27-bignum b {
  transform: translate3d(0, 10px, 0) scale(1.16);
  filter: blur(8px);
  opacity: 0;
}
.h27 .h27-bignum b {
  display: inline-block;
  transition:
    transform 900ms var(--m27-ease-out),
    filter 700ms var(--m27-ease),
    opacity 620ms linear;
}
.h27 .m27-in .h27-bignum b { transform: none; filter: none; opacity: 1; }

/* The right-hand "1" waits for the left-hand "5" to finish, so the comparison
   lands in the order the sentence makes it. */
.h27 .h27-split__side--new .h27-bignum b { transition-delay: 260ms; }

:where(html.m27-ready) .h27 .h27-bignum span { opacity: 0; transform: translate3d(0, 8px, 0); }
.h27 .h27-bignum span {
  transition: opacity 620ms var(--m27-ease) 240ms, transform 720ms var(--m27-ease) 240ms;
}
.h27 .m27-in .h27-bignum span { opacity: 1; transform: none; }
.h27 .m27-in .h27-split__side--new .h27-bignum span { transition-delay: 500ms; }

/* The chips. The dead side deals slower and settles slightly grey; the live
   side deals quicker and snaps to full colour — the same content, two
   different tempos. */
:where(html.m27-ready) .h27 .h27-chip {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(.96);
}
.h27 .h27-chip {
  transition:
    opacity 460ms var(--m27-ease),
    transform 560ms var(--m27-ease),
    color 260ms linear,
    box-shadow 320ms var(--m27-ease);
  transition-delay: calc(420ms + var(--m27-i, 0) * 90ms);
}
.h27 .h27-split__side--new .h27-chip { transition-delay: calc(640ms + var(--m27-i, 0) * 70ms); }
.h27 .m27-in .h27-chip { opacity: 1; transform: none; }

.h27 .h27-chips .h27-chip:nth-child(1) { --m27-i: 0; }
.h27 .h27-chips .h27-chip:nth-child(2) { --m27-i: 1; }
.h27 .h27-chips .h27-chip:nth-child(3) { --m27-i: 2; }
.h27 .h27-chips .h27-chip:nth-child(4) { --m27-i: 3; }
.h27 .h27-chips .h27-chip:nth-child(5) { --m27-i: 4; }

/* Panel weight. The two sides sit at different elevations once they have
   landed, so the eye is told which one is the answer before it reads either. */
.h27 .h27-split__side {
  transition: transform 620ms var(--m27-ease), box-shadow 620ms var(--m27-ease);
}
.h27 .h27-split__side--new:hover { transform: translateY(-3px); }

/* The tag line on each side wipes in from its own edge. */
:where(html.m27-ready) .h27 .h27-split__tag { opacity: 0; transform: translate3d(-10px, 0, 0); }
.h27 .h27-split__tag { transition: opacity 520ms var(--m27-ease) 120ms, transform 620ms var(--m27-ease) 120ms; }
.h27 .m27-in .h27-split__tag { opacity: 1; transform: none; }


/* ══ §4 · ACT 02 · RUN & GROW ═══════════════════════════════════════════════
   Nine cells that all appeared at once. They now build left-to-right in two
   passes — the five Run cells, then the weld, then the four Grow cells — so
   the "everything in Run, plus…" claim is demonstrated by the order things
   appear in rather than only stated in the copy. */

.h27 .h27-os__cell {
  --m27-y: 20px;
  --m27-b: 6px;
  transition:
    transform var(--m27-dur) var(--m27-ease),
    filter 560ms var(--m27-ease),
    background-color 320ms var(--m27-ease),
    box-shadow 380ms var(--m27-ease);
  transition-delay: calc(var(--m27-i, 0) * 78ms);
}
:where(html.m27-ready) .h27 .h27-os__cell {
  transform: translate3d(0, 20px, 0);
  filter: blur(6px);
}
.h27 .h27-os.m27-in .h27-os__cell { transform: none; filter: none; }

.h27 .h27-os__row--run  .h27-os__cell:nth-child(1) { --m27-i: 0; }
.h27 .h27-os__row--run  .h27-os__cell:nth-child(2) { --m27-i: 1; }
.h27 .h27-os__row--run  .h27-os__cell:nth-child(3) { --m27-i: 2; }
.h27 .h27-os__row--run  .h27-os__cell:nth-child(4) { --m27-i: 3; }
.h27 .h27-os__row--run  .h27-os__cell:nth-child(5) { --m27-i: 4; }
/* Grow starts after the weld has drawn, not immediately after Run. */
.h27 .h27-os__row--grow .h27-os__cell:nth-child(1) { --m27-i: 8; }
.h27 .h27-os__row--grow .h27-os__cell:nth-child(2) { --m27-i: 9; }
.h27 .h27-os__row--grow .h27-os__cell:nth-child(3) { --m27-i: 10; }
.h27 .h27-os__row--grow .h27-os__cell:nth-child(4) { --m27-i: 11; }

/* The icon tile pops a beat after its cell has settled. */
:where(html.m27-ready) .h27 .h27-os__tile { transform: scale(.72); opacity: 0; }
.h27 .h27-os__tile {
  transition:
    transform 640ms var(--m27-ease-out),
    opacity 420ms linear,
    background-color 300ms var(--m27-ease);
  transition-delay: calc(140ms + var(--m27-i, 0) * 78ms);
}
.h27 .h27-os.m27-in .h27-os__tile { transform: none; opacity: 1; }

/* The + between the planes draws itself. */
:where(html.m27-ready) .h27 .h27-os__weld span { transform: rotate(-90deg) scale(.4); opacity: 0; }
.h27 .h27-os__weld span {
  display: inline-block;
  transition: transform 900ms var(--m27-ease-out) 420ms, opacity 520ms linear 420ms;
}
.h27 .h27-os.m27-in .h27-os__weld span { transform: none; opacity: 1; }

/* Hover: the cell lifts and its number brightens. Kept to 2px — these are
   nine tiles in a tight grid and anything larger reads as a wobble. */
.h27 .h27-os__cell:hover { transform: translateY(-2px); }
.h27 .h27-os__no { transition: opacity 280ms var(--m27-ease), transform 320ms var(--m27-ease); }
.h27 .h27-os__cell:hover .h27-os__no { opacity: 1; transform: translateY(-1px); }
.h27 .h27-os__cell:hover .h27-os__tile { transform: scale(1.06); }


/* ══ §5 · THE AI-LAYER STAGE ════════════════════════════════════════════════
   The console runs its own conversation (home-2027.js §9). All this adds is
   the frame around it: the three facts deal in under the headline, and the
   window itself arrives with a little perspective so it reads as a panel
   sitting in space rather than a picture pasted on the background. */

:where(html.m27-ready) .h27 .h27-ail__win {
  transform: perspective(1400px) rotateX(5deg) translate3d(0, 26px, 0);
  opacity: 0;
}
.h27 .h27-ail__win {
  transition: transform 1100ms var(--m27-ease-out), opacity 700ms linear;
}
.h27 .h27-ail__stage.m27-in .h27-ail__win { transform: none; opacity: 1; }

/* The corner ticks draw after the panel has settled. */
:where(html.m27-ready) .h27 .h27-ail__corner { opacity: 0; transform: scale(.6); }
.h27 .h27-ail__corner {
  transition: opacity 420ms linear 720ms, transform 620ms var(--m27-ease-out) 720ms;
}
.h27 .h27-ail__stage.m27-in .h27-ail__corner { opacity: 1; transform: none; }


/* ══ §6 · ACT 03 · THE DASHBOARD ════════════════════════════════════════════
   The centrepiece, and until now the most static thing on the page: a
   full-size product shot that slid up once and then sat there. It is a
   *dashboard* — the entire claim of the section is that it is live — so it now
   boots the way the real one does.

   The sequence, in order:
     0ms    the window lands (§20 owns this)
     280ms  sidebar nav deals down
     520ms  the four KPI tiles deal across
     700ms  spark bars grow from their baselines, meter fills
     900ms  the chart wipes left→right, nodes pop along it
     1250ms the end dot lands and the flag slides out
     1000ms schedule rows deal in from the right
     1400ms the float badges arrive

   Everything is transform/opacity only. The chart "draw" is a clip-path wipe
   of the whole plot rather than a stroke-dashoffset trick, because the path
   carries vector-effect:non-scaling-stroke inside a stretched viewBox and dash
   patterns are computed in user space there — the wipe is exact at any width
   and takes the area fill with it for free. */

/* ── sidebar ── */
:where(html.m27-ready) .h27 .dashboard-v2__nav-item { opacity: 0; transform: translate3d(-14px, 0, 0); }
.h27 .dashboard-v2__nav-item {
  transition:
    opacity 440ms var(--m27-ease),
    transform 560ms var(--m27-ease),
    background-color 260ms var(--m27-ease);
  transition-delay: calc(280ms + var(--m27-i, 0) * 44ms);
}
.h27 .m27-dash.m27-in .dashboard-v2__nav-item { opacity: 1; transform: none; }

.h27 .dashboard-v2__nav-item:nth-child(1)  { --m27-i: 0; }
.h27 .dashboard-v2__nav-item:nth-child(2)  { --m27-i: 1; }
.h27 .dashboard-v2__nav-item:nth-child(3)  { --m27-i: 2; }
.h27 .dashboard-v2__nav-item:nth-child(4)  { --m27-i: 3; }
.h27 .dashboard-v2__nav-item:nth-child(5)  { --m27-i: 4; }
.h27 .dashboard-v2__nav-item:nth-child(6)  { --m27-i: 5; }
.h27 .dashboard-v2__nav-item:nth-child(7)  { --m27-i: 6; }
.h27 .dashboard-v2__nav-item:nth-child(8)  { --m27-i: 7; }
.h27 .dashboard-v2__nav-item:nth-child(9)  { --m27-i: 8; }
.h27 .dashboard-v2__nav-item:nth-child(10) { --m27-i: 9; }

/* The "New" badge on AI Tools arrives last and announces itself once. */
.h27 .m27-dash.m27-in .dashboard-v2__nav-badge {
  animation: m27-pop 620ms var(--m27-ease-out) 900ms both;
}
@keyframes m27-pop {
  0%   { transform: scale(.4); opacity: 0; }
  60%  { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

/* ── KPI tiles ── */
:where(html.m27-ready) .h27 .dashboard-v2__metric { opacity: 0; transform: translate3d(0, 16px, 0); }
.h27 .dashboard-v2__metric {
  transition: opacity 520ms var(--m27-ease), transform 660ms var(--m27-ease);
  transition-delay: calc(520ms + var(--m27-i, 0) * 90ms);
}
.h27 .m27-dash.m27-in .dashboard-v2__metric { opacity: 1; transform: none; }
.h27 .dashboard-v2__metric:nth-child(1) { --m27-i: 0; }
.h27 .dashboard-v2__metric:nth-child(2) { --m27-i: 1; }
.h27 .dashboard-v2__metric:nth-child(3) { --m27-i: 2; }
.h27 .dashboard-v2__metric:nth-child(4) { --m27-i: 3; }

/* The accent rail down the left of each tile draws top-to-bottom. */
:where(html.m27-ready) .h27 .dashboard-v2__metric::before { transform: scaleY(0); transform-origin: top; }
.h27 .dashboard-v2__metric::before { transition: transform 640ms var(--m27-ease-out); transition-delay: calc(660ms + var(--m27-i, 0) * 90ms); }
.h27 .m27-dash.m27-in .dashboard-v2__metric::before { transform: scaleY(1); }

/* Spark bars. The bar heights are inline styles, so these grow with scaleY off
   a bottom origin — the declared height is never touched. */
:where(html.m27-ready) .h27 .dashboard-v2__metric-spark span { transform: scaleY(0); }
.h27 .dashboard-v2__metric-spark span {
  transform-origin: bottom;
  transition: transform 560ms var(--m27-ease-out);
  transition-delay: calc(760ms + var(--m27-i, 0) * 90ms + var(--m27-j, 0) * 46ms);
}
.h27 .m27-dash.m27-in .dashboard-v2__metric-spark span { transform: none; }
.h27 .dashboard-v2__metric-spark span:nth-child(1) { --m27-j: 0; }
.h27 .dashboard-v2__metric-spark span:nth-child(2) { --m27-j: 1; }
.h27 .dashboard-v2__metric-spark span:nth-child(3) { --m27-j: 2; }
.h27 .dashboard-v2__metric-spark span:nth-child(4) { --m27-j: 3; }
.h27 .dashboard-v2__metric-spark span:nth-child(5) { --m27-j: 4; }
.h27 .dashboard-v2__metric-spark span:nth-child(6) { --m27-j: 5; }
.h27 .dashboard-v2__metric-spark span:nth-child(7) { --m27-j: 6; }

/* Clean-claim meter — same idea on the horizontal. */
:where(html.m27-ready) .h27 .dashboard-v2__metric-meter i { transform: scaleX(0); }
.h27 .dashboard-v2__metric-meter i {
  transform-origin: left;
  transition: transform 1000ms var(--m27-ease-out);
  transition-delay: calc(800ms + var(--m27-i, 0) * 90ms);
}
.h27 .m27-dash.m27-in .dashboard-v2__metric-meter i { transform: none; }

/* ── revenue chart ── */
:where(html.m27-ready) .h27 .dashboard-v2__plot-area svg { clip-path: inset(0 100% 0 0); }
.h27 .dashboard-v2__plot-area svg {
  transition: clip-path 1400ms var(--m27-ease-out) 900ms;
  will-change: clip-path;
}
.h27 .m27-dash.m27-in .dashboard-v2__plot-area svg { clip-path: inset(0 0 0 0); }

/* The nodes land as the wipe passes each of them — the delays below are the
   wipe's arrival time at 20/40/60/80% of the plot.

   These three use the individual `scale` / `translate` properties rather than
   `transform`, and they have to. Every marker on this plot is positioned by a
   `transform` in home-2027.css §… — `translate(-50%,-50%)` on the nodes and the
   end dot, `translate(calc(-100% - 11px),-50%)` on the flag — which is what
   centres them on their data point. Animating `transform` here would replace
   that centring, and each marker would settle half its own size away from the
   point it is supposed to be marking. The individual properties compose with
   the existing `transform` instead of overwriting it, so the markers scale
   about the point they are already pinned to. */
:where(html.m27-ready) .h27 .dashboard-v2__plot-node { opacity: 0; scale: .3; }
.h27 .dashboard-v2__plot-node {
  transition: opacity 300ms linear, scale 520ms var(--m27-ease-out);
  transition-delay: calc(1080ms + var(--m27-i, 0) * 210ms);
}
.h27 .m27-dash.m27-in .dashboard-v2__plot-node { opacity: 1; scale: 1; }
.h27 .dashboard-v2__plot-node:nth-of-type(1) { --m27-i: 0; }
.h27 .dashboard-v2__plot-node:nth-of-type(2) { --m27-i: 1; }
.h27 .dashboard-v2__plot-node:nth-of-type(3) { --m27-i: 2; }
.h27 .dashboard-v2__plot-node:nth-of-type(4) { --m27-i: 3; }

:where(html.m27-ready) .h27 .dashboard-v2__plot-dot { opacity: 0; scale: .2; }
.h27 .dashboard-v2__plot-dot { transition: opacity 320ms linear 2050ms, scale 700ms var(--m27-ease-out) 2050ms; }
.h27 .m27-dash.m27-in .dashboard-v2__plot-dot { opacity: 1; scale: 1; }

/* The flag slides out of the dot rather than fading on top of it. */
:where(html.m27-ready) .h27 .dashboard-v2__plot-flag { opacity: 0; translate: -8px 0; scale: .9; }
.h27 .dashboard-v2__plot-flag {
  transition:
    opacity 380ms linear 2260ms,
    translate 640ms var(--m27-ease-out) 2260ms,
    scale 640ms var(--m27-ease-out) 2260ms;
}
.h27 .m27-dash.m27-in .dashboard-v2__plot-flag { opacity: 1; translate: none; scale: 1; }

/* Gridlines fade up under the plot so the chart has a floor before the line
   arrives on it. */
:where(html.m27-ready) .h27 .dashboard-v2__plot-y span { opacity: 0; }
.h27 .dashboard-v2__plot-y span { transition: opacity 520ms linear 760ms; }
.h27 .m27-dash.m27-in .dashboard-v2__plot-y span { opacity: 1; }

:where(html.m27-ready) .h27 .dashboard-v2__chart-months span { opacity: 0; transform: translate3d(0, 5px, 0); }
.h27 .dashboard-v2__chart-months span {
  transition: opacity 400ms linear, transform 480ms var(--m27-ease);
  transition-delay: calc(1000ms + var(--m27-i, 0) * 90ms);
}
.h27 .m27-dash.m27-in .dashboard-v2__chart-months span { opacity: 1; transform: none; }
.h27 .dashboard-v2__chart-months span:nth-child(1) { --m27-i: 0; }
.h27 .dashboard-v2__chart-months span:nth-child(2) { --m27-i: 1; }
.h27 .dashboard-v2__chart-months span:nth-child(3) { --m27-i: 2; }
.h27 .dashboard-v2__chart-months span:nth-child(4) { --m27-i: 3; }
.h27 .dashboard-v2__chart-months span:nth-child(5) { --m27-i: 4; }
.h27 .dashboard-v2__chart-months span:nth-child(6) { --m27-i: 5; }

/* ── today's schedule ── */
:where(html.m27-ready) .h27 .dashboard-v2__appt { opacity: 0; transform: translate3d(14px, 0, 0); }
.h27 .dashboard-v2__appt {
  transition:
    opacity 420ms var(--m27-ease),
    transform 560ms var(--m27-ease),
    background-color 260ms var(--m27-ease);
  transition-delay: calc(1000ms + var(--m27-i, 0) * 78ms);
}
.h27 .m27-dash.m27-in .dashboard-v2__appt { opacity: 1; transform: none; }
.h27 .dashboard-v2__appt:nth-child(1) { --m27-i: 0; }
.h27 .dashboard-v2__appt:nth-child(2) { --m27-i: 1; }
.h27 .dashboard-v2__appt:nth-child(3) { --m27-i: 2; }
.h27 .dashboard-v2__appt:nth-child(4) { --m27-i: 3; }
.h27 .dashboard-v2__appt:nth-child(5) { --m27-i: 4; }
.h27 .dashboard-v2__appt:nth-child(6) { --m27-i: 5; }

/* The in-room row is the one live thing in the shot, so it gets the one
   sustained loop in this section — slow enough to read as a status light
   rather than a blink. */
.h27 .m27-dash.m27-in .dashboard-v2__appt--active .dashboard-v2__appt-tag--now {
  animation: m27-live 2600ms var(--m27-ease) 1900ms infinite;
}
@keyframes m27-live {
  0%, 100% { opacity: 1; }
  50%      { opacity: .58; }
}

/* ── chrome + topbar ── */
:where(html.m27-ready) .h27 .dashboard-v2__ai-pill { opacity: 0; transform: translate3d(0, -6px, 0); }
.h27 .dashboard-v2__ai-pill { transition: opacity 480ms linear 780ms, transform 620ms var(--m27-ease) 780ms; }
.h27 .m27-dash.m27-in .dashboard-v2__ai-pill { opacity: 1; transform: none; }

.h27 .m27-dash.m27-in .dashboard-v2__notif-badge { animation: m27-pop 560ms var(--m27-ease-out) 1050ms both; }

/* ── float badges ── */
:where(html.m27-ready) .h27 .float-badge { opacity: 0; transform: translate3d(0, 12px, 0) scale(.9); }
.h27 .float-badge {
  transition: opacity 520ms var(--m27-ease), transform 780ms var(--m27-ease-out);
  transition-delay: calc(1400ms + var(--m27-i, 0) * 160ms);
}
.h27 .m27-dash.m27-in .float-badge { opacity: 1; transform: none; }
.h27 .float-badge--success { --m27-i: 0; }
.h27 .float-badge--info    { --m27-i: 1; }
.h27 .float-badge--ai      { --m27-i: 2; }

/* The whole window tilts a degree toward the pointer. The rotation is small on
   purpose: this is a 1180px-wide object and anything past ~1.2° starts to
   distort the text inside it. JS writes --m27-rx/--m27-ry (§15). */
.h27 .h27-stage .home-dash-wrap {
  transform:
    perspective(2200px)
    rotateX(var(--m27-rx, 0deg))
    rotateY(var(--m27-ry, 0deg));
  transition: transform 700ms var(--m27-ease);
  transform-style: preserve-3d;
}
.h27 .h27-stage.m27-tilting .home-dash-wrap { transition-duration: 180ms; }


/* ══ §7 · ACT 04 · NINE MODULES ═════════════════════════════════════════════ */

:where(html.m27-ready) .h27 .mod-tabs__pill { opacity: 0; transform: translate3d(-12px, 0, 0); }
.h27 .mod-tabs__pill {
  transition:
    opacity 420ms var(--m27-ease),
    transform 540ms var(--m27-ease),
    background-color 240ms var(--m27-ease),
    color 240ms var(--m27-ease);
  transition-delay: calc(var(--m27-i, 0) * 56ms);
}
.h27 .h27-mod.m27-in .mod-tabs__pill { opacity: 1; transform: none; }
.h27 .mod-tabs__pill:nth-child(1) { --m27-i: 0; }
.h27 .mod-tabs__pill:nth-child(2) { --m27-i: 1; }
.h27 .mod-tabs__pill:nth-child(3) { --m27-i: 2; }
.h27 .mod-tabs__pill:nth-child(4) { --m27-i: 3; }
.h27 .mod-tabs__pill:nth-child(5) { --m27-i: 4; }
.h27 .mod-tabs__pill:nth-child(6) { --m27-i: 5; }
.h27 .mod-tabs__pill:nth-child(7) { --m27-i: 6; }
.h27 .mod-tabs__pill:nth-child(8) { --m27-i: 7; }
.h27 .mod-tabs__pill:nth-child(9) { --m27-i: 8; }

/* The arrow on each pill only exists once you are pointing at it. */
.h27 .h27-mod__go {
  transition: opacity 260ms var(--m27-ease), transform 340ms var(--m27-ease-out);
}
.h27 .mod-tabs__pill:not(:hover):not(.mod-tabs__pill--active) .h27-mod__go {
  opacity: 0; transform: translateX(-5px);
}
.h27 .mod-tabs__pill:hover .h27-mod__go { transform: translateX(2px); }


/* ══ §8 · ACT 05 · THE RELAY ════════════════════════════════════════════════
   The stations already carry a running highlight. What was missing is that
   they all existed before the line reached them — so the handoff had nothing
   to hand off to. They now light in sequence along the lane. */

:where(html.m27-ready) .h27 .h27-stop { opacity: 0; transform: translate3d(0, 14px, 0); }
.h27 .h27-stop {
  transition: opacity 460ms var(--m27-ease), transform 620ms var(--m27-ease);
  transition-delay: calc(var(--m27-i, 0) * 96ms);
}
.h27 .m27-in .h27-stop { opacity: 1; transform: none; }
.h27 .h27-lane .h27-stop:nth-child(1) { --m27-i: 0; }
.h27 .h27-lane .h27-stop:nth-child(2) { --m27-i: 1; }
.h27 .h27-lane .h27-stop:nth-child(3) { --m27-i: 2; }
.h27 .h27-lane .h27-stop:nth-child(4) { --m27-i: 3; }
.h27 .h27-lane .h27-stop:nth-child(5) { --m27-i: 4; }


/* ══ §9 · INTEGRATIONS MARQUEE ══════════════════════════════════════════════
   Two counter-running lanes. They now pause under the pointer, which is the
   only way to actually read one, and each name lifts on hover. */

.h27 .h27-bigrun:hover .h27-bigrun__track { animation-play-state: paused; }
.h27 .h27-bigrun__item {
  transition: color 300ms var(--m27-ease), transform 460ms var(--m27-ease-out), opacity 300ms linear;
}
.h27 .h27-bigrun:hover .h27-bigrun__item { opacity: .45; }
.h27 .h27-bigrun__item:hover { opacity: 1; transform: translateY(-3px); }


/* ══ §10 · ACT 06 · AI CARDS ════════════════════════════════════════════════
   Three cards, each containing a mock-up of the product doing the thing the
   card describes — and all three were frozen. The mock-ups now animate on
   entry: skeleton lines type themselves in, pills arrive, and the primary
   button lands last. */

:where(html.m27-ready) .h27 .ai-card__mockup-line { transform: scaleX(0); }
.h27 .ai-card__mockup-line {
  transform-origin: left;
  transition: transform 760ms var(--m27-ease-out);
  transition-delay: calc(420ms + var(--m27-i, 0) * 180ms);
}
.h27 .m27-in .ai-card__mockup-line { transform: none; }
/* Adjacent-sibling rather than :nth-of-type — the lines share a parent with
   several unclassed <div>s, so type-counting lands on the wrong elements. */
.h27 .ai-card__mockup-line + .ai-card__mockup-line { --m27-i: 1; }

:where(html.m27-ready) .h27 .notification-pill,
:where(html.m27-ready) .h27 .ai-card__mockup-pill { opacity: 0; transform: translate3d(0, -6px, 0); }
.h27 .notification-pill,
.h27 .ai-card__mockup-pill {
  transition: opacity 460ms var(--m27-ease) 260ms, transform 560ms var(--m27-ease) 260ms;
}
.h27 .m27-in .notification-pill,
.h27 .m27-in .ai-card__mockup-pill { opacity: 1; transform: none; }

/* The traffic dots on each mock-up window blink on once, in order. */
.h27 .m27-in .ai-card__mockup-bar span {
  animation: m27-pop 460ms var(--m27-ease-out) both;
}
.h27 .ai-card__mockup-bar span:nth-child(1) { animation-delay: 200ms; }
.h27 .ai-card__mockup-bar span:nth-child(2) { animation-delay: 270ms; }
.h27 .ai-card__mockup-bar span:nth-child(3) { animation-delay: 340ms; }

/* Card lift. The window inside lifts fractionally more than the card, which is
   what makes the two read as separate planes instead of one flat sticker. */
.h27 .h27-card--glass { transition: transform 520ms var(--m27-ease), box-shadow 520ms var(--m27-ease); }
.h27 .h27-card--glass:hover { transform: translateY(-5px); }
.h27 .h27-aiwin { transition: transform 620ms var(--m27-ease-out); }
.h27 .h27-card--glass:hover .h27-aiwin { transform: translateY(-3px) scale(1.012); }


/* ══ §11 · ACT 07 · THE NUMBER WALL ═════════════════════════════════════════
   The counters and the fill rules already live in home-2027.css. This adds the
   thing that was missing between them — the numeral itself resolving out of
   blur as it counts, so the count reads as the number arriving rather than as
   a digit spinner running next to a static label. */

:where(html.m27-ready) .h27 .h27-wall__num { filter: blur(6px); transform: translate3d(0, 8px, 0); }
.h27 .h27-wall__num {
  transition: filter 620ms var(--m27-ease), transform 760ms var(--m27-ease-out);
  transition-delay: calc(var(--m27-i, 0) * 80ms);
}
.h27 .h27-wall__cell.m27-in .h27-wall__num { filter: none; transform: none; }

.h27 .h27-wall__cell { transition: transform 520ms var(--m27-ease), background-color 320ms var(--m27-ease); }
.h27 .h27-wall__cell:hover { transform: translateY(-3px); }


/* ══ §12 · COVERAGE MAP ═════════════════════════════════════════════════════
   This section predates the H27 rebuild and carries the old `animate-on-scroll`
   hook, which main.js still honours for the header and the canvas — but the
   HUD panels floating over the map had nothing at all. They now settle onto the
   map from their own corners after the canvas has landed, which is also the
   order that makes them legible: map first, readings second. */

:where(html.m27-ready) .h27 .cov-map-hud { opacity: 0; }
.h27 .cov-map-hud {
  transition: opacity 620ms var(--m27-ease), transform 820ms var(--m27-ease-out);
  transition-delay: calc(420ms + var(--m27-i, 0) * 130ms);
}
:where(html.m27-ready) .h27 .cov-map-hud--tl { transform: translate3d(-14px, -10px, 0); }
:where(html.m27-ready) .h27 .cov-map-hud--tr { transform: translate3d(14px, -10px, 0); }
:where(html.m27-ready) .h27 .cov-map-hud--bl { transform: translate3d(-14px, 10px, 0); }
:where(html.m27-ready) .h27 .cov-map-hud--br { transform: translate3d(14px, 10px, 0); }
.h27 .cov-map-canvas.m27-in .cov-map-hud { opacity: 1; transform: none; }
.h27 .cov-map-hud--tl { --m27-i: 0; }
.h27 .cov-map-hud--tr { --m27-i: 1; }
.h27 .cov-map-hud--bl { --m27-i: 2; }
.h27 .cov-map-hud--br { --m27-i: 3; }

/* The instrumentation ticks draw in at the corners of the frame. */
:where(html.m27-ready) .h27 .cov-map-corner { opacity: 0; transform: scale(.5); }
.h27 .cov-map-corner {
  transition: opacity 400ms linear, transform 560ms var(--m27-ease-out);
  transition-delay: calc(240ms + var(--m27-i, 0) * 80ms);
}
.h27 .cov-map-canvas.m27-in .cov-map-corner { opacity: 1; transform: none; }
.h27 .cov-map-corner--tl { --m27-i: 0; }
.h27 .cov-map-corner--tr { --m27-i: 1; }
.h27 .cov-map-corner--bl { --m27-i: 2; }
.h27 .cov-map-corner--br { --m27-i: 3; }

/* The footer note and CTA close the section. It sits *outside* .cov-map-canvas
   in the markup, so it carries `.m27-in` itself rather than inheriting the
   canvas's — gating it on the canvas would have hidden it permanently. */
:where(html.m27-ready) .h27 .cov-map-foot { opacity: 0; transform: translate3d(0, 12px, 0); }
.h27 .cov-map-foot { transition: opacity 560ms var(--m27-ease), transform 700ms var(--m27-ease); }
.h27 .cov-map-foot.m27-in { opacity: 1; transform: none; }


/* ══ §13 · ACT 10 · FAQ ═════════════════════════════════════════════════════ */

:where(html.m27-ready) .h27 .h27-acc__item { opacity: 0; transform: translate3d(0, 12px, 0); }
.h27 .h27-acc__item {
  transition: opacity 440ms var(--m27-ease), transform 580ms var(--m27-ease);
  transition-delay: calc(var(--m27-i, 0) * 64ms);
}
.h27 .h27-acc.m27-in .h27-acc__item { opacity: 1; transform: none; }
.h27 .h27-acc__item:nth-child(1) { --m27-i: 0; }
.h27 .h27-acc__item:nth-child(2) { --m27-i: 1; }
.h27 .h27-acc__item:nth-child(3) { --m27-i: 2; }
.h27 .h27-acc__item:nth-child(4) { --m27-i: 3; }
.h27 .h27-acc__item:nth-child(5) { --m27-i: 4; }
.h27 .h27-acc__item:nth-child(6) { --m27-i: 5; }

/* The trigger nudges toward the reader on hover — a row this wide needs some
   signal that the whole thing is the hit area, not just the words. */
.h27 .h27-acc__trigger { transition: padding-left 380ms var(--m27-ease), color 240ms linear; }
.h27 .h27-acc__item:not(.is-open) .h27-acc__trigger:hover { padding-left: 8px; }


/* ══ §14 · SCROLL-DRIVEN DEPTH ══════════════════════════════════════════════
   Pure progressive enhancement, and the one genuinely new-platform thing in
   this file: where the browser supports scroll-driven animations, the act
   numerals and the background textures move at a slightly different rate to
   the content in front of them, tied to scroll position rather than to a
   timer. It costs no JS and no scroll listener — it runs off the compositor.

   Browsers without `animation-timeline` simply get the static page they get
   today. Nothing below is load-bearing. */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 900px) {

    /* The oversized act numeral drifts up past its section.

       This animates the independent `translate` property, not `transform`.
       `.h27-head--mid .h27-actno` already carries `transform: translateX(-50%)`
       for its centring, and a keyframe on `transform` would drop that and
       kick every centred numeral back to the left edge. `translate` composes
       with the existing transform instead of replacing it. */
    .h27 .h27-actno {
      animation: m27-drift-up linear both;
      animation-timeline: view();
      animation-range: entry 10% exit 90%;
    }
    @keyframes m27-drift-up {
      from { translate: 0 26px; }
      to   { translate: 0 -26px; }
    }

    /* No parallax on .h27-tex: `.h27-act` sets `isolation` but never clips, and
       the texture is `inset: 0`, so any translate on it bleeds straight into
       the neighbouring act. The numeral and the stage glow carry the depth. */

    /* The dashboard settles the last few pixels as it centres — the shot is
       still slightly small and slightly low when it first crosses the fold. */
    .h27 .h27-stage__glow {
      animation: m27-glow-swell linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 55%;
    }
    @keyframes m27-glow-swell {
      from { opacity: .25; transform: scale(.9); }
      to   { opacity: 1;   transform: scale(1); }
    }
  }
}


/* ══ §15 · POINTER SPOTLIGHT ════════════════════════════════════════════════
   On the dark acts a soft warm light tracks the pointer. It is a single
   radial-gradient layer at very low alpha over an already-textured ground —
   the point is that the surface reacts at all, not that anyone notices a
   spotlight. Coordinates come from home-motion-2027.js as percentages.

   Pointer-only: attached on `(hover: hover)` so it never fires on touch, where
   the coordinates would freeze wherever the last tap landed. */

@media (hover: hover) and (pointer: fine) {
  .h27 .m27-spot::after {
    content: '';
    position: absolute; inset: 0;
    pointer-events: none;
    z-index: 0;
    opacity: 0;
    transition: opacity 700ms var(--m27-ease);
    background: none;
  }
  .h27 .m27-spot.m27-lit::after { opacity: 1; }

  /* The lit/ice acts take a cooler, fainter version — coral over a light
     ground reads as a stain rather than as light. */
  .h27 .h27-act--lit.m27-spot::after,
  .h27 .h27-act--ice.m27-spot::after,
  .h27 .h27-act--paper.m27-spot::after {
    background: none;
  }

  /* Keep the spotlight behind everything the act actually contains. */
  .h27 .m27-spot > .h27-edge,
  .h27 .m27-spot > .h27-bigrun { position: relative; z-index: 1; }
}


/* ══ §16 · REDUCED MOTION ═══════════════════════════════════════════════════
   One switch, and it has to be total: every hidden state in this file is
   released and every sequence collapses to its end frame.

   home-motion-2027.js bails before setting `html.m27-ready` under reduced
   motion, so in practice none of the hidden states are ever armed — this block
   is the belt to that braces, and it also covers a reader who flips the OS
   setting after the page has loaded.

   It is split in two because `transform: none !important` is not safe to apply
   blanket. Several of these elements are *positioned* by a transform that has
   nothing to do with motion — the chart markers are centred on their data
   points with `translate(-50%,-50%)`, the y-axis labels with `translateY(-50%)`
   — and nuking it would scatter them across the card for exactly the readers
   who asked for less movement. Group B therefore releases only what this file
   actually set on them. */

/* ── group A · transform is ours to reset ── */
@media (prefers-reduced-motion: reduce) {
  .h27 .h27-os__cell,
  .h27 .h27-os__tile,
  .h27 .h27-os__weld span,
  .h27 .h27-bignum b,
  .h27 .h27-bignum span,
  .h27 .h27-chip,
  .h27 .h27-split__tag,
  .h27 .h27-ail__win,
  .h27 .h27-ail__corner,
  .h27 .h27-ticker__track,
  .h27 .dashboard-v2__nav-item,
  .h27 .dashboard-v2__metric,
  .h27 .dashboard-v2__metric::before,
  .h27 .dashboard-v2__metric-spark span,
  .h27 .dashboard-v2__metric-meter i,
  .h27 .dashboard-v2__plot-area svg,
  .h27 .dashboard-v2__chart-months span,
  .h27 .dashboard-v2__appt,
  .h27 .dashboard-v2__ai-pill,
  .h27 .float-badge,
  .h27 .mod-tabs__pill,
  .h27 .h27-stop,
  .h27 .ai-card__mockup-line,
  .h27 .notification-pill,
  .h27 .ai-card__mockup-pill,
  .h27 .h27-wall__num,
  .h27 .cov-map-hud,
  .h27 .cov-map-corner,
  .h27 .cov-map-foot,
  .h27 .h27-acc__item {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    clip-path: none !important;
    transition: none !important;
    animation: none !important;
  }

  /* ── group B · transform is load-bearing — release only what §6 set ──
     These four are pinned to the plot by a transform: the nodes and the end dot
     with `translate(-50%,-50%)`, the flag with `translate(calc(-100% - 11px),
     -50%)`, the y-axis labels with `translateY(-50%)`. §6 animates their
     `scale`/`translate` properties precisely so it never has to touch that, and
     this block resets only those. */
  .h27 .dashboard-v2__plot-node,
  .h27 .dashboard-v2__plot-dot,
  .h27 .dashboard-v2__plot-flag,
  .h27 .dashboard-v2__plot-y span {
    opacity: 1 !important;
    filter: none !important;
    scale: none !important;
    translate: none !important;
    transition: none !important;
  }

  .h27 .m27-spot::after { display: none !important; }
  .h27 .h27-stage .home-dash-wrap { transform: none !important; }
  .h27 .dashboard-v2__appt-tag--now { animation: none !important; }
  .h27 .h27-actno { animation: none !important; translate: none !important; }
  .h27 .h27-stage__glow { animation: none !important; }
}
