: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. */
/* scale, not a smaller height: in the pinned layout the figure is flex: 1 1 auto, so its height is
   whatever the panel has left over and there is no number to reduce. Downscaling a composited iframe
   costs nothing in sharpness -- it is the upscale direction that softens. */
[data-shot][data-live] { aspect-ratio: 2.3 / 1; border-color: transparent; background: none; transform: scale(0.9); }

/* Capped rather than full-bleed: a 4x2 of short fact plates stretched across a viewport-wide panel
   turns eight small facts into eight billboards. */
/* Column gap is Build's flat 27px. These cells are 291px to Build's 253px, close enough that the same
   absolute number reads as the same amount of air -- which is why the homepage, whose cells are
   682px, deliberately does not copy it.
   Row gap is Build's vertical distance rebuilt, not Build's row-gap. Copying the 27/18 pair looked
   tighter than Build and it took measuring to see why: Build's groups are each a one-row grid, so the
   space between two of its blocks is never row-gap at all. It is [data-blist]'s clamp(10px, 1.5vh,
   20px) plus the 18px of extrusion padding on the grid below -- 31px at 1440x900, where a copied
   row-gap gives 18px and the top face fills the whole of it. The calc reproduces both halves so the
   two sections stay level as the viewport height changes.
   That 18px top/right padding is room for the extrusion, which is drawn outside the box and would
   otherwise be cut off by the pane's overflow clip. */
[data-facts] { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: 1fr; column-gap: 27px; row-gap: clamp(28px, calc(18px + 1.5vh), 38px); padding: 18px 18px 0 0; 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; }

/* Extension panel. A drawn browser window: toolbar, the store listing the address bar names, the
   real install button on that page, and the wallet popup dropped over it. The install is shown as
   the state you end up in rather than as a store badge, which is the same argument panel 4 of the
   homepage makes about signing in.
   Everything is sized off --u so the whole mock scales as one object with the pinned screen. */
/* The drawn frame is the only frame this needs, so the generic screenshot border and letterbox come
   off -- same reason [data-shot][data-live] clears them for the map. */
/* justify-content is flex-start, not center: the figure is a flex item in a fixed-height panel, so
   when the drawn window is taller than the track it is given the window overflows. Centred, half of
   that overflow goes upwards over the panel's heading. */
/* Scaled down 10% at the unit rather than with a transform, which the map next door has to use. Every
   part of the drawn window is a multiple of --u, so one number resizes the whole object and it
   re-lays-out at the new size instead of being resampled. */
[data-ext] { --u: clamp(9px, 2.16svh, 21.6px); aspect-ratio: auto; border-color: transparent; background: none; overflow: visible;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start; }
/* The width has to be set on the figure, not the frame. The pinned layout centres the figure with
   align-self, which makes it fit-content -- so a `min(100%, ...)` on the frame resolves against its
   own contents and the whole window collapses to popup width.
   height:auto overrides the shared [data-epane] [data-shot] clamp. That clamp caps the box at 430px
   while the drawn window is taller than that, so the window overflowed its own figure in both
   directions and rode up over the panel's heading. */
[data-epane] [data-shot][data-ext] { width: min(100%, calc(var(--u) * 44)); height: auto; margin-top: clamp(22px, 3.6vh, 38px); }
[data-ext-frame] { --notch: calc(var(--u) * 1.5); position: relative; width: 100%; padding: calc(var(--u) * 0.85); }
/* The window body. Without it the toolbar read as a floating strip with a popup attached to nothing;
   a page behind the popup is what makes the object a browser rather than a widget. The min-height is
   what the popup hangs in: it is absolutely positioned, so nothing else reserves room for it. */
[data-ext-page] { position: relative; margin-top: calc(var(--u) * 0.85); padding: calc(var(--u) * 1.1);
  display: flex; flex-direction: column; gap: calc(var(--u) * 0.9); min-height: calc(var(--u) * 14);
  background: #07040f; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.07); }
/* Store-listing furniture, drawn as bars rather than lorem text for the same reason the popup's
   balances are bars: invented copy here would put claims on the page that nothing backs. */
[data-ext-listing] { display: flex; align-items: center; gap: calc(var(--u) * 0.8); }
[data-ext-icon] { flex: none; display: grid; place-items: center; width: calc(var(--u) * 2.8); height: calc(var(--u) * 2.8);
  background: rgba(255,255,255,0.05); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.09); }
[data-ext-icon] img { width: 62%; height: auto; }
[data-ext-meta] { display: flex; flex-direction: column; gap: calc(var(--u) * 0.45); width: calc(var(--u) * 11); }
[data-ext-meta] i { height: calc(var(--u) * 0.45); background: rgba(255,255,255,0.2); }
[data-ext-meta] i:last-child { width: 54%; background: rgba(255,255,255,0.1); }
[data-ext-lines] { display: flex; flex-direction: column; gap: calc(var(--u) * 0.42); width: calc(var(--u) * 16); }
[data-ext-lines] i { height: calc(var(--u) * 0.34); background: rgba(255,255,255,0.08); }
[data-ext-lines] i:last-child { width: 62%; }
[data-ext-bar] { display: flex; align-items: center; gap: calc(var(--u) * 0.7); }
[data-ext-dots] { display: flex; gap: calc(var(--u) * 0.35); flex: none; }
[data-ext-dots] i { width: calc(var(--u) * 0.45); height: calc(var(--u) * 0.45); background: rgba(255,255,255,0.28); }
/* The address bar is a real string rather than a grey pill: it names where the button goes. */
[data-ext-url] { flex: 1 1 auto; min-width: 0; overflow: hidden; padding: calc(var(--u) * 0.32) calc(var(--u) * 0.6); background: rgba(255,255,255,0.06);
  font-size: calc(var(--u) * 0.72); font-weight: 300; line-height: 1.5; letter-spacing: 0.01em; color: var(--ink-3); white-space: nowrap; }
/* The installed extension sitting in the toolbar. It is the thing the popup below is hanging off:
   without it the popup dropped out of the top edge of the page with nothing above it to have opened
   it, which is the one part of the sequence -- install, pin, open -- that the drawing was missing. */
[data-ext-chip] { flex: none; display: grid; place-items: center; width: calc(var(--u) * 1.5); height: calc(var(--u) * 1.5);
  background: rgba(255,255,255,0.08); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.16); }
[data-ext-chip] img { width: 72%; height: auto; }
/* Drops from the top right over the page, the way an extension popup does, and carries the tall
   narrow proportion a real one has. Absolute rather than in flow so the page furniture keeps its own
   layout underneath it, and lifted off the page with a drop shadow so the two planes read apart. */
[data-ext-pop] { position: absolute; top: calc(var(--u) * -0.5); right: calc(var(--u) * -0.1); z-index: 2;
  width: calc(var(--u) * 12.4); height: calc(var(--u) * 13);
  display: flex; flex-direction: column; gap: calc(var(--u) * 0.55); padding: calc(var(--u) * 0.85);
  background: #0c0718; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14), 0 calc(var(--u) * 0.7) calc(var(--u) * 2.2) rgba(0,0,0,0.65); }
[data-ext-pop-head] { display: flex; align-items: center; gap: calc(var(--u) * 0.5); }
[data-ext-pop-head] img { width: calc(var(--u) * 1.15); height: auto; }
[data-ext-pop-head] b { font-size: calc(var(--u) * 0.76); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink); }
/* The balance block and the rows below it stand in for holdings. Drawn as bars, not lorem text:
   inventing figures here would put numbers on the page that nothing backs. */
[data-ext-balance] { display: flex; flex-direction: column; gap: calc(var(--u) * 0.4); margin: calc(var(--u) * 0.35) 0; }
[data-ext-balance] i:first-child { width: 72%; height: calc(var(--u) * 0.9); background: rgba(255,255,255,0.24); }
[data-ext-balance] i:last-child { width: 46%; height: calc(var(--u) * 0.38); background: rgba(255,255,255,0.12); }
[data-ext-row] { display: flex; align-items: center; justify-content: space-between; gap: calc(var(--u) * 0.6); }
[data-ext-row] i { height: calc(var(--u) * 0.4); background: rgba(255,255,255,0.16); }
[data-ext-row] i:first-child { flex: 0 0 42%; }
[data-ext-row] i:last-child { flex: 0 0 22%; background: rgba(255,255,255,0.3); }
/* Pushed to the foot of the popup, which is now a fixed height rather than a content-sized box. */
[data-ext-cta] { margin-top: auto; padding: calc(var(--u) * 0.42) 0; background: var(--acc, #6F3DE0); text-align: center;
  font-size: calc(var(--u) * 0.68); font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: #fff; }
/* On the drawn store page, at its foot, because that is where the page it is imitating puts it.
   margin-top:auto holds it to the bottom of the page box however little furniture sits above it. */
[data-ext-btn] { align-self: flex-start; margin-top: auto; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 901px) {
    /* 490svh against a 100svh pin leaves 390svh of travel: four dwells of ~53svh and three moves of
       ~59svh. Both figures match the homepage Stack rail, so the two tracks travel at the same speed
       and hold for the same time. */
    [data-etrack] { height: 490svh; 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: calc(clamp(110px, 22svh, 250px) + clamp(20px, 2.4vh, 28px)); --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 panels 1 to 3
   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. It leaves on
   the last slide, where the extension window needs the room and the argument has moved from the
   chain to the thing you install. */
/* 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. */
/* Height is the cube plus its depth face, then --hop of clear air on top: the rail clips overflow,
   and without the extra headroom a hopping cube is sliced off at its apex. Every part of the rail is
   bottom-anchored, so the added height only ever opens space above. */
/* No ground line. It was drawn as two hairlines a depth-offset apart, which worked while a row of
   unlit cubes stood on it; with only the produced blocks left, the line ran on unoccluded and showed
   through every gap between them. The blocks carry the ground plane on their own. */
[data-beat-rail] { --hop: calc(var(--bh) * 0.55); position: relative; height: calc(var(--bh) + var(--bd) + var(--hop)); overflow: hidden; }

/* 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: 0; 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. */
/* The extra 1px on the right inset is not cosmetic slack. Each step lands the clip edge exactly on
   the next cube's left edge, and at that coincidence the edge antialiases: a hairline of the front
   face (#7b45ea, full cube height) painted in the gap a beat before the block itself arrived. The
   1px is carried on both keyframes so it is a constant offset rather than something that ramps --
   steps() interpolates the calc, so a px on one end only would drift the edge further each step and
   eventually start revealing the next cube early. Live mode applies the same offset in explore.html. */
[data-beat-lit] { position: absolute; inset: 0; clip-path: inset(0 calc(100% + 1px) 0 0); animation: beatFill 6s steps(12, start) infinite; }
@keyframes beatFill { from { clip-path: inset(0 calc(100% + 1px) 0 0); } to { clip-path: inset(0 1px 0 0); } }

/* A block lands rather than simply appearing. The clip reveal alone reads as a bar filling; the hop
   makes each block a discrete event, which is the same point the fixed 500ms spacing is making.
   Overshoot is downward on settle, so the cube looks like it has weight. */
/* Percentages are of the 6s round, so the hop itself runs 0-520ms and the cube is at rest for the
   remainder. Both modes use the same 6s duration, so the movement reads identically in each. */
@keyframes beatPop {
  0%    { transform: translate3d(0, -48%, 0) scale(1.06); }
  4.3%  { transform: translate3d(0, 0, 0) scale(1); }
  6%    { transform: translate3d(0, 7%, 0) scaleY(0.9); }
  8.7%  { transform: translate3d(0, 0, 0) scale(1); }
  100%  { transform: translate3d(0, 0, 0) scale(1); }
}
/* Fallback loop: each cube carries its column index, so one rule staggers all twelve onto the same
   6s clock the clip runs on. Block n lights at n * 500ms, which is n/12 of the cycle. */
[data-beat-on] > i { transform-origin: bottom center; animation: beatPop 6s linear infinite; animation-delay: calc(var(--i, 0) * 0.5s); }
/* Live mode drives the clip off real slot numbers, which are not in phase with the CSS clock above,
   so the loop is dropped and JS pops the one cube that just lit. */
[data-beat-live] [data-beat-on] > i { animation: none; }
[data-beat-live] [data-beat-on] > i[data-pop] { animation: beatPop 6s linear 1; }
@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; }
/* The name list is a strip that JS slides by whole rows, and with no transition the handoff swapped
   one name for another between two frames while the map next to it took 450ms to move the light.
   Matched to the map so the two read as one event. Gated behind a second attribute rather than
   living on [data-beat-live] because the very first render jumps the strip from row zero to
   wherever the chain actually is, which as a slide is a slot machine spinning on page load. */
[data-beat-slide] [data-beat-names] { transition: transform 450ms cubic-bezier(0.16, 1, 0.3, 1); }

/* The load gate. Both readouts start blank and frozen at frame zero, and are only uncovered once
   the clock knows which producer is actually signing -- by which point the fill and the name have
   already been put in the right place, so the reveal has nothing to correct. Previously the
   decorative loop ran from first paint on the page-load clock and the first real slot number moved
   it somewhere else, which is the jump: the run visibly skipped on arriving at the section.
   Only the two moving parts are held. The unlit row of cubes underneath stays visible throughout,
   so the gate reads as a run waiting to fill rather than as a hole in the layout.
   Opacity and not visibility, because it has to be able to fade. */
[data-beat-wait] [data-beat-lit], [data-beat-wait] [data-beat-names] { animation-play-state: paused !important; }
[data-beat-wait] [data-beat-lit] { opacity: 0; }
[data-beat-lit] { transition: opacity 520ms ease; }
[data-beat-wait] [data-beat-who] { opacity: 0; }
[data-beat-who] { transition: opacity 520ms ease; }
@media (prefers-reduced-motion: reduce) {
  [data-beat-lit] { animation: none; clip-path: inset(0 0 0 0); }
  [data-beat-names] { animation: none; }
  [data-beat-slide] [data-beat-names] { transition: none; }
  [data-beat-on] > i, [data-beat-live] [data-beat-on] > i[data-pop] { animation: none !important; }
  /* The gate still holds -- it is what stops the run jumping -- but it cuts rather than fades. */
  [data-beat-lit], [data-beat-who] { transition: none; }
}

/* Four dwells and three moves, holding the 19.2:21.2 dwell-to-move ratio the three-panel version
   used, so a panel rests for the same share of the scroll as it did before the fourth was added.
   4d + 3m = 100 at m = 1.104d gives d = 13.7, m = 15.1. */
@keyframes eRail {
  0%     { transform: translate3d(0, 0, 0); }
  13.7%  { transform: translate3d(0, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  28.8%  { transform: translate3d(-100%, 0, 0); }
  42.5%  { transform: translate3d(-100%, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  57.6%  { transform: translate3d(-200%, 0, 0); }
  71.2%  { transform: translate3d(-200%, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  86.3%  { transform: translate3d(-300%, 0, 0); }
  100%   { transform: translate3d(-300%, 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 moves. The run shrinks and drops once on the first slide, holds through panels 2
   and 3, then leaves on the third slide with the panel that replaces it -- same window, same easing,
   so it reads as one movement rather than a second animation. It exits left while panel 4 enters
   from the right, so at no point in the move do the two occupy the same horizontal space.
   -100vw rather than -100%: the run is only as wide as the text column, and body's overflow-x is
   what clips it, so it has to clear the screen edge and not just its own box. */
@keyframes eBeatDrop {
  0%     { --bw: var(--bw-big); transform: translate3d(0, calc(var(--beat-rise) * -1), 0); }
  13.7%  { --bw: var(--bw-big); transform: translate3d(0, calc(var(--beat-rise) * -1), 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  28.8%  { --bw: var(--bw-rest); transform: translate3d(0, 0, 0); }
  71.2%  { --bw: var(--bw-rest); transform: translate3d(0, 0, 0); animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  86.3%  { --bw: var(--bw-rest); transform: translate3d(-100vw, 0, 0); }
  100%   { --bw: var(--bw-rest); transform: translate3d(-100vw, 0, 0); }
}
/* Layout only -- the frame, extrusion and hover all come from [data-block], so these read as the same
   object as the Build inventory and the homepage stat cards rather than a third kind of box. The
   label sits above the figure, which is why space-between overrides the cube's own flex-end. */
[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.
   It used to be five full-width rows with an accent rule and a purple wash sweeping in from the left
   on hover, on [data-index]/[data-item]; it became the Build inventory's cube in 2026-09-23 so the
   two "what is there" sections on the site share one object. The row markup and every rule it needed
   are in history:
     git log --oneline -S "data-item-desc" -- site/explore.html site/site.css
   Nothing but layout lives here now -- frame, extrusion and hover all come from [data-block]. */
/* 18px column gap, not the 27px the Build inventory uses. 27px between five cells is 108px of the
   row spent on air, and the row cannot afford it: the widest name, "Browser Extension", measures
   191px and the cell has to hold that on one line. 18px is the extrusion depth, which is already the
   grid's own row-gap, so the row and the column now separate by the same number. */
[data-rindex] { margin: clamp(24px, 4vh, 46px) 0 0; }
/* Doubled up on itself, and [data-rb] below is qualified by its parent, purely to outrank
   [data-blockgrid] and [data-block]: those are defined further down this file at the same
   specificity, so a single-attribute selector here loses on source order alone. */
[data-rindex][data-blockgrid] { column-gap: 18px; }
/* flex-start, not the cube's own flex-end: these have three lines of content, so the name anchors to
   the top and the destination is pushed to the floor by its own auto margin.
   Padding is tighter than [data-block]'s 29px for the same reason as the gap above -- at a fifth of
   the page, 29px each side is a quarter of the cell spent on margin. It stays a clamp rather than a
   flat number because the name's font-size is a vw and the cell's width is very nearly one too: a
   flat padding holds at 1440 and wraps again around 1250, where the cell has shrunk but the padding
   has not. Scaling all three together keeps roughly 6-10px of slack across the whole pinned range. */
[data-rindex] [data-rb] { justify-content: flex-start; gap: clamp(8px, 0.9vw, 12px); padding: clamp(15px, 1.45vw, 21px); }
[data-rb] [data-rb-desc] { text-wrap: pretty; }
/* The old row list could set nowrap here because a row was the width of the page. In a fifth-width
   cube the longest domain is wider than the cube, so it has to be allowed to wrap -- at the <wbr> in
   the markup, not anywhere, which breaks mid-word. The tracking closes up to buy a character back. */
[data-rb] [data-rb-to] { margin-top: auto; padding-top: clamp(10px, 1.2vw, 16px); letter-spacing: 0.12em; transition: color 300ms ease; }
[data-rb]:hover [data-rb-to] { color: var(--ink); }
/* Five across needs about 1240px to keep a cell wide enough for a sentence. Between there and the
   1000px step where [data-blockgrid] takes over at two, the directory goes to three and scrolls
   normally -- the pin below is gated at the same 1240 for the same reason. !important because --cols
   is set inline on the element, which a stylesheet rule cannot outrank any other way; it is what
   [data-blockgrid]'s own two steps do, and for the same reason. */
@media (min-width: 1001px) and (max-width: 1240px) { [data-rindex] { --cols: 3 !important; } }

/* 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()) {
  /* 1241px, not the 701px this used to gate on. The pin only works while the five cubes are one row,
     and below 1240 they are not -- two or three rows plus a heading cannot clear a 100svh screen.
     Under the gate the section is a plain grid and scrolls normally, which is the right answer at
     that width anyway. Keep this number and the --cols step above in step with each other. */
  @media (prefers-reduced-motion: no-preference) and (min-width: 1241px) {
    [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); }
    /* blockIn animates `translate`, not `transform`. The cube's hover lift is a transform, and an
       animation with fill: both keeps winning the cascade for whatever property it animates long
       after it has finished -- so animating transform here would leave every cube unable to lift.
       The two properties compose, so the lift still works mid-entrance. */
    [data-ripin] [data-rb] { animation: blockIn cubic-bezier(0.16, 1, 0.3, 1) both; animation-timeline: --rindex; will-change: translate, opacity; }
    [data-ripin] [data-rb]:nth-child(1) { animation-range: contain 2%  contain 15%; }
    [data-ripin] [data-rb]:nth-child(2) { animation-range: contain 18% contain 31%; }
    [data-ripin] [data-rb]:nth-child(3) { animation-range: contain 34% contain 47%; }
    [data-ripin] [data-rb]:nth-child(4) { animation-range: contain 50% contain 63%; }
    [data-ripin] [data-rb]:nth-child(5) { animation-range: contain 66% contain 79%; }

    /* Five cubes plus a heading have to clear one viewport. On a short laptop the cube's own padding
       and the pin's 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-rb] { min-height: 0; padding: clamp(14px, 1.6vw, 22px); }
      [data-ripin] [data-rindex] { margin-top: clamp(16px, 2.6vh, 30px); }
    }
  }
}
@keyframes blockIn { from { opacity: 0; translate: 0 30px; } to { opacity: 1; translate: 0 0; } }

/* Blog index. Same cube again, so an article listing needs no object of its own; only the stacking
   order differs, because a post is dated and the other two grids are not. Qualified by the grid for
   the same reason [data-rb] is -- [data-block] is defined below this and would otherwise win on
   source order. */
[data-postgrid] [data-post] { justify-content: flex-start; gap: clamp(8px, 0.9vw, 12px); }
[data-postgrid] [data-post] [data-post-date] { color: var(--ink-3); transition: color 300ms ease; }
[data-postgrid] [data-post]:hover [data-post-date] { color: var(--ink); }
[data-postgrid] [data-post] [data-post-desc] { margin-top: auto; padding-top: clamp(10px, 1.2vw, 16px); color: var(--ink-2); text-wrap: pretty; }

/* 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. */
/* At rest the plate is a wireframe: edges only, the page showing through all three faces. Hovering
   fills it in. The inventory is a list of things you could build with, so an empty box that solidifies
   under the cursor says "pick one" in a way seventeen finished solids did not -- and it gives the row
   you are pointing at the only fill on the screen.
   The resting edges are brighter than the filled ones (0.3 against 0.14): with no fill behind them
   the border is the whole object, and at the pinned list's 70-108px plate height the old value was
   barely there. */
[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: transparent; border: 1px solid rgba(255,255,255,0.3); transition: transform 340ms cubic-bezier(0.16,1,0.3,1), background 340ms ease, border-color 340ms ease; }
[data-block]:hover { transform: translate(4px, -4px); background: linear-gradient(158deg, #141220 0%, #0a0a0d 64%, #070708 100%); border-color: rgba(255,255,255,0.14); }
/* Top and right faces of the extrusion. Each face omits exactly the one border that would double up
   on an edge another face already draws: the top face drops its bottom border (shared with the front
   face's top edge) and the right face drops its left border (shared with the front face's right
   edge). Every other border is a silhouette edge and has to be drawn.
   The top face's LEFT border is one of those silhouette edges -- the diagonal running up-right from
   the plate's top-left corner -- and it used to be suppressed too. That was correct while these
   plates were solid, because the fill closed the shape and the extra diagonal read as a lit edge
   pulling the box leftwards. Once they became wireframes at rest there was no fill to close it, so
   the top face sat open at its top-left corner. If these ever go back to solid-at-rest, this is the
   border to reconsider first.
   The fills are opaque when they are there at all, never a translucent wash. A wash 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 read as an outline. So the faces switch between fully clear
   and fully opaque with the body, and all three states change together. */
[data-block]::before { content: ""; position: absolute; left: 0; right: 0; top: -18px; height: 18px; background: none; border: 1px solid rgba(255,255,255,0.3); border-bottom: 0; transform: skewX(-45deg); transform-origin: bottom left; transition: background 340ms ease, border-color 340ms ease; }
[data-block]::after { content: ""; position: absolute; top: 0; bottom: 0; right: -18px; width: 18px; background: none; border: 1px solid rgba(255,255,255,0.3); border-left: 0; transform: skewY(-45deg); transform-origin: top left; transition: background 340ms ease, border-color 340ms ease; }
[data-block]:hover::before { background: linear-gradient(100deg, #4b4665, #2a2637); border-color: rgba(255,255,255,0.15); }
[data-block]:hover::after { background: linear-gradient(to bottom, #423d59, #1e1b28); border-color: rgba(255,255,255,0.2); }
/* 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; }

/* Every grid of cubes is a wireframe at rest and fills on hover, Build's and the three on Home and
   Explore alike -- one object, one behaviour, no exceptions. There was briefly a solid-at-rest
   variant for the three that sit over the wave artwork; it was removed on 2026-09-23 because keeping
   them identical to Build matters more than the backdrop showing through. Known cost: on Explore the
   fixed wave layer runs behind both grids, so a cube over the bright part of it has purple inside it
   and its 11px label loses contrast. If that ever has to be solved, solve it by moving the wave off
   these sections, not by filling the cubes. */

/* 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. */
/* Uncapped, unlike every other lede on the site. At 62ch this broke over two lines, and the
   sentence is a single run from prompt to deployed dapp -- splitting it put a break in the middle of
   the one claim the section makes. It is ~100 characters, so it holds one line down to the mobile
   breakpoint and runs to well under half the column at any width above it. */
[data-kb-sub] { margin: clamp(16px, 2.2vh, 26px) 0 0; }
[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); 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's three block-level layouts all sat here and are all gone as of 2026-09-22, leaving the page
   with no rules of its own -- it is a heading, a paragraph and the shared close, laid out inline.
   [data-phases]/[data-phase] dressed the Scope / Configure / Launch plates, [data-split]/[data-side]
   the "Where the line sits" two-column split, and [data-uc*] the UltraChain rail. The rail is parked
   in _tools/_parked-ultrachain.frag; the other two come back with the markup:
     git log --oneline -S "data-phase" -- site/deploy.html
     git log --oneline -S "Where the line sits" -- site/deploy.html */

/* 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. */
[data-cta] { display: inline-flex; align-items: center; white-space: nowrap; padding: 12px 24px; border: 1px solid rgba(255,255,255,0.34); 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); color: var(--ink); }
/* Every other button on the site is an anchor. A real <button> carries the user agent's buttonface
   background, which paints this one solid white over a black plate. */
button[data-btn-sm] { appearance: none; background: none; font-family: inherit; cursor: pointer; }
[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: 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 while the shadow
   falls the other way, 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.
   The move is diagonal and the stack is four deep because the corners are square. On the old 12px
   radius a diagonal offset exposed the crescent where the two curves fall out of register, so the
   button could only slide sideways and the depth had to stay shallow enough to hide it. Square corners
   put the offsets in register, so the stack closes into a clean 45-degree slab -- the same angle the
   extruded cubes are drawn at, which is the point of squaring these off in the first place. */
[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: translate(-4px, -4px); box-shadow: 1px 1px #fff, 2px 2px #fff, 3px 3px #fff, 4px 4px #fff; }
[data-cta]:active, [data-btn]:active, [data-btn-sm]:active { transform: translate(0, 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; } }

/* This grid used to have a hover of its own -- an accent rule sweeping along the bottom edge, drawn
   on ::after, plus overflow:hidden to keep it inside the corner. Both had to go when the cells became
   [data-block] cubes: ::after is the cube's right-hand face, and the more specific rule here was
   overwriting it with a 2px bar, while overflow:hidden clipped the extrusion off entirely. The cube's
   own lift-and-fill is the hover now. If these ever go back to flat plates, this is what to restore:
   git log --oneline -S "data-stats6] > div::after" -- site/site.css */

/* 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); }
/* The six obligations are inert. Plates light their frame on hover by default, which on a grid of
   numbered tiles reads as "these are buttons" -- so the hover values are pinned to the resting ones
   and only the Sign in plate opposite responds to the pointer. */
[data-step] { --plate-frame: rgba(255,255,255,0.18); --plate-fill-hover: #000; 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.18); }
[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; }

/* The invitation sits in the corner rather than under "Sign in.", so the plate keeps reading as the
   one step it is arguing for. It grows leftwards from the top right, away from the 45-degree cut. */
[data-demo-mark] {
  position: absolute;
  top: clamp(14px, 1.4vw, 20px);
  right: clamp(14px, 1.4vw, 20px);
  font-size: 13px;
  line-height: 1;
  white-space: nowrap;
  color: var(--ink-3);
  transition: color 320ms ease;
}
[data-demo]:hover [data-demo-mark] { color: var(--ink); }

/* The demo runs here rather than in the plate. Pane 04 sits inside a pinned scroll track, so an
   asynchronous connect-and-sign would be carried off screen by any scroll gesture; a modal dialog
   is fixed to the viewport and survives it. */
[data-demo-dialog] {
  width: min(92vw, 470px);
  max-height: 86svh;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  overflow: visible;
}
[data-demo-dialog]::backdrop { background: rgba(0,0,0,0.72); backdrop-filter: blur(3px); }
[data-demo-card] {
  --notch: clamp(16px, 1.5vw, 24px);
  --plate-frame: var(--acc, #6F3DE0);
  --plate-fill: #0e0820;
  --plate-fill-hover: #0e0820;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(10px, 1.6vh, 16px);
  padding: clamp(26px, 3.4vw, 40px);
}
[data-demo-close] {
  position: absolute;
  top: 0;
  right: 0;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 0;
  appearance: none;
  background: none;
  font: 300 20px/1 inherit;
  color: var(--ink-3);
  cursor: pointer;
}
[data-demo-close]:hover { color: var(--ink); }
/* The card's own rules set display on the facts list and the two buttons, and an author declaration
   outranks the user-agent's [hidden] rule whatever its specificity. Without this the overlay shows an
   empty Account row and an explorer link to nowhere from the moment it opens. */
[data-demo-card] [hidden] { display: none !important; }
/* Chrome focuses the close button when the dialog opens. Its default ring is a bright square in the
   cut corner; the house ring is the accent one, and on the close button a colour change alone reads
   better than an outline clipped by the 45-degree cut. */
[data-demo-card] :focus-visible { outline: 2px solid var(--acc, #6F3DE0); outline-offset: 3px; }
[data-demo-close]:focus-visible { outline: none; color: var(--ink); }
[data-demo-card] > h3 { margin: 0; }
[data-demo-card] > p { margin: 0; max-width: 46ch; color: var(--ink-2); }
[data-demo-card][data-demo-done] > p { color: var(--ink); }
[data-demo-card] > [data-btn-sm] { margin-top: clamp(4px, 0.8vh, 10px); }
/* The one button in the final state, now that the explorer lives on the hash itself. */
[data-demo-next] { margin-top: clamp(4px, 0.8vh, 10px); }

/* The account name and transaction id are the only strings here the page does not author. A 64
   character hash has no break opportunities, so it is given one explicitly and set in a face where
   a transposed character is visible. */
[data-demo-facts] { display: grid; gap: clamp(8px, 1.2vh, 14px); width: 100%; margin: 0; }
[data-demo-facts] dt { margin: 0 0 3px; }
[data-demo-facts] dd { margin: 0; overflow-wrap: anywhere; }
/* The hash is the link, and it is set exactly as the account name above it: the card already carries
   a label, a heading, body copy and a button, and a fifth face for one field made it read as a
   different kind of thing rather than the other half of the same pair. Colour and a standing
   underline are the whole difference. */
[data-demo-tx] {
  /* A lift on --acc, which on the card's near-black fill is too dark to read at body size. */
  color: #A886FF;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 200ms ease;
}
[data-demo-tx]:hover { color: var(--ink); }
/* Says what following the hash does, without spending a button on it. */
[data-demo-hint] { color: var(--ink-3); }

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

[data-contrast] { row-gap: clamp(10px, 1.5vh, 18px); }

@media (max-width: 700px) {
  [data-contrast] { grid-template-columns: minmax(0, 1fr) !important; }
  /* 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; }
}
