:root { --acc: #6F3DE0; --acc-30: rgba(111,61,224,0.3); --acc-12: rgba(111,61,224,0.12); --ink: #fff; --ink-2: rgba(255,255,255,0.75); --ink-3: rgba(255,255,255,0.55); }
@keyframes livePulse { 0% { transform: scale(1); opacity: 0.7; } 100% { transform: scale(2.6); opacity: 0; } }
* { box-sizing: border-box; }
/* Scroll cue parked on every page. Delete this one rule to bring the wave indicator back -- the
   markup and the scroll handler that drives it are untouched. */
[data-cue] { display: none !important; }
html { background: #000; scrollbar-gutter: stable; }
body { margin: 0; background: #000; color: #fff; font-family: "Nunito Sans", system-ui, sans-serif; -webkit-font-smoothing: antialiased; overflow-x: hidden; }
a { color: #fff; text-decoration: none; }
a:hover { color: #fff; }
::selection { background: var(--acc, #6F3DE0); color: #fff !important; }
::-moz-selection { background: var(--acc, #6F3DE0); color: #fff !important; }
input { font-family: inherit; }
button { font-family: inherit; }

/* Type scale. Nine styles replacing the thirty font-size values these pages had grown into.
   Each one bundles size, weight, line-height, tracking and colour together, because the drift
   came from every element picking those five independently. Layout stays inline; only
   typography lives here. */
/* Display styles are sentence case. Tracking goes negative because large sentence case needs
   tightening where uppercase needed opening out. */
.t-hero { font-size: clamp(24px, min(4.2vw, 7.2vh), 62px); font-weight: 600; line-height: 1.1; letter-spacing: -0.012em; color: var(--ink); }
.t-figure { font-size: clamp(28px, 3.2vw, 56px); font-weight: 200; line-height: 1; letter-spacing: -0.018em; color: var(--ink); }
.t-h2 { font-size: clamp(22px, 2.3vw, 42px); font-weight: 600; line-height: 1.16; letter-spacing: -0.01em; color: var(--ink); }
/* h3 follows h2 out of uppercase: leaving it capitalised made the smaller heading read louder than
   the larger one it sits under. */
.t-h3 { font-size: clamp(16px, 1.45vw, 24px); font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; color: var(--ink); }
.t-card { font-size: clamp(17px, 1.7vw, 26px); font-weight: 200; line-height: 1.2; letter-spacing: -0.02em; color: var(--ink); }
.t-lede { font-size: clamp(16px, 1.35vw, 22px); font-weight: 300; line-height: 1.5; letter-spacing: -0.01em; color: var(--ink-2); }
.t-body { font-size: clamp(14px, 1.2vw, 18px); font-weight: 300; line-height: 1.6; letter-spacing: -0.01em; color: var(--ink-2); }
.t-label { font-size: 11px; font-weight: 700; line-height: 1.5; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3); }
.t-action { font-size: 14px; font-weight: 700; line-height: 1; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink); }
/* A grid track is minmax(auto, ...) by default, so it grows to its widest child's min-content. The
   logo marquee is intrinsically ~2800px, which stretched the hero's single track and every sibling
   in it — the sub-lede rendered on one 831px line and got clipped. Cap the track at the container
   and stop the section's own children escaping it. */
section[data-sec] { grid-template-columns: minmax(0, 1fr); }
section[data-sec] > * { min-width: 0; }
@keyframes breathe { 0%, 100% { transform: scale(1.01) translateY(0); } 50% { transform: scale(1.07) translateY(-1.6%); } }
@keyframes breathe2 { 0%, 100% { transform: scale(1.16) translateY(1%) ; } 50% { transform: scale(1.05) translateY(-2%); } }
@keyframes mq { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes mqr { from { transform: translateX(-50%); } to { transform: translateX(0); } }
@keyframes cue { 0% { transform: translateY(-6px); opacity: 0; } 40% { opacity: 1; } 100% { transform: translateY(10px); opacity: 0; } }
@keyframes glow { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }
@keyframes enter { 0%, 4% { background-color: transparent; } 10%, 94% { background-color: var(--acc, #6F3DE0); } 100% { background-color: transparent; } }
@keyframes drain { 0% { background-color: transparent; } 12%, 42% { background-color: var(--acc, #6F3DE0); } 58%, 100% { background-color: transparent; } }
@keyframes pop { 0%, 5% { background-color: transparent; } 12%, 100% { background-color: var(--acc, #6F3DE0); } }
@keyframes popg { 0%, 5% { background-color: transparent; } 12%, 100% { background-color: rgba(255,255,255,0.3); } }
@keyframes tps { 0% { transform: scaleY(0.18); } 50% { transform: scaleY(1); } 100% { transform: scaleY(0.18); } }
@keyframes gateBlink { 0%, 46% { opacity: 1; } 47%, 100% { opacity: 0.06; } }

@keyframes gateLogoOut { 0% { opacity: 1; } 5% { opacity: 0.05; } 10% { opacity: 1; } 18% { opacity: 0.1; } 25% { opacity: 1; } 34% { opacity: 0.04; } 41% { opacity: 0.92; } 52% { opacity: 0.06; } 60% { opacity: 0.72; } 70% { opacity: 0.03; } 78% { opacity: 0.42; } 88%, 100% { opacity: 0; } }
@keyframes gateFlash { 0% { opacity: 0; } 3% { opacity: 0.5; } 7% { opacity: 0; } 16% { opacity: 0.28; } 21% { opacity: 0; } 34% { opacity: 0.18; } 39% { opacity: 0; } 55% { opacity: 0.1; } 60%, 100% { opacity: 0; } }
@keyframes gateIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/* Layout is authored as inline styles, so overrides need !important. */

/* Editor drag artifacts: fixed px boxes clip text and force wrapping at every viewport. */
h1 [style*="width: 1101px"] { width: auto !important; height: auto !important; }
/* The validator sentence needs ~1165px at its largest font step. Seven of twelve columns only
   gives it ~716px, so it broke to two lines at every width; span the full grid instead. */
[style*="width: 1016px"] { width: 100% !important; max-width: 1200px !important; height: auto !important; grid-column: 1 / -1 !important; }

/* The two-line headline needs the full grid width; 9 of 12 columns is too narrow at every size. */
h1[style*="grid-column: 1 / span 9"] { grid-column: 1 / -1 !important; }

/* "Seamless by design" cost cards.
   auto-fit fitted three columns around 768px, orphaning the fourth card alone on a second row;
   the counts below are explicit so the set always breaks 4 -> 2x2 -> 1. */
[data-cards] { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; }
@media (max-width: 1150px) { [data-cards] { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; } }
@media (max-width: 470px) { [data-cards] { grid-template-columns: minmax(0, 1fr) !important; } }

/* Keyed plates. Every data cell on the homepage -- the four proof figures, the three $0 blocks, the
   four use cases, both sides of the comparison -- is now one object: a black plate whose top-right
   corner is cut away at 45 degrees, with the hairline following the cut. A rounded card reads as an
   app surface; a machined corner reads as a part that keys into other parts, which is what Ultra is
   selling. The frame is accent-tinted rather than neutral grey so the grids belong to this palette
   instead of looking like borrowed chrome.
   Construction: the element's own background IS the frame colour and an inset pseudo-element carries
   the fill, because a border cannot follow a clip-path -- the 1px edge has to be two stacked clipped
   rectangles. isolation keeps the negative z-index inside the plate instead of punching through to
   the page behind it. */
[data-plate] {
  --notch: clamp(12px, 1.1vw, 18px);
  --plate-frame: rgba(111,61,224,0.56);
  /* Fills must be opaque. The frame colour is the element's own background sitting directly behind the
     fill, so a translucent fill lets the frame flood the plate the moment hover brightens it. */
  --plate-fill: #000;
  --plate-fill-hover: #0c0718;
  position: relative;
  isolation: isolate;
  border: 0 !important;
  background-color: var(--plate-frame) !important;
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  transition: background-color 320ms ease;
}
[data-plate]::before {
  content: "";
  position: absolute;
  inset: 1px;
  z-index: -1;
  background: var(--plate-fill);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  transition: background 320ms ease;
}
/* One change on hover, and it is the frame: the cut corner is the identity mark, so lighting the edge
   lights the mark. The fill barely moves. */
[data-plate]:hover { --plate-frame: var(--acc, #6F3DE0); }
[data-plate]:hover::before { background: var(--plate-fill-hover); }
/* The cut is a proportion of the plate, not a fixed dimension: a 17px chamfer that reads as deliberate
   on a 300px figure block looks like a rendering fault on a 620px use case. */
[data-case] { --notch: clamp(18px, 1.7vw, 28px); }

/* Plates are discrete objects now, not cells sharing a slab, so the grids carry real gaps. The use
   cases get the wider one because they arrive one at a time and need to read as four arrivals. */
[data-stats6], [data-zeros] { gap: clamp(7px, 0.7vw, 11px); }
[data-usecases] { gap: clamp(9px, 0.9vw, 14px); }

/* "What we make possible" use cases. The 900px catch-all below matches on the inline value, so a
   2-column grid needs its own hook to collapse on mobile. Body copy is capped because a half-width
   card at desktop runs to ~90ch otherwise. */
[data-usecases] { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
@media (max-width: 700px) { [data-usecases] { grid-template-columns: minmax(0, 1fr) !important; } }
[data-usecases] .t-body { max-width: 46ch; }


/* The use cases pin like the Stack track above them: the section holds the screen and each plate is
   brought in by the scroll, one at a time, from off the right. Letting them stagger past in a normal
   scroll meant the fourth had already landed before anyone had read the first. Pinned, the scroll
   becomes the thing that deals them out.
   Same construction as [data-track]: 300svh of section against a 100svh sticky child, so the contain
   range is exactly the 200svh the pin is held for. Each plate gets a move segment followed by a dwell,
   and the tail of the range is empty so the finished grid can be read before the pin releases.
   Progressive enhancement: without scroll-driven animation, or with reduced motion, or on a phone, the
   section is a plain block with all four plates in place. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 701px) {
    [data-ctrack] { height: 300svh; padding-block: 0 !important; view-timeline-name: --cases; view-timeline-axis: block; }
    [data-cpin] { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; justify-content: center; padding-block: clamp(120px, 16vh, 180px) clamp(60px, 8vh, 100px); }
    [data-case] { animation: caseIn cubic-bezier(0.16, 1, 0.3, 1) both; animation-timeline: --cases; will-change: transform; }
    [data-case]:nth-child(1) { animation-range: contain 2%  contain 17%; }
    [data-case]:nth-child(2) { animation-range: contain 25% contain 40%; }
    [data-case]:nth-child(3) { animation-range: contain 48% contain 63%; }
    [data-case]:nth-child(4) { animation-range: contain 71% contain 86%; }
  }
}
@keyframes caseIn { from { transform: translate3d(100vw, 0, 0); } to { transform: translate3d(0, 0, 0); } }

/* The $0 figures run 30% over the shared t-figure size. Scoped rather than changing the scale
   itself, because t-figure also sets "Sign in." in the comparison box, which stays as it is. */
[data-zero] .t-figure { font-size: clamp(36px, 4.2vw, 73px); }

/* Embed frame: fixed ratio so the row keeps its height whatever the iframe does inside it. */
/* margin: 0 kills the UA default on <figure> (1em 40px), which otherwise pushed the frame out of
   its own column and added 32px that stopped it squaring up with the facts beside it. */
[data-shot] { position: relative; margin: 0; width: 100%; aspect-ratio: 16 / 10; border: 1px solid rgba(255,255,255,0.16); background: #060608; overflow: hidden; }
[data-shot] > iframe { position: absolute; inset: 0; width: 100%; height: 100%; display: block; border: 0; }
/* The validator map draws its own space and must not be cropped or boxed. */
/* 2.3/1 rather than the projection's own ~2.06/1: the map is widened 10% about its centre inside
   this frame, and the extra ratio is the room that widening needs. At 2.1 it ran into the edges. */
[data-shot][data-live] { aspect-ratio: 2.3 / 1; border-color: transparent; background: none; }

/* Capped rather than full-bleed: a 4x2 of short fact plates stretched across a viewport-wide panel
   turns eight small facts into eight billboards. */
[data-facts] { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: clamp(8px, 0.9vw, 14px); max-width: 1320px; }
/* Straight to two columns rather than stepping through three: below the rail's 901px gate the
   panels stack, so this grid is reading down a narrow page, not across a pinned screen. */
@media (max-width: 900px) { [data-facts] { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 420px) { [data-facts] { grid-template-columns: minmax(0, 1fr); } }

/* Explore's opening is one pinned track with a fixed head. The h1 and the lede never move -- they
   caption all three panels -- and the panels travel sideways underneath them, same rail mechanics as
   the homepage Stack track so the two pages pin identically.
   The head is deliberately static rather than swapping copy per panel: the panels already carry a
   heading each, and cross-fading a second line of type on the same beat as the rail was tried on the
   homepage and rejected -- two things moving at once reads as a glitch, not a transition.
   No pane counter here. On the homepage it earned its place because a four-pane rail can strand you
   with no sense of how much is left; on three panels it would label an amount the reader can already
   feel the end of.
   Fallback without scroll-driven animation, on reduced motion, or below 901px: the head sits above
   the three panels stacked vertically, which is the same reading order. */
[data-ehead] { margin-bottom: clamp(26px, 4.5vh, 56px); }
[data-epane] + [data-epane] { margin-top: clamp(44px, 7vh, 86px); }
/* Height-driven, not width-driven: the map has to share one pinned screen with the head and its own
   heading, so the space left over sets its size and 2.1/1 (geoNaturalEarth1 is ~2.06:1) derives the
   width from it. width: auto overrides the 100% on [data-shot]. */
[data-epane] [data-shot] { width: auto; max-width: 100%; height: clamp(180px, 36svh, 430px); margin: clamp(6px, 1vh, 14px) 0 0; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 901px) {
    /* 370svh against a 100svh pin leaves 270svh of travel: three dwells of ~52svh and two moves of
       ~57svh. Both figures match the homepage Stack rail, so the two tracks travel at the same speed
       and hold for the same time even though this one has a pane fewer. */
    [data-etrack] { height: 370svh; padding-block: 0 !important; view-timeline-name: --etrack; view-timeline-axis: block; }
    /* Top-anchored, not centred like the homepage Stack pin. Every hero on the site hangs off the same
       clamp(130px, 25vh, 300px) so the four headlines land on one line across pages; centring would
       make this one's position depend on how tall the panels happen to be, which is exactly what used
       to put it out of step with Home, Build and Deploy. */
    [data-epin] { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; justify-content: flex-start; padding-block: clamp(130px, 25vh, 300px) clamp(48px, 6vh, 84px); }
    /* overflow: clip, not hidden -- hidden would make this an ancestor scroll container and kill the
       sticky pin. The negative margin cancels the section's side padding so a panel clips at the
       screen edge rather than at the text column, and each panel takes that padding back internally. */
    [data-epanes] { display: flex; flex: 1 1 auto; min-height: 0; overflow-x: clip; overflow-y: visible; margin-inline: calc(clamp(24px, 6vw, 96px) * -1); }
    [data-epane] { flex: 0 0 100%; margin: 0 !important; padding-inline: clamp(24px, 6vw, 96px); display: flex; flex-direction: column; animation: eRail linear both; animation-timeline: --etrack; animation-range: contain 0% contain 100%; will-change: transform; }
    /* The map takes whatever height the panel has left instead of a fixed clamp, so it runs to the
       bottom of the pinned screen. aspect-ratio derives the width from that height, and align-self
       is load-bearing either way -- the default stretch would blow the width out to full. */
    [data-epane] [data-shot] { flex: 1 1 auto; align-self: center; height: auto; min-height: clamp(180px, 24svh, 260px); margin-bottom: 0; }

    /* Panel 1 has one paragraph and a lot of empty screen under it, so the run opens oversized and
       carries that space -- it is the only live thing on the first screen and should be the thing you
       look at. It then shrinks to reading size and rides down to the foot of the pin as panel 2
       arrives, on eRail's dwell/move split, so the two read as one movement rather than as a second,
       competing animation. Two numbers to tune: --beat-rise is how far above the resting line panel 1
       holds it, --bw-big how wide a block is up there.
       --bw-big is a fraction of one rail column rather than a multiple of the resting size, because
       the resting size is capped at 27px while the columns keep widening: a multiplier would have the
       run look full at 1366 and sparse at 1920. 0.6 of a column leaves the depth face room to land
       inside the last column, so the run still stops on the page margin. */
    [data-beat] { --beat-rise: clamp(110px, 22svh, 250px); --bw-big: calc((100vw - 2 * clamp(24px, 6vw, 96px)) / 12 * 0.6); animation: eBeatDrop linear both; animation-timeline: --etrack; animation-range: contain 0% contain 100%; will-change: transform; }

    /* Top padding is deliberately not touched here -- it is what holds the hero in line with Build and
       Deploy, and 15vh has already shrunk itself on a short screen. Only the space below gives way. */
    @media (max-height: 880px) {
      [data-epin] { padding-bottom: clamp(34px, 4.5vh, 60px); }
      [data-ehead] { margin-bottom: clamp(18px, 3vh, 34px); }
      [data-epane] [data-shot] { min-height: clamp(150px, 20svh, 220px); }
      [data-epane] [data-facts] > div { min-height: clamp(72px, 9vh, 96px) !important; }
      [data-beat] { padding-top: clamp(14px, 2.4vh, 26px); }
      [data-beat-top] { margin-bottom: clamp(8px, 1.4vh, 14px); }
    }
  }
}
/* Block heartbeat. Every other chain's "live" graphic shows activity as flutter -- a jittering
   sparkline, a random pulse. Ultra's block production is the opposite of random: one block every
   500ms on a fixed schedule, a fixed run of blocks per producer, then the next producer takes over.
   So this is drawn as a ruler rather than a pulse, and the regularity is the point being made.
   The 500ms figure is the same one loadBlockHeight() advances the "Blocks produced" plate on, so
   the blocks here and the number over on panel 2 stay in step with each other.
   Twelve blocks at 500ms is the 6s round; five producer names at 6s each is the 30s full rotation.
   Both run off the same page-load clock, so the name always changes on a reset, never mid-run.
   Verified against mainnet 2026-09-15 by walking blocks 330612760-330612829: exactly 12 blocks per
   producer, 500ms apart, in schedule order eosriobrazil, cryptolions1, uosswedenorg, ivote4eosusa,
   eosphereiobp. The name list is in that order -- get_producer_schedule is not alphabetical, so it
   is the only source for it, and re-check it if the active set ever changes. */
/* Sits outside [data-epanes], as a direct child of the pin, so it holds still under all three
   panels instead of sliding away with the first one. The network is producing the whole time you
   are reading, which is the point -- it should not be an argument that only panel 1 makes. */
/* Every dimension derives from --bw, so the run can be resized by setting one length and the cubes
   keep their proportions. 0.41 is the depth ratio the two original clamps described at both ends. */
[data-beat] { margin-top: auto; padding-top: clamp(20px, 4vh, 44px);
  --bw-rest: clamp(17px, 1.9vw, 27px); --bw: var(--bw-rest); --bh: var(--bw); --bd: calc(var(--bw) * 0.41); }
[data-beat-top] { display: flex; align-items: baseline; gap: 12px; margin-bottom: clamp(12px, 2vh, 22px); }
/* A fixed-height window over a stacked list of names, stepped one name at a time. Cheaper and
   steadier than swapping text content, and there is no state to get out of sync with the blocks. */
[data-beat-who] { display: inline-block; height: 1.35em; overflow: hidden; vertical-align: bottom; font-size: clamp(14px, 1.15vw, 18px); font-weight: 300; }
[data-beat-names] { display: block; animation: beatWho 30s steps(5, end) infinite; }
[data-beat-names] > span { display: block; height: 1.35em; line-height: 1.35em; white-space: nowrap; color: var(--ink); }
[data-beat-names] i { font-style: normal; color: var(--ink-3); }
[data-beat-names] i::before { content: ", "; }

/* Oblique 45-degree projection rather than real 3D: every face is one skewed box, so there is no
   perspective container, no preserve-3d and no compositing surface per cube. Depth (--bd) reads as
   an exact pixel offset up and to the right, which is what keeps twelve of them optically identical. */
/* Runs the full content column rather than full-bleed: the left end lines up with the headline and
   the right end stops on the page margin, so the run stays inside the grid everything else sits on. */
[data-beat-rail] { position: relative; height: calc(var(--bh) + var(--bd) + 1px); overflow: hidden; }
/* Two parallel baselines one depth-offset apart are the ground plane. ::before paints ahead of the
   block rows so the far line is occluded by the cubes standing on it; ::after paints last but sits
   a pixel below them, so the near line never draws over a cube face. */
[data-beat-rail]::before { content: ""; position: absolute; left: var(--bd); right: calc(var(--bd) * -1); bottom: var(--bd); height: 1px; background: rgba(255, 255, 255, 0.09); }
[data-beat-rail]::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: rgba(255, 255, 255, 0.16); }

/* Twelve even columns, block anchored to the column start, so the stepped clip below always lands
   between blocks and can never slice one in half. Cell width is ~60px against a 38px cube footprint. */
[data-beat-row] { position: absolute; left: 0; right: 0; bottom: 1px; height: var(--bh);
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); align-items: end; justify-items: start; }
/* Depth runs up and to the right: skewX(-45deg) shifts the top face's back edge right by exactly
   --bd, skewY(-45deg) lifts the side face's back edge by the same. The two signs must disagree --
   matching them is what pulls the top face left and tears it off the side panel. */
[data-beat-row] > i { position: relative; width: var(--bw); height: var(--bh); background: rgba(0, 0, 0, 0.3); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2); }
[data-beat-row] > i::before, [data-beat-row] > i::after { content: ""; position: absolute; }
[data-beat-row] > i::before { left: 0; bottom: 100%; width: 100%; height: var(--bd); transform: skewX(-45deg); transform-origin: bottom left; background: rgba(255, 255, 255, 0.12); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2); }
[data-beat-row] > i::after { left: 100%; top: 0; width: var(--bd); height: 100%; transform: skewY(-45deg); transform-origin: left top; background: rgba(0, 0, 0, 0.44); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2); }
/* Produced blocks read as solid objects by luminance spread across the three faces, not by hue --
   a lit face at the accent's own value would vanish into the purple behind the rail. */
[data-beat-on] > i { background: #7b45ea; box-shadow: none; }
[data-beat-on] > i::before { background: #c3a8ff; box-shadow: none; }
[data-beat-on] > i::after { background: #36167c; box-shadow: none; }

/* The produced run is a second copy of the row revealed by one stepped clip, rather than twelve
   elements each on their own delay -- a per-element delay resets staggered and reads as a wave.
   The clip sits on a wrapper the full size of the rail so it cannot shave off the cube top faces. */
[data-beat-lit] { position: absolute; inset: 0; clip-path: inset(0 100% 0 0); animation: beatFill 6s steps(12, start) infinite; }
@keyframes beatFill { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes beatWho { from { transform: translateY(0); } to { transform: translateY(-100%); } }
/* Set by JS only once get_info has actually answered, at which point the name and the fill are
   driven off the real schedule instead of the loop above. Until then -- and permanently, if the
   chain is unreachable -- the CSS animations run, so the section never sits frozen on one name. */
[data-beat-live] [data-beat-names], [data-beat-live] [data-beat-lit] { animation: none !important; }
@media (prefers-reduced-motion: reduce) {
  [data-beat-lit] { animation: none; clip-path: inset(0 0 0 0); }
  [data-beat-names] { animation: none; }
}

@keyframes eRail {
  0%     { transform: translate3d(0, 0, 0); }
  19.2%  { transform: translate3d(0, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  40.4%  { transform: translate3d(-100%, 0, 0); }
  59.6%  { transform: translate3d(-100%, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  80.8%  { transform: translate3d(-200%, 0, 0); }
  100%   { transform: translate3d(-200%, 0, 0); }
}
/* Registered so it can be interpolated -- an unregistered custom property would jump between the two
   sizes at the midpoint instead of shrinking with the slide. */
@property --bw { syntax: "<length>"; inherits: true; initial-value: 17px; }
/* Keyed to eRail's first move (19.2% to 40.4%) and then held, so the run shrinks and drops once,
   with panel 2, and stays put for panels 2 and 3 rather than moving again on the second slide. */
@keyframes eBeatDrop {
  0%     { --bw: var(--bw-big); transform: translate3d(0, calc(var(--beat-rise) * -1), 0); }
  19.2%  { --bw: var(--bw-big); transform: translate3d(0, calc(var(--beat-rise) * -1), 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  40.4%  { --bw: var(--bw-rest); transform: translate3d(0, 0, 0); }
  100%   { --bw: var(--bw-rest); transform: translate3d(0, 0, 0); }
}
/* Layout only -- the frame, fill, cut corner and hover all come from [data-plate], so these read as
   the same object as the homepage stat cards rather than a second kind of box. */
[data-facts] > div { display: flex; flex-direction: column; justify-content: space-between; gap: clamp(8px, 1vw, 14px); min-height: clamp(86px, 6.8vw, 108px); padding: clamp(11px, 1.1vw, 16px); }

/* "Running on it" is a directory of five live destinations -- a name, one line, where it goes --
   not five feature stories. Rows rather than the Build page's tile grid, so the two pages stay
   visually distinct and the whole list scans in a single screen. */
[data-index] { margin: clamp(24px, 4vh, 46px) 0 0; border-top: 1px solid rgba(255,255,255,0.14); }
/* Every column is fr-based, never auto: each row is its own grid container, so an auto column
   would size to that row's own text and the columns would not line up down the list. The
   destination sits under the name it belongs to rather than in a third column, which also keeps
   the longest domain clear of the description. */
[data-item] { position: relative; display: grid; grid-template-columns: minmax(230px, 0.85fr) minmax(0, 1.65fr); gap: 0 clamp(14px, 3vw, 52px); padding: clamp(19px, 2.8vh, 34px) 0; border-bottom: 1px solid rgba(255,255,255,0.14); }
[data-item] [data-item-name] { grid-column: 1; grid-row: 1; }
[data-item] [data-item-to] { grid-column: 1; grid-row: 2; margin-top: 7px; }
[data-item] [data-item-desc] { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
/* Accent rule sweeping in from the left on hover, the same gesture as the homepage stat cards. */
[data-item]::after { content: ""; position: absolute; left: 0; bottom: -1px; height: 1px; width: 100%; background: var(--acc, #6F3DE0); transform: scaleX(0); transform-origin: left center; transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1); }
[data-item]:hover::after { transform: scaleX(1); }
[data-item] [data-item-name], [data-item] [data-item-to] { transition: color 300ms ease; }
[data-item] [data-item-to] { white-space: nowrap; }
/* The name stays white on hover. Tinting the type purple made the accent compete with the row's own
   rule for the same job and left the destination -- the thing you are actually about to do -- as the
   dimmest text in a row you had already chosen. The accent now only ever appears as the rule and the
   wash, both entering from the left edge on the same easing, so one hover reads as one gesture. */
[data-item] { background-image: linear-gradient(90deg, rgba(111,61,224,0.17), rgba(111,61,224,0) 68%); background-repeat: no-repeat; background-size: 0% 100%; transition: background-size 460ms cubic-bezier(0.22, 1, 0.36, 1); }
[data-item]:hover { background-size: 100% 100%; }
[data-item]:hover [data-item-to] { color: var(--ink); }
@media (max-width: 860px) {
  [data-item] { grid-template-columns: minmax(0, 1fr); gap: 0; }
  [data-item] [data-item-desc] { grid-column: 1; grid-row: 2; margin-top: 9px; }
  [data-item] [data-item-to] { grid-row: 3; margin-top: 11px; }
}

/* The directory is dealt out by the scroll rather than arriving as one block. Pinned, not a
   scroll-past stagger: unpinned, a five-row list clears the viewport fast enough that the last rows
   land before the first has been read. 340svh of section against a 100svh sticky child leaves 240svh
   of travel, which the contain range splits into five entrances of roughly 30svh each -- "a few
   scrolls" per row -- with the last fifth of the range empty so the finished list can be read before
   the pin releases.
   Rows carry no [data-r] here: the scroll drives them, so the IntersectionObserver reveal would be a
   second opinion about the same opacity. Without scroll-driven animation, on reduced motion, or on a
   phone, the section is a plain list with every row in place. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 701px) {
    [data-ritrack] { height: 340svh; padding-block: 0 !important; view-timeline-name: --rindex; view-timeline-axis: block; }
    [data-ripin] { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; justify-content: center; padding-block: clamp(112px, 14vh, 156px) clamp(40px, 5vh, 74px); }
    [data-ripin] [data-item] { animation: rowIn cubic-bezier(0.16, 1, 0.3, 1) both; animation-timeline: --rindex; will-change: transform, opacity; }
    [data-ripin] [data-item]:nth-child(1) { animation-range: contain 2%  contain 15%; }
    [data-ripin] [data-item]:nth-child(2) { animation-range: contain 18% contain 31%; }
    [data-ripin] [data-item]:nth-child(3) { animation-range: contain 34% contain 47%; }
    [data-ripin] [data-item]:nth-child(4) { animation-range: contain 50% contain 63%; }
    [data-ripin] [data-item]:nth-child(5) { animation-range: contain 66% contain 79%; }

    /* Five rows plus a heading have to clear one viewport. On a short laptop the row padding and the
       pin's own padding step down together rather than the pin scrolling internally, which would
       break the illusion that it is fixed. */
    @media (max-height: 880px) {
      [data-ripin] { padding-block: clamp(96px, 12vh, 128px) clamp(28px, 4vh, 52px); }
      [data-ripin] [data-item] { padding-block: clamp(12px, 1.9vh, 24px); }
      [data-ripin] [data-index] { margin-top: clamp(16px, 2.6vh, 30px); }
    }
  }
}
@keyframes rowIn { from { opacity: 0; transform: translate3d(0, 30px, 0); } to { opacity: 1; transform: translate3d(0, 0, 0); } }

/* Building blocks. The extruded top and right faces are real pseudo-elements rather than the
   runtime's style-before/style-after attributes, so all nine tiles share one definition and the
   whole set rescales from the single 18px depth value. Sized at 80% of the original tiles.
   Named blockgrid, not blocks: [data-blocks] is the runtime's live block-count hook and it
   overwrites the children of anything carrying that attribute.
   Column count comes from --cols so each pane of the track can size its own row: the groups are
   2, 3 and 4 wide and a fixed three across would have stranded a hole in the short ones. */
[data-blockgrid] { display: grid; grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); column-gap: 27px; row-gap: 18px; padding: 18px 18px 0 0; perspective: 1600px; }
@media (max-width: 1000px) { [data-blockgrid] { --cols: 2 !important; } }
@media (max-width: 640px) { [data-blockgrid] { --cols: 1 !important; } }

/* Anchored to the foot of the plate. The name used to sit at the top with "Learn more" pushed to the
   bottom by space-between; now that the whole plate is the link there is only one child, and leaving
   it top-aligned stranded it above a block of empty box. */
[data-block] { position: relative; display: flex; flex-direction: column; justify-content: flex-end; gap: clamp(14px, 1.6vw, 21px); min-height: clamp(160px, 15.2vw, 208px); padding: clamp(19px, 2.1vw, 29px); background: linear-gradient(158deg, #141220 0%, #0a0a0d 64%, #070708 100%); border: 1px solid rgba(255,255,255,0.14); transition: transform 340ms cubic-bezier(0.16,1,0.3,1), background 340ms ease; }
[data-block]:hover { transform: translate(4px, -4px); background: linear-gradient(158deg, rgba(111,61,224,0.24) 0%, #0a0a0d 72%); }
/* Top and right faces of the extrusion. The top face carries no left border: that border drew a
   bright diagonal down the plate's top-left corner, and at list size it was the most visible edge on
   the box, so the solid read as extruded to the left. The right face is lit hard enough to be seen
   against the page -- at its old 0.1 it vanished into the background below about 200px wide, which
   left the plate with a lit left edge and nothing on the right. */
/* Opaque, not a white wash over whatever is behind. These were translucent, which made the lighting
   depend on the backdrop: the one row that happens to sit over the purple wave art read as a lit
   solid, and every row over plain black lost both faces and read as a wireframe outline of a box.
   The body fill is opaque for the same reason, so the faces have to be too or the object is only
   half a solid. */
[data-block]::before { content: ""; position: absolute; left: 0; right: 0; top: -18px; height: 18px; background: linear-gradient(100deg, #4b4665, #2a2637); border: 1px solid rgba(255,255,255,0.15); border-bottom: 0; border-left: 0; transform: skewX(-45deg); transform-origin: bottom left; }
[data-block]::after { content: ""; position: absolute; top: 0; bottom: 0; right: -18px; width: 18px; background: linear-gradient(to bottom, #423d59, #1e1b28); border: 1px solid rgba(255,255,255,0.2); border-left: 0; transform: skewY(-45deg); transform-origin: top left; }
/* The plates are links now, so they are keyboard stops. The site sets no focus style anywhere, and
   the UA ring is easy to lose against the plate's near-black fill. */
[data-block]:focus-visible { outline: 2px solid var(--acc, #6F3DE0); outline-offset: 3px; }

/* Try the Ultra Agent Knowledge Base. A single left-aligned stack: heading, one line of what it is,
   then the prompt full width -- the widest thing in the section is the thing you are meant to take
   away from it. */
/* Capped near 70 characters. The section runs the full page width, which would otherwise put this
   line well past where type at lede size stops being comfortable to read. */
[data-kb-sub] { margin: clamp(16px, 2.2vh, 26px) 0 0; max-width: 62ch; }
[data-kb] { margin-top: clamp(30px, 4.4vh, 58px); }
/* Sized to the prompt rather than to the page. The full content column left most of the plate empty
   and pushed the Copy button a clear half-screen away from the string it copies, which made it read
   as a section-level control instead of as the action on that line. fit-content keeps that true now
   that the string sets its own width: the plate hugs the one unbroken line. */
[data-kb] [data-kb-prompt] { width: fit-content; max-width: 100%; padding: clamp(18px, 2.2vw, 30px) clamp(18px, 2.4vw, 34px) clamp(20px, 2.4vw, 32px); }
/* Padded clear of the notch: the cut corner is the plate's identity mark and the label must not
   run under it. */
[data-kb] [data-kb-prompt-tag] { display: block; margin-bottom: clamp(12px, 1.7vh, 20px); padding-right: clamp(30px, 3vw, 56px); }
/* Button beside the string rather than off in the corner, so it reads as the action on that string. */
[data-kb] [data-kb-prompt-row] { display: flex; align-items: center; justify-content: space-between; gap: clamp(18px, 2.4vw, 40px); }
/* Monospace because this is a literal string to be pasted, not a styling choice: proportional type
   here would misrepresent it as prose. One unbroken line on desktop -- a wrapped prompt reads as two
   instructions -- so the size tracks the viewport to keep it fitting; below 1100px it wraps instead,
   because holding one line there would need type too small to read. The breakpoint sits well above
   where the line stops fitting on this machine on purpose: Menlo on macOS sets ~9% wider than the
   Consolas measured here, and with nowrap an underestimate clips the string rather than wrapping. */
[data-kb] [data-kb-prompt-text] { display: block; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: clamp(13px, 1.05vw, 17px); line-height: 1.65; color: var(--ink); word-break: break-word; }
@media (min-width: 1100px) {
  [data-kb] [data-kb-prompt-text] { white-space: nowrap; word-break: normal; font-size: clamp(11px, 1vw, 17px); }
}
[data-kb] [data-kb-copy] { flex: 0 0 auto; padding: 10px 18px; background: transparent; border: 1px solid rgba(255,255,255,0.28); border-radius: 8px; color: var(--ink); cursor: pointer; transition: background 220ms ease, border-color 220ms ease; }
[data-kb] [data-kb-copy]:hover { background: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.5); }
/* The only state change on the page that answers a click in place, so it is the accent that
   confirms it -- a colour the rest of the section does not use for anything passive. */
[data-kb] [data-kb-copy][data-copied] { background: var(--acc, #6F3DE0); border-color: var(--acc, #6F3DE0); }
[data-kb] [data-kb-act] { margin-top: clamp(28px, 4vh, 52px); }
@media (max-width: 860px) {
  [data-kb] [data-kb-prompt-row] { flex-direction: column; align-items: flex-start; gap: 18px; }
}

/* Deploy: the eight-week phases. Equal-height plates so the three week ranges land on one axis and
   read as a row the eye can add up to the number in the heading. */
[data-phases] { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(10px, 1.2vw, 18px); }
@media (max-width: 900px) { [data-phases] { grid-template-columns: minmax(0, 1fr); } }
[data-phase] { display: flex; flex-direction: column; gap: clamp(8px, 1.1vh, 13px); padding: clamp(20px, 2.2vw, 32px); }
[data-phase] .t-body { color: var(--ink-3); }
/* margin-top:auto pins the ranges to one axis across the three plates so the eye can add them up to
   the number in the heading. */
[data-phase] [data-phase-when] { margin-top: auto; padding-top: clamp(14px, 2vh, 22px); color: var(--ink); }

/* Targeted by class rather than child position. These rules used to say :first-child / :last-child,
   which silently encoded "figure above label"; when the order flipped to match the stats grid, the
   label's two-line height reservation landed on the 73px figure and opened 188px of dead space.
   The reservation itself is gone -- the grid hairline now draws the rule the label used to carry. */
[data-zero] .t-label { color: rgba(255,255,255,0.7); transition: color 320ms ease; }
[data-zero] .t-figure { transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1); }
[data-zero]:hover .t-label { color: #fff; }
[data-zero]:hover .t-figure { transform: translateY(-4px); }
@media (hover: none) { [data-zero] .t-label { color: #fff; } }
@media (prefers-reduced-motion: reduce) { [data-zero] .t-figure { transition: none; } [data-zero]:hover .t-figure { transform: none; } }

/* Nav links stay full white; hover adds a glow rather than a box behind the word, so the accent
   stays reserved for the one thing in the header worth clicking. */
/* ---- Pinned claim track -------------------------------------------------------------------
   The Stack claims share one opening statement, so they scroll as one pinned sequence rather than
   separate screens. Everything here is progressive enhancement: the base rules below lay the four
   panes out stacked and readable, and only the @supports block turns them into a cross-faded track.
   A browser without scroll-driven animation, or a reader who asked for less motion, gets the plain
   stacked version and loses nothing but the transition. */
/* Pane counter. The track opens on a title pane that otherwise repeats the hero's shape, so the
   counter is what tells the reader the pin is a sequence with more coming, not a stall. Current
   number sits at full ink and the total stays dimmed, so position reads before the arithmetic. */
[data-pane-num] { display: block; margin-bottom: clamp(14px, 2vh, 24px); color: var(--ink); }
[data-pane-num] span { color: var(--ink-3); }

[data-track] { padding: clamp(88px, 9vh, 120px) clamp(24px, 6vw, 96px) clamp(40px, 4.4vh, 72px); }
[data-pane] { margin-top: clamp(64px, 10vh, 132px); }
[data-pane="1"] { margin-top: clamp(18px, 3vh, 40px); }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 901px) {
    /* 430svh of track against a 100svh pin leaves 330svh of travel, which the contain range splits
       into four dwells of roughly half a screen each plus three longer transitions. The extra 30svh
       over a flat 400 is what buys the slower pane-to-pane move without shortening the dwells --
       taking it out of the dwells instead would have slowed the travel by stealing reading time. */
    [data-track] { height: 430svh; padding: 0; view-timeline-name: --track; view-timeline-axis: block; }
    /* Top padding clears the 88px fixed header and then some. Centring alone put the heading ~36px
       under the header, close enough to read as stuck to it; the pin needs to look deliberately
       placed on the screen, not pushed up against the chrome.
       The panes are unequal heights but sit in one flex row, so the row is a single box: centring
       it still leaves every heading on one line. The headings ride the row, not their own pane. */
    [data-pin] { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; justify-content: center; padding: clamp(120px, 16vh, 180px) clamp(24px, 6vw, 96px) clamp(100px, 13vh, 150px); }
    /* The panes are one horizontal rail, not separate things that fade in place. They sit edge to
       edge in a flex row, each exactly one viewport wide, and all of them carry the identical transform
       animation -- so they travel together and the row reads as sideways scrolling rather than as a
       swap. The rail is full-bleed: negative margins cancel the pin's side padding and each pane takes
       that padding back internally, so a pane clips at the screen edge, not at the text column.
       overflow: clip rather than hidden, because hidden here would make an ancestor scroll container
       and kill the sticky pin. */
    [data-panes] { display: flex; overflow-x: clip; overflow-y: visible; margin-inline: calc(clamp(24px, 6vw, 96px) * -1); }
    [data-pane] { flex: 0 0 100%; margin: 0; padding-inline: clamp(24px, 6vw, 96px); animation: paneRail linear both; animation-timeline: --track; animation-range: contain 0% contain 100%; will-change: transform; }

    /* The pin is one viewport tall, so on a short laptop screen its contents have nowhere to go and
       would spill behind the next section. Padding and cell heights step down together below 880px
       rather than the pin scrolling internally, which would break the illusion that it is fixed. */
    @media (max-height: 880px) {
      [data-pin] { padding-top: clamp(110px, 15vh, 150px); padding-bottom: clamp(70px, 9vh, 110px); }
      [data-panes] [data-stats6] > div, [data-panes] [data-zero] { min-height: clamp(64px, 8vh, 96px) !important; padding: clamp(11px, 1.5vh, 18px) !important; }
      [data-panes] [data-step] { padding: clamp(9px, 1.2vh, 15px) !important; }
      [data-panes] [data-block] { min-height: clamp(104px, 13vh, 150px); }
      [data-panes] [data-pane-num] { margin-bottom: clamp(8px, 1.2vh, 14px); }
      [data-panes] .t-body { font-size: clamp(13px, 1.6vh, 16px); }
    }
  }
}
/* Holds at 0 / -100% / -200% / -300% give each pane a stationary spell to be read in; the three move
   segments carry an ease so the rail settles into place instead of stopping dead on a linear scroll. */
@keyframes paneRail {
  0%     { transform: translate3d(0, 0, 0); }
  8.25%  { transform: translate3d(0, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  25.75% { transform: translate3d(-100%, 0, 0); }
  41.5%  { transform: translate3d(-100%, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  59%    { transform: translate3d(-200%, 0, 0); }
  74.75% { transform: translate3d(-200%, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  92.25% { transform: translate3d(-300%, 0, 0); }
  100%   { transform: translate3d(-300%, 0, 0); }
}

/* ---- Building-blocks list track (Build) ----------------------------------------------------
   Same pin-and-scroll construction as [data-track], opposite behaviour. The Stack claims compete,
   so that rail shows one pane at a time; the components are one inventory, so this track
   accumulates -- each group rises into its slot and stays, and the pin ends with all five rows on
   screen at once. Reading back over what you have already passed is the point.
   The rows keep their grid slots from the start even while transparent, so nothing reflows as they
   arrive: the list fills in rather than growing downward and pushing itself off-centre.
   Base rules stack the rows plainly for mobile, for no scroll-timeline support, and for reduced
   motion -- all of which get the whole inventory immediately, which is the honest fallback. */
[data-btrack] { padding: clamp(88px, 9vh, 120px) clamp(24px, 6vw, 96px) clamp(40px, 4.4vh, 72px); }
[data-brow] { margin-top: clamp(48px, 7vh, 96px); }
[data-brow="1"] { margin-top: clamp(18px, 3vh, 40px); }
[data-brow-head] { margin: 0 0 clamp(14px, 2.2vh, 26px); }

/* 1100px, not the 901px the homepage rail uses. This track gives up a column of width to the group
   names and still has to fit four plates beside them, and below about 1100 those plates are too
   narrow to hold a component name -- the plain stacked fallback is the better answer there. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 1100px) {
    /* 360svh against a 100svh pin gives 260svh of travel: five reveals of ~12% each with a beat
       between them, then a third of the track left over as a dwell so the finished list can be read
       without the next section already pulling up.
       The margin is what keeps the pin out of the intro's screen. A sticky pin becomes visible the
       moment its section enters the viewport, so without a gap the empty pin slides up under the
       intro's CTA while that section is still being read. */
    [data-btrack] { height: 360svh; margin-top: clamp(56px, 10vh, 150px); padding: 0; view-timeline-name: --btrack; view-timeline-axis: block; }
    [data-bpin] { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; justify-content: center; padding: clamp(104px, 14vh, 152px) clamp(24px, 6vw, 96px) clamp(70px, 9vh, 104px); }
    /* column-reverse, so the list builds upward: row 1 is the chain and lands at the bottom, and
       every later group stacks on what is already there. Source order stays foundation-first, which
       is what the plain stacked fallback and a screen reader want; only the pinned view inverts. */
    [data-blist] { display: flex; flex-direction: column-reverse; gap: clamp(10px, 1.5vh, 20px); }
    /* Group name in a fixed right-hand column against its plates, so the five rows share one spine
       and read as a list. Stacked headings would have cost a line of height per row, which is the
       budget that lets all seventeen plates fit on one screen. The names start on a common left
       edge rather than flushing to the page margin: flush right left a gap that grew with every
       short name, so each heading drifted away from the row it belongs to. */
    /* Every row enters at the top of the list and slides down to its own slot, so the distance is a
       row pitch per slot it has to pass. Reveal order runs bottom-up, which means row 1 falls the
       whole height to the floor and each later group stops higher on what is already there -- the
       pile fills from the top of the screen rather than each row easing in where it will sit.
       --brow-lead keeps the topmost row moving at all, and is short because anything longer sends it
       up behind the section heading. The pitch is in svh rather than derived from the row's
       min-height: rows run taller than their minimum once the plate labels are in, so the min-height
       understates the real spacing by about 17px a row and row 1 would stop short of the top.
       The easing holds the row up, drops it through the middle of its range and settles; an ease-out
       put it down in the first few pixels of scroll and left the rest of the range as dead travel. */
    [data-brow] { --brow-pitch: clamp(84px, 12.4svh, 130px); --brow-lead: clamp(16px, 2.6svh, 30px); --brow-slot: 0; --brow-drop: calc(var(--brow-pitch) * var(--brow-slot) + var(--brow-lead)); display: grid; grid-template-columns: minmax(0, 1fr) clamp(170px, 17vw, 260px); align-items: center; column-gap: clamp(20px, 2.4vw, 40px); margin: 0; animation: browDrop cubic-bezier(0.55, 0, 0.2, 1) both, browFade cubic-bezier(0.2, 0.9, 0.3, 1) both; animation-timeline: --btrack; will-change: transform, opacity; }
    /* Explicit placement, because the heading comes first in source and would otherwise auto-place
       into the plates' column. */
    [data-brow-head] { grid-area: 1 / 2; margin: 0; text-wrap: balance; }
    /* Every row spans the full width on its own --cols, so a two-plate row stretches to meet a
       four-plate one. Plate widths differ row to row, but the stack keeps one flush silhouette --
       ragged right-hand ends read as an unfinished grid rather than as a built pile. */
    [data-bpin] [data-blockgrid] { grid-area: 1 / 1; }
    /* --brow-slot counts down, not up: the list is column-reverse, so row 5 sits at the top with no
       slots above it to fall past and row 1 sits on the floor with four. */
    [data-brow="1"] { animation-range: contain 3%  contain 15%; --brow-slot: 4; }
    [data-brow="2"] { animation-range: contain 17% contain 29%; --brow-slot: 3; }
    [data-brow="3"] { animation-range: contain 31% contain 43%; --brow-slot: 2; }
    [data-brow="4"] { animation-range: contain 45% contain 57%; --brow-slot: 1; }
    [data-brow="5"] { animation-range: contain 59% contain 71%; --brow-slot: 0; }

    /* Seventeen plates at their standalone 160-208px will not fit five rows deep in one viewport.
       They keep the extrusion and the fill -- the plate language is the same object -- and lose only
       the height they were using as breathing room when they were the whole section. */
    [data-bpin] [data-blockgrid] { row-gap: 14px; }
    [data-bpin] [data-block] { min-height: clamp(70px, 9vh, 108px); padding: clamp(12px, 1.4vw, 18px); }
    [data-bpin] .t-card { font-size: clamp(13px, 1.05vw, 18px); }

    @media (max-height: 880px) {
      [data-bpin] { padding-top: clamp(96px, 13vh, 132px); padding-bottom: clamp(58px, 7.5vh, 88px); }
      [data-bpin] [data-blockgrid] { row-gap: 11px; }
    }
  }
}
/* Rows come down onto the pile rather than up out of it: the list builds upward, so each group
   arrives at the top of the list and lands on what is already stacked.
   Two animations rather than one because the fade and the fall want opposite curves and a keyframe's
   timing function governs every property in that keyframe. The fall holds high, drops through the
   middle and settles; the fade has to be over almost immediately, or the row spends the top of its
   travel invisible and reads as appearing halfway down instead of entering from the top. */
@keyframes browDrop {
  from { transform: translate3d(0, calc(var(--brow-drop) * -1), 0); }
  to   { transform: translate3d(0, 0, 0); }
}
@keyframes browFade {
  from { opacity: 0; }
  14%  { opacity: 1; }
  to   { opacity: 1; }
}

/* ---- Contact CTA hold ----------------------------------------------------------------------
   The closing CTA used to pass at scroll speed, which gave the last line on the page no more weight
   than the footer beneath it. The section runs 140svh against a 100svh sticky child, so once it has
   filled the screen it holds there for the remaining 40svh -- two or three wheel notches -- before
   releasing into the footer.
   The section is switched to block flow here because a sticky child of a centred flex container
   offsets from its centred position rather than the section top, which shortens the hold to
   something arbitrary. Bottom padding stays heavier than top so the block sits above centre.
   Progressive enhancement: below 901px, or under reduced motion, it is the plain centred block it
   has always been and nothing holds. */
@media (prefers-reduced-motion: no-preference) and (min-width: 901px) {
  [data-hold] { display: block !important; height: 140svh; padding-top: 0 !important; padding-bottom: 0 !important; }
  [data-holdpin] { position: sticky; top: 0; height: 100svh; padding: clamp(72px, 10vh, 130px) 0 clamp(134px, 17vh, 230px); }
}

/* Inline exits. The homepage previously had no link out of its body at all -- every route on was the
   nav or one of three copies of the same booking form -- so a reader who wanted to evaluate rather
   than talk to sales had nowhere to go. Underline sits off the baseline so descenders stay legible. */
[data-inline-link] { align-self: flex-start; display: inline-block; color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--acc, #6F3DE0); padding-bottom: 3px; transition: border-color 220ms ease, color 220ms ease; }
[data-inline-link]:hover { color: #fff; border-bottom-color: #fff; }

[data-navlink] { position: relative; padding: 10px 14px; transition: text-shadow 220ms ease; }
[data-navlink]:hover { text-shadow: 0 0 14px rgba(255,255,255,0.75), 0 0 32px rgba(255,255,255,0.35); }
/* Current page. The rule is inset to the text rather than the padding box so it reads as an
   underline on the word, not a tab. */
[data-navlink][aria-current="page"]::after { content: ""; position: absolute; left: 14px; right: 14px; bottom: 3px; height: 2px; background: #fff; }

/* Header CTA. White hairline outline at rest, filling with accent on hover -- the same treatment and
   the same border colour as the page-bottom CTA, so "Book a call" is one recognisable object wherever
   it appears rather than two buttons that happen to share a label. Outlined at rest also keeps the
   header quiet: a filled button sitting over every hero competed with the headline it was floating on,
   and the colour now arrives as a response to the pointer instead of shouting from page load.
   Radius is 8px, not the hero's 12px -- the same number on a 40px button reads far rounder than on a
   58px one, so it is scaled to hold the same proportion. */
[data-cta] { display: inline-flex; align-items: center; white-space: nowrap; padding: 12px 24px; border: 1px solid rgba(255,255,255,0.34); border-radius: 8px; background: transparent; color: var(--ink); }
[data-cta]:hover { background: var(--acc, #6F3DE0); border-color: var(--acc, #6F3DE0); color: #fff; }

/* Secondary routes out of the claim track. Same small geometry as the header CTA so they read as
   buttons, but outlined rather than filled: a solid accent pair here would compete with "Book a
   call", which is meant to be the only committing action on the page. */
[data-btn-sm] { display: inline-flex; align-items: center; white-space: nowrap; padding: 11px 22px; border: 1px solid rgba(255,255,255,0.3); border-radius: 8px; color: var(--ink); }
[data-btn-sm]:hover { border-color: #fff; color: #fff; }

/* Every committing action on the site -- the header button and the five Book a call buttons -- lifts
   on hover into a stacked hard shadow: three one-pixel offsets rather than a blur, so the button reads
   as a solid object picked up off the page instead of a glowing one. The element moves left while the
   shadow falls right, which is what sells the lift; without the move it just grows a smear.
   Shadow is white: on a purple button a purple shadow reads as a smear of the same object, where white
   separates the two edges and the lift actually registers. */
[data-cta], [data-btn], [data-btn-sm] { transition: transform 200ms ease-in-out, box-shadow 200ms ease-in-out, background 200ms ease-in-out, border-color 200ms ease-in-out, color 200ms ease-in-out; }
[data-cta]:hover, [data-btn]:hover, [data-btn-sm]:hover { transform: translateX(-3px); box-shadow: 1px 1px #fff, 2px 2px #fff, 3px 3px #fff; }
[data-cta]:active, [data-btn]:active, [data-btn-sm]:active { transform: translateX(0); box-shadow: 1px 1px #fff; }
@media (prefers-reduced-motion: reduce) {
  [data-cta], [data-btn], [data-btn-sm] { transition: background 200ms ease, border-color 200ms ease, color 200ms ease; }
  [data-cta]:hover, [data-btn]:hover, [data-btn-sm]:hover, [data-cta]:active, [data-btn]:active, [data-btn-sm]:active { transform: none; }
}

@media (max-width: 900px) {
  header { flex-wrap: wrap !important; gap: 12px !important; padding: 12px 18px !important; }
  header img { height: 31px !important; }
  header nav { flex-wrap: wrap !important; gap: 14px !important; row-gap: 8px !important; }
  header nav a { font-size: 13px !important; letter-spacing: 0.1em !important; }
  header nav span a { padding: 8px 10px !important; }

  section[data-sec] { padding-left: 18px !important; padding-right: 18px !important; }

  [style*="grid-template-columns: repeat(12, 1fr)"],
  [style*="grid-template-columns: repeat(10, minmax(0, 1fr))"],
  [style*="grid-template-columns: repeat(10, 1fr)"],
  [style*="grid-template-columns: repeat(6, 1fr)"],
  [style*="grid-template-columns: repeat(4, 1fr)"],
  [style*="grid-template-columns: 1fr 1fr"] { grid-template-columns: 1fr !important; }
  [style*="grid-column:"] { grid-column: 1 / -1 !important; }

  /* The hero sub-line breaks one sentence per line. Too narrow for that here, so let
     it flow as a paragraph rather than rag badly. The markup carries a space after the
     <br> so the sentences do not run together once the break is gone. */
  section[data-sec="01"] p br { display: none; }

  [style*="align-items: flex-end"] { align-items: flex-start !important; }
  [style*="align-self: flex-end"] { align-self: flex-start !important; }

  h1 [style*="white-space: nowrap"], h2 [style*="white-space: nowrap"] { white-space: normal !important; }
  h1, h2 { overflow-wrap: anywhere !important; }

  /* The page-level buttons are sized for desktop — the widest carries a 22px face and 70px of
     side padding, which is wider than the screen. Normalise the face and let labels wrap. */
  [data-btn] { padding: 18px 32px !important; font-size: 14px !important; white-space: normal !important; max-width: 100%; text-align: center; }

}

/* Footer. Nested halves rather than a 12-col grid: the row splits 50/50 and the link half splits
   again, which lands Company at 50% and Help at 75% — the same axes the old grid-column 7 and 10
   produced, without the twelve tracks nothing else in the footer used. */
[data-foot] { padding: clamp(36px, 5vh, 58px) clamp(24px, 6vw, 96px) clamp(56px, 8vh, 92px); background: linear-gradient(180deg, transparent, rgba(0,0,0,0.55) 55%, rgba(0,0,0,0.8)); }
[data-foot-row] { display: grid; grid-template-columns: 1fr 1fr; align-items: start; gap: clamp(20px, 3vw, 48px); }
[data-foot-brand] { display: flex; flex-direction: column; gap: clamp(16px, 2.4vh, 24px); }
/* Negative nudge because the label's half-leading pushes its cap-height a few px below the top of
   the logo bitmap next to it. Optical alignment, not box alignment. */
[data-foot-cols] { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(20px, 3vw, 48px); margin-top: -5px; }
[data-foot-col] { display: flex; flex-direction: column; gap: 15px; }
/* The heading is the quiet label and the links carry the weight. It used to be the other way round:
   a 26px t-card heading over dimmed body links made the two words you cannot click the loudest
   thing in the footer. */
[data-foot-col] > .t-label { margin-bottom: 5px; }
/* Near-white at weight 400. The 300-weight ink-2 these used to carry made them read as disabled:
   at footer size, thin plus 75% opacity is below the point where a link looks clickable. */
[data-foot-col] a { font-size: 17px; font-weight: 400; line-height: 1.35; letter-spacing: -0.01em; color: rgba(255,255,255,0.9); transition: color 200ms ease; }
[data-foot-col] a:hover { color: var(--ink); }
/* Columns lead on narrow screens so the brand block closes the page rather than trailing links
   after the copyright line. */
@media (max-width: 760px) {
  [data-foot-row] { grid-template-columns: 1fr; gap: 40px; }
  [data-foot-cols] { order: -1; }
}

/* Declared after the 900px block on purpose: these carry the same specificity as the
   [style*="grid-template-columns"] stacking rules above, so source order is what makes them win.
   The blanket single-column stack is wrong for these three — six proof cards and four $0 figures
   in one column each run longer than a phone screen and break the "all on one page" premise. */
/* 2x2: four figures, all of them track record. The live-performance pair moved to Explore. */
[data-stats6] { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
@media (max-width: 460px) { [data-stats6] { grid-template-columns: minmax(0, 1fr) !important; } }

/* Hover reuses the $0 blocks' vocabulary — accent rule sweeping in, figure nudging up — so the
   two grids feel like one system. Neither property affects layout, so nothing reflows. */
[data-stats6] > div { position: relative; overflow: hidden; }
[data-stats6] > div::after { content: ""; position: absolute; left: 1px; right: 1px; bottom: 1px; height: 2px; background: var(--acc, #6F3DE0); transform: scaleX(0); transform-origin: left center; transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1); }
[data-stats6] > div:hover::after { transform: scaleX(1); }
[data-stats6] > div > span:last-child { transition: transform 380ms cubic-bezier(0.22, 1, 0.36, 1); }
[data-stats6] > div:hover > span:last-child { transform: translateY(-3px); }

/* Three across holds all the way down to phone width: the figure clamp shrinks it, and the label
   min-height above keeps the three hairlines on one axis when the labels wrap to two lines.
   Below 460px three columns leave too little room for "Transaction fees" to wrap cleanly. */
[data-zeros] { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
@media (max-width: 460px) { [data-zeros] { grid-template-columns: minmax(0, 1fr) !important; } }

/* Two sides with a "vs" between them, headings sitting outside the plates so the plates are only
   the comparison itself. The sides are equal width -- the six obligations sit two across rather
   than three so they fit that half -- and the centre column is auto so the marker costs only what
   it needs. */
[data-contrast] { grid-template-columns: 1fr auto 1fr !important; }

/* Each obligation is its own plate in a 2x3 grid, so the count reads as six separate things to do
   rather than one list. The left plates stay unaccented because they are the status quo; only the
   Ultra plate opposite carries the accent frame. */
[data-steps] { gap: clamp(7px, 0.7vw, 11px); }
[data-step] { --plate-frame: rgba(255,255,255,0.18); --plate-fill-hover: #0a0a0a; display: flex; align-items: baseline; gap: clamp(8px, 0.9vw, 14px); padding: clamp(12px, 1.2vw, 18px); }
[data-step]:hover { --plate-frame: rgba(255,255,255,0.34); }
[data-step] > .t-label { flex: none; color: var(--ink); }
[data-step] > .t-body { color: var(--ink); }
[data-signin] { --plate-fill: #0e0820; --plate-fill-hover: #170d2e; }

/* Holds the "vs" level with the plates instead of with the plates plus the headings above them. */
[data-vs-spacer] { visibility: hidden; }

/* A second grid row rather than a child of the Ultra Stack column. Inside the column it took height
   from the Sign in plate, which has to stay level with the six-step list it is argued against;
   here the plate keeps its full height and the button still starts on the column's left edge. */
/* Row gap lives here, not in the element's inline `gap`, so the button can sit tight under the Sign
   in plate while the columns keep their much wider horizontal gutter. */
[data-contrast] { row-gap: clamp(10px, 1.5vh, 18px); }
[data-contrast] > [data-contrast-cta] { grid-column: 3; justify-self: start; }

@media (max-width: 700px) {
  [data-contrast] { grid-template-columns: minmax(0, 1fr) !important; }
  [data-contrast] > [data-contrast-cta] { grid-column: 1; }
  /* Stacked, the two sides sit one above the other, so the "vs" needs no heading-height offset. */
  [data-vs] { padding: clamp(4px, 1.4vh, 12px) 0; }
  [data-vs-spacer] { display: none; }
}

@media (max-width: 440px) {
  [data-steps] { grid-template-columns: minmax(0, 1fr) !important; }
}

@media (max-width: 560px) {
  section[data-sec] { padding-top: clamp(56px, 9vh, 96px) !important; padding-bottom: clamp(56px, 9vh, 96px) !important; }
  /* The fixed header wraps to three rows below 480px and stands 136px tall, but the rule above
     reserves only 76px, so the first heading loaded underneath it. Only the first section needs
     the clearance -- every later one is reached by scrolling, which clears the header anyway. */
  main > section[data-sec]:first-of-type { padding-top: 152px !important; }
  /* The CTA lede is broken one sentence per line. Too narrow for that here, so the second
     sentence wraps anyway and the forced break just makes the rag uneven. */
  section[data-sec="12"] p br { display: none; }
  /* "Bring your business onchain" alone needs about 320px at the smallest heading step, which is
     the whole column on a phone. Let the heading find its own breaks rather than forcing three. */
  section[data-sec="01b"] h2 br { display: none; }
}
