:root { color-scheme: light; --paper: #fff; --surface: #fff; --ink: #101010; --sub: #5d5d59; --faint: #858580; --line: #d8d8d3; --hover: #f5f5f2; --active: #e9e9e4; --blue-paper: #e7edf1; --blue-hover: #dce7ee; --blue-ink: #394b59; --blue-line: #c4cfd6; --font-sans: 'Helvetica Neue', Helvetica, Arial, sans-serif; --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; font-family: var(--font-sans); }
* { box-sizing: border-box; }
/* No double-tap zoom delay on anything tappable. */
:where(a, button, summary, [role='tab']) { touch-action: manipulation; }
/* The header is sticky, so an anchor jump would land under it. */
main :where(h1, h2, h3, h4, [id]) { scroll-margin-top: 5rem; }
/* Headings set their own line breaks rather than leaving a one-word last line. */
:where(h1, h2, h3) { text-wrap: balance; }
body { background: var(--paper); color: var(--ink); letter-spacing: -.008em; margin: 0; overflow-x: hidden; }
/* Light on dark needs tracking back, not tightening: the paper world's -.008em rides down the
 * cascade onto every small label and table cell on the field, where the glow of light letters
 * on a dark ground already closes their counters. Neutral here; the display roles set their
 * own tracking and do not see this. */
body:has(.landing-main) { letter-spacing: 0; }
a { color: inherit; }
/* Skip link: off-screen until focused, then a pill at the top-left above the sticky header. */
.skip-link { background: var(--ink); border-radius: .4rem; color: var(--paper); font-size: .86rem; font-weight: 500; left: 1rem; padding: .6rem .9rem; position: absolute; text-decoration: none; top: -100vh; z-index: 300; }
.skip-link:focus-visible { outline: 2px solid var(--paper); outline-offset: 2px; top: .75rem; }
#main:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.site-header, footer { align-items: center; border-color: var(--line); display: flex; justify-content: space-between; padding: .5rem 1rem; }
.site-header { background: transparent; border-bottom: 1px solid var(--line); gap: 1.5rem; min-height: 60px; position: sticky; top: 0; z-index: 200; }
/* The page scrolls under a frosted bar, as on ALX Protocol: mostly paper, with the content behind it blurred. The blur sits on a layer behind the header's content, not on the header itself: backdrop-filter would make the header the containing block for the fixed phone drawer inside it. */
.site-header::before { -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); background: color-mix(in srgb, var(--paper) 85%, transparent); content: ''; inset: 0; pointer-events: none; position: absolute; z-index: -1; }
/* Solid paper where the blur cannot render, or where the visitor asks for less transparency. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .site-header::before { background: var(--paper); } }
@media (prefers-reduced-transparency: reduce) { .site-header::before { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--paper); } }
.wordmark { flex: 0 0 auto; font-size: .78rem; font-weight: 700; letter-spacing: .08em; text-decoration: none; white-space: nowrap; }
/* One line above the landing headline. It names the explorer release and stays a label. */
.hero-wallpaper .hero-copy > .explorer-beta { -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); background: rgb(13 11 22 / 62%); border: 1px solid rgb(246 243 251 / 50%); border-radius: 999px; color: var(--ink); cursor: default; display: inline-flex; font-size: .78rem; font-weight: 500; line-height: 1; margin: 0 0 1.25rem; padding: .55rem 1rem; transition: background-color 140ms ease, border-color 140ms ease; white-space: nowrap; }
.hero-wallpaper .hero-copy > .explorer-beta:hover { background: rgb(30 26 49 / 78%); border-color: rgb(246 243 251 / 72%); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero-wallpaper .hero-copy > .explorer-beta { background: #14111f; }
}
@media (prefers-reduced-transparency: reduce) {
  .hero-wallpaper .hero-copy > .explorer-beta { -webkit-backdrop-filter: none; backdrop-filter: none; background: #14111f; }
}
.wordmark img { display: block; height: 40px; width: auto; }
/* Docked beside the wordmark at one width: the header reads mark, field, navigation. */
.docs-search { left: 4.5rem; position: absolute; transform: none; width: min(20rem, calc(100vw - 11.125rem)); }
.docs-search input { background: var(--surface); border: 1px solid var(--line); border-radius: .4rem; color: var(--ink); font: .82rem var(--font-sans); height: 44px; padding: 0 1rem 0 2.3rem; width: 100%; }
.docs-search svg { fill: none; height: 16px; left: .7rem; pointer-events: none; position: absolute; stroke: var(--sub); stroke-width: 1.5; top: 14px; width: 16px; }
.docs-search input::placeholder { color: var(--sub); opacity: 1; }
.docs-search input:focus { border-color: var(--ink); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ink) 12%, transparent); outline: none; }
nav { align-items: center; display: flex; gap: .5rem; margin-left: auto; }
nav a { align-items: center; border-radius: .32rem; color: var(--sub); display: inline-flex; font-size: .86rem; min-height: 2.75rem; padding: 0 .55rem; text-decoration: none; transition: background-color 120ms ease, color 120ms ease; white-space: nowrap; }
nav a:hover, nav a:focus-visible { background: var(--hover); color: var(--ink); }
.menu-toggle { background: transparent; border: 0; display: none; font: inherit; height: 44px; padding: .55rem; width: 44px; }
.menu-toggle > span:not(.sr-only) { background: var(--ink); display: block; height: 1px; margin: 5px auto; width: 16px; }
.nav-backdrop { background: rgb(16 16 16 / 8%); border: 0; inset: 0; position: fixed; z-index: 199; }
.marketing-nav { position: relative; }
.nav-dropdown { position: relative; }
.nav-dropdown__trigger { align-items: center; background: transparent; border: 0; border-radius: .32rem; color: var(--sub); cursor: pointer; display: inline-flex; font: inherit; font-size: .86rem; font-weight: 500; gap: .45rem; min-height: 2.75rem; padding: 0 .55rem; transition: background-color 120ms ease, color 120ms ease; white-space: nowrap; }
.nav-dropdown__trigger:after { border: solid currentColor; border-width: 0 1px 1px 0; content: ''; height: .32rem; margin-top: -.18rem; transform: rotate(45deg); transition: transform 120ms ease; width: .32rem; }
.nav-dropdown__trigger:hover, .nav-dropdown__trigger:focus-visible, .nav-dropdown.is-open .nav-dropdown__trigger { background: var(--hover); color: var(--ink); }
.nav-dropdown.is-open .nav-dropdown__trigger:after { margin-top: .18rem; transform: rotate(225deg); }
/* The group holding the current page reads as ink, not grey. Each page marks its
 * own link with aria-current="page"; the docs mark their groups from the path. */
.nav-dropdown:has(a[aria-current='page']) > .nav-dropdown__trigger { color: var(--ink); }
.nav-dropdown__panel { background: var(--surface); border: 1px solid var(--line); border-radius: .5rem; box-shadow: 0 9px 24px rgb(0 0 0 / 8%); display: none; margin-top: .35rem; padding: .5rem; position: absolute; right: 0; top: 100%; width: 248px; z-index: 10; }
.nav-dropdown__panel:before { content: ''; height: .4rem; left: 0; position: absolute; right: 0; top: -.4rem; }
.nav-dropdown.is-open .nav-dropdown__panel { display: grid; }
.nav-dropdown__panel a { align-items: center; border-radius: .25rem; color: var(--sub); display: flex; font-size: .82rem; gap: .6rem; margin: 0; padding: .55rem .62rem; }
.nav-dropdown__panel a:hover, .nav-dropdown__panel a:focus-visible, .nav-dropdown__panel a[aria-current='page'] { background: var(--hover); color: var(--ink); outline: none; }
.nav-dropdown__label { color: var(--ink); font-size: .78rem; font-weight: 600; letter-spacing: .02em; margin: .35rem .65rem .2rem; }
.nav-dropdown__rule { background: var(--line); height: 1px; margin: .42rem 0; }
/* One rule for the navbar CTA and every primary action, so the hero button is
 * the navbar button: same size, weight, padding and hover glow. */
.explorer-cta, .primary-action { align-items: center; background: var(--ink); border: 1px solid var(--ink); border-radius: .4rem; box-shadow: none; color: var(--paper); display: inline-flex; font-size: .86rem; font-weight: 500; justify-content: center; min-height: 2.75rem; padding: 0 .85rem; text-decoration: none; transition: background-color 140ms ease, border-color 140ms ease, box-shadow 160ms ease, scale 120ms ease; }
.explorer-cta { margin-left: .25rem; }
/* Live results for the header search. The form is already absolutely
 * positioned above, so it is the containing block for this panel. */
.search-results { background: var(--paper); border: 1px solid var(--line); border-radius: .5rem; box-shadow: 0 10px 30px rgb(16 16 16 / 8%), 0 1px 2px rgb(16 16 16 / 6%); left: 0; margin-top: .35rem; max-height: min(28rem, 60vh); overflow-y: auto; position: absolute; right: 0; top: 100%; z-index: 200; }
.search-results[hidden] { display: none; }
.search-results__item { border-bottom: 1px solid var(--line); color: var(--ink); display: grid; gap: .15rem; padding: .7rem .85rem; text-decoration: none; }
.search-results__item:last-child { border-bottom: 0; }
.search-results__item:hover, .search-results__item.is-active { background: var(--hover); text-decoration: none; }
.search-results__item:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.search-results__item strong { font-size: .86rem; font-weight: 500; letter-spacing: -.01em; }
.search-results__section { color: var(--sub); font-size: .7rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
.search-results__item small { color: var(--sub); font-size: .78rem; line-height: 1.45; }
.search-results mark { background: transparent; color: var(--ink); font-weight: 600; }
.search-results__empty { color: var(--sub); font-size: .82rem; margin: 0; padding: .9rem .85rem; }
/* While the presale dialog loads, the button says it is working rather than looking dead: the wallet code behind it
 * is about a megabyte, and on a slow connection the wait is long enough to click twice. */
.primary-action[data-presale-modal-state="loading"] { cursor: progress; opacity: .72; pointer-events: none; }
/* Buy XNDR is hidden until the opening instant. After that it can show while still inactive,
 * until a phase is live. inline-flex would otherwise beat the hidden attribute. */
.primary-action[hidden] { display: none; }
.primary-action[aria-disabled="true"] { cursor: default; opacity: .55; pointer-events: none; }
.explorer-cta:hover, .explorer-cta:focus-visible, .primary-action:hover, .primary-action:focus-visible { background: color-mix(in srgb, var(--ink) 86%, var(--paper)); border-color: var(--ink); box-shadow: 0 0 0 3px rgb(16 16 16 / 10%), 0 0 18px rgb(16 16 16 / 22%); color: var(--paper); text-decoration: none; }
/* Press feedback. The hover glow above never fires on a phone, so until now a
 * tap produced no acknowledgement at all before the page changed. One shared
 * rule so every action on the site answers a press the same way. Dropped under
 * reduced motion, where the hover and focus states still carry the feedback.
 * The `scale` longhand, not `transform`, so this composes with a hover nudge
 * rather than replacing it and leaving the two rules fighting over order. */
@media (prefers-reduced-motion: no-preference) {
  .explorer-cta:active, .faq-action:active, .presale-strip__action:active, .primary-action:active, .text-action:active { scale: .98; }
}
/* Presale announcement: a floating notification card pinned bottom-right (white, bordered, soft shadow), matching the Explorer's card. Shown until the presale opens. */
.presale-strip { align-items: center; background: var(--paper); border: 1px solid var(--line); border-radius: .6rem; bottom: 1rem; box-shadow: 0 10px 30px rgb(16 16 16 / 8%), 0 1px 2px rgb(16 16 16 / 6%); display: flex; gap: .85rem; max-width: 400px; padding: 1rem; position: fixed; right: 1rem; transition: opacity 200ms cubic-bezier(.2,.8,.2,1), transform 200ms cubic-bezier(.2,.8,.2,1), visibility 200ms; width: calc(100vw - 2rem); z-index: 150; }
/* Both ways out lead toward the card's own corner rather than a teleport: the
 * dismiss button, which removes it once this finishes, and the footer dodge,
 * which holds the element so scrolling back up brings it straight in. The
 * visibility leg is what keeps a dodged strip out of the accessibility tree,
 * and it steps at the end of the fade rather than the start. */
.presale-strip[data-presale-out] { opacity: 0; pointer-events: none; transform: translateY(8px); visibility: hidden; }
.presale-strip > .presale-strip__icon { align-items: center; border: 1px solid var(--line); border-radius: 50%; color: var(--ink); display: inline-flex; flex: none; height: 2.25rem; justify-content: center; opacity: .6; width: 2.25rem; }
.presale-strip__icon svg { fill: none; height: 16px; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2; width: 16px; }
.presale-strip__copy { display: grid; flex: 1 1 auto; gap: .2rem; min-width: 0; text-align: left; }
.presale-strip__copy strong { font-size: .86rem; font-weight: 500; }
.presale-strip__copy p { color: var(--sub); font-size: .78rem; line-height: 1.4; margin: 0; }
.presale-strip__action { align-items: center; background: var(--ink); border: 1px solid var(--ink); border-radius: .4rem; color: var(--paper); display: inline-flex; flex: none; font-size: .82rem; font-weight: 500; min-height: 2.75rem; padding: 0 .9rem; text-decoration: none; transition: background-color 140ms ease, scale 120ms ease; }
.presale-strip__action:hover, .presale-strip__action:focus-visible { background: color-mix(in srgb, var(--ink) 86%, var(--paper)); color: var(--paper); }
.presale-strip__action:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (max-width: 440px) { .presale-strip { flex-wrap: wrap; }.presale-strip__action { margin-left: 3.1rem; } }
.landing-main { margin: 0 auto; width: min(calc(100% - 3rem), 72rem); }
.hero { align-items: center; display: flex; flex-direction: column; isolation: isolate; justify-content: center; min-height: clamp(30rem, calc(100svh - 17rem), 36rem); padding: 4.5rem 0 4rem; position: relative; text-align: center; }
.hero-copy { align-items: center; display: flex; flex-direction: column; }.hero-copy > p { color: var(--sub); font-size: clamp(1rem, 2vw, 1.25rem); line-height: 1.65; margin: 2.25rem auto 0; max-width: 42rem; }
.eyebrow, .entry-content > span, .architecture-list span, .developer-card > span, .presale-strip > span { color: var(--sub); font: .7rem var(--font-mono); font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
.hero h1 { font-size: clamp(2.65rem, 6vw, 5.2rem); font-weight: 400; letter-spacing: -.035em; line-height: .94; margin: 0; max-width: 62rem; }
.hero > p:not(.eyebrow) { color: var(--sub); font-size: clamp(1rem, 2vw, 1.25rem); line-height: 1.65; margin: 2.25rem auto 0; max-width: 42rem; }
.hero-actions { align-items: center; display: flex; flex-wrap: wrap; gap: .7rem; justify-content: center; margin-top: 2.25rem; }
/* A standalone action link is a target, not prose: WCAG 2.2 Target Size wants
 * 24 CSS px, and these sit beside 44px buttons. Links inline in a sentence are
 * exempt and keep their natural height. */
.text-action { align-items: center; color: var(--ink); display: inline-flex; font-size: .86rem; font-weight: 500; min-height: 2.75rem; text-underline-offset: .22em; transition: scale 120ms ease, translate 120ms ease; }
/* The same 2px nudge the footer directory already uses, so a hovered link
 * leans toward where it goes. Pointer-gated: on a touch screen a tap fires a
 * false hover and the link would sit nudged after the finger leaves. */
@media (hover: hover) and (pointer: fine) { .text-action:hover { translate: 2px; } }
/* 404, after the owner's reference (2026-09-15): the digits drawn faint behind
 * the message. The art keeps its aspect ratio, its height 145/362 of its width,
 * and the title is pulled up by half that height and a little more, so it sits
 * across the middle of the digits. A percentage margin resolves against the
 * width, as the art's own width does. */
.not-found__art { color: var(--ink); display: block; flex: none; height: auto; opacity: .04; pointer-events: none; width: min(100%, 64rem); }
.hero .not-found__art + h1 { margin-top: calc(min(20%, 12.8rem) * -1 - 2rem); }
/* Go Back is the quiet action beside Home, and its arrow leans back on hover. */
.not-found__back { align-items: center; background: var(--hover); border: 1px solid var(--hover); border-radius: .4rem; color: var(--ink); display: inline-flex; font-size: .86rem; font-weight: 500; gap: .45rem; justify-content: center; min-height: 2.75rem; padding: 0 .85rem 0 .7rem; text-decoration: none; transition: background-color 140ms ease, border-color 140ms ease; }
.not-found__back:hover { background: var(--active); border-color: var(--active); }
.not-found__back:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.not-found__back svg { fill: none; height: 16px; opacity: .6; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2; transition: translate 120ms ease; width: 16px; }
@media (hover: hover) and (pointer: fine) { .not-found__back:hover svg { translate: -2px; } }
.entry-section, .home-section { border-top: 1px solid var(--line); padding: clamp(6rem, 10vw, 8rem) 0; }
/* A heading sits close to what it names: 2.25-3rem, and nothing at all when the
 * header is the whole section. */
/* No second column to fill, so the headline takes the full width. */
.section-header { align-items: end; display: grid; gap: clamp(2rem, 5vw, 5rem); grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); margin-bottom: clamp(2.25rem, 4vw, 3rem); }.section-header:last-child { margin-bottom: 0; }
.section-header h2 { font-size: clamp(2rem, 4vw, 3.35rem); font-weight: 400; letter-spacing: -.03em; line-height: 1.02; margin: 0; max-width: 35rem; }
.section-header .eyebrow { margin: 0 0 .75rem; }.section-header > p { color: var(--sub); margin: 0; max-width: 38rem; }
/* The home page's cards carry no title or subtitle inside them (owner, 2026-09-21). Each title
 * sits above its card instead, on the ground, and later the same day the owner asked for them to
 * be seen: centred, like the hero, which also puts them on the ground's darker middle, where large
 * text keeps its contrast without a shell. The block's section is what aria-labelledby names; the
 * card inside it is a div wearing the shell. */
.home-block { margin-top: clamp(3rem, 7vw, 5.5rem); }
.home-block__title { font-size: clamp(2rem, 4vw, 3.35rem); font-weight: 400; letter-spacing: -.03em; line-height: 1.02; margin: 0 0 clamp(1.25rem, 2.5vw, 1.75rem); text-align: center; }
/* Landing sections were 88px apart under cards up to 425px tall, so the next title sat on the
 * card above it (owner, 2026-09-28). The extra space is above the title; the title stays with
 * its own card, a little further from it than the 28px it had. */
body:has(.landing-main) .home-block { margin-top: clamp(5rem, 10vw, 8rem); }
body:has(.landing-main) .home-block__title { margin-bottom: clamp(1.75rem, 3vw, 2.5rem); }
/* A line under a block's title says what the cards below it add up to (owner, 2026-09-21). It sits
 * on the bare ground, so it is ink, not --sub. */
.home-block__lede { color: var(--ink); font-size: clamp(1rem, 1.5vw, 1.15rem); line-height: 1.55; margin: calc(-1 * clamp(.5rem, 1vw, .85rem)) auto clamp(1.75rem, 3vw, 2.5rem); max-width: 36rem; text-align: center; text-wrap: balance; }
/* Between 400 and 440px wide the page ground's amber ribbon crosses the lede, and body-size ink
 * on it read 3.32:1. A halo of the field's own dark, hugging the letters, holds it at 4.93:1 or
 * better at every width from 360 to 1920 (swept every 40px and both sides of each breakpoint);
 * on the dark ground it is invisible. */
body:has(.landing-main) .home-block__lede { text-shadow: 0 0 .35em rgb(13 11 22 / 90%), 0 0 .9em rgb(13 11 22 / 80%), 0 0 2em rgb(13 11 22 / 60%); }
/* How It Works (owner, 2026-09-21): the landing is the hero, this and the FAQ, so this is where the
 * technology is explained, and the definition of XNDR Network is left to the docs. Three steps, each
 * opening on an illustration of the same real Block from the demo index (0xfcfa...7bce, two declared
 * parents and two indexed children), so each picture and the sentences under it describe one
 * thing: its two identities, its lineage, and the API's answer about its children. The first
 * sentence is the capability, the second the technology behind it, and the link is where the docs
 * take over. Wide: three columns on shared rows (subgrid), so figures, titles and links line up.
 * Mid: each step is a row, the figure beside its text. Narrow: stacked. */
.how-steps { display: grid; gap: clamp(2.75rem, 5vw, 3.5rem); list-style: none; margin: 0; padding: 0; }
.how-step { align-items: center; display: grid; gap: 1.25rem clamp(2rem, 5vw, 4rem); grid-template-columns: minmax(0, 21.25rem) minmax(0, 1fr); }
.how-step__figure { margin: 0; }
.how-step__text h3 { font-size: 1.35rem; font-weight: 500; letter-spacing: -.02em; line-height: 1.2; margin: 0; }
.how-step__text p { color: var(--sub); font-size: .88rem; line-height: 1.65; margin: .7rem 0 0; max-width: 34rem; }
.how-step__text p + p { margin-top: .55rem; }
.how-step__text code { background: var(--hover); border: 1px solid var(--line); border-radius: .2rem; color: var(--ink); font: .8em var(--font-mono); padding: .05rem .22rem; }
.how-step__text .text-action { margin-top: .9rem; }
@media (min-width: 1180px) {
  .how-steps { column-gap: clamp(2rem, 3.5vw, 3.25rem); grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(5, auto); row-gap: 0; }
  .how-step { align-items: start; grid-row: span 5; grid-template-columns: none; grid-template-rows: subgrid; row-gap: 0; }
  .how-step__figure { align-self: center; margin-bottom: 2rem; }
  .how-step__text { display: contents; }
  .how-step__text .text-action { align-self: end; justify-self: start; }
}
@media (max-width: 699px) { .how-step { grid-template-columns: minmax(0, 1fr); } .how-step__figure { max-width: 21.25rem; } }
/* The illustrations, simplified (owner, 2026-09-21: "lets simplify the images"; the data "dont
 * have to be real"). One: a search field that types a query, shows what the index found, clears
 * and types the next (landing.js); hashes only, in a squared field like the header's (owner). Two:
 * seven plain blocks, four joined into two and two into one, with no labels (owner), by square
 * lines that turn a right angle halfway (owner: "make the lineage card more square", then "not the
 * drawing but the lines connecting"), the columns close together (owner: "The horizontal lines
 * are too long"), a pulse of light running along them and each block it reaches brightening.
 * Three: the
 * neighbourhood request, its blocks listed one after another, hashes only. No picture says parent
 * or child (owner). Hairlines and mono on the card's glass. */
.how-search { display: grid; gap: .8rem; }
.how-search__field { align-items: center; background: rgb(246 243 251 / 5%); border: 1px solid rgb(246 243 251 / 30%); border-radius: .4rem; display: flex; min-height: 3rem; padding: 0 1.1rem; }
/* Only the icon keeps a space after it: the caret ends the hash, 1px after its last character
 * (owner, 2026-09-21), where a gap on the field put it .6rem out. */
.how-search__icon { fill: none; flex: none; height: 1.1rem; margin-right: .6rem; stroke: var(--sub); stroke-linecap: round; stroke-width: 1.6; width: 1.1rem; }
.how-search__query { color: var(--ink); font: 500 .82rem var(--font-mono); white-space: nowrap; }
.how-search__caret { background: var(--ink); display: inline-block; height: 1.05rem; margin-left: 1px; width: 1.5px; }
.how-search__result { color: var(--sub); font: .76rem var(--font-mono); margin: 0; padding-left: 1.1rem; transition: opacity 250ms ease; }
.how-search[data-typing-live] .how-search__result:not([data-shown]) { opacity: 0; }
.how-lineage { display: block; height: auto; margin-inline: auto; max-width: 15.5rem; overflow: visible; width: 100%; }
.how-lineage__edge { fill: none; stroke: rgb(246 243 251 / 40%); stroke-linejoin: miter; stroke-width: 1.25; }
.how-lineage__pulse { fill: none; stroke: var(--ink); stroke-dasharray: .16 1.2; stroke-dashoffset: .2; stroke-linecap: round; stroke-width: 1.8; }
.how-lineage__block { fill: rgb(246 243 251 / 4%); stroke: rgb(246 243 251 / 42%); }
.how-lineage__block--self { fill: rgb(246 243 251 / 12%); stroke: var(--ink); }
.how-api { background: rgb(246 243 251 / 5%); border: 1px solid var(--line); border-radius: .6rem; font: .74rem/1 var(--font-mono); padding: .9rem 1rem; }
.how-api__request { color: var(--sub); margin: 0 0 .7rem; overflow-wrap: anywhere; }
.how-api__method { color: var(--ink); font-weight: 600; }
.how-api__feed { -webkit-mask-image: linear-gradient(transparent, #000 16%, #000 84%, transparent); height: 8.5rem; mask-image: linear-gradient(transparent, #000 16%, #000 84%, transparent); overflow: hidden; }
.how-api__rows { list-style: none; margin: 0; padding: 0; }
.how-api__rows li { color: var(--ink); height: 1.7rem; line-height: 1.7rem; white-space: nowrap; }
@media (prefers-reduced-motion: no-preference) {
  .how-search__caret { animation: caret-blink 1.06s steps(1, end) infinite var(--play, running); }
  /* As a real caret does, it holds solid while keys move and blinks while the field waits. */
  .how-search[data-typing-busy] .how-search__caret { animation: none; }
  .how-lineage__pulse--in { animation: lineage-in 2.8s ease-in-out infinite var(--play, running); }
  .how-lineage__pulse--out { animation: lineage-out 2.8s ease-in-out infinite var(--play, running); }
  .how-lineage__block--mid { animation: lineage-mid 2.8s ease-in-out infinite var(--play, running); }
  .how-lineage__block--self { animation: lineage-self 2.8s ease-in-out infinite var(--play, running); }
  .how-api__rows { animation: api-feed 5.6s ease-in-out infinite var(--play, running); }
}
@keyframes caret-blink { 0% { opacity: 1; } 50% { opacity: 0; } }
@keyframes lineage-in { 0% { stroke-dashoffset: .2; } 38%, 100% { stroke-dashoffset: -1.04; } }
@keyframes lineage-out { 0%, 45% { stroke-dashoffset: .2; } 80%, 100% { stroke-dashoffset: -1.04; } }
@keyframes lineage-mid { 0%, 32%, 56%, 100% { fill: rgb(246 243 251 / 4%); } 42% { fill: rgb(246 243 251 / 20%); } }
@keyframes lineage-self { 0%, 72%, 96%, 100% { fill: rgb(246 243 251 / 12%); } 84% { fill: rgb(246 243 251 / 28%); } }
@keyframes api-feed { 10.0% { transform: translateY(-0rem); } 12.5% { transform: translateY(-1.7rem); } 22.5% { transform: translateY(-1.7rem); } 25.0% { transform: translateY(-3.4rem); } 35.0% { transform: translateY(-3.4rem); } 37.5% { transform: translateY(-5.1rem); } 47.5% { transform: translateY(-5.1rem); } 50.0% { transform: translateY(-6.8rem); } 60.0% { transform: translateY(-6.8rem); } 62.5% { transform: translateY(-8.5rem); } 72.5% { transform: translateY(-8.5rem); } 75.0% { transform: translateY(-10.2rem); } 85.0% { transform: translateY(-10.2rem); } 87.5% { transform: translateY(-11.9rem); } 97.5% { transform: translateY(-11.9rem); } 100.0% { transform: translateY(-13.6rem); } }
.entry-grid { display: grid; gap: 1rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.entry-card { --accent: #85a4bb; color: var(--ink); display: flex; isolation: isolate; min-height: 10.5rem; padding: 1.4rem; position: relative; text-decoration: none; }.entry-card:nth-child(2) { --accent: #628aa8; }.entry-card:nth-child(3) { --accent: #97b1c5; }
.entry-fragments { border: 1px solid var(--line); border-radius: .5rem; display: grid; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr); inset: 0; pointer-events: none; position: absolute; z-index: 0; }.entry-fragments i { background: var(--paper); border: 1px solid transparent; transition: transform 260ms cubic-bezier(.2,.8,.2,1), border-color 200ms ease, background-color 200ms ease, box-shadow 220ms ease; }.entry-fragments i:nth-child(1) { border-radius: .5rem 0 0 0; }.entry-fragments i:nth-child(2) { border-radius: 0 .5rem 0 0; }.entry-fragments i:nth-child(3) { border-radius: 0 0 0 .5rem; }.entry-fragments i:nth-child(4) { border-radius: 0 0 .5rem 0; }
.entry-card:hover .entry-fragments i, .entry-card:focus-visible .entry-fragments i { background: color-mix(in srgb, var(--accent) 8%, var(--paper)); border-color: color-mix(in srgb, var(--accent) 62%, transparent); box-shadow: 0 .5rem 1rem rgb(0 0 0 / 9%); }.entry-card:hover .entry-fragments i:nth-child(1), .entry-card:focus-visible .entry-fragments i:nth-child(1) { transform: translate3d(-5px,-5px,0); }.entry-card:hover .entry-fragments i:nth-child(2), .entry-card:focus-visible .entry-fragments i:nth-child(2) { transform: translate3d(5px,-5px,0); }.entry-card:hover .entry-fragments i:nth-child(3), .entry-card:focus-visible .entry-fragments i:nth-child(3) { transform: translate3d(-5px,5px,0); }.entry-card:hover .entry-fragments i:nth-child(4), .entry-card:focus-visible .entry-fragments i:nth-child(4) { transform: translate3d(5px,5px,0); }
.entry-card:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }.entry-content { align-self: flex-end; display: grid; gap: .65rem; position: relative; width: 100%; z-index: 1; }.entry-content strong { font-size: 1.05rem; font-weight: 600; }.entry-content small { color: var(--sub); font-size: .88rem; line-height: 1.55; }
.architecture-list { border-bottom: 1px solid var(--line); border-top: 1px solid var(--line); display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); list-style: none; margin: 0; padding: 0; }.architecture-list li { min-width: 0; padding: 1.6rem 1.5rem 1.75rem; }.architecture-list li:first-child { padding-left: 0; }.architecture-list li + li { border-left: 1px solid var(--line); }.architecture-list span { color: var(--sub); display: block; margin-bottom: 2.75rem; }.architecture-list strong { display: block; font-size: 1rem; font-weight: 600; }.architecture-list p { color: var(--sub); font-size: .86rem; line-height: 1.55; margin: .45rem 0 0; }
/* .feature-grid is retired. The use-cases row it was written for now runs on
 * .feature-step-card like every other card on the site. */

.feature-steps__grid { display: grid; gap: 1rem; grid-template-columns: repeat(3, minmax(0, 1fr)); list-style: none; margin: 0; padding: 0; }
/* The presale flow has four steps rather than three. Its own breakpoints keep
 * it from inheriting the three-column collapse at the wrong width. */
.feature-steps__grid--four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1024px) { .feature-steps__grid--four { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .feature-steps__grid--four { grid-template-columns: 1fr; } }
/* The presale flow has four steps rather than three. Its own breakpoints keep
 * it from inheriting the three-column collapse at the wrong width. */
.feature-step-card { background: var(--surface); border: 1px solid var(--line); border-radius: .65rem; display: flex; flex-direction: column; min-height: 17rem; padding: 1.5rem; position: relative; }.feature-step-card > .text-action { align-self: flex-start; margin-top: auto; padding-top: 1.25rem; }
/* The fixed height exists to line up the link row; a card without a link takes its content's height. */
.feature-step-card:not(:has(.text-action)) { min-height: auto; }.feature-step-card h3 { font-size: 1.35rem; font-weight: 500; letter-spacing: -.02em; margin: 2rem 0 0; }
/* The card's glyph is its centrepiece (owner, 2026-09-21: "make the icons on the cards the central
 * icon"): the step's own stroke drawing, 4.5rem across and thin, centred in the card's top band. It
 * is still not a tile: no box and no border around it. These rules are what draw it at all; the
 * token's Utility cards and the presale's Participation cards depend on them, and once, when they
 * went with the home page's use cases by mistake, every glyph drew as a black shape. */
.feature-step-card__glyph { box-sizing: border-box; display: block; fill: none; height: 7.5rem; justify-self: center; margin: 0 auto 1rem; overflow: visible; padding: 1.5rem; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: .55; width: 7.5rem; }
.feature-step-card__glyph .g-dot { fill: currentColor; stroke: none; }
.feature-step-card > p { color: var(--sub); font-size: .86rem; line-height: 1.6; margin: .7rem 0 0; }.feature-step-card code { background: var(--hover); border: 1px solid var(--line); border-radius: .2rem; color: var(--ink); font: .78em var(--font-mono); padding: .1rem .22rem; }
/* The documentation exit closes the FAQ card, centred under both columns of questions. */
.faq-section__exit { margin-top: clamp(2.25rem, 4vw, 3rem); text-align: center; }.faq-section__exit .faq-action { margin-top: 0; }
.faq-action { align-items: center; background: var(--ink); border: 1px solid var(--ink); border-radius: .4rem; color: var(--paper); display: inline-flex; font-size: .82rem; font-weight: 500; justify-content: center; margin-top: 1.5rem; min-height: 2.75rem; padding: .5rem 1rem; text-decoration: none; transition: background-color 140ms ease, box-shadow 160ms ease, scale 120ms ease; }.faq-action:hover, .faq-action:focus-visible { background: color-mix(in srgb, var(--ink) 86%, var(--paper)); box-shadow: 0 0 0 3px rgb(16 16 16 / 10%); color: var(--paper); }.faq-grid { display: grid; gap: clamp(2rem, 5vw, 4.5rem); grid-template-columns: repeat(2, minmax(0, 1fr)); }.faq-column details { border-bottom: 1px solid var(--line); }.faq-column summary { align-items: center; cursor: pointer; display: flex; font-size: .9rem; font-weight: 500; gap: 1rem; justify-content: space-between; list-style: none; padding: 1.25rem 0; }.faq-column summary::-webkit-details-marker { display: none; }.faq-column summary::after { border: solid var(--sub); border-width: 0 1px 1px 0; content: ''; flex: none; height: .32rem; margin: -.18rem .2rem 0 0; transform: rotate(45deg); transition: margin 120ms ease, transform 120ms ease; width: .32rem; }.faq-column details[open] summary::after { margin-top: .18rem; transform: rotate(225deg); }
/* The answers still snap open. The ::details-content recipe (block-size 0 to
 * auto under interpolate-size) animates a bare <details> on this very page but
 * leaves these ones open-with-no-content: the panel takes the open attribute
 * and stays 60px. Measured in Chrome 152 at 1280x720, cause not isolated. A
 * rule that can swallow an answer is worse than a panel that jumps. */.faq-column details > p { color: var(--sub); font-size: .86rem; line-height: 1.65; margin: -.15rem 0 1.25rem; max-width: 34rem; }.faq-column details > p a { color: var(--ink); text-decoration-color: var(--line); text-underline-offset: .22rem; }.faq-column details > p a:hover, .faq-column details > p a:focus-visible { text-decoration-color: var(--ink); }.faq-column code { background: var(--hover); border: 1px solid var(--line); border-radius: .2rem; color: var(--ink); font: .78em var(--font-mono); padding: .1rem .22rem; }
.developer-grid { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 50rem; }.developer-card { border: 1px solid var(--line); border-radius: .5rem; color: var(--ink); display: grid; gap: .7rem; min-height: 13rem; padding: 1.4rem; text-decoration: none; transition: background-color 140ms ease, border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease; }.developer-card:hover, .developer-card:focus-visible { background: var(--hover); border-color: var(--sub); box-shadow: 0 .35rem 1rem rgb(0 0 0 / 4%); transform: translateY(-2px); }.developer-card:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }.developer-card > span { color: var(--sub); }.developer-card strong { align-self: end; font-size: 1.05rem; font-weight: 600; }.developer-card p { color: var(--sub); font-size: .88rem; line-height: 1.55; margin: 0; }.closing-section { align-items: center; display: flex; flex-direction: column; padding-bottom: clamp(7rem, 12vw, 10rem); text-align: center; }.closing-section h2 { font-size: clamp(2rem, 4vw, 3.35rem); font-weight: 400; letter-spacing: -.03em; line-height: 1.02; margin: 0; }.closing-section > p { color: var(--sub); line-height: 1.6; margin: 1.25rem auto 0; max-width: 38rem; }
.reference-table { border-collapse: collapse; font-size: .9rem; width: min(100%, 46rem); }.reference-table th, .reference-table td { border-bottom: 1px solid var(--line); padding: .85rem 1rem; text-align: left; }.reference-table th { color: var(--sub); font-weight: 500; width: 42%; }.reference-table td { color: var(--ink); }
.section-note { color: var(--sub); font-size: .9rem; line-height: 1.6; margin: 1.5rem 0; max-width: 43rem; }.section-note a { color: var(--ink); text-decoration-color: var(--line); text-underline-offset: .22rem; }.section-note a:hover, .section-note a:focus-visible { text-decoration-color: var(--ink); }.document-grid a { color: var(--ink); text-decoration: none; transition: background-color 140ms ease, border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease; }.document-grid a:hover, .document-grid a:focus-visible { background: var(--hover); border-color: var(--sub); box-shadow: 0 .35rem 1rem rgb(0 0 0 / 4%); outline: 2px solid var(--ink); outline-offset: 4px; transform: translateY(-2px); }
footer { border-top: 1px solid var(--line); color: var(--sub); font: .78rem var(--font-sans); letter-spacing: 0; padding-bottom: 1.5rem; padding-top: 1.5rem; }footer a { text-underline-offset: .2em; }.site-footer { align-items: flex-start; display: grid; gap: 2rem; grid-template-columns: minmax(0, 1fr) auto; padding-bottom: 2.5rem; padding-top: 2.5rem; }.footer-brand p { font: .72rem/1.55 var(--font-mono); margin: .75rem 0 0; max-width: 27ch; }.footer-links { display: grid; gap: 3rem; grid-template-columns: repeat(2, minmax(8rem, 1fr)); }.footer-links section { display: grid; gap: .55rem; }.footer-links h2 { color: var(--sub); font: .66rem var(--font-mono); font-weight: 600; letter-spacing: .1em; margin: 0 0 .25rem; text-transform: uppercase; }.footer-links a { color: var(--sub); font: .82rem var(--font-sans); letter-spacing: 0; text-decoration: none; }.footer-links a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: .22rem; }.section-page { min-height: calc(100vh - 132px); padding: clamp(4rem, 9vw, 7rem) 0 6rem; }.section-page .hero { align-items: flex-start; min-height: auto; padding: 0; text-align: left; }.section-page .hero > p:not(.eyebrow) { margin-left: 0; }.routes--two { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 5rem; max-width: 760px; }.routes--two a { border: 1px solid var(--line); border-radius: .5rem; display: grid; gap: .85rem; min-height: 230px; padding: 1.45rem 1.2rem; text-decoration: none; }.routes--two a:hover { background: var(--hover); }.routes--two span { color: var(--sub); font: .66rem var(--font-mono); letter-spacing: .1em; text-transform: uppercase; }.routes--two strong { font-size: 1.15rem; }.routes--two p { color: var(--sub); font-size: .86rem; line-height: 1.6; margin: 0; }
.site-footer--expanded { display: block; padding: clamp(3.5rem, 5vw, 4rem) max(1.5rem, calc((100vw - 72rem) / 2)); }
.footer-main { display: grid; gap: clamp(3rem, 6vw, 5rem); grid-template-columns: minmax(17rem, .9fr) minmax(0, 1.7fr); }
.site-footer--expanded .footer-brand .wordmark img { height: 40px; }
.site-footer--expanded .footer-brand p { color: color-mix(in srgb, var(--ink) 72%, var(--paper)); font: .86rem/1.65 var(--font-sans); letter-spacing: 0; max-width: 28rem; }
.footer-directory { display: grid; gap: clamp(2rem, 4vw, 4rem); grid-template-columns: repeat(2, minmax(8rem, 1fr)); }
.footer-directory section { align-content: start; display: grid; gap: .64rem; }
.footer-directory h2 { color: var(--ink); font: .78rem var(--font-sans); font-weight: 600; letter-spacing: 0; margin: 0 0 .5rem; }
.footer-directory a, .footer-legal a { align-items: center; color: var(--sub); display: inline-flex; font: .82rem var(--font-sans); letter-spacing: 0; min-height: 1.75rem; text-decoration: none; transition: color 120ms ease, transform 120ms ease; width: fit-content; }
.footer-directory a:hover, .footer-directory a:focus-visible, .footer-legal a:hover, .footer-legal a:focus-visible { color: var(--ink); text-decoration: none; transform: translateX(2px); }
.footer-directory a:focus-visible, .footer-legal a:focus-visible { outline-offset: 4px; }
/* One bottom line: copyright, legal links, social glyphs. */
.footer-meta { align-items: center; display: flex; flex-wrap: wrap; gap: 1rem 2rem; justify-content: space-between; margin-top: 2rem; padding-top: 0; }
.footer-meta p { font-size: .78rem; margin: 0; }
.footer-legal { display: flex; flex-wrap: wrap; gap: 1rem 1.5rem; }
.sr-only { clip-path: inset(50%); height: 1px; margin: -1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px; }:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
@media (max-width: 760px) { .landing-main { width: min(calc(100% - 2rem), 72rem); }.hero { min-height: 30rem; padding: 4rem 0; }.entry-grid, .architecture-list, .feature-steps__grid, .developer-grid, .section-header { margin-bottom: 2rem; }.architecture-list li, .architecture-list li:first-child { padding: 1.25rem 0; }.architecture-list li + li { border-left: 0; border-top: 1px solid var(--line); }.architecture-list span { margin-bottom: 1.25rem; }.feature-step-card { min-height: 14.5rem; }.site-footer { grid-template-columns: 1fr; }.site-footer--expanded { padding-block: 3rem; padding-inline: 1rem; }.footer-directory { gap: 2rem 1.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }.footer-meta { align-items: flex-start; flex-direction: column; gap: 1.25rem; }.footer-legal { flex-direction: column; gap: .65rem; }.footer-links { gap: 2rem; }.reference-table { min-width: 34rem; }.table-wrap { overflow-x: auto; }footer { align-items: flex-start; flex-direction: column; gap: .5rem; } }
@media (max-width: 440px) { .footer-directory { grid-template-columns: 1fr; }.footer-directory section:last-child { grid-column: auto; } }
/* The drawer's own header, logo and close button, exists only on the phone layout. */
.marketing-nav__head { display: none; }
/* Phone and tablet header, on the docs breakpoint, as on ALX Protocol (owner, 2026-09-14): the 44px menu button first on the page margin, the wordmark, then one search object in the width that is left. The menu is a full-height drawer from the left over a dimmed page; each group opens as its own list with a way back, and Open Explorer closes the main list. */
@media (max-width: 996px) {
  .site-header { align-items: center; flex-wrap: nowrap; gap: 0; min-height: 56px; }
  /* Mark and search read as one group on the left; the menu button is pushed to the right
   * edge by the auto margin, where a thumb reaches it (owner, 2026-09-20). This replaces an
   * earlier arrangement that clustered the mark with the MENU button and left search last:
   * .search-trigger carries order: 1 further down, which put the button between them.
   * The button keeps its 44px box; only the margin moves it. */
  .site-header > .menu-toggle { display: block; margin: 0 0 0 auto; order: 2; }
  .site-header > .wordmark { margin-right: .625rem; }
  .site-header > .search-trigger { flex: 0 1 auto; justify-content: flex-start; margin-left: 0; order: 1; }
  .docs-search { left: 4.5rem; width: min(20rem, calc(100vw - 7.25rem)); }
  .marketing-nav { align-items: stretch; background: var(--paper); border-right: 1px solid var(--line); box-shadow: 0 0 24px rgb(0 0 0 / 12%); display: none; flex-direction: column; gap: 0; height: 100dvh; inset: 0 auto 0 0; margin: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 .75rem 1rem; position: fixed; width: min(88vw, 22rem); z-index: 201; }
  .navigation-open .marketing-nav { display: flex; transition: translate 200ms cubic-bezier(.23, 1, .32, 1); translate: 0; }
  @starting-style { .navigation-open .marketing-nav { translate: -100% 0; } }
  /* The dim covers the whole screen, the bar included; the drawer sits above it. */
  .navigation-open .site-header::after { background: rgb(0 0 0 / 36%); content: ''; inset: 0; pointer-events: none; position: fixed; z-index: 200; }
  body.navigation-open { overflow: hidden; }
  .navigation-open .docs-search, .navigation-open .search-trigger { pointer-events: none; visibility: hidden; }
  .marketing-nav__head { align-items: center; border-bottom: 1px solid var(--line); display: flex; flex: 0 0 56px; justify-content: space-between; margin: 0 -.75rem .5rem; padding: 0 .25rem 0 1rem; }
  .marketing-nav__close { background: transparent; border: 0; border-radius: .4rem; cursor: pointer; height: 44px; position: relative; width: 44px; }
  .marketing-nav__close:hover { background: var(--hover); }
  .marketing-nav__close:before, .marketing-nav__close:after { background: var(--ink); content: ''; height: 1px; left: 13px; position: absolute; top: 21.5px; width: 18px; }
  .marketing-nav__close:before { rotate: 45deg; }
  .marketing-nav__close:after { rotate: -45deg; }
  .nav-dropdown { border-bottom: 1px solid var(--line); width: 100%; }
  .marketing-nav > a:not(.explorer-cta), .nav-dropdown__trigger { border-radius: 0; color: var(--ink); font-size: .875rem; justify-content: space-between; margin: 0; min-height: 44px; padding: .7rem; text-align: left; width: 100%; }
  .marketing-nav > a:not(.explorer-cta):hover, .nav-dropdown__trigger:hover { background: var(--hover); }
  /* A group row points onward; its open row points back to the main list. */
  .nav-dropdown__trigger:after { margin-top: 0; transform: rotate(-45deg); }
  .nav-dropdown.is-open .nav-dropdown__trigger { background: transparent; justify-content: flex-start; }
  .nav-dropdown.is-open .nav-dropdown__trigger:after { display: none; }
  .nav-dropdown.is-open .nav-dropdown__trigger:before { border: solid currentColor; border-width: 0 0 1px 1px; content: ''; height: .32rem; rotate: 45deg; width: .32rem; }
  .marketing-nav:has(.nav-dropdown.is-open) > :is(.nav-dropdown:not(.is-open), .explorer-cta) { display: none; }
  .nav-dropdown__panel { border: 0; border-radius: 0; box-shadow: none; margin: 0; padding: 0; position: static; width: auto; }
  .nav-dropdown__panel a { border-bottom: 1px solid var(--line); border-radius: 0; font-size: .875rem; min-height: 44px; padding: .7rem; }
  .nav-dropdown__panel a:last-child { border-bottom: 0; }
  .marketing-nav > .explorer-cta { color: var(--paper); margin: 1rem 0 .1rem; }
}
/* Between the docs breakpoint and the wide layout the field sits left of the four groups instead of under them. */
/* Search on phones: a trigger and a full-screen dialog, the same object the docs use. Wider than a phone the wrappers vanish and the field sits in the header. */
.search-trigger { display: none; }
.search-modal, .search-dialog { display: contents; }
.search-dialog__header { display: none; }
@media (max-width: 680px) {
  .search-trigger { align-items: center; background: transparent; border: 1px solid var(--line); border-radius: .4rem; color: var(--ink); cursor: pointer; display: inline-flex; font: 500 .82rem var(--font-sans); gap: .5rem; height: 2.75rem; margin-left: auto; order: 1; padding: 0 .75rem; }
  .search-trigger:hover { background: var(--hover); }
  .search-trigger__icon { border: 1.5px solid currentColor; border-radius: 50%; display: inline-block; height: .75rem; position: relative; width: .75rem; }
  .search-trigger__icon:after { background: currentColor; content: ''; height: 1px; position: absolute; right: -.3rem; top: .65rem; transform: rotate(45deg); width: .38rem; }
  .menu-toggle { margin-left: .25rem; }
  .search-modal { background: rgb(0 0 0 / 36%); display: none; inset: 0; position: fixed; z-index: 1000; }
  .search-modal--open { display: flex; }
  .search-dialog { background: var(--paper); display: flex; flex-direction: column; height: 100%; width: 100%; }
  .search-dialog__header { align-items: center; border-bottom: 1px solid var(--line); display: flex; flex: 0 0 57px; font-size: .88rem; justify-content: space-between; padding: 0 .25rem 0 1rem; }
  .search-dialog__close { background: transparent; border: 0; color: var(--ink); cursor: pointer; font: 500 .86rem var(--font-sans); min-height: 2.75rem; padding: 0 .75rem; }
  .search-dialog .docs-search { left: auto; margin: 1rem; position: relative; transform: none; visibility: visible; width: auto; }
  .search-dialog .docs-search input { font-size: 1rem; height: 48px; }
  .search-dialog .docs-search svg { top: 16px; }
  body.search-open { overflow: hidden; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; } }

/* Homepage hero: the Block Graph drawn on the page's own paper. A hairline card
 * holds a sparse node-and-edge drawing in the wordmark's notched block; the
 * drawing is masked lighter under the centred copy and one chain of three ink
 * blocks runs down the left. Edges draw in once on load; nothing moves under
 * reduced motion. No gradient, no blur, no inline styles (style-src 'self'). */
.hero--wallpaper { --hero-bleed: clamp(0rem, (100vw - 78rem) / 2, 8rem); margin-inline: calc(-1 * var(--hero-bleed)); min-height: auto; padding: clamp(1.25rem, 2vw, 1.5rem) 0 0; }
.hero-wallpaper { background: var(--paper); border: 1px solid var(--line); border-radius: clamp(1.25rem, 2.5vw, 2rem); color: var(--ink); isolation: isolate; overflow: hidden; padding: clamp(6rem, 12vw, 9rem) clamp(1.25rem, 4vw, 3rem); position: relative; width: 100%; }
 }
 to { opacity: 1; } }
.hero-wallpaper .hero-copy { align-items: center; display: flex; flex-direction: column; position: relative; }
.hero-wallpaper .hero-copy > p { color: var(--sub); }
@media (max-width: 760px) { .hero--wallpaper { --hero-bleed: 0rem; padding-top: 1rem; } .hero-wallpaper { border-radius: 1rem; padding: 4rem 1.25rem; } }

/* Long-form legal prose. The two legal pages used to ship their own stylesheet
 * with a slightly different black and rule colour; they now use these tokens. */
.legal-document { margin: 0 auto; max-width: 790px; padding: clamp(3.5rem, 9vw, 7rem) 1rem 6rem; }
.legal-document h1 { font-size: clamp(2.4rem, 5vw, 4.6rem); letter-spacing: -.035em; line-height: .95; margin: 0 0 1.25rem; }
.legal-document h2 { border-top: 1px solid var(--line); font-size: 1.32rem; letter-spacing: -.035em; line-height: 1.15; margin: 3.8rem 0 1rem; padding-top: 1rem; }
.legal-document h3 { font-size: 1rem; letter-spacing: -.015em; margin: 2.1rem 0 .5rem; }
.legal-document p, .legal-document li { color: var(--sub); line-height: 1.65; }
.legal-document p { margin: .6rem 0 1rem; }
.legal-document ul, .legal-document ol { margin: .5rem 0 1.15rem; padding-left: 1.25rem; }
.legal-document strong { color: var(--ink); }

/* Presale status strip: the four facts a visitor needs before anything else.
 * Values stay as placeholders until the token model is final. */

@media (max-width: 760px) {  }

/* Phase progress. The fill width comes from a data attribute rather than an
 * inline style, because the marketing CSP is style-src 'self'. */

/* Tabset. Panels are visible in the markup and hidden by landing.js, so the
 * content is present for search and readable if the script never runs. */
.tabset { margin: 2rem 0 0; max-width: 46rem; }
.tabset__list { border-bottom: 1px solid var(--line); display: flex; flex-wrap: wrap; gap: .25rem; }
/* The tab carries its own vertical padding, and the list carries none. The
 * list's border-bottom IS the tab underline: the active tab's 2px bottom border
 * is pulled onto it by margin-bottom: -1px, so padding-bottom on the list would
 * lift the rule off the indicator. 11.2px a side costs no height, because the
 * natural box is 39.4px and the 2.75rem minimum still sets the rail; past about
 * 13px a side the rail starts to grow. */
.tabset__tab { background: transparent; border: 0; border-bottom: 2px solid transparent; color: var(--sub); cursor: pointer; font: 500 .86rem var(--font-sans); margin-bottom: -1px; min-height: 2.75rem; padding: .7rem .9rem; transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease; }
.tabset__tab:hover { background: var(--hover); color: var(--ink); }
.tabset__tab:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.tabset__tab[aria-selected='true'] { border-bottom-color: var(--ink); color: var(--ink); }
/* On the field the selected tab keeps the same ink underline, which reads white on the night sky
 * (owner, 2026-09-23). The hero ribbon sat under the labels and read as a second, coloured rule. */
.tabset__panel { padding-top: .35rem; }
.tabset__panel:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.tabset__panel table { margin-top: 1.25rem; }
.tabset__panel > p { color: var(--sub); line-height: 1.65; margin: 1.25rem 0 0; max-width: 44rem; }

/* Presale countdown: bare figures, no tiles. 48px semibold sans digits over
 * small sentence-case labels, bold colons between units. Ships at zero and
 * stays there until an opening date is published. The digits are aria-hidden:
 * a per-second live region is unusable with a screen reader, and the note
 * below carries the same information as text. */
.countdown { display: grid; gap: .75rem; justify-items: center; margin: 2.75rem 0 0; }
/* display: grid outranks the user-agent [hidden] rule, so the countdown would
 * keep its space after the progress bar replaces it. */
.countdown[hidden] { display: none; }
/* The caption is set like the countdown's own labels, in the sans face, not
 * like the mono page kicker: it belongs to the figures beneath it. */
.countdown__eyebrow { color: var(--sub); font: 500 .875rem var(--font-sans); margin: 0; }
.countdown__row { align-items: center; display: flex; gap: 1rem; justify-content: center; }
.countdown__unit { align-items: center; display: flex; flex-direction: column; gap: .15rem; }
.countdown__number { display: inline-flex; font: 600 3rem/1 var(--font-sans); font-variant-numeric: tabular-nums; gap: .06em; }
.countdown__label { color: var(--sub); font-size: .875rem; }
.countdown__colon { font: 700 1.5rem/1 var(--font-sans); }
.countdown__note { color: var(--ink); font: 600 1.5rem/1.15 var(--font-sans); margin: 0; }
/* The note ships empty; an empty note must not hold a grid gap open. */
.countdown__note:empty { display: none; }
/* One digit: a clipped column of 0-9. Changing data-digit slides the column,
 * so the figure rolls into place; transform only, so it stays off the main
 * thread and the global reduced-motion rule turns it into a cut. */
.cd-digit { --cd-step: 1.2em; display: inline-block; height: var(--cd-step); line-height: var(--cd-step); overflow: hidden; text-align: center; width: 1ch; }
.cd-digit__strip { display: flex; flex-direction: column; transition: transform 480ms cubic-bezier(.2, .7, .2, 1); }
.cd-digit__strip i { display: block; font-style: normal; height: var(--cd-step); line-height: var(--cd-step); }
.cd-digit[data-digit="0"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * 0)); }
.cd-digit[data-digit="1"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -1)); }
.cd-digit[data-digit="2"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -2)); }
.cd-digit[data-digit="3"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -3)); }
.cd-digit[data-digit="4"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -4)); }
.cd-digit[data-digit="5"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -5)); }
.cd-digit[data-digit="6"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -6)); }
.cd-digit[data-digit="7"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -7)); }
.cd-digit[data-digit="8"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -8)); }
.cd-digit[data-digit="9"] .cd-digit__strip { transform: translateY(calc(var(--cd-step) * -9)); }
@media (max-width: 520px) { .countdown__row { gap: .6rem; } .countdown__number { font-size: 2.25rem; } .countdown__colon { font-size: 1.25rem; } }

/* Live sale progress. Takes the countdown's place once a phase opens: before the
 * sale the decision-relevant number is when, during it the number is which phase.
 * The price sits on its own line under the bar. Five discrete segments, one per
 * phase, rather than one continuous fill: a bar at 40% read as "40% sold", and
 * phases are neither equal in size nor a sold-through measure. Segment state is
 * data-sale, the same axis the stepper uses, so the two can never disagree. No
 * inline styles: the CSP forbids them, and transform is the only property that
 * animates. */
.sale-progress { display: grid; gap: .75rem; justify-items: center; margin: 2.75rem 0 0; width: min(100%, 30rem); }
.sale-progress[hidden] { display: none; }
/* The live phase is the fact the bar is answering, so it is set like the price
 * beneath it: ink, semibold, and large enough to read as the status, not a
 * caption. */
.sale-progress__caption { color: var(--ink); font: 600 1.75rem/1.15 var(--font-sans); letter-spacing: -.03em; margin: 0; }
/* Segment widths follow phase capacity (150M, 300M, 450M, 600M), so the bar
 * reads as the sale's shape rather than four equal steps. */
.sale-progress__track { display: grid; gap: .35rem; grid-template-columns: 1fr 2fr 3fr 4fr; width: 100%; }
.sale-progress__segment { background: var(--hover); border: 1px solid var(--line); border-radius: 999px; height: .55rem; overflow: hidden; position: relative; }
.sale-progress__segment::after { background: var(--ink); content: ''; inset: 0; position: absolute; transform: scaleX(0); transform-origin: left center; transition: transform 320ms ease; }
.sale-progress__segment[data-sale="past"], .sale-progress__segment[data-sale="live"] { border-color: var(--ink); }
.sale-progress__segment[data-sale="past"]::after, .sale-progress__segment[data-sale="live"]::after { transform: scaleX(1); }
/* Closed phases are a lighter tone, so past and live differ by weight as well
 * as by the ring: ink is reserved for the phase that is live right now. */
.sale-progress__segment[data-sale="past"] { border: 1px solid var(--faint); }
.sale-progress__segment[data-sale="past"]::after { background: var(--faint); }
/* The live phase is ringed so it reads as "in progress", not just "done". */
.sale-progress__segment[data-sale="live"] { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Sale state is a separate axis from selection: a reader may browse Phase 4's
 * terms while Phase 2 is live, and neither reading may contradict the other. */
.stepper__item[data-sale="past"] .stepper__indicator { background: var(--faint); }
.stepper__item[data-sale="live"] .stepper__indicator { background: var(--ink); }
.stepper__item[data-sale="past"] .stepper__price { color: var(--sub); }
.stepper__item[data-sale="live"] .stepper__title { color: var(--ink); font-weight: 600; }
/* The tag exists for one phase at a time; the others say nothing, their bar does. */
.stepper__item[data-sale="live"] .stepper__tag { display: block; }
.stepper__now:not(:empty) { display: grid; visibility: hidden; }
.stepper__item[data-sale="live"] .stepper__now { visibility: visible; }

/* The sale card. One surface holds the decision: when it opens (or which phase
 * is live), the price, the amount raised against the target, and the action.
 * The countdown and the phase bar keep their own rules; in here their outer
 * margin is the card's job. */
/* Not a panel on the hero: the hero. No surface, border or shadow; the
 * countdown, price and actions are grouped by spacing and measure alone, so
 * nothing draws an edge between them and the headline above. */
.sale-card { background: transparent; border: 0; box-shadow: none; display: grid; gap: 2rem; margin: 2.75rem auto 0; padding: 0; width: min(100%, 36rem); }
/* The presale hero carries a countdown, a price and actions under the headline,
 * so it is content-sized rather than centred in a viewport-height box: even
 * padding above and below, and a clear break between the headline tier and the
 * sale tier, with nothing floating in leftover space. */
.hero--presale { justify-content: flex-start; min-height: 0; padding: 5rem 0 4.5rem; }
.hero--presale .sale-card { margin-top: 3.5rem; }
@media (max-width: 760px) { .hero--presale { padding: 3.5rem 0 3rem; } .hero--presale .sale-card { margin-top: 2.75rem; } }
.sale-card > .countdown, .sale-card > .sale-progress { margin: 0; width: 100%; }
.sale-card > .hero-actions { margin: 0; }
.presale-modal-error { align-items: center; color: var(--ink); display: flex; flex-wrap: wrap; font-size: .86rem; gap: .35rem .7rem; margin: 0; }
.presale-modal-error[hidden] { display: none; }
.presale-modal-error__retry { appearance: none; background: none; border: 0; cursor: pointer; font-family: inherit; padding: 0; }
.sale-card__price { align-items: baseline; display: flex; flex-wrap: wrap; gap: .4rem; justify-content: center; margin: 0; }
.sale-card__price[hidden] { display: none; }
.sale-card__price strong { font-size: 1.75rem; font-variant-numeric: tabular-nums; font-weight: 600; letter-spacing: -.03em; }
.sale-card__price span { color: var(--sub); font-size: .9rem; }
/* Amount raised, against the published target when there is one. Ships hidden
 * and is revealed only when a fresh figure arrives; the track shows only with a
 * target, so the page never invents a denominator. Fill is transform only, in
 * 5% steps by data attribute, because the CSP forbids inline widths. */
.sale-card__raised { display: grid; gap: .6rem; }
.sale-card__raised[hidden] { display: none; }
.sale-card__raised-track { background: var(--hover); border: 1px solid var(--line); border-radius: 999px; height: .55rem; overflow: hidden; width: 100%; }
.sale-card__raised-track[hidden] { display: none; }
.sale-card__raised-fill { background: var(--ink); display: block; height: 100%; transform: scaleX(0); transform-origin: left center; transition: transform 320ms ease; width: 100%; }
.sale-card__raised[data-raised-pct="0"] .sale-card__raised-fill { transform: scaleX(0); }
.sale-card__raised[data-raised-pct="5"] .sale-card__raised-fill { transform: scaleX(0.05); }
.sale-card__raised[data-raised-pct="10"] .sale-card__raised-fill { transform: scaleX(0.1); }
.sale-card__raised[data-raised-pct="15"] .sale-card__raised-fill { transform: scaleX(0.15); }
.sale-card__raised[data-raised-pct="20"] .sale-card__raised-fill { transform: scaleX(0.2); }
.sale-card__raised[data-raised-pct="25"] .sale-card__raised-fill { transform: scaleX(0.25); }
.sale-card__raised[data-raised-pct="30"] .sale-card__raised-fill { transform: scaleX(0.3); }
.sale-card__raised[data-raised-pct="35"] .sale-card__raised-fill { transform: scaleX(0.35); }
.sale-card__raised[data-raised-pct="40"] .sale-card__raised-fill { transform: scaleX(0.4); }
.sale-card__raised[data-raised-pct="45"] .sale-card__raised-fill { transform: scaleX(0.45); }
.sale-card__raised[data-raised-pct="50"] .sale-card__raised-fill { transform: scaleX(0.5); }
.sale-card__raised[data-raised-pct="55"] .sale-card__raised-fill { transform: scaleX(0.55); }
.sale-card__raised[data-raised-pct="60"] .sale-card__raised-fill { transform: scaleX(0.6); }
.sale-card__raised[data-raised-pct="65"] .sale-card__raised-fill { transform: scaleX(0.65); }
.sale-card__raised[data-raised-pct="70"] .sale-card__raised-fill { transform: scaleX(0.7); }
.sale-card__raised[data-raised-pct="75"] .sale-card__raised-fill { transform: scaleX(0.75); }
.sale-card__raised[data-raised-pct="80"] .sale-card__raised-fill { transform: scaleX(0.8); }
.sale-card__raised[data-raised-pct="85"] .sale-card__raised-fill { transform: scaleX(0.85); }
.sale-card__raised[data-raised-pct="90"] .sale-card__raised-fill { transform: scaleX(0.9); }
.sale-card__raised[data-raised-pct="95"] .sale-card__raised-fill { transform: scaleX(0.95); }
.sale-card__raised[data-raised-pct="100"] .sale-card__raised-fill { transform: scaleX(1); }
.sale-card__raised-meta { align-items: baseline; display: flex; flex-wrap: wrap; gap: .35rem .75rem; justify-content: space-between; margin: 0; }
.sale-card__raised-label { color: var(--sub); font: .6875rem var(--font-mono); letter-spacing: .12em; text-transform: uppercase; }
.sale-card__raised-meta strong { font-size: 1.05rem; font-variant-numeric: tabular-nums; font-weight: 600; letter-spacing: -.02em; }
.sale-card__raised-meta [data-presale-raised-target] { color: var(--sub); font-variant-numeric: tabular-nums; }
.sale-card__raised-time { color: var(--sub); font-size: .78rem; }
@media (max-width: 520px) { .sale-card__price strong { font-size: 1.5rem; } }

/* Presale phase indicator. Five phases with price and state, nothing to click:
 * the panels behind the old tabs restated the tab, so they went. State rides on
 * data-sale, the same axis the hero's segments use, so the two cannot disagree. */
.stepper { display: grid; gap: .85rem; grid-template-columns: repeat(4, minmax(0, 1fr)); list-style: none; margin: 2rem 0 0; padding: 0; }
.stepper__item { display: grid; gap: .25rem; justify-items: start; }
.stepper__indicator { background: var(--line); border-radius: 999px; display: block; height: .3rem; margin-bottom: .5rem; width: 100%; }
.stepper__title { color: var(--sub); font-size: .82rem; font-weight: 500; }
.stepper__price { font-size: 1.05rem; font-variant-numeric: tabular-nums; font-weight: 600; letter-spacing: -.02em; }
.stepper__cap { color: var(--sub); font-size: .78rem; font-variant-numeric: tabular-nums; }
.stepper__tag { color: var(--ink); display: none; font: 500 .82rem var(--font-sans); }
.stepper__now { display: none; gap: .15rem; }
.stepper__remaining { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 500; }@media (max-width: 760px) { .stepper { gap: .6rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .stepper { grid-template-columns: 1fr; } }

/* Footer social row: placeholder profiles until the accounts exist. Bare 16px
 * glyphs in the footer's muted tone, ink on hover; each keeps a 44px target. The
 * row is pulled right by half a target so the last glyph sits on the edge. */
.footer-social { display: flex; gap: 0; margin: 0 -.875rem 0 0; }
.footer-social a { align-items: center; border-radius: .4rem; color: var(--sub); display: inline-flex; height: 2.75rem; justify-content: center; transition: color 140ms ease; width: 2.75rem; }
.footer-social a:hover, .footer-social a:focus-visible { color: var(--ink); }
.footer-social svg { fill: currentColor; height: 1rem; width: 1rem; }

/* Presale strip: no dismiss (owner, 2026-09-20). It stays until the countdown
 * opens the sale, and dodges the footer on the way past. */
.presale-strip[hidden] { display: none; }

/* The signal (owner, 2026-09-20). The strip is the header's glass - the same tint, blur and
 * solid fallbacks as .site-header::before on a field page - with no rainbow frame: the owner
 * asked for the border off and the glow kept (2026-09-20), so the card keeps its plain --line
 * hairline and every colour lives in the glow beneath it, in the hero's own ribbon.
 * The glow drifts continuously, 24s a lap (owner: the strip "should continuously move"). Two
 * layers of six soft shadows sit around the card, the second holding the same ribbon turned
 * half a turn, and they cross-fade: the colours travel around the strip while nothing moves
 * across the text. It steps rather than sliding, 24 steps a lap: everything that repaints inside
 * this card also re-runs its backdrop blur, so a smooth fade measured 39fps against a 58.7 paused
 * baseline - and so did animating the box-shadow colours directly, and so did pulsing the mark.
 * Stepping twice a second measures 58.6, and at this speed each step moves the glow about 2% of
 * its opacity, which no eye resolves. Anything faster or smoother here has to leave the glass:
 * a pseudo-element cannot be promoted away from it. That is motion
 * which starts by itself and never stops, which WCAG 2.2.2 wants a pause, stop or hide control
 * for; the dismiss that would have been one went by the owner's earlier decision, so the stops
 * that remain are the global prefers-reduced-motion rule above, a background tab, and the
 * footer dodge, which pauses the drift rather than letting it run behind a hidden card.
 * The eight --flow-* values are DESIGN.md's colors.flow-*, copied because the hero bakes them
 * into an SVG where a custom property cannot reach: change one and change both.
 * Colour stays off the fill: a light swept over the card measured 2.08:1 on the detail line
 * against the 4.5:1 floor. The glow is box-shadow blur, not a CSS filter - the hero's guard
 * bans a filter blur in this file - and an outer shadow is never painted inside its own box,
 * so none of it reaches the glass.
 * The glass is the header's exactly: rgb(13 11 22) at 62%. The thinnest line on it is this card's
 * 12.5px --sub detail line, at 4.7:1 at 390 where a near-white section passes behind it - over the
 * 4.5 floor, and the tightest margin on either glass surface, so measure it before changing the
 * tint. Measure with an UNCLIPPED screenshot: a capture clipped to the text's own box renders
 * backdrop-filter from a truncated backdrop and reads the glass far lighter than it is, which
 * once turned this 4.7:1 into a 4.2:1 failure that was never there.
 */
.presale-strip { --flow-pink: #d06a96; --flow-peach: #e6aa7c; --flow-tan: #d49f6a; --flow-rose: #b56f86; --flow-lilac: #a07f98; --flow-periwinkle: #6079b4; --flow-blue: #4b86c0; --flow-sky: #55a2cd; -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); background: rgb(13 11 22 / 62%); box-shadow: none; }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .presale-strip { background: var(--paper); } }
@media (prefers-reduced-transparency: reduce) { .presale-strip { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--paper); } }
.presale-strip::before, .presale-strip::after { border-radius: .6rem; content: ''; inset: 1px; pointer-events: none; position: absolute; z-index: -2; }
.presale-strip::after { animation: strip-glow-near 24s steps(24) infinite; box-shadow: -18px 0 30px -10px var(--flow-pink), -9px -14px 30px -12px var(--flow-peach), 9px -14px 30px -12px var(--flow-tan), 18px 0 30px -10px var(--flow-lilac), 9px 14px 30px -12px var(--flow-periwinkle), -9px 14px 30px -12px var(--flow-sky); opacity: .55; }
.presale-strip::before { animation: strip-glow-far 24s steps(24) infinite; box-shadow: -18px 0 30px -10px var(--flow-lilac), -9px -14px 30px -12px var(--flow-periwinkle), 9px -14px 30px -12px var(--flow-sky), 18px 0 30px -10px var(--flow-pink), 9px 14px 30px -12px var(--flow-peach), -9px 14px 30px -12px var(--flow-tan); opacity: 0; }
.presale-strip[data-presale-out]::before, .presale-strip[data-presale-out]::after { animation-play-state: paused; }
@keyframes strip-glow-near { 0%, 100% { opacity: .55; } 50% { opacity: 0; } }
@keyframes strip-glow-far { 0%, 100% { opacity: 0; } 50% { opacity: .55; } }
.presale-strip[data-presale-signal] > .presale-strip__icon { animation: strip-signal-wake 760ms cubic-bezier(.16,1,.3,1) 170ms both; }
@keyframes strip-signal-wake { 0% { border-color: var(--line); opacity: .6; } 30% { border-color: var(--flow-pink); opacity: 1; } 100% { border-color: var(--line); opacity: .6; } }
.presale-strip[data-presale-signal] .presale-strip__icon > svg { overflow: visible; }
.presale-strip[data-presale-signal] .presale-strip__icon > svg > * { animation: strip-signal-ping 700ms cubic-bezier(.16,1,.3,1) both; transform-origin: 12px 12px; }
.presale-strip[data-presale-signal] .presale-strip__icon > svg > :nth-child(3) { animation-delay: 170ms; }
.presale-strip[data-presale-signal] .presale-strip__icon > svg > :nth-child(2), .presale-strip[data-presale-signal] .presale-strip__icon > svg > :nth-child(4) { animation-delay: 300ms; }
.presale-strip[data-presale-signal] .presale-strip__icon > svg > :nth-child(1), .presale-strip[data-presale-signal] .presale-strip__icon > svg > :nth-child(5) { animation-delay: 430ms; }
@keyframes strip-signal-ping { 0% { stroke-opacity: 1; transform: scale(1); } 25% { stroke-opacity: .15; transform: scale(.7); } 60% { stroke-opacity: 1; transform: scale(1.12); } 100% { stroke-opacity: 1; transform: scale(1); } }

/* The countdown row is flex, so the hidden attribute needs a rule of its own. */
.countdown__row[hidden] { display: none; }

/* The wallpaper hero bleeds past the content column only above 1400px; between
 * 1248 and 1400 the bleed was a 16px near-miss against every other edge. */
@media (max-width: 1400px) { .hero--wallpaper { --hero-bleed: 0rem; } }

/* Phones: every multi-column composition becomes one column. Before this the
 * 760px query only added margins, so the first scroll on a phone showed three
 * 104px cards and a two-column FAQ. The four-card grid keeps its own breakpoints. */
@media (max-width: 760px) {
  .feature-steps__grid:not(.feature-steps__grid--four), .faq-grid, .developer-grid, .routes--two, .architecture-list { grid-template-columns: 1fr; }
  .faq-grid { gap: 0; }
  .section-header { align-items: start; gap: 1rem; grid-template-columns: 1fr; }
  .section-header h2 { max-width: none; }
  /* Two-column key-value tables fit the column; only wide data tables scroll. */
  .reference-table { min-width: 0; }
  .reference-table th, .reference-table td { padding: .7rem .6rem; }
}
/* Stacked footer: the glyph row hangs off the left edge instead of the right. */
@media (max-width: 760px) { .footer-social { margin: 0 0 0 -.875rem; }
  .footer-main { grid-template-columns: 1fr; } }

/* Keep the expanded desktop navigation clear of the centered search. */
/* Chromatic field. The homepage carries one continuous gradient the page
 * scrolls over, fixed so the composition holds still while content moves
 * through it, rather than a background repeated per section. Scoped with
 * :has() on .landing-main, carried by the home, token, presale and 404 pages but
 * not by the legal pages, which keep the paper world because long-form reading
 * over a star field is worse reading. No markup has to move.
 * The ground IS the hero's flow (owner, 2026-09-20): the page used to fall out of the
 * hero into five fixed radial blobs, which is the "focused blots" the same owner had
 * already rejected for the hero itself - the blend was applied there and never below it.
 * One --flow-mesh now carries the whole page, so the background under the last section is
 * the background under the header. It costs nothing to composite: a blurred raster it is
 * not, but an SVG with preserveAspectRatio='none', rasterised once and stretched to the
 * viewport, and the mid and narrow variants further down still swap in at their contrast
 * breakpoints. --ink and
 * --paper swap rather than being replaced, so every rule already written in
 * terms of them inverts correctly; --sub and --faint are tinted from the
 * field's own violet instead of grey. */
body:has(.landing-main) {
  --chroma-void: #04040e;
  /* The page ground: one composition that carries on from where the hero's ribbon ends and
   * keeps developing down the document, rather than the hero's frame repeated behind every
   * screen. Generated - the source and the reasoning are in the scratchpad generator, and
   * the two rules it keeps are that it opens on the hero's closing chord and that a dark
   * spine runs the full height down the middle, in proportional coordinates so the spine
   * still covers the text column once the canvas is squeezed onto a phone. */
  --flow-page: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 3600' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='n' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%231b1e37'/%3E%3Cstop offset='1' stop-color='%23141225'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-25%' y='-25%' width='150%' height='150%' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='120'/%3E%3C/filter%3E%3C/defs%3E%3Crect x='-432' y='-400' width='2304' height='4400' fill='url(%23n)'/%3E%3Cg filter='url(%23b)'%3E%3Crect x='-432' y='-400' width='2304' height='4400' fill='url(%23n)'/%3E%3Cellipse cx='1590' cy='60' rx='330' ry='300' fill='%2355a2cd'/%3E%3Cellipse cx='1590' cy='50' rx='240' ry='230' fill='%237cc0e2'/%3E%3Cellipse cx='1350' cy='150' rx='300' ry='190' fill='%234b86c0' transform='rotate(-50 1350 150)'/%3E%3Cellipse cx='1270' cy='330' rx='320' ry='150' fill='%236079b4' transform='rotate(-18 1270 330)'/%3E%3Cellipse cx='880' cy='190' rx='420' ry='130' fill='%23a07f98' transform='rotate(-3 880 190)'/%3E%3Cellipse cx='210' cy='120' rx='340' ry='140' fill='%23b56f86' transform='rotate(16 210 120)'/%3E%3Cellipse cx='-160' cy='40' rx='330' ry='260' fill='%23e6aa7c'/%3E%3Cellipse cx='-180' cy='620' rx='360' ry='300' fill='%23e6aa7c'/%3E%3Cellipse cx='-190' cy='600' rx='250' ry='220' fill='%23f4c192'/%3E%3Cellipse cx='30' cy='880' rx='330' ry='180' fill='%23d49f6a' transform='rotate(34 30 880)'/%3E%3Cellipse cx='-60' cy='1180' rx='320' ry='240' fill='%23d06a96' transform='rotate(-12 -60 1180)'/%3E%3Cellipse cx='1620' cy='900' rx='340' ry='320' fill='%234b86c0'/%3E%3Cellipse cx='1640' cy='1250' rx='330' ry='300' fill='%2355a2cd'/%3E%3Cellipse cx='1660' cy='1230' rx='230' ry='210' fill='%237cc0e2'/%3E%3Cellipse cx='1390' cy='1520' rx='300' ry='170' fill='%236079b4' transform='rotate(-28 1390 1520)'/%3E%3Cellipse cx='-170' cy='1700' rx='340' ry='280' fill='%23b56f86'/%3E%3Cellipse cx='80' cy='1980' rx='330' ry='190' fill='%23d49f6a' transform='rotate(28 80 1980)'/%3E%3Cellipse cx='1600' cy='1900' rx='320' ry='300' fill='%234b86c0'/%3E%3Cellipse cx='1630' cy='2220' rx='320' ry='280' fill='%236079b4'/%3E%3Cellipse cx='-180' cy='2480' rx='340' ry='300' fill='%23e6aa7c'/%3E%3Cellipse cx='-190' cy='2460' rx='230' ry='200' fill='%23f4c192'/%3E%3Cellipse cx='1640' cy='2650' rx='330' ry='300' fill='%2355a2cd'/%3E%3Cellipse cx='120' cy='2900' rx='360' ry='200' fill='%236c3860' transform='rotate(22 120 2900)'/%3E%3Cellipse cx='1360' cy='3020' rx='340' ry='200' fill='%234b438f' transform='rotate(-22 1360 3020)'/%3E%3Cellipse cx='-160' cy='3180' rx='330' ry='280' fill='%23b56f86'/%3E%3Cellipse cx='1620' cy='3320' rx='320' ry='280' fill='%234b86c0'/%3E%3Cellipse cx='738' cy='80' rx='380' ry='250' fill='%232b1c46' transform='rotate(1 738 80)'/%3E%3Cellipse cx='793' cy='340' rx='380' ry='250' fill='%231d1a3f' transform='rotate(5 793 340)'/%3E%3Cellipse cx='835' cy='600' rx='380' ry='250' fill='%23172b52' transform='rotate(9 835 600)'/%3E%3Cellipse cx='858' cy='860' rx='380' ry='250' fill='%232b1c46' transform='rotate(11 858 860)'/%3E%3Cellipse cx='856' cy='1120' rx='380' ry='250' fill='%231d1a3f' transform='rotate(13 856 1120)'/%3E%3Cellipse cx='831' cy='1380' rx='380' ry='250' fill='%23172b52' transform='rotate(14 831 1380)'/%3E%3Cellipse cx='787' cy='1640' rx='380' ry='250' fill='%232b1c46' transform='rotate(14 787 1640)'/%3E%3Cellipse cx='731' cy='1900' rx='380' ry='250' fill='%231d1a3f' transform='rotate(12 731 1900)'/%3E%3Cellipse cx='673' cy='2160' rx='380' ry='250' fill='%23172b52' transform='rotate(9 673 2160)'/%3E%3Cellipse cx='623' cy='2420' rx='380' ry='250' fill='%232b1c46' transform='rotate(6 623 2420)'/%3E%3Cellipse cx='591' cy='2680' rx='380' ry='250' fill='%231d1a3f' transform='rotate(2 591 2680)'/%3E%3Cellipse cx='580' cy='2940' rx='380' ry='250' fill='%23172b52' transform='rotate(-2 580 2940)'/%3E%3Cellipse cx='594' cy='3200' rx='380' ry='250' fill='%232b1c46' transform='rotate(-6 594 3200)'/%3E%3Cellipse cx='630' cy='3460' rx='380' ry='250' fill='%231d1a3f' transform='rotate(-9 630 3460)'/%3E%3Cellipse cx='681' cy='3720' rx='380' ry='250' fill='%23172b52' transform='rotate(-12 681 3720)'/%3E%3Cellipse cx='806' cy='200' rx='300' ry='300' fill='%231d1a3f'/%3E%3Cellipse cx='745' cy='900' rx='300' ry='300' fill='%231d1a3f'/%3E%3Cellipse cx='661' cy='1600' rx='300' ry='300' fill='%231d1a3f'/%3E%3Cellipse cx='631' cy='2300' rx='300' ry='300' fill='%231d1a3f'/%3E%3Cellipse cx='683' cy='3000' rx='300' ry='300' fill='%231d1a3f'/%3E%3C/g%3E%3C/svg%3E");
  /* Retired 2026-09-20, kept for reference and for the tonal ramps in .impeccable/design.json:
   * these five were the page's ground until the hero's flow took it over, and nothing reads them
   * now except this comment. --chroma-void is still the base colour under the flow. */
  --chroma-plum: #5c1d7c;
  --chroma-rose: #7a1f4d;
  --chroma-azure: #1d497c;
  --chroma-amber: #7d501a;
  --chroma-teal: #196464;
  --ink: #f6f3fb;
  --paper: #0d0b16;
  --surface: rgb(246 243 251 / 6%);
  --sub: #d6cfe6;
  --faint: #b3a9c9;
  --line: rgb(246 243 251 / 18%);
  --hover: rgb(246 243 251 / 9%);
  --active: rgb(246 243 251 / 14%);
  background:
    linear-gradient(112deg, transparent 32%, rgb(214 207 230 / 6%) 46%, rgb(214 207 230 / 9%) 52%, rgb(214 207 230 / 5%) 58%, transparent 72%),
    var(--flow-page),
    var(--chroma-void);
  /* Per-layer attachment buys real parallax for nothing: the near stars scroll with the
   * content while the far stars, the band and the flow stay locked to the viewport, so the
   * sky gains depth over a 4,000px page instead of being inert. The flow is locked rather
   * than the flow, which maps to the document and develops as the page scrolls. Stretching
   * the HERO's mesh down the document was tried first and failed: its bright peach arm landed
   * behind body copy on a tall phone page at 2.3:1 against the 4.5 floor. --flow-page is drawn
   * for the job instead, with its bright mass kept out of the middle third at every width.
   * Four layers, in the order declared above. */
  background-attachment: fixed, scroll;
  background-repeat: repeat, no-repeat;
  background-size: auto, 100% 100%;
  color: var(--ink);
}
/* The page carries the hero's grain (owner, 2026-09-21: "The rest of the webpage doesn't have that
 * same gradient feel as the hero. It becomes blurred"). The ground below the hero had the flow's
 * colours without its texture, so where the hero's grain faded out the page turned to a smooth
 * blur. It now takes the same mono grain, laid hard-light as the hero's is, and scrolling with the
 * document as the hero does, so the texture runs unbroken from the first screen to the footer. The
 * glass cards stay smooth: hard-light grain on their dark fill would speckle light under small
 * text. The presale page keeps its night sky.
 * Every layer scrolls here, the faint light band too. As the one fixed layer, the band made the
 * browser repaint the whole ground on every scroll frame. On a 2015 integrated GPU (Intel HD 520)
 * that held scrolling to 29-32fps before the grain and 23-27 with it; with the band scrolling it
 * measures 58-60 with the grain. Its parallax, a 6-9% light streak, is the price. */
body:has(.landing-main):not(:has(.hero--moon)) {
  background-attachment: scroll, scroll, scroll;
  background-blend-mode: hard-light, normal, normal;
  background-image: var(--flow-grain), linear-gradient(112deg, transparent 32%, rgb(214 207 230 / 6%) 46%, rgb(214 207 230 / 9%) 52%, rgb(214 207 230 / 5%) 58%, transparent 72%), var(--flow-page);
  background-repeat: repeat, repeat, no-repeat;
  background-size: auto, auto, 100% 100%;
}
/* Film grain over the field, as a fixed overlay so it does not scroll with the
 * content and never tiles visibly. Negative z-index puts it above the canvas
 * background and below every element. */
body:has(.landing-main)::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  content: '';
  inset: 0;
  mix-blend-mode: overlay;
  opacity: .19;
  pointer-events: none;
  position: fixed;
  z-index: -1;
}
/* The hero card stops being paper and becomes a hairline pane over the field. */
/* With the field running the full page the hero card stops being a card: no pane, no
 * hairline, so the galaxy reads through the first viewport unbroken. */
body:has(.landing-main) .hero-wallpaper { background: transparent; border-color: transparent; }
/* The reduced-transparency fallback has to be repeated at this specificity: the base one sits on
 * .site-header::before, which this rule outranks, so without it a field page kept its translucency
 * and lost only its blur - see-through and sharp, the worse pair of the two.
 * The tint itself is measured and stays: at 62% the links hold 7.6:1 and better across the whole
 * scroll at 1440 and 390. Measure it with an UNCLIPPED screenshot - see the presale strip. */
body:has(.landing-main) .site-header::before { background: rgb(13 11 22 / 62%); }
@media (prefers-reduced-transparency: reduce) { body:has(.landing-main) .site-header::before { background: var(--paper); } }
/* Browser surfaces carry the field's palette rather than the UA defaults. */
/* The footer is the densest small text on the page and it lands on the amber pool,
 * where .footer-brand's mixed grey-violet measured 3.21:1 at p99. The field deepens
 * toward the foot rather than the text being brightened: keeps the secondary
 * hierarchy, and lifts every element in the footer at once. */
body:has(.landing-main) .site-footer { background: linear-gradient(to bottom, rgb(4 4 14 / 35%), rgb(4 4 14 / 82%)); }
/* Role tokens, not borrowed ones. Both of these used to point at --chroma-amber, which was later
 * re-solved darker so body copy could sit on it, and the focus ring (2.39:1 against the ground)
 * and selected text (2.78:1) went dark with it. A nebula core is a ground; a focus ring is a
 * signal. They now take the flow's peach: ring 8.9:1 against navy, selected text 9.6:1. */
body:has(.landing-main) { --focus-ring: #e6aa7c; --selection-bg: #e6aa7c; --selection-ink: #150d02; }
body:has(.landing-main) ::selection { background: var(--selection-bg); color: var(--selection-ink); }
body:has(.landing-main) :focus-visible { outline-color: var(--focus-ring); }
/* The filled actions' hover glow is near-black, drawn for paper. On the field a dark glow over a
 * dark ground is no glow at all, so here it is light. The paper rule above is left as it is. */
body:has(.landing-main) :is(.explorer-cta, .primary-action, .faq-action, .presale-strip__action):is(:hover, :focus-visible) { box-shadow: 0 0 0 3px rgb(246 243 251 / 14%), 0 0 22px rgb(246 243 251 / 20%); }
body:has(.landing-main) { scrollbar-color: rgb(246 243 251 / 30%) transparent; }
/* Finish-review fixes, 2026-09-20.
 * The field's own logic, applied consistently: the hero dissolved its pane, so the
 * section hairlines and the feature-card panes go too — a continuous field cannot be
 * ruled into stacked boxes. Scoped, so the legal pages keep their rules on paper. */
/* Section shells (owner, 2026-09-20). Each section, and the 404's block, sits in a card made of
 * the header's own glass - rgb(13 11 22 / 62%) under a 12px backdrop blur - so the bar, the
 * actions and the cards are one material rather than three densities.
 * The cards hold the text, because the ground no longer can: it is as luminous as the hero so
 * the page reads as one composition. Density is therefore a contrast budget, swept on that
 * ground across home, token and presale: 72% measured 8.4:1, 62%-60% 6.3:1, 50% 5.1:1, and 40%
 * failed at 4.1:1. A truly clear card is not available here - light text on a thin film over a
 * bright ground is the one combination that cannot work - and 62% is the lightest density that
 * keeps about two points of margin, which is also why it could simply be the header's value.
 * The 404 is in the selector because its copy is not in a section: on a short page the ground
 * compresses and put its brightest arm behind the text, at 1.8:1 bare.
 * The blur costs frames on a full-width surface that scrolls; it is here for the material, and
 * the fallback under prefers-reduced-transparency is a solid --paper, the most readable a card
 * gets. The radius is the hero shell's, the one large-shell radius in the vocabulary; the side
 * margin pulls the shell outward so the text column stays exactly where it was. */
body:has(.landing-main) .reference-table td { line-height: 1.55; }
body:has(.landing-main) :is(.entry-section, .home-section, .hero.not-found) { -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); background: rgb(13 11 22 / 62%); border: 1px solid var(--line); border-radius: clamp(1.25rem, 2.5vw, 2rem); margin-block: clamp(.75rem, 1.6vw, 1.25rem); margin-inline: calc(-1 * clamp(.75rem, 2vw, 2rem)); padding: clamp(3rem, 6vw, 4.5rem) clamp(.75rem, 2vw, 2rem); }
@media (prefers-reduced-transparency: reduce) { body:has(.landing-main) :is(.entry-section, .home-section, .hero.not-found) { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--paper); } }
/* The pane was holding a 17rem floor. With it dissolved the floor only stranded each
 * card's link in empty field, so the card shrinks to its content. */
body:has(.landing-main) .feature-step-card { background: transparent; border-color: transparent; min-height: 0; padding: 0 1.25rem 0 0; }
/* Subgrid aligns heading, prose and action across the whole row, so four parallel
 * actions land on one line without a min-height stranding them in empty field.
 * Each card spans the three tracks the grid declares. */
body:has(.landing-main) .feature-steps__grid { grid-template-rows: auto auto auto; }
/* row-gap: 0 because a subgrid item inherits the parent's row gap across the tracks it
 * spans: without it the separation BETWEEN cards is also pushed INSIDE each one. */
body:has(.landing-main) .feature-step-card { display: grid; grid-row: span 3; grid-template-rows: subgrid; justify-items: start; row-gap: 0; }
body:has(.landing-main) .feature-step-card > .text-action { align-self: end; margin-top: 1.1rem; }
/* The first viewport is the thesis: h1, one sentence, one action, and the field.
 * min-height auto let the next section ride up into the fold on all three pages. */
body:has(.landing-main) .hero-wallpaper { align-content: center; display: grid; min-height: calc(100svh - 60px); }
/* Token and presale use .hero--presale, not the wallpaper card, so they need the
 * same hold: the section under each was riding into the first viewport. */
/* .hero is a centred flex column; switching this to grid knocked the token and presale
 * copy off-centre against the header. Hold the height and re-centre, nothing else. */
body:has(.landing-main) .hero--presale { justify-content: center; min-height: calc(100svh - 60px); }
@media (max-width: 760px) {
  body:has(.landing-main) .hero-wallpaper, body:has(.landing-main) .hero--presale { min-height: calc(100svh - 56px); }
  /* One column: separation between cards has to beat the spacing inside one. */
  body:has(.landing-main) .feature-steps__grid { gap: 2.75rem; }
}
/* Prose measure: the token and presale blocks ran to 96-98 characters against a 65-75ch floor. */
body:has(.landing-main) .section-note, body:has(.landing-main) .hero--presale > p { max-width: 68ch; }
body:has(.landing-main) .hero--presale > p { margin-inline: auto; }
/* Luminous hero flow. The first viewport carries the reference's chromatic flow at its real lightness,
 * as ONE blend rather than separate pools. It is an inline SVG: ellipses ROTATED along a single
 * U-shaped ribbon (pink running down into peach at the left edge, turning through tan, rose and
 * lilac beneath the copy, lifting through periwinkle and blue into sky at the right) and melted
 * together by a Gaussian blur inside the image. CSS radial gradients were the first build and read
 * as blots: they cannot rotate, cannot melt into each other, and needed a flat navy ellipse under
 * the copy that showed as a porthole.
 * The copy's ground is carved last and blurred with everything else, so it has no edge. It is ONE
 * hue family, indigo, with a plum tint and a blue tint laid along the same diagonal. An earlier
 * pass set saturated plum and saturated blue side by side under the copy, which scored better on a
 * "share of the frame not changing" measure (31% against 37% now; the radial build was 40%, the
 * reference 9%) and read as the colours being split down the middle, with the seam on the very
 * line the copy is centred on. Composition outranks the number: saturation follows light here, so
 * the luminous edges are vivid and the dark ground is quiet. Most of what stays "flat" is the
 * ground the copy needs. Behind the lede the measured ground holds above 6.5:1 from 360 to 1920.
 * The flow belongs to the hero and scrolls away with it, handing over to the solved dark field,
 * because the field is fixed and every part of it eventually sits under body copy, while these
 * colours are far too light for that: white on the peach is 2.0:1.
 * The blur lives inside the SVG, so the browser rasterises it once as an image; it is not a CSS
 * filter composited per frame, which is what the no-blur guard in the tests protects against.
 * The grain is mono fractal noise laid hard-light, so it darkens and lightens around the base
 * colour: overlay grain vanishes on dark ground and normal-blend grey fogs the hue. Its contrast
 * (slope .38) was tuned until the luminance deviation matched the reference's film grain. */
body:has(.landing-main) {
  --flow-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cfilter id='g' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.38' intercept='0.31'/%3E%3CfeFuncG type='linear' slope='0.38' intercept='0.31'/%3E%3CfeFuncB type='linear' slope='0.38' intercept='0.31'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  --flow-mesh: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 1050' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='n' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%231b1e37'/%3E%3Cstop offset='1' stop-color='%23181624'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-25%25' y='-25%25' width='150%25' height='150%25' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='70'/%3E%3C/filter%3E%3C/defs%3E%3Crect x='-432' y='-315' width='2304' height='1680' fill='url(%23n)'/%3E%3Cg filter='url(%23b)'%3E%3Crect x='-432' y='-315' width='2304' height='1680' fill='url(%23n)'/%3E%3Cellipse cx='1430' cy='70' rx='420' ry='230' fill='%23254178'/%3E%3Cellipse cx='1440' cy='1040' rx='400' ry='200' fill='%234b438f'/%3E%3Cellipse cx='60' cy='110' rx='480' ry='270' fill='%23d06a96' transform='rotate(-18 60 110)'/%3E%3Cellipse cx='470' cy='60' rx='300' ry='150' fill='%236c3860'/%3E%3Cellipse cx='-30' cy='450' rx='330' ry='300' fill='%23e6aa7c'/%3E%3Cellipse cx='-30' cy='430' rx='250' ry='240' fill='%23f4c192'/%3E%3Cellipse cx='170' cy='700' rx='330' ry='170' fill='%23d49f6a' transform='rotate(35 170 700)'/%3E%3Cellipse cx='430' cy='850' rx='340' ry='130' fill='%23b56f86' transform='rotate(16 430 850)'/%3E%3Cellipse cx='840' cy='905' rx='420' ry='125' fill='%23a07f98' transform='rotate(-3 840 905)'/%3E%3Cellipse cx='1190' cy='850' rx='300' ry='130' fill='%236079b4' transform='rotate(-22 1190 850)'/%3E%3Cellipse cx='1290' cy='640' rx='300' ry='200' fill='%234b86c0' transform='rotate(-50 1290 640)'/%3E%3Cellipse cx='1470' cy='430' rx='330' ry='320' fill='%2355a2cd'/%3E%3Cellipse cx='1470' cy='420' rx='240' ry='240' fill='%237cc0e2'/%3E%3Cellipse cx='470' cy='210' rx='470' ry='150' fill='%233a2350' transform='rotate(24 470 210)'/%3E%3Cellipse cx='1040' cy='150' rx='420' ry='130' fill='%231b2a54' transform='rotate(24 1040 150)'/%3E%3Cellipse cx='720' cy='495' rx='640' ry='320' fill='%231d1a3f'/%3E%3Cellipse cx='470' cy='420' rx='400' ry='210' fill='%232b1c46' transform='rotate(-32 470 420)'/%3E%3Cellipse cx='990' cy='600' rx='400' ry='210' fill='%23172b52' transform='rotate(-32 990 600)'/%3E%3C/g%3E%3C/svg%3E");
}
body:has(.landing-main) :is(.hero--wallpaper, .hero--presale)::before {
  background-blend-mode: hard-light, normal;
  background-image: var(--flow-grain), var(--flow-mesh);
  background-repeat: repeat, no-repeat;
  background-size: auto, 100% 100%;
  bottom: -8rem;
  content: '';
  left: 50%;
  margin-left: -50vw;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 12rem), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 12rem), transparent 100%);
  pointer-events: none;
  position: absolute;
  top: -5rem;
  width: 100vw;
  z-index: -1;
}
/* Mid widths: the text column is a fixed width while the mesh scales with the viewport, so here the
 * column fills far more of the frame. Same ribbon, bright edges moved outward, the copy's ground
 * widened. Without it the lede measured 3.64:1 at 770px and 4.49:1 at 820px, and the token page's
 * one-line headline, 902px wide at a 1181px viewport, measured 2.0:1 at its ends: failures that
 * sat exactly between the usual 390, 1024 and 1440 test widths. Hence 1320px, not 1180. */
@media (max-width: 1320px) {
  body:has(.landing-main) { --flow-mesh: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 1050' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='n' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%231b1e37'/%3E%3Cstop offset='1' stop-color='%23181624'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-25%25' y='-25%25' width='150%25' height='150%25' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='70'/%3E%3C/filter%3E%3C/defs%3E%3Crect x='-432' y='-315' width='2304' height='1680' fill='url(%23n)'/%3E%3Cg filter='url(%23b)'%3E%3Crect x='-432' y='-315' width='2304' height='1680' fill='url(%23n)'/%3E%3Cellipse cx='1430' cy='70' rx='420' ry='230' fill='%23254178'/%3E%3Cellipse cx='1440' cy='1040' rx='400' ry='200' fill='%234b438f'/%3E%3Cellipse cx='-20' cy='90' rx='400' ry='250' fill='%23d06a96' transform='rotate(-18 -20 90)'/%3E%3Cellipse cx='420' cy='50' rx='300' ry='140' fill='%236c3860'/%3E%3Cellipse cx='-150' cy='450' rx='330' ry='300' fill='%23e6aa7c'/%3E%3Cellipse cx='-150' cy='430' rx='250' ry='240' fill='%23f4c192'/%3E%3Cellipse cx='60' cy='740' rx='300' ry='160' fill='%23d49f6a' transform='rotate(35 60 740)'/%3E%3Cellipse cx='400' cy='900' rx='340' ry='120' fill='%23b56f86' transform='rotate(12 400 900)'/%3E%3Cellipse cx='860' cy='930' rx='420' ry='115' fill='%23a07f98' transform='rotate(-3 860 930)'/%3E%3Cellipse cx='1210' cy='890' rx='300' ry='120' fill='%236079b4' transform='rotate(-16 1210 890)'/%3E%3Cellipse cx='1400' cy='700' rx='280' ry='190' fill='%234b86c0' transform='rotate(-50 1400 700)'/%3E%3Cellipse cx='1590' cy='430' rx='330' ry='320' fill='%2355a2cd'/%3E%3Cellipse cx='1590' cy='420' rx='240' ry='240' fill='%237cc0e2'/%3E%3Cellipse cx='470' cy='210' rx='470' ry='150' fill='%233a2350' transform='rotate(24 470 210)'/%3E%3Cellipse cx='1040' cy='150' rx='420' ry='130' fill='%231b2a54' transform='rotate(24 1040 150)'/%3E%3Cellipse cx='720' cy='490' rx='640' ry='340' fill='%231d1a3f'/%3E%3Cellipse cx='470' cy='420' rx='400' ry='220' fill='%232b1c46' transform='rotate(-32 470 420)'/%3E%3Cellipse cx='990' cy='600' rx='400' ry='220' fill='%23172b52' transform='rotate(-32 990 600)'/%3E%3C/g%3E%3C/svg%3E"); }
}
/* One column: the copy runs edge to edge, so the bright colour sits above and below it and the
 * dark ground becomes a band across the middle. It takes over at 900px, not the layout's 760px,
 * because that is where the measured ground under the lede stops holding. */
@media (max-width: 900px) {
  body:has(.landing-main) { --flow-mesh: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 390 1000' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='n' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%231b1e37'/%3E%3Cstop offset='1' stop-color='%23181624'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-25%25' y='-25%25' width='150%25' height='150%25' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='38'/%3E%3C/filter%3E%3C/defs%3E%3Crect x='-117' y='-300' width='624' height='1600' fill='url(%23n)'/%3E%3Cg filter='url(%23b)'%3E%3Crect x='-117' y='-300' width='624' height='1600' fill='url(%23n)'/%3E%3Cellipse cx='400' cy='20' rx='200' ry='110' fill='%23254178'/%3E%3Cellipse cx='400' cy='1000' rx='220' ry='130' fill='%234b438f'/%3E%3Cellipse cx='20' cy='60' rx='260' ry='170' fill='%23c8648f' transform='rotate(-14 20 60)'/%3E%3Cellipse cx='-20' cy='235' rx='180' ry='130' fill='%23e6aa7c'/%3E%3Cellipse cx='420' cy='150' rx='190' ry='160' fill='%2355a2cd'/%3E%3Cellipse cx='30' cy='810' rx='220' ry='110' fill='%23d49f6a' transform='rotate(25 30 810)'/%3E%3Cellipse cx='150' cy='885' rx='230' ry='100' fill='%23b56f86' transform='rotate(10 150 885)'/%3E%3Cellipse cx='260' cy='905' rx='220' ry='95' fill='%239a7f95'/%3E%3Cellipse cx='370' cy='840' rx='200' ry='110' fill='%236079b4' transform='rotate(-25 370 840)'/%3E%3Cellipse cx='195' cy='470' rx='330' ry='300' fill='%231d1a3f'/%3E%3Cellipse cx='90' cy='400' rx='200' ry='160' fill='%232b1c46' transform='rotate(-35 90 400)'/%3E%3Cellipse cx='300' cy='590' rx='200' ry='160' fill='%23172b52' transform='rotate(-35 300 590)'/%3E%3C/g%3E%3C/svg%3E"); }
}
/* The headline is filled rather than coloured (owner's reference, 2026-09-20): a satin gradient
 * that takes warm white from the peach on its left and ice from the sky on its right, with one
 * soft specular dip set at 118deg so it crosses the letterforms like a reflection instead of
 * tinting a single word, and the ground's own film grain laid into the letters. color stays ink,
 * so a browser that cannot clip a background to text still draws a solid legible headline. Every
 * stop clears 6:1 against the brightest ground behind the h1, where large text needs 3:1; the
 * reference lets its last letter fade into the ground, which suits a wordmark and not a page's
 * only h1. The padding gives the clipped background room for descenders under a .94 line-height,
 * and the equal negative margin hands the space back so nothing below the headline moves. */
body:has(.landing-main) :is(.hero--wallpaper, .hero--presale) h1 {
  -webkit-background-clip: text;
  background-clip: text;
  background-blend-mode: hard-light, normal;
  background-image: var(--flow-grain), linear-gradient(118deg, #ffdfc4 0%, #fff8f1 16%, #f3ecff 36%, #d2c6f1 50%, #fbf9ff 61%, #e6f1fc 80%, #c9e3f9 100%);
  margin-bottom: -.08em;
  padding-bottom: .08em;
  -webkit-text-fill-color: transparent;
}
body:has(.landing-main) :is(.hero--wallpaper, .hero--presale) h1::selection { -webkit-text-fill-color: var(--selection-ink); }
@media (forced-colors: active) {
  body:has(.landing-main) :is(.hero--wallpaper, .hero--presale) h1 { background: none; -webkit-text-fill-color: CanvasText; }
}
/* Field actions: dark fill, light label (owner, 2026-09-20). On paper the site's filled action is a
 * near-black pill with a white label. On the field --ink and --paper swap, so that same rule drew a
 * light pill with a dark label; these pages invert it back, and the hero action and the navbar CTA
 * now carry one treatment rather than two.
 * A dark fill cannot carry a control's edge on a dark ground: measured against the mesh the fill
 * sits at 1.28:1, where WCAG 1.4.11 wants 3:1 for a boundary. The border carries it, at 50% ink.
 * That is not a style value to nudge: swept across the wide, mid and narrow meshes at 390 through
 * 1920, 30% measures 1.76:1 and 42% measures 2.65:1, both failing; 46% is the crossover at 3.01:1
 * with no margin; 50% holds 3.4:1 or better everywhere, and better again under the header, whose
 * frosted strip darkens the ground beneath it.
 * The shared rule's hover flips the fill to light and the label to dark, which would invert these
 * buttons mid-interaction, so hover is redefined here: the fill lifts, the border brightens, both
 * stay dark. The drawer's own `.marketing-nav > .explorer-cta` sets a dark label at phone width and
 * is included above for the same reason.
 * Nothing in the first viewport is a solid fill now. That is the cost of matching the two: filled
 * colour no longer marks one action as primary, and the hero carries that job by scale instead.
 * The fill is the header's own glass (owner, 2026-09-20), the same tint and blur, so the actions
 * read as the material the bar and the section shells are made of. Measured: the hero label holds
 * 15.9:1 against 16.9:1 on the old solid fill, and the header's Open Explorer 14.8:1, with the
 * brightest section scrolled beneath the bar. The thin 6% film the shells use was measured too and
 * is NOT what this is: on the hero action it still read 10.1:1, but it dissolves the only action
 * above the fold into the ground behind it. On the navbar itself it was worse than a preference -
 * the links fell to 2.7:1 at 1440 and 1.3:1 at 390, because a section shell sits over ground we
 * authored while the bar sits over whatever the visitor scrolled to.
 * In the header the button's fill now sits 1.05:1 against the bar, against 1.15:1 before, so it is
 * the border that says a control is there. That was already true and is why the border is 50%.
 * The paper pages are untouched and keep the near-black pill. */
body:has(.landing-main) :is(.hero-actions .primary-action, .explorer-cta, .marketing-nav > .explorer-cta, .presale-strip__action) {
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  background: rgb(13 11 22 / 62%);
  border-color: rgb(246 243 251 / 50%);
  color: var(--ink);
}
body:has(.landing-main) :is(.hero-actions .primary-action, .explorer-cta, .marketing-nav > .explorer-cta, .presale-strip__action):is(:hover, :focus-visible) {
  background: rgb(30 26 49 / 78%);
  border-color: rgb(246 243 251 / 72%);
  color: var(--ink);
}
/* The solid fill these carried before the glass, for the two cases that cannot show it. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  body:has(.landing-main) :is(.hero-actions .primary-action, .explorer-cta, .marketing-nav > .explorer-cta, .presale-strip__action) { background: #14111f; }
}
@media (prefers-reduced-transparency: reduce) {
  body:has(.landing-main) :is(.hero-actions .primary-action, .explorer-cta, .marketing-nav > .explorer-cta, .presale-strip__action) { -webkit-backdrop-filter: none; backdrop-filter: none; background: #14111f; }
}
/* The FAQ's documentation button is the card's own colour (owner, 2026-09-21): no fill of its own,
 * so the shell's glass shows through, and the 50% border every field action carries, since a fill
 * the colour of its ground cannot draw its own edge. Paper pages keep the ink button. */
body:has(.landing-main) .faq-action { background: transparent; border-color: rgb(246 243 251 / 50%); color: var(--ink); }
body:has(.landing-main) .faq-action:is(:hover, :focus-visible) { background: rgb(246 243 251 / 8%); border-color: rgb(246 243 251 / 72%); color: var(--ink); }
/* A reflective ring around the home hero's Open Explorer (owner, 2026-09-21): a hairline of ink 5px
 * outside the button, cut from a conic gradient with two bright glints, the way light sits on a
 * polished ring. The glints turn once every 6 seconds when motion is welcome; with reduced motion
 * the ring holds still. It is drawn by masking a padded box down to its padding, so it is a ring
 * whatever the button's size. */
@property --ring-turn { inherits: false; initial-value: 0deg; syntax: '<angle>'; }
/* The button is the ring's box. Its blur makes it one already, but where transparency is reduced
 * the blur goes, and the ring would otherwise hang from the hero. The header's Open Explorer wears
 * the same ring and radius (owner, 2026-09-21: the navbar CTA "doesn't match the hero cta"); it
 * stays 44px tall, the ring 56px, inside the 60px bar. The strip's action takes the radius and the
 * glass but no ring: the strip carries no frame (owner, 2026-09-20). */
body:has(.landing-main) :is(.hero--wallpaper .hero-actions .primary-action, .marketing-nav > .explorer-cta) { position: relative; }
body:has(.landing-main) :is(.marketing-nav > .explorer-cta, .presale-strip__action) { border-radius: .5rem; }
body:has(.landing-main) :is(.hero--wallpaper .hero-actions .primary-action, .marketing-nav > .explorer-cta)::before { background: conic-gradient(from var(--ring-turn), rgb(246 243 251 / 16%), rgb(255 255 255 / 95%) 7%, rgb(246 243 251 / 22%) 16%, rgb(246 243 251 / 10%) 38%, rgb(255 255 255 / 60%) 52%, rgb(246 243 251 / 16%) 62%, rgb(246 243 251 / 10%) 86%, rgb(246 243 251 / 16%)); border-radius: calc(.5rem + 5px); content: ''; inset: -6px; -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask-composite: exclude; padding: 1.5px; pointer-events: none; position: absolute; }
@media (prefers-reduced-motion: no-preference) {
  body:has(.landing-main) :is(.hero--wallpaper .hero-actions .primary-action, .marketing-nav > .explorer-cta)::before { animation: ring-turn 6s linear infinite; }
}
@keyframes ring-turn { to { --ring-turn: 360deg; } }
/* Hero scale (owner, 2026-09-20): at the shared 44px and .86rem the action was a sixth of the
 * headline's size and 7.2% of its area, which the finish review called a default-scale pill in a
 * viewport built for one gesture. 56px and 1rem take it to 12.2%.
 * Only the three marketing heroes: 404 puts a 44px "Go Back" beside this button, and growing one of
 * a pair leaves the pair mismatched. The header CTA keeps 44px too — it lives in a 60px bar, and
 * the hero's size is now what separates the two. The shared rule is untouched, so every other
 * action on the site is unaffected. */
body:has(.landing-main) :is(.hero--wallpaper, .hero--presale) .hero-actions .primary-action {
  border-radius: .5rem;
  font-size: 1rem;
  min-height: 3.5rem;
  padding: 0 1.6rem;
}

/* Presale night (owner, 2026-09-21). The presale page alone trades the luminous flow for a night
 * sky and the owner's moon photograph. The rest of the field stays as it is - glass header,
 * shells, actions, band - and so do the hero's layout, copy and type: the sky and the moon are
 * background only.
 * The stars are two seamless sheets from tools/presale-stars.mjs. The far sheet holds still and
 * the near one scrolls with the page, so the sky has depth for nothing at runtime; the tiles are
 * 997 and 1409px, so their repeats never line up. */
body:has(.hero--moon) {
  background:
    linear-gradient(112deg, transparent 32%, rgb(214 207 230 / 6%) 46%, rgb(214 207 230 / 9%) 52%, rgb(214 207 230 / 5%) 58%, transparent 72%),
    url('/presale-stars-far.svg'),
    url('/presale-stars-near.svg'),
    var(--chroma-void);
  background-attachment: fixed, fixed, scroll;
  background-repeat: repeat;
  background-size: auto;
}
/* The shared field action is dark glass, which disappears into this page's near-black sky.
 * Presale gives its one transactional action a light surface instead: the inverse palette keeps
 * it unmistakably primary without changing the home, token or navigation actions. It keeps the
 * hero ratio, 1rem type in a 56px control, rather than a smaller label in a shorter button. */
body:has(.hero--moon) .hero--moon .primary-action {
  background: var(--ink);
  border-color: var(--ink);
  box-shadow: 0 10px 30px rgb(0 0 0 / 24%), 0 0 0 1px rgb(255 255 255 / 10%);
  color: var(--paper);
}
body:has(.hero--moon) .hero--moon .primary-action:is(:hover, :focus-visible) {
  background: #fff;
  border-color: #fff;
  box-shadow: 0 10px 30px rgb(0 0 0 / 28%), 0 0 0 3px rgb(246 243 251 / 18%);
  color: var(--paper);
}
body:has(.hero--moon) .hero--moon .primary-action[aria-disabled="true"] {
  box-shadow: none;
  opacity: .72;
}
/* The flow belongs to home and token. Here the hero is open sky. */
body:has(.landing-main) .hero--presale.hero--moon::before { content: none; }
/* The moon rises from the hero's lower lip. It is the photograph cut out along its own limb
 * (tools/presale-moon.mjs): the disc is opaque, so its night side still hides the stars, and the
 * photograph's glow is kept around it as translucent light.
 * Owner, 2026-09-21, "bigger and more defined": the whole lit dome now shows, its lowest point on
 * the lip, and the first screen does not change. The limb still starts 2rem under the sale card;
 * the hero then grows below the fold by exactly the dome it needs (--moon-stage), added to both its
 * min-height and its bottom padding, so its content box - and the copy centred in it - is the
 * box it always was. landing.js reports where the card ends (--moon-card-end), which does not
 * depend on the stage, so the two never chase each other. The disc is at most 83rem, the
 * photograph's own 1328px, so it is never enlarged past its source on a 1x screen.
 * Geometry of the cut, as fractions of the disc: the glow above the limb .0908, the limb to the
 * terminator's lowest point .6773, the asset's width 1.1807. */
body:has(.landing-main) .hero--presale.hero--moon {
  --moon-top: 60px;
  --moon-pad: 4.5rem;
  --moon-d: clamp(21rem, 80vw, 83rem);
  --moon-gap: 2rem;
  --moon-room: max(calc(100svh - var(--moon-top) - var(--moon-card-end, 30rem)), var(--moon-pad));
  --moon-stage: max(0px, calc(var(--moon-gap) + var(--moon-d) * .6773 - var(--moon-room)));
  min-height: calc(100svh - var(--moon-top) + var(--moon-stage));
  padding-bottom: calc(var(--moon-pad) + var(--moon-stage));
}
@media (max-width: 760px) {
  body:has(.landing-main) .hero--presale.hero--moon { --moon-top: 56px; --moon-pad: 3rem; --moon-d: clamp(20rem, 88vw, 32rem); }
}
.hero--moon::after {
  background: url('/presale-moon.webp') 50% 0 / calc(var(--moon-d) * 1.1807) auto no-repeat;
  bottom: 0;
  content: '';
  height: calc(var(--moon-room) + var(--moon-stage) - var(--moon-gap) + var(--moon-d) * .0908);
  left: 50%;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--moon-d) * .045), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - var(--moon-d) * .045), transparent);
  pointer-events: none;
  position: absolute;
  translate: -50% 0;
  /* On a phone the disc and its glow are wider than the screen: the box stops at the viewport and
   * the image, drawn at full size, is cropped by it rather than widening the page (it did, by 52px). */
  width: min(calc(var(--moon-d) * 1.1807), 100vw);
  z-index: -1;
}
/* Copy beside the moon (owner, 2026-09-21: option 2 of two mocks, over lifting the copy above the
 * moon). The hero's copy is left-aligned at every width, the sale card and its launch-day rows with
 * it. From 960px the hero splits: the copy holds the left column and the whole lit dome sits in
 * the right one, centred on the copy, so the first screen shows both. There is no stage then, and
 * the moon's underside dissolves into the sky rather than resting on the lip. Narrower screens
 * cannot fit two columns: the copy starts near the top and the moon rises under the sale card as
 * above, its dome resting on the lip, which is the one arrangement that never meets the copy on a
 * short phone. The disc stops at 37rem so the headline's second line keeps 34px clear of it (39rem
 * left 18px, and the glow on the letters), and its
 * box is held inside the viewport so the glow's transparent edge never widens the page. */
body:has(.landing-main) .hero--presale.hero--moon { align-items: flex-start; justify-content: flex-start; padding-top: clamp(2rem, 5svh, 3rem); text-align: left; }
body:has(.landing-main) .hero--presale.hero--moon > p { margin-inline: 0; }
.hero--moon .sale-card { justify-items: start; margin-inline: 0; }
.hero--moon :is(.countdown, .sale-progress) { justify-items: start; }
.hero--moon :is(.sale-card__price, .hero-actions, .countdown__row) { justify-content: flex-start; }
@media (min-width: 960px) {
  body:has(.landing-main) .hero--presale.hero--moon {
    --moon-d: min(43vw, 37rem);
    --moon-split-gap: clamp(2rem, 5vw, 4rem);
    --moon-stage: 0px;
    align-content: center;
    column-gap: var(--moon-split-gap);
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    justify-items: start;
    padding-block: 3rem;
  }
  .hero--moon > * { grid-column: 1; }
  /* Centred on the right column, and on the copy's middle: the dome's own centre sits .559 of the
   * way down the cut (the glow above the limb is .0908 of the disc, the dome .6773). */
  .hero--moon::after {
    bottom: auto;
    height: calc(var(--moon-d) * .7681);
    left: calc((100% - var(--moon-split-gap)) * .7727 + var(--moon-split-gap));
    -webkit-mask-image: linear-gradient(to bottom, #000 76%, transparent 99%);
    mask-image: linear-gradient(to bottom, #000 76%, transparent 99%);
    top: 50%;
    translate: -50% -55.9%;
    width: min(calc(var(--moon-d) * 1.1807), calc((100% - var(--moon-split-gap)) * .4546 + 100vw - 100%));
  }
}

/* Hero intro (owner, 2026-09-21: "movement similar to the alx pro header, a smooth intro"). The home
 * and token heroes keep ALX Protocol's title rise followed by the copy and action fades. Presale
 * rises in three synchronized groups: title with subtitle, phase with price, then both actions.
 * Its moon fades in beneath those three beats over 3.6s, starting 200ms in, so it
 * settles at 3.8s. Twinkles and shooting stars wait until that moment; the three
 * text groups are the only movement before it. All
 * three field heroes run it, reduced motion skips it, and everything rests where it lands. The fill
 * is backwards, not both: the last frame is the resting style anyway, and holding it kept the title
 * and the moon on layers of their own, which resampled them - 4.6% of the presale's first screen
 * differed from the reduced-motion page once settled. */
@media (prefers-reduced-motion: no-preference) {
  body:has(.landing-main) :is(.hero--wallpaper, .hero--presale:not(.hero--moon)) h1 { animation: hero-intro-rise 1400ms cubic-bezier(.39, .575, .565, 1) backwards; }
  body:has(.landing-main) :is(.hero-copy > p, .hero--presale:not(.hero--moon) > p:not(.sr-only)) { animation: hero-intro-fade 800ms 620ms cubic-bezier(.39, .575, .565, 1) backwards; }
  body:has(.landing-main) :is(.hero-copy > .hero-actions, .hero--presale:not(.hero--moon) > .hero-actions) { animation: hero-intro-fade 800ms 760ms cubic-bezier(.39, .575, .565, 1) backwards; }
  body:has(.landing-main) .hero--moon > :is(h1, p:not(.sr-only)) { animation: hero-intro-rise 1200ms cubic-bezier(.39, .575, .565, 1) backwards; }
  body:has(.landing-main) .hero--moon .sale-card > :is(.sale-progress, .countdown, .sale-card__price) { animation: hero-intro-rise 1000ms 360ms cubic-bezier(.39, .575, .565, 1) backwards; }
  body:has(.landing-main) .hero--moon .sale-card > .hero-actions { animation: hero-intro-rise 800ms 720ms cubic-bezier(.39, .575, .565, 1) backwards; }
  body:has(.landing-main) .hero--moon::after { animation: hero-intro-fade 3600ms 200ms cubic-bezier(.39, .575, .565, 1) backwards; }
}
@keyframes hero-intro-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes hero-intro-fade { from { opacity: 0; } to { opacity: 1; } }

/* Drift (mock for the owner, 2026-09-21, after the pointer lean measured 10.8fps): the home hero's
 * field slowly changes shape. A second copy of the field sits over the first, the same mesh drawn
 * 24% wider and 8% taller and shifted right and down, under the same grain tile at the same origin
 * so the grain never moves. Only its opacity animates, 0 to 1 and back over 18s, which composites
 * at 58fps where moving the field ran at 26. Fine with reduced motion: the copy stays invisible. */
body:has(.landing-main) .hero--wallpaper::after {
  background-blend-mode: hard-light, normal;
  background-image: var(--flow-grain), var(--flow-mesh);
  background-position: 0 0, 62% 40%;
  background-repeat: repeat, no-repeat;
  background-size: auto, 124% 108%;
  bottom: -8rem;
  content: '';
  left: 50%;
  margin-left: -50vw;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 12rem), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 12rem), transparent 100%);
  opacity: 0;
  pointer-events: none;
  position: absolute;
  top: -5rem;
  width: 100vw;
  z-index: -1;
}
@media (prefers-reduced-motion: no-preference) {
  body:has(.landing-main) .hero--wallpaper::after { animation: field-drift 18s ease-in-out infinite; }
}
@keyframes field-drift { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }

/* Twinkling stars (owner, 2026-09-21; more of the sky, 2026-09-23), in the presale page's first
 * screen. Six overlays of the near sheet's own stars (tools/presale-stars.mjs), tiled from the same
 * origin so they sit exactly on the stars already there. Four lift lone mid-bright stars, never
 * past the brightest still star; the two on .sky-twinkle--dim dim the brightest with the sky's own
 * void. So the brightest point on the page never rises, and the Stars Are Capped rule's measurement
 * still holds (the tool has the numbers). Only each overlay's opacity animates, which is composited
 * and never repaints, on six uneven cycles and starts, 2.1 to 3.2 seconds. Two groups made every
 * lifted star brighten at once, which read as a pulse rather than a twinkle. The layer covers the
 * first screen from below the header, so no glass has to re-blur behind it while the page is at
 * rest. Each cycle waits until the moon has settled at 3.8s, keeping the stagger
 * between stars, and reduced motion leaves the sky still. */
.sky-twinkle { clip-path: inset(61px 0 0 0); height: 100svh; left: 0; pointer-events: none; position: absolute; top: 0; width: 100%; z-index: -1; }
.sky-twinkle::before, .sky-twinkle::after { background-repeat: repeat; content: ''; inset: 0; opacity: 0; position: absolute; }
.sky-twinkle::before { background-image: url('/presale-stars-twinkle-1.svg'); }
.sky-twinkle::after { background-image: url('/presale-stars-twinkle-2.svg'); }
.sky-twinkle--more::before { background-image: url('/presale-stars-twinkle-5.svg'); }
.sky-twinkle--more::after { background-image: url('/presale-stars-twinkle-6.svg'); }
.sky-twinkle--dim::before { background-image: url('/presale-stars-twinkle-3.svg'); }
.sky-twinkle--dim::after { background-image: url('/presale-stars-twinkle-4.svg'); }
@media (prefers-reduced-motion: no-preference) {
  .sky-twinkle::before { animation: star-twinkle 2.1s 3.8s ease-in-out infinite; }
  .sky-twinkle::after { animation: star-twinkle 2.6s 4.1s ease-in-out infinite; }
  .sky-twinkle--more::before { animation: star-twinkle 2.3s 4.3s ease-in-out infinite; }
  .sky-twinkle--more::after { animation: star-twinkle 2.8s 4.6s ease-in-out infinite; }
  .sky-twinkle--dim::before { animation: star-twinkle 2.4s 4s ease-in-out infinite; }
  .sky-twinkle--dim::after { animation: star-twinkle 3.2s 4.4s ease-in-out infinite; }
}
@keyframes star-twinkle { 0%, 100% { opacity: 0; } 10% { opacity: 1; } 18% { opacity: .12; } 26% { opacity: .9; } 36% { opacity: 0; } 50% { opacity: 1; } 60% { opacity: .18; } 70% { opacity: .75; } 82% { opacity: 0; } }

/* A shooting star (owner, 2026-09-23): one thin streak across the first-screen sky, then a long
 * rest, so it reads as an occasional meteor rather than a loop on a track. Two paths, staggered,
 * kept to the open sky on the right so they do not cross the copy. Reduced motion leaves it still. */
.sky-shoot { clip-path: inset(61px 0 0 0); height: 100svh; left: 0; overflow: hidden; pointer-events: none; position: absolute; top: 0; width: 100%; z-index: -1; }
.sky-shoot::before, .sky-shoot::after { background: linear-gradient(90deg, rgb(255 255 255 / 0), rgb(255 255 255 / .5) 70%, #fff); border-radius: 999px; box-shadow: 0 0 4px 1px rgb(255 255 255 / .28); content: ''; height: 1.5px; opacity: 0; position: absolute; width: 5.5rem; }
.sky-shoot::before { rotate: -32deg; }
.sky-shoot::after { rotate: -24deg; width: 4.25rem; }
@media (prefers-reduced-motion: no-preference) {
  .sky-shoot::before { animation: sky-shoot 14s 5.4s linear infinite; }
  .sky-shoot::after { animation: sky-shoot-b 19s 10.7s linear infinite; }
}
@keyframes sky-shoot { 0%, 88% { opacity: 0; translate: 94vw 11vh; } 89.5% { opacity: 1; } 96% { opacity: 0; translate: 62vw 30vh; } 100% { opacity: 0; translate: 62vw 30vh; } }
@keyframes sky-shoot-b { 0%, 90% { opacity: 0; translate: 82vw 7vh; } 91.5% { opacity: 1; } 97% { opacity: 0; translate: 52vw 22vh; } 100% { opacity: 0; translate: 52vw 22vh; } }

/* Separate cards (owner, 2026-09-21: "sections that would look better as separate cards ...
 * instead of one whole card, such as Participation. It should also include numbering"). Where a
 * section is a row of parallel items (the presale's Phases and Participation, the token's
 * Utility), each item is its own card of the shells' glass, and the section's title stands on
 * the ground above them, as on the home page. A section that is one piece (a tabset, a table, the
 * FAQ) keeps one shell under its title. The row is pulled out by the shells' own side margin, so
 * its outer edges line up with the shells below it, and cards sit as far apart as shells do. */
body:has(.landing-main) .home-block > :is(.stepper, .feature-steps__grid, .how-steps) { gap: clamp(.75rem, 1.6vw, 1.25rem); margin: 0 calc(-1 * clamp(.75rem, 2vw, 2rem)); }
/* How It Works shares its rows through subgrid from 1180px, and a subgrid takes its parent's row
 * gap inside each card, so there the row gap stays zero; the cards sit side by side. */
@media (min-width: 1180px) { body:has(.landing-main) .home-block > .how-steps { row-gap: 0; } }
/* The glass is on each card's ::before rather than the card, so a card can arrive after the line
 * into it has drawn: the line lives in the card, and a card at opacity 0 would hide it too. The
 * padding carries the 1px the border no longer takes, so nothing inside moves. */
body:has(.landing-main) :is(.feature-step-card, .stepper__item, .how-step) { background: none; border: 0; border-radius: 1.25rem; isolation: isolate; padding: calc(clamp(1.25rem, 2vw, 1.75rem) + 1px); position: relative; }
body:has(.landing-main) :is(.feature-step-card, .stepper__item, .how-step)::before { -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); background: rgb(13 11 22 / 62%); border: 1px solid var(--line); border-radius: inherit; content: ''; inset: 0; position: absolute; z-index: -1; }
@media (prefers-reduced-transparency: reduce) { body:has(.landing-main) :is(.feature-step-card, .stepper__item, .how-step)::before { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--paper); } }
/* A note under a row of cards is small text, and the token page's ground is too luminous to hold
 * small text bare: the Utility note read 3.0:1 there at 620px. So it sits on the cards' glass, as
 * a slim strip the width of the row. */
body:has(.landing-main) .home-block > .section-note { -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); background: rgb(13 11 22 / 62%); border: 1px solid var(--line); border-radius: 1.25rem; margin: clamp(.75rem, 1.6vw, 1.25rem) calc(-1 * clamp(.75rem, 2vw, 2rem)) 0; max-width: none; padding: 1rem clamp(1.25rem, 2vw, 1.75rem); text-align: center; }
@media (prefers-reduced-transparency: reduce) { body:has(.landing-main) .home-block > .section-note { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--paper); } }
/* A shell under a title starts and ends at its own padding: the tabset's top margin made the
 * Details shell open on 104px of empty glass where the others open on 72px, and the closing
 * note's bottom margin did the same at the foot. */
body:has(.landing-main) .home-block > .home-section > :first-child { margin-top: 0; }
body:has(.landing-main) .home-block > .home-section > :last-child { margin-bottom: 0; }
/* The Details title is already centered with the other section titles. The tabset, table, and
 * note share that center inside the shell. Row text stays left-aligned so a label and its value
 * still read as a pair. */
body:has(.landing-main) #details-title + .home-section > .tabset { margin-inline: auto; }
body:has(.landing-main) #details-title + .home-section .tabset__list { justify-content: center; }
body:has(.landing-main) #details-title + .home-section .reference-table { margin-inline: auto; }
body:has(.landing-main) #details-title + .home-section > .section-note { margin-inline: auto; text-align: center; }
/* A card is four subgrid rows, glyph, title, text and link, so each lines up across the row. A
 * title that wraps where its neighbour does not starts at the top of its track instead of
 * floating halfway down it. On the field the glyph stands in a faint light of the page's ink. */
body:has(.landing-main) .home-block > .feature-steps__grid { grid-template-rows: repeat(4, auto); }
body:has(.landing-main) .home-block > .feature-steps__grid > .feature-step-card { grid-row: span 4; }
body:has(.landing-main) .feature-step-card h3 { align-self: start; line-height: 1.2; margin: 0; text-wrap: balance; }
body:has(.landing-main) .feature-step-card__glyph { background: radial-gradient(closest-side, rgb(246 243 251 / 9%), transparent); color: var(--ink); }
/* Participation is a sequence, so its cards are numbered (owner, 2026-09-21); the token's Utility
 * cards are not, because they are not one. The number sits in the card's corner, beside the
 * glyph, in the headings' light weight. The list is an <ol role="list"> (WebKit drops the
 * semantics of a list without markers otherwise), so the order is in the markup as well, and the
 * digit is hidden from assistive technology rather than read twice. */
.feature-step-card__number { color: var(--sub); font-size: 2rem; font-variant-numeric: tabular-nums; font-weight: 300; left: clamp(1.25rem, 2vw, 1.75rem); letter-spacing: -.02em; line-height: 1; position: absolute; top: clamp(1.25rem, 2vw, 1.75rem); }
/* A phase card: its bar along the top, then the phase and its state on one line, the price, the
 * allocation. The tag has its own place on the phase's line, so a phase going live moves nothing;
 * in the old list it added a row and nudged its neighbours down 4px.
 * Larger and price-led (owner, 2026-09-21, the second of three mocks): the price is display type
 * at the card's foot, with its unit, and the card is about 1.7x as tall. Each bar is as long as its
 * phase's allocation against the largest (150M, 300M, 450M, 600M: a quarter, a half, three
 * quarters, full), so the row reads as the sale's rising capacity, as the hero's segments do. The
 * bar has no track behind it on purpose: a part-filled track reads as "25% sold", and these bars
 * say how big a phase is, not how much of it has sold. Its colour is still the phase's state. */
body:has(.landing-main) .stepper__item { align-content: start; column-gap: .75rem; grid-template-areas: "bar bar" "title tag" "price price" "cap cap" "now now"; grid-template-columns: minmax(0, 1fr) auto; row-gap: .3rem; }
body:has(.landing-main) .stepper__indicator { grid-area: bar; height: .35rem; margin-bottom: 1.4rem; width: var(--cap, 100%); }
.stepper__item:nth-child(1) { --cap: 25%; }
.stepper__item:nth-child(2) { --cap: 50%; }
.stepper__item:nth-child(3) { --cap: 75%; }
body:has(.landing-main) .stepper__title { font-size: .9rem; grid-area: title; }
body:has(.landing-main) .stepper__tag { align-self: center; font-size: .9rem; grid-area: tag; }
@media (min-width: 761px) {
  body:has(.landing-main) .stepper.flow > .stepper__item { --phase-pad: calc(clamp(1.75rem, 2.4vw, 2.25rem) + 1px); grid-template-rows: auto auto 1fr auto auto; min-height: clamp(15rem, 19vw, 17rem); padding: var(--phase-pad); }
}
body:has(.landing-main) .stepper__tag::before { background: currentColor; border-radius: 50%; content: ''; display: inline-block; height: .45rem; margin-right: .4rem; vertical-align: .05em; width: .45rem; }
body:has(.landing-main) .stepper__price { align-self: end; font-size: clamp(2.25rem, 3.2vw, 2.9rem); font-weight: 500; grid-area: price; letter-spacing: -.045em; line-height: 1; margin-top: 1.5rem; white-space: nowrap; }
.stepper__unit { color: var(--sub); font-size: .82rem; font-weight: 400; letter-spacing: 0; }
body:has(.landing-main) .stepper__cap { font-size: .9rem; grid-area: cap; margin-top: .55rem; white-space: nowrap; }
/* The live phase alone answers what is left, under a rule so it reads as the sale's current state
 * rather than the phase's published terms above it. Every card holds the same line, hidden (from assistive technology too) except on the live one, so the cards stay one
 * height and the prices one line. A count, not a bar: the allocation bars stay trackless, so
 * nothing on the row reads as sold. */
body:has(.landing-main) .stepper__now { border-top: 1px solid var(--line); font-size: .9rem; grid-area: now; justify-self: stretch; margin-top: 1rem; padding-top: .85rem; }
/* The live phase glows (owner, 2026-09-21: "can the current phase have a glow and a live message
 * that is the same font as the rest of the page"): a bright edge, light inside the glass, and a halo
 * of the page's own ink in three rings, a tight one, a soft one and a wide one. That is on top of
 * the filled bar and bold title it already had. The halo is not the glass's own shadow: animating
 * that would re-blur the glass every frame, as the presale strip's glow did. It is a layer of its
 * own behind the card (.stepper__halo), whose rings only ever change opacity. So a phase handing
 * over fades the halo off one card and onto the next, and on the live card a wider ring breathes
 * every 4.2 seconds beside the dot's 1.4-second pulse. Reduced motion holds both still. Past phases
 * keep their fainter bar and grey price; upcoming ones stay plain glass. Before the sale opens no
 * phase is live, so none glows. */
body:has(.landing-main) .stepper__item[data-sale="live"]::before { background: linear-gradient(rgb(246 243 251 / 8%), rgb(246 243 251 / 8%)), rgb(13 11 22 / 62%); border-color: rgb(246 243 251 / 78%); }
body:has(.landing-main) .stepper__item::before { transition: background-color 200ms ease, border-color 700ms cubic-bezier(.23, 1, .32, 1), box-shadow 200ms ease; }
body:has(.landing-main) .stepper__halo { border-radius: inherit; inset: 0; pointer-events: none; position: absolute; z-index: -2; }
body:has(.landing-main) .stepper__halo::before, body:has(.landing-main) .stepper__halo::after { border-radius: inherit; content: ''; inset: 0; opacity: 0; position: absolute; transition: opacity 700ms cubic-bezier(.23, 1, .32, 1); }
body:has(.landing-main) .stepper__halo::before { box-shadow: 0 0 0 1px rgb(246 243 251 / 30%), 0 0 18px rgb(246 243 251 / 26%), 0 0 52px rgb(246 243 251 / 16%); }
body:has(.landing-main) .stepper__halo::after { box-shadow: 0 0 36px 2px rgb(246 243 251 / 16%), 0 0 90px 8px rgb(246 243 251 / 9%); }
body:has(.landing-main) .stepper__item[data-sale="live"] .stepper__halo::before { opacity: 1; }
body:has(.landing-main) .stepper__indicator, body:has(.landing-main) .stepper__title { transition: background-color 700ms cubic-bezier(.23, 1, .32, 1), color 700ms cubic-bezier(.23, 1, .32, 1); }
@media (prefers-reduced-motion: no-preference) { body:has(.landing-main) .stepper__item[data-sale="live"] .stepper__halo::after { animation: halo-breathe 4.2s ease-in-out infinite; } }
@keyframes halo-breathe { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
/* The phases answer the pointer as Participation's cards do: a brighter edge and a 3px lift. The
 * live card keeps its own edge. */
@media (hover: hover) and (pointer: fine) {
  body:has(.landing-main) .stepper__item:not([data-sale="live"]):hover::before { background-color: rgb(28 26 40 / 66%); border-color: rgb(246 243 251 / 45%); box-shadow: 0 0 0 1px rgb(246 243 251 / 10%), 0 0 28px rgb(246 243 251 / 10%); }
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  body:has(.landing-main) .stepper.flow > .stepper__item { transition: translate 200ms cubic-bezier(.23, 1, .32, 1); }
  body:has(.landing-main) .stepper.flow > .stepper__item:hover { translate: 0 -3px; }
}
@media (prefers-reduced-transparency: reduce) { body:has(.landing-main) .stepper__item[data-sale="live"]::before { background: linear-gradient(rgb(246 243 251 / 8%), rgb(246 243 251 / 8%)), var(--paper); } }
@media (prefers-reduced-motion: no-preference) { body:has(.landing-main) .stepper__item[data-sale="live"] .stepper__tag::before { animation: live-dot 1.4s ease-in-out infinite; } }
@keyframes live-dot { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* Cards arrive the way ALX Protocol's commitment path does on its ALX on Base page (owner,
 * 2026-09-21: "the cards should be introduced similar to ALX on Base, onchain data"). Phases is
 * the one row drawn as a path: a chevron centered in each gap, in the price's own
 * white (owner, 2026-09-28). In two columns the order runs left to right and then down, which
 * no straight mark can show, so there are none. Participation carries its order in its numbers
 * alone (owner: "remove the arrows from participation and just leave the numbering"). The marks
 * stay once drawn, as on ALX on Base. */
body:has(.landing-main) .home-block > .stepper.flow { --flow-gap: clamp(2.75rem, 3.6vw, 3.5rem); }
.flow-link { color: var(--sub); display: none; fill: none; overflow: visible; pointer-events: none; position: absolute; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.5; }
.flow-link--across { color: color-mix(in srgb, var(--ink) 70%, transparent); grid-area: 1 / 1 / -1 / -1; height: 1rem; right: calc(100% + var(--phase-pad) + var(--flow-gap) / 2 - .5rem); stroke-width: 2; top: calc(50% - .5rem); width: 1rem; }
@media (min-width: 761px) {
  body:has(.landing-main) .home-block > .stepper.flow { column-gap: var(--flow-gap); }
  .stepper.flow .flow-link--across { display: block; }
}
/* In one column there is no neighbour to line up with, so a card drops its subgrid, which would
 * otherwise carry the row's gap inside it. */
@media (max-width: 620px) {
  body:has(.landing-main) .home-block > .feature-steps__grid { grid-template-rows: none; }
  body:has(.landing-main) .home-block > .feature-steps__grid > .feature-step-card { grid-row: auto; grid-template-rows: none; }
}
/* Phases and Participation move with the scroll (owner, 2026-09-21: "introduce a movement when
 * scrolling"; 2026-09-28: Participation matches Phases). The arrival is tied to the scroll instead
 * of the clock, so it runs back when the reader scrolls back. As the row rises from the foot of
 * the screen to about its middle (cover 6% to 42% of its view timeline), each card settles in
 * turn: 10% of the row's travel a card. A phase's chevron draws into the next card, and its bar
 * fills just after it lands; a step's glyph draws on that same beat. Where scroll-driven animation
 * is unsupported, landing.js plays the timed arrival instead; reduced motion leaves the row still.
 * The animation shorthand comes first, because it resets the timeline and the range written after
 * it. */
.stepper.flow > :nth-child(2) { --i: 1; }
.stepper.flow > :nth-child(3) { --i: 2; }
.stepper.flow > :nth-child(4) { --i: 3; }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .stepper.flow { view-timeline: --phases block; }
    .feature-steps__grid--steps { view-timeline: --steps block; }
    .stepper.flow > .stepper__item::before { animation: flow-settle cubic-bezier(.23, 1, .32, 1) both; animation-timeline: --phases; animation-range: cover calc(6% + var(--i, 0) * 10%) cover calc(12% + var(--i, 0) * 10%); }
    .stepper.flow > .stepper__item > :not(.flow-link) { animation: flow-fade linear both; animation-timeline: --phases; animation-range: cover calc(6% + var(--i, 0) * 10%) cover calc(12% + var(--i, 0) * 10%); }
    .stepper.flow > .stepper__item > .stepper__indicator { animation: flow-fade linear both, flow-bar ease-out both; animation-timeline: --phases, --phases; animation-range: cover calc(6% + var(--i, 0) * 10%) cover calc(12% + var(--i, 0) * 10%), cover calc(9% + var(--i, 0) * 10%) cover calc(16% + var(--i, 0) * 10%); transform-origin: left; }
    .stepper.flow .flow-line { animation: flow-stroke linear both; animation-timeline: --phases; animation-range: cover calc(2% + var(--i, 0) * 10%) cover calc(5% + var(--i, 0) * 10%); }
    .stepper.flow .flow-head { animation: flow-stroke linear both; animation-timeline: --phases; animation-range: cover calc(4.5% + var(--i, 0) * 10%) cover calc(6% + var(--i, 0) * 10%); }
    .feature-steps__grid--steps > .feature-step-card::before { animation: flow-settle cubic-bezier(.23, 1, .32, 1) both; animation-timeline: --steps; animation-range: cover calc(6% + var(--card, 0) * 10%) cover calc(12% + var(--card, 0) * 10%); }
    .feature-steps__grid--steps > .feature-step-card > * { animation: flow-fade linear both; animation-timeline: --steps; animation-range: cover calc(6% + var(--card, 0) * 10%) cover calc(12% + var(--card, 0) * 10%); }
    .feature-steps__grid--steps > .feature-step-card:not([data-flow="done"]) .feature-step-card__glyph > * { animation: glyph-draw linear both; animation-timeline: --steps; animation-range: cover calc(9% + var(--card, 0) * 10%) cover calc(16% + var(--card, 0) * 10%); }
  }
}
/* Participation answers the pointer (owner, 2026-09-21: "participation can use hovering"). A step
 * under the pointer lifts 3px and brightens the way a live phase does, and its glyph gives a small
 * bounce. The same brightening shows while a step's link holds focus, so a keyboard reader sees
 * the step they are in. Hover is pointer-gated, so a tap never leaves a card stuck lifted, and
 * reduced motion keeps the brightening without the lift or the bounce. */
body:has(.landing-main) .feature-steps__grid--steps > .feature-step-card::before { transition: background-color 200ms ease, border-color 200ms ease, box-shadow 200ms ease; }
body:has(.landing-main) .feature-steps__grid--steps > .feature-step-card:focus-within::before { background-color: rgb(28 26 40 / 66%); border-color: rgb(246 243 251 / 45%); box-shadow: 0 0 0 1px rgb(246 243 251 / 10%), 0 0 28px rgb(246 243 251 / 10%); }
@media (hover: hover) and (pointer: fine) {
  body:has(.landing-main) .feature-steps__grid--steps > .feature-step-card:hover::before { background-color: rgb(28 26 40 / 66%); border-color: rgb(246 243 251 / 45%); box-shadow: 0 0 0 1px rgb(246 243 251 / 10%), 0 0 28px rgb(246 243 251 / 10%); }
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  body:has(.landing-main) .feature-steps__grid--steps > .feature-step-card { transition: translate 200ms cubic-bezier(.23, 1, .32, 1); }
  body:has(.landing-main) .feature-steps__grid--steps > .feature-step-card:hover { translate: 0 -3px; }
  body:has(.landing-main) .feature-steps__grid--steps > .feature-step-card:hover .feature-step-card__glyph { animation: glyph-nudge 520ms cubic-bezier(.34, 1.56, .64, 1); }
}
@keyframes glyph-nudge { 0%, 100% { transform: scale(1); } 40% { transform: scale(1.1); } }
/* The arrival, in ALX's own steps and curves. landing.js gives each card data-flow "pending"
 * until it is well in view, then "in" with two steps of 100ms: --flow-at, when the line into it
 * starts, and --flow-land, when the card lands. The line draws in 300ms, its head two steps
 * later, and four steps on the card settles: its glass from 95% and its contents fading up, in
 * 250ms on ALX's quick curve. One step after landing a phase's bar draws; two after, a step's
 * glyph draws itself as before. Then "done", which is the page at rest. Without script, or with
 * reduced motion, no card is ever pending, so nothing is hidden. */
.feature-steps__grid > :nth-child(2) { --card: 1; }
.feature-steps__grid > :nth-child(3) { --card: 2; }
.feature-steps__grid > :nth-child(4) { --card: 3; }
.feature-step-card__glyph > :nth-child(2) { --part: 1; }
.feature-step-card__glyph > :nth-child(3) { --part: 2; }
.feature-step-card__glyph > :nth-child(4) { --part: 3; }
.feature-step-card__glyph > * { transform-box: fill-box; transform-origin: center; }
@media (prefers-reduced-motion: no-preference) {
  [data-flow="pending"]::before, [data-flow="pending"] > :not(.flow-link, .how-step__text), [data-flow="pending"] > .how-step__text > *, [data-flow="pending"] :is(.flow-line, .flow-head) { opacity: 0; }
  [data-flow="in"] .flow-line { animation: flow-stroke 300ms cubic-bezier(.23, 1, .32, 1) calc(var(--flow-at, 0) * 100ms) backwards; }
  [data-flow="in"] .flow-head { animation: flow-stroke 300ms cubic-bezier(.23, 1, .32, 1) calc((var(--flow-at, 0) + 2) * 100ms) backwards; }
  [data-flow="in"]::before { animation: flow-settle 250ms cubic-bezier(.23, 1, .32, 1) calc(var(--flow-land, 0) * 100ms) backwards; }
  [data-flow="in"] > :not(.flow-link, .how-step__text), [data-flow="in"] > .how-step__text > * { animation: flow-fade 250ms cubic-bezier(.23, 1, .32, 1) calc(var(--flow-land, 0) * 100ms) backwards; }
  .stepper__item[data-flow="in"] > .stepper__indicator { animation: flow-fade 250ms cubic-bezier(.23, 1, .32, 1) calc(var(--flow-land, 0) * 100ms) backwards, flow-bar 600ms ease-out calc((var(--flow-land, 0) + 1) * 100ms) backwards; transform-origin: left; }
  .feature-step-card[data-flow="in"] .feature-step-card__glyph > * { --at: calc((var(--flow-land, 0) + 2) * 100ms + var(--part, 0) * 130ms); }
  .feature-step-card[data-flow="in"] .g-draw { animation: glyph-draw 900ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards; }
  .feature-step-card[data-flow="in"] .g-draw.g-slide-r { animation: glyph-draw 900ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards, glyph-slide-r 1100ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards; }
  .feature-step-card[data-flow="in"] .g-draw.g-slide-l { animation: glyph-draw 900ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards, glyph-slide-l 1100ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards; }
  .feature-step-card[data-flow="in"] .g-draw.g-drop { animation: glyph-draw 900ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards, glyph-drop 900ms cubic-bezier(.34, 1.3, .64, 1) var(--at) backwards; }
  .feature-step-card[data-flow="in"] .g-draw.g-rise { animation: glyph-draw 900ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards, glyph-rise 900ms cubic-bezier(.34, 1.3, .64, 1) var(--at) backwards; }
  .feature-step-card[data-flow="in"] .g-draw.g-grow { animation: glyph-draw 700ms cubic-bezier(.39, .575, .565, 1) var(--at) backwards, glyph-grow 700ms cubic-bezier(.34, 1.4, .64, 1) var(--at) backwards; }
  .feature-step-card[data-flow="in"] .g-pop { animation: glyph-pop 520ms cubic-bezier(.34, 1.56, .64, 1) calc(var(--at) + 450ms) backwards; }
}
@keyframes flow-stroke { from { stroke-dasharray: 1 1.01; stroke-dashoffset: 1; } to { stroke-dasharray: 1 1.01; stroke-dashoffset: 0; } }
@keyframes flow-settle { from { opacity: 0; transform: scale(.95); } to { opacity: 1; transform: none; } }
@keyframes flow-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes flow-bar { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes glyph-draw { 0% { opacity: 0; stroke-dasharray: 1 1.01; stroke-dashoffset: 1; } 6% { opacity: 1; } 100% { opacity: 1; stroke-dasharray: 1 1.01; stroke-dashoffset: 0; } }
@keyframes glyph-pop { 0% { opacity: 0; transform: scale(0); } 60% { opacity: 1; transform: scale(1.35); } 100% { opacity: 1; transform: scale(1); } }
@keyframes glyph-slide-r { from { transform: translateX(-3.5px); } to { transform: translateX(0); } }
@keyframes glyph-slide-l { from { transform: translateX(3.5px); } to { transform: translateX(0); } }
@keyframes glyph-drop { from { transform: translateY(-4px); } to { transform: translateY(0); } }
@keyframes glyph-rise { from { transform: translateY(3.5px); } to { transform: translateY(0); } }
@keyframes glyph-grow { from { transform: scale(.55); } to { transform: scale(1); } }
/* Then the glyphs keep moving (owner, 2026-09-21: "yes but all animations should move more
 * frequently"). Every 2.8 seconds each one acts out its step again, 700ms after the card before
 * it, so a row always has one in motion. The clasp snaps, the tick redraws, the arrows swap, the
 * token drops and the reward rises. The second person steps in, the programmes pulse one by
 * one, and the window's lights blink in turn. Each move takes the first 40% of the cycle, and
 * the drawing rests for the remainder. Only transform, opacity and one tick's dash move. A card
 * off screen (data-live "0") holds still. The pause travels as --play inside each loop's own
 * shorthand: a separate play-state rule loses to any loop rule more specific than it, and the
 * clasp's rule was. Reduced motion never starts them. On a 2015 integrated GPU (Intel HD 520)
 * the loops cost nothing at rest (57-60fps) and 2-5fps while scrolling; without a GPU, as in
 * headless capture, they take the page to 17fps, because each moving glyph re-blurs the glass
 * behind it. This is motion that starts on its own and runs on, which WCAG 2.2.2 wants a pause
 * for: a known gap, as with the presale strip, not an oversight. */
.feature-step-card { --beat: calc(var(--card, 0) * 700ms); }
@media (prefers-reduced-motion: no-preference) {
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-pop:not(.g-light) { animation: loop-pop 2.8s cubic-bezier(.34, 1.56, .64, 1) var(--beat) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-light { animation: loop-blink 2.8s ease-in-out calc(var(--beat) + var(--part, 0) * 180ms) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-tick { animation: loop-tick 2.8s cubic-bezier(.39, .575, .565, 1) var(--beat) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-slide-r { animation: loop-swap-r 2.8s cubic-bezier(.45, 0, .55, 1) var(--beat) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-slide-l { animation: loop-swap-l 2.8s cubic-bezier(.45, 0, .55, 1) var(--beat) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-drop { animation: loop-drop 2.8s cubic-bezier(.45, 0, .55, 1) var(--beat) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-rise { animation: loop-rise 2.8s cubic-bezier(.45, 0, .55, 1) var(--beat) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-join { animation: loop-join 2.8s cubic-bezier(.45, 0, .55, 1) var(--beat) infinite var(--play, running); }
  .feature-step-card:not([data-flow="pending"], [data-flow="in"]) .g-grow { animation: loop-pulse 2.8s cubic-bezier(.34, 1.56, .64, 1) calc(var(--beat) + var(--part, 0) * 180ms) infinite var(--play, running); }
  [data-live="0"] { --play: paused; }
}
@keyframes loop-pop { 0%, 40%, 100% { transform: scale(1); } 12% { transform: scale(1.6); } 24% { transform: scale(.9); } }
@keyframes loop-blink { 0%, 30%, 100% { opacity: 1; } 12% { opacity: .15; } }
@keyframes loop-tick { 0% { opacity: 1; stroke-dasharray: 1 1.01; stroke-dashoffset: 1; } 30% { stroke-dasharray: 1 1.01; stroke-dashoffset: 0; } 88% { opacity: 1; stroke-dasharray: 1 1.01; stroke-dashoffset: 0; } 97%, 100% { opacity: 0; stroke-dasharray: 1 1.01; stroke-dashoffset: 0; } }
@keyframes loop-swap-r { 0%, 40%, 100% { transform: translateX(0); } 20% { transform: translateX(2.5px); } }
@keyframes loop-swap-l { 0%, 40%, 100% { transform: translateX(0); } 20% { transform: translateX(-2.5px); } }
@keyframes loop-drop { 0%, 40%, 100% { transform: translateY(0); } 18% { transform: translateY(2.5px); } }
@keyframes loop-rise { 0%, 40%, 100% { transform: translateY(0); } 18% { transform: translateY(-2.5px); } }
@keyframes loop-join { 0%, 40%, 100% { opacity: 1; transform: translateX(0); } 18% { opacity: .45; transform: translateX(1.5px); } }
@keyframes loop-pulse { 0%, 30%, 100% { transform: scale(1); } 12% { transform: scale(1.14); } }
