/* NIX — / , the front door
   ══════════════════════════════════════════════════════════════════════════
   One screen: the mark, the drop, a name and a line, and the ways in. Then a
   page under it — the releases, the writing, the ask — that the reader scrolls
   to once the door has been opened.

   THE PAGE HAS FOUR STATES AND THEY ARE ALL ON <body>:

     is-loading   the veil is up; nothing behind it has arrived
     is-sealed    the veil has lifted and the page IS the drop, alone. Every
                  other section is display:none, not faded — three invisible
                  rows still take their height, and that is what pushed the
                  drop into the top third of the screen
     is-revealed  the drop is rising
     is-open      the drop has been pressed: the sections come back, in reading
                  order, on a 110ms beat

   The cards on it are components.css's .menu-card with artwork inside, and the
   artwork is shared with the menu overlays and the store — it is in that file,
   not this one. What is here is the door, the loader, and the three sections
   the door opens onto.
   ══════════════════════════════════════════════════════════════════════════ */

/* The mark, part of the page now that the header band is gone, and above the
   drop rather than under it. Same ink SVG as everywhere else, so it needs the
   same inversion — that filter is keyed to a list of classes further up this
   file and this class is in it. */
.index__mark {
  display: block;
  width: 30px;
  height: 30px;
  margin: 0 auto var(--space-3);
}

/* A BAND OF ITS OWN, above the stage. Sharing row 1 with the arch put it on
   the sphere: that row is a full screen and the drop is centred in it, so
   "top of the cell" is the sphere's crown — the mark landed on the glass.

   --mark-h is what keeps the fold honest. The stage row was 100% of the
   container, one screenful exactly; a second row above it would have pushed
   that screenful down by its own height and left a strip of the releases
   showing under the drop. The two rows subtract instead, so mark plus stage
   is still one screen. */
/* The band is exactly what it holds: the rail pad above the mark, the mark,
   and the same pad under it. */
body.is-open .index { --mark-h: calc(var(--shell-pad) * 2 + var(--rail-logo)); }

/* LEVEL WITH THE RAIL MARK, and that is arithmetic rather than a nudge. The
   rail logo sits inside two paddings — the shell gap and the rail own pad —
   centred in a 24px box, so its middle is gap + pad + 12 down from the top of
   the shell. This mark is in the other panel under the same gap, so matching
   means the same pad above it, the same 24px box, and nothing else in
   between: the page top padding has to go to zero or it stacks on the sum. */
body.is-open .index__mark {
  grid-area: 1 / 1;
  align-self: start;
  justify-self: center;
  width: var(--rail-logo);
  height: var(--rail-logo);
  /* 32 less half the box. Unlike the ring this one IS inside the shell
     padding, so the two states take different numbers: shut there is none to
     count, open there are 8px of it already spent. */
  margin: 20px 0 0;
}

body.is-nav.is-open .index__mark { margin-top: var(--shell-pad); }

.index__hero {
  text-align: center;
  /* the grid's own width: the heading speaks for the two cards under it and
     should break on the same measure they do */
  max-width: min(820px, 92vw);
}

/* an h1 since the headline above it went: the weight is written out
   because a bare h1 inherits the UA's bold, and nothing on this site is
   heavier than 400. Same scale as .landing__lead — this is the hero of its
   page the way "Your business, finally organized." is the hero of /home. */
.index__word {
  /* the name, sitting under the mark: the hero scale .landing__lead carries
     on /home, and the weight written out because a bare h1 inherits the UA's
     bold and nothing on this site is heavier than 400 */
  color: var(--nix-text);
  font-size: clamp(26px, 4vw, 40px);
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-1);
}

/* the line under the name: a description, so it steps back down in size and
   into the soft ink the cards' own descriptions use */
.index__text {
  color: var(--nix-text-soft);
  font-size: 18px;
  font-weight: 400;
  line-height: 1.6;
  /* Two lines, always. A measure in ch rather than a <br>: the Spanish is
     laid over this node by replacing its text, so any tag inside it is gone
     the first time the language changes. Both sentences are in the sixties
     of characters, so half of that plus a little is where they break. */
  max-width: 36ch;
  margin: 0 auto;
}

/* ---- the index preloader --------------------------------------------- *
 * The logo in the middle, a ring filling clockwise around it from 0 to
 * 100, and then the page. The ring is a single SVG circle whose dash
 * offset unwinds; 339.3 is 2*pi*54, the circle's own circumference.
 * ---------------------------------------------------------------------- */
.index-loader {
  position: fixed;
  inset: 0;
  z-index: var(--z-over);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--nix-paper);
  transition: opacity 400ms ease;
}

/* The flight: the veil goes glass so the cards can rise behind the logo
   while it travels to the header's seat; the ring bows out first. */
.index-loader.is-flying { background: transparent; pointer-events: none; }

.index-loader__ring { transition: opacity 250ms ease; }

.index-loader.is-flying .index-loader__ring { opacity: 0; }

/* THE PAGE'S OWN MARK WAITS FOR THE TRAVELLER TO LAND ON IT — and for a long
   time that sentence was the only thing here. The rule it describes named the
   header's logo, the header was deleted, and the declaration went with it
   while the comment stayed: prose standing exactly where a declaration should
   be, which is the failure this module keeps having.

   What it cost: the veil goes `is-flying`, which makes its background
   transparent, and the logo then takes 600ms to travel. The page's mark is
   already sitting at the destination the whole time, so a reader watched one
   logo fly across the screen and land on a second one that had been there
   since the first frame. It comes back at the end of the flight, when
   is-loading is dropped 40ms after the traveller arrives.

   And the cards hold their breath under the veil and rise once it lifts. An
   animation rather than a transition on purpose: when it ends the card
   returns to the stylesheet's own transform, so the hover lift keeps
   working. */
body.is-loading .index__mark,
body.is-loading .index__orb,
body.is-loading .index__word,
body.is-loading .index__text,
body.is-loading .index__cta,
body.is-loading .index__grid .menu-card { opacity: 0; }

/* Sealed: the veil has lifted and the page is the drop and nothing else,
   until the drop is pressed. display:none rather than a held opacity — the
   flex column centres what is in it, and three invisible rows still take
   their height, which is what pushed the drop off centre and into the top
   third of the screen.

   EVERY SECTION HAS TO BE ON THIS LIST. One left off is not merely visible —
   it is the one thing standing beside the door, which reads as the whole
   content of the page. The buttons were missing once and sat under the drop
   with nothing around them; the talk card was missing once and opened the
   page. */
body.is-sealed .index__word,
body.is-sealed .index__text,
body.is-sealed .index__cta,
body.is-sealed .rel,
body.is-sealed .talk,
body.is-sealed .writing,
body.is-sealed .index-foot,
body.is-sealed .index__grid { display: none; }

/* And the ring goes with them. Sealed, the drop is the one door on the page;
   a second way in beside it makes the door a choice rather than the entrance.
   It comes back the moment the page opens, which is when there is anywhere to
   go. */
body.is-sealed .site-menu__trigger { display: none; }

body.is-sealed .index__orb { margin-bottom: 0; }

/* The same rise /services uses on its own sections: 22px, 900ms, expo out,
   one behind the other. The drop comes up with the veil; the name, the line
   and the two cards wait for the press and then arrive in reading order. */
body.is-revealed .index__orb {
  /* backwards, not both: a filling animation keeps ownership of transform
     after it ends, and the press hands this box a transform of its own to
     travel on. Backwards still holds the from-state before it starts, which
     is all this one needed the fill for. */
  animation: index-card-rise var(--dur-6) var(--ease-out-expo) backwards;
}

body.is-open .index__word,
body.is-open .index__text,
body.is-open .index__cta,
body.is-open .index__grid .menu-card {
  animation: index-card-rise var(--dur-6) var(--ease-out-expo) both;
}

body.is-open .index__text { animation-delay: 110ms; }

/* Third on the same 110ms beat: name, line, then the doors. They arrive last
   because they are what to do once the first two have been read. */
body.is-open .index__cta { animation-delay: 220ms; }

body.is-open .index__grid .menu-card { animation-delay: 330ms; }

body.is-open .index__grid .menu-card:nth-child(2) { animation-delay: 440ms; }

/* The third card arrives on the same 110ms beat. Without its own delay it
   would land with the first one and the row would read as two arrivals
   instead of three. */
body.is-open .index__grid .menu-card:nth-child(3) { animation-delay: 550ms; }

@keyframes index-card-rise {
  from { opacity: 0; transform: translateY(22px); }
}

/* Reduced motion never sees the veil, so it must never see the rise either —
   without this the cards sit at opacity 0 waiting for a class that the
   preloader's early return never adds. */
@media (prefers-reduced-motion: reduce) {
  /* The mark is on this list for the same reason as the rest of it: there is
     no flight to wait for here, so nothing would ever bring it back. */
  body.is-loading .index__mark,
  body.is-loading .index__orb,
  body.is-loading .index__word,
  body.is-loading .index__text,
  body.is-loading .index__cta,
  body.is-loading .index__grid .menu-card { opacity: 1; }
  body.is-revealed .index__orb,
  body.is-open .index__word,
  body.is-open .index__text,
  body.is-open .index__cta,
  body.is-open .index__grid .menu-card { animation: none; }
  /* the door still has to be shut, and still has to open on a press */
  body.is-sealed .index__word,
  body.is-sealed .index__text,
  body.is-sealed .index__grid { display: none; }
}

/* The cipher holds the stage first: the ring and the logo wait, faded out,
   until the noise has finished resolving into base64. */
.index-loader__cipher {
  position: absolute;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  white-space: nowrap;
  color: color-mix(in srgb, var(--nix-text) 55%, transparent);
  transition: opacity 300ms ease;
}

.index-loader.is-decoded .index-loader__cipher { opacity: 0; }

.index-loader__stage {
  position: relative;
  width: 120px;
  height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 300ms ease 100ms;
}

.index-loader:has(.index-loader__cipher) .index-loader__stage { opacity: 0; }

.index-loader.is-decoded .index-loader__stage { opacity: 1; }

.index-loader__ring {
  position: absolute;
  inset: 0;
  transform: rotate(-90deg); /* the fill starts at 12 o'clock */
}

.index-loader__track,
.index-loader__bar {
  fill: none;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.index-loader__track { stroke: color-mix(in srgb, var(--nix-text) 12%, transparent); }

.index-loader__bar {
  stroke: var(--nix-text);
  stroke-dasharray: 339.3;
  stroke-dashoffset: 339.3;
  animation: index-loader-fill 1.4s ease-in-out forwards;
}

/* Held at zero until the cipher resolves — an animationend that fires while
   the ring is still invisible would lift the veil on a blank screen.
   Scoped with :has() on purpose: this file deploys with the build while the
   template only moves on a module upgrade, so for a window the browser has
   the new CSS and the old markup. Keyed on the cipher's presence, that
   window renders exactly the old preloader instead of a black screen. */
.index-loader:has(.index-loader__cipher) .index-loader__bar { animation-play-state: paused; }

.index-loader.is-decoded .index-loader__bar { animation-play-state: running; }

@keyframes index-loader-fill { to { stroke-dashoffset: 0; } }

.index-loader__logo { position: relative; }

.index__grid {
  /* Takes the leftover height and lets the cards shrink into it — min-height:0
     because a grid item will not shrink below its content otherwise, which is
     what pushed the row off the bottom of the screen. */
  min-height: 0;
  display: grid;
  /* Three since the marketplace joined. auto-fit with a floor rather than a
     fixed count: the same rule then carries the two-up pages that have not
     grown a third card, and a fourth would land without a media query. */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-3);
  width: min(1180px, 92vw);
}

/* Above the name, and the only thing on this page that reacts to a pointer
   for its own sake. No eyes on the stand-in here: this drop rests on the
   butterflies, and a pair of bars would promise a face that only appears
   once it is touched. */
.index__orb {
  /* Takes the window's number instead of declaring one. .card-orb sets
     --orb on this very element, and a declaration on an element beats a
     value inherited into it — which is how the open size, set on the arch,
     was being ignored down here. */
  --orb: inherit;
  margin: 0 auto var(--space-2);
  /* never squeezed by the column it sits in */
  flex: none;
  /* it is the door: it has to look pressable before anyone knows it is one */
  cursor: pointer;
}

/* Once it has been pressed it is scenery, and it steps back to let the page
   it opened have the screen. No transition on the size: the press also
   brings three rows back into the flow, so the drop's seat moves as well as
   its size, and the two together are a FLIP the script drives on transform.
   A width transition here would fight it. */
/* Open, it is scenery: bigger than it was as a door's handle, out of the
   flow and behind the whole composition, centred in the scroller by
   inset + margin:auto. Centred that way and not by a translate, because the
   press hands this box an inline transform to travel on and the two would
   overwrite each other. */
/* Open, the drop becomes the arch: a sphere far too big for the page,
   parked at the bottom edge with most of itself below it, so what shows is
   the crown — a lit curve rising behind the composition rather than a ball
   sitting under it.

   Fixed and centred with left + a negative margin rather than a translate,
   because the press hands this box an inline transform to travel on and the
   two would overwrite each other. */
/* The one control on this page, under the line it belongs to. In the page's
   ink, not its surface — it is the only thing here anyone is meant to press,
   and everything else on this ground is a shade of the ground. */
.index__tour {
  margin-top: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 20px 12px 17px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--nix-text);
  color: var(--nix-paper);
  font: inherit;
  font-size: 14px;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: opacity 220ms var(--ease-out-expo);
}

.index__tour svg { width: 15px; height: 15px; display: block; }

.index__tour:hover { opacity: 0.86; }

/* A ring, not an opacity. 14% of opacity is what hover already says, and a
   focus state that is indistinguishable from hover tells a keyboard nothing. */
.index__tour:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* under the veil with everything else */
body.is-loading .index__tour { opacity: 0; }

/* Sealed, the page is a single drop and the drop is the door. A button under
   nothing is a second thing to look at, and the wrong one. */
body.is-sealed .index__tour { display: none; }

/* The arch is a window, and the sphere sits behind it. A negative margin
   only takes the sphere out of the parent's height; this parent scrolls,
   and a scroll container's scrollable area covers whatever its children
   paint, so the page kept going down to the half that was meant to be
   gone — and clipping the sphere alone left that scroll as dead space
   under it. A box with a height and overflow:hidden ends the page exactly
   where the sphere is cut.

   display:contents while sealed, so the door is a child of the column and
   centres itself; a box only once the page is open. */
/* The size lives here, on the window, and the sphere inherits it — the
   window needs the number too, to know where to cut. */
.index__arch {
  --orb: clamp(280px, 42svh, 420px);
  display: contents;
}

/* THE DROP, IN A CARD OF ITS OWN, above the three it introduces.
   It used to hang at the foot of the page with its lower half running off
   the bottom of the window — which read as an object the page had failed to
   contain rather than one it was showing. In a rectangle it is a banner, and
   a banner belongs over the row it announces.

   The card's chrome is .menu-card's, by value rather than by class: this is
   a span holding a canvas, not a link, and giving it that class would hand
   it the card's padding, its hover and its centred column layout as well.
   What it borrows is the surface, the radius and the lift. */
body.is-open .index__arch {
  /* The sphere is the picture on a full screen now, so it is sized against
     the SHORTER of the two axes — measured on width alone it went tiny on a
     wide short window and overflowed a tall narrow one. */
  --orb: min(40vw, 48svh, 460px);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  /* Room under the mark. The drop is pinned to the TOP of its row, so this is
     the only thing that can sit between the two — there is no free space for
     a centring to give back. */
  padding-top: clamp(28px, 9svh, 130px);
  /* Between the hero and the grid. The three cards carry explicit orders
     too, so the sequence is stated rather than inherited from the DOM — the
     arch is written FIRST in the markup, where it has to be for the sealed
     state to centre it alone. */
  order: 2;
  /* NO CARD. The stage is the page here — surface, radius and lift are gone,
     and with them the frame that made the sphere an illustration inside
     something rather than the thing the screen is showing.

     Full-bleed with it: a stage still boxed into the 1180px column reads as a
     card whose background someone forgot. The negative inline margin takes it
     to the window's edges, and the caption below puts itself back on the
     column with a padding of its own. */
  width: auto;
  margin-inline: calc(50% - 50vw);
  /* Fills the row, and the row is a screen. */
  height: 100%;
  min-height: 0;
  margin-top: 0;
  /* the column's main axis is height, and a flex item shrinks along it by
     default — this one holds a sphere taller than itself and collapsed to
     nothing without this */
  flex: none;
}

/* Both in the first cell, the hero on top. z-index rather than paint order
   so the caption is above the canvas whatever the stacking context does. */
body.is-open .index__arch { grid-area: 2 / 1; }

body.is-open .index__hero {
  /* The stage row, the same cell as the arch — the hero is laid OVER the
     drop, not under it. Row 1 belongs to the mark now. */
  grid-area: 2 / 1;
  /* z-index comes from the "page rides over it" rule above, which already
     lifts the hero and the grid off the canvas. */
  /* Low and left, inside the card's own padding. */
  /* CENTRED, under the sphere. Ranged into the corner it read as a caption
     on a plate; centred with two doors under it, it is the page addressing
     whoever just arrived. */
  align-self: end;
  justify-self: center;
  text-align: center;
  max-width: min(640px, 92%);
  margin: 0 0 clamp(24px, 6svh, 72px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  /* The sphere is bright at its crown and the caption sits over its lower
     half, so the type needs no scrim here — but it is left-aligned and
     ragged-right, which a centred measure is not. */
}

body.is-open .rel { grid-area: 3 / 1; }

/* The gap the grid no longer supplies, carried by the stage above it. */
body.is-open .index__arch { margin-bottom: clamp(32px, 6svh, 80px); }

/* Every section on this grid is PLACED, none are auto-flowed — which is why
   a new one has to be named here as well as written into the markup. Left to
   itself it finds the first free cell, and with rows 1 to 4 already spoken
   for that is a fifth row past the end: under the foot, which is where the
   talk card landed. */
body.is-open .writing { grid-area: 4 / 1; }

body.is-open .talk { grid-area: 5 / 1; }

body.is-open .index-foot { grid-area: 6 / 1; }

/* Bigger, because it is what the page says first. The line beneath it stays
   quiet — one voice at a time. */
body.is-open .index__word { font-size: clamp(30px, 5vw, 52px); }

/* Two lines, not one. At 46ch the sentence ran the full width of the stage
   as a single rule of type — a line that long stops being read and starts
   being scanned. 30ch breaks it near the middle. */
body.is-open .index__text { max-width: 30ch; }

/* The row of doors, tightened against the line above it: .landing__cta was
   drawn for a page where it followed a paragraph and a heading, and its
   var(--space-4) leaves the buttons adrift under two centred lines. */
.index__cta {
  /* Stacked, not side by side. .landing__cta is a row because on /services it
     sits under a paragraph with the width to hold one; here it sits under two
     centred lines, and a row put the two doors wider than the sentence above
     them. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: var(--space-2);
}

/* Both to the width of the wider label, so the stack has one edge instead of
   two ragged ones — min-content on the row's own box, and the buttons fill it. */
.index__cta .btn {
  width: 100%;
  max-width: 260px;
  text-align: center;
}

/* WHAT ACTUALLY SETS THE ROW'S HEIGHT is .menu-card--art's min-height: 362px,
   not its padding — the padding is already 12px there. 362 was measured for
   the agents panel on a page that scrolled; on a short window it is what
   pushes the row off the bottom.

   Clamped against the viewport HERE ONLY. The same cards live in four overlay
   copies that open over a page rather than sharing one with a banner, and
   they keep the fixed height they were measured for. */
/* The clamp that squeezed these into one screen is gone with the screen it
   was for: the page scrolls now, and a card cropped to fit a viewport it no
   longer has to share is a card made smaller for nothing. They keep the 362px
   they were measured for. */
/* not until the drop has landed: clipped from the first frame, the travel
   would be cut in half on its way down */
body.is-open.is-settled .index__arch { overflow: hidden; }

/* and the window opens again while the drop is on its way to the guide, so
   the half that is cut here is not cut off mid-flight */
body.is-touring .index__arch { overflow: visible; }

body.is-open .index__orb {
  /* Inside the arch, and no margins of its own: the card decides where the
     picture ends, the sphere just hangs in it. flex:none because a flex item
     shrinks along the main axis by default, and this one is deliberately
     taller than the box it sits in — what shows is its crown, cropped by the
     card's own radius. */
  margin: 0;
  flex: none;
  z-index: 0;
  cursor: default;
}

/* and the page rides over it. The grid moved inside .rel, so the section is
   what has to be lifted now — lifting the grid alone would leave its heading
   under the canvas. */
body.is-open .index__hero,
body.is-open .rel,
body.is-open .writing,
body.is-open .talk,
body.is-open .index-foot {
  position: relative;
  z-index: 1;
}

/* THE HERO IS THE DROP, and the name sits in its corner.

   Two comments stood here describing arrangements this one replaced — a flex
   column that centred what the arch had left behind, and a banner taking a vh
   share above the cards. Both were true of their own layout and neither is
   true of this one; a rule with three explanations has none.

   The name used to float above the sphere in the middle of the page, which
   made two centred things stacked — the composition had no anchor and the
   drop was decoration under a title. Overlapping them makes one object: the
   sphere fills the frame, and the wordmark and its line sit low and left in
   it, the way a plate is captioned.

   Grid rather than the flex column it was: the arch and the hero have to
   occupy the SAME cell for one to sit over the other, and flex has no way to
   say that without taking one of them out of the flow. `order` still applies
   in grid, so the sealed state's sequencing is untouched. */
body.is-open .index {
  display: grid;
  grid-template-columns: min(1180px, 92vw);
  /* THREE SECTIONS, and only the first is promised a screen. The hero is one
     screenful because it is a stage; the releases and the foot are as tall as
     their contents and the page scrolls to them, which is what a page with
     more than one thing to say does.

     100% and not 100svh: this grid is the scroller, and its own height is
     already the window minus the header. 100svh would be a screenful measured
     from above the header and would push exactly the header's height of the
     hero off the bottom. A percentage resolves against the container, which
     is the box actually being filled.

     `1fr` cannot do this job either — free space is zero once the sections
     below overflow the container, so the row would collapse to its content. */
  /* The mark band, then the stage, then the sections. The first two
     subtract to exactly one screen, so adding the mark did not push the
     stage down and leave a strip of the releases showing under it. */
  grid-template-rows: var(--mark-h, 62px) calc(100% - var(--mark-h, 62px)) auto auto auto auto;
  justify-content: center;
  align-content: start;
  /* Only between the stage and the band. The band and the foot are meant to
     meet at an edge, so the row gap is put on the stage instead of the grid —
     a grid gap would reopen a strip of page between the two grounds. */
  gap: 0;
  /* Zero, so the mark band starts at the shell gap and nothing else. The rail
     mark sits gap + rail pad + half its box down from the top of the shell;
     any padding here would stack on that sum and the two could never line up.
     This declaration is the one that counts — a rule of the same specificity
     sits earlier in this file and would lose to it. */
  padding-top: 0;
  padding-bottom: 0;
  /* No floor under the arch. The negative margin says how much of the
     sphere the page does not make room for, and any bottom padding is
     scrollable space added straight back under it — which is how a sphere
     meant to be cut in half ended up fully visible at the end of the
     scroll. */
  padding-bottom: 0;
}

/* And its stand-in is a dark ball, not the lit one. The shared stand-in
   paints the FACE — pink, teal and violet, the colours the drop wears once
   it has been pressed — which is right inside a card that shows the face and
   wrong on a door that opens with the butterflies. It was the first thing on
   screen for as long as three.js took to arrive, and now that it fades out
   over a second it is on screen long enough to read as the wrong drop. */
/* Both classes on purpose: .card-orb::before is written further down this
   file, and at equal specificity the later rule wins — which is why the lit
   stand-in was still the one painting. */
.card-orb.index__orb::before {
  background:
    radial-gradient(58% 48% at 34% 28%, rgba(255, 255, 255, 0.10), transparent 62%),
    radial-gradient(70% 70% at 68% 76%, rgba(150, 170, 255, 0.10), transparent 70%),
    radial-gradient(100% 100% at 50% 50%, var(--nix-surface), var(--nix-rule));
  filter: blur(10px);
}

/* And it waits out of sight while the real one is on its way. The stand-in
   exists for a browser that will never paint a canvas; on one that will, it
   is a grey plate sitting in the dark for as long as three.js takes, which
   is the silhouette that turned up before the drop. is-lighting is set when
   the scene is asked for and dropped again if it never arrives, so the
   fallback still has its job. */
.card-orb.index__orb.is-lighting:not(.is-live)::before { opacity: 0; }

/* The scene arrives, it does not switch on: three.js lands whenever it
   lands, and 700ms of fade on a canvas that appears mid-visit still reads as
   a cut. Longer here, where the drop IS the page. */
.index__orb .card-orb__canvas { transition: opacity 1400ms var(--ease-out-expo); }

/* ══ The index's lower sections ═════════════════════════════════════════════
   The hero is a stage and the two below it are a page. They share the grid's
   column, so everything on this document lines up on one measure — the cards,
   the heading over them and the footer's first column all start at the same x.
   ─────────────────────────────────────────────────────────────────────────── */

/* ── New releases ────────────────────────────────────────────────────────── */
/* NO GROUND OF ITS OWN. It was a lifted band on --nix-surface, and that was
   the one value the cards inside it also carry — a card in its container's
   colour has no edge, so with the rail shut the three cards vanished into the
   band and only their pictures and their rules were visible.

   The band goes back to the page, and the cards keep --nix-surface: one step
   up from --nix-paper, which is what a card is. Nothing here is lifted except
   the things that are actually objects.

   Full-bleed by the same arithmetic the foot uses — a negative inline margin
   out to the window, and padding that puts the contents back on the column
   the hero's caption and the cards already sit on. Still full-bleed even with
   no ground to paint: the padding is what holds the column. */
.rel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-inline: calc(50% - 50vw);
  padding: clamp(40px, 7svh, 88px) max(calc(50vw - 590px), 4vw);
}

.rel .menu-card {
  /* --nix-surface, the value every card on the site carries. It was --nix-chip
     for as long as .rel painted --nix-surface under it — a card in its
     container's own value has no edge. The band carries no ground now, in
     either state, so the card steps up off whatever is behind it: --nix-paper
     with the rail shut, --nix-panel with it open. */
  background: var(--nix-surface);
  /* The rules inside the card, and NOT --nix-rule. In dark that token is
     #2E2D2B and so is --nix-chip — the exact same value — so a hairline drawn
     in it was painted on the card in the card's own colour and could not be
     seen. It looked like the borders had not shipped; they had, invisibly.

     A share of the text colour instead, which is the one thing guaranteed to
     contrast with the ground it sits on, in either theme. */
}

/* A PORTRAIT CARD with the door at the foot of it. The art is the top of the
   card, the name and its line sit under it, and the button is pushed to the
   bottom edge so all three cards end on the same line whatever their copy
   does — which is the whole reason a row of cards has a button at all rather
   than being one big link.

   Scoped to .rel. The identical cards in the four overlay copies are a menu,
   and a menu item does not need a button inside it. */
.rel .menu-card--art {
  justify-content: flex-start;
  align-items: stretch;
  text-align: left;
  /* Taller than what is in it. The picture, the title, the paragraph and the
     button add up to about 440px, and at exactly that the card is a stack of
     content with no card around it.

     Written against --art-h rather than as a flat number so the two cannot
     drift: change the frame and the card follows by the same amount, and the
     240 stays what it is — the air, not a total anybody has to recompute.

     It is a floor, not a height. Copy that runs to a third line still grows
     the card rather than spilling out of it, and margin-top:auto on the
     button keeps all three ending on the same line either way. */
  min-height: calc(var(--art-h) + 300px);
  /* Less frame, more picture. The card's own padding is what stands between
     the graphic and the card edge, so taking it down widens the art box by
     twice what comes off — the three graphics gain more from this number than
     from any change to their own. */
  padding: 14px 14px 18px;
  gap: 6px;
  /* The phone block gives .menu-card--art a 5/4 ratio, written for the menu
     cards, which are a label over artwork and nothing else. These carry a
     title, a paragraph and a button under the same art, so a ratio that
     squat makes the content run out the bottom of the card. Height comes
     from what is in it. */
  aspect-ratio: auto;
}

/* ONE BOX, ONE HEIGHT, THREE PICTURES. The three graphics are different
   components with different natural sizes — the agents panel and the shelf
   run around 250px, the orbit is a 131px crop of an arc. Left to themselves
   the names started at three different heights and the row read as ragged.

   So the art gets a box rather than the cards getting a minimum: the frame is
   fixed, each picture centres in it at its own size, and everything after it
   begins on the same line in all three. */
.rel .menu-card--art { --art-h: 260px; }

/* THE BOX IS THE THING THAT IS THE SAME. Two of the three graphics carried a
   white panel of their own and the third — the orbit — carried nothing, so
   the row read as two screenshots beside a set of loose floating icons.

   The panel moves up here, onto the frame, and all three sit inside one
   identical container: same ground, same radius, same lift, same height. What
   varies is only the picture drawn in it.

   container-type is load-bearing, not decoration: it makes cqw below mean
   "this frame's width", which is how the orbit gets to scale with the card
   while staying a LENGTH. */
.rel .menu-card__art {
  height: var(--art-h);
  display: grid;
  place-items: center;
  overflow: hidden;
  margin-bottom: var(--space-2);
  padding: 12px;
  background: var(--nix-paper);
  border-radius: 14px;
  /* The same pair every surface on this site is lifted by. On a light page
     paper on chip is white on white, so the shadow is the entire edge; in
     dark the two grounds differ and it costs nothing visible. */
  box-shadow: var(--lift-1);
  container-type: inline-size;
}

/* In flow inside that box, and stripped of the panel it used to be. Its
   ground, radius and shadow are now the frame's — kept here as well, it would
   be a white box drawn inside a white box. */
.rel .menu-card--art .card-shot {
  position: relative;
  left: auto;
  top: auto;
  transform: none;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  background: none;
  box-shadow: none;
}

/* No caption. The card's own title says "Agent Base" two lines below it, so
   the word inside the picture was the same label twice. Hidden rather than
   cut from the markup: eleven other copies of this component live in the
   overlay menus, where the picture has no title under it and the caption is
   the only thing naming it. */
.rel .menu-card--art .card-shot__head { display: none; }

/* THE TILES TAKE THE ROOM THE CAPTION LEFT. The grid was three rows of a
   fixed 16/10, which is a height it computes from its own width and then
   stops at — so it sat in the top two-thirds of the frame with a band of
   empty paper under it. Dropping the ratio and giving the grid the leftover
   height makes the rows share whatever the frame actually is. */
.rel .menu-card--art .card-shot__grid {
  flex: 1;
  min-height: 0;
  grid-template-rows: repeat(3, 1fr);
}

.rel .menu-card--art .card-shot__cell,
.rel .menu-card--art .card-shot__new { aspect-ratio: auto; }

/* THE ORBIT FILLS THE SAME FRAME. --size is the diameter its rings are drawn
   at AND the source of every satellite's radius (--r is --size/2), so it has
   to stay a length: a percentage there would resolve against the 38px chip
   instead of the ring, and the icons would collapse onto the centre.

   min() of two lengths is still a length. cqw is a share of the frame's own
   width — which is what container-type above buys — so the ring tracks the
   card at every width instead of being clipped at the narrow end.

   THE BOX AND THE RING ARE NOW TWO DIFFERENT WIDTHS, and that is what stops
   the apps being sliced. Each chip is centred ON the ring, so it hangs half
   its own width past it — and .card-orbit was exactly --size wide with
   overflow: hidden, so the element clipped its own satellites. Backing the
   ring off the frame did not help: the box shrank with it and the chips went
   straight back over the same edge.

   So the box takes the whole frame and the ring is sized to leave a chip's
   width of room inside it. The stage is then centred by hand — it is
   absolutely positioned at left: 0, which put the rings against the left wall
   the moment the box stopped being the same width as them. */
.rel .menu-card--art .card-orbit {
  position: relative;
  inset: auto;
  margin: 0;
  max-height: none;
  --size: min(calc(100cqw - 44px), 280px);
  width: 100%;
  height: 100%;
}

.rel .menu-card--art .card-orbit__stage {
  left: 50%;
  transform: translateX(-50%);
}

/* ONE FADE, THREE PICTURES, AND IT IS AN ARC.

   A linear-gradient fades along a straight line, so sides and bottom needed
   two of them intersected, and the boundary was a rectangle with soft edges —
   three flat walls meeting at corners. What the picture wants is one curve:
   lowest in the middle, rising toward both sides, so the drawing dissolves
   into the paper along the same shape the rings are drawn in.

   A radial-gradient IS that curve. One gradient replaces both, and no
   compositing is needed: the boundary of an ellipse centred above the top
   edge crosses the box as a smile.

   The numbers are that geometry, not taste. The centre sits at 50% -10%, the
   vertical radius is 118%, so directly below the centre the boundary bottoms
   out at 108% of the height — past the bottom edge, which is the point: the
   picture is still dissolving when it runs out of card rather than having
   finished halfway up it. The horizontal radius 99% is what puts the same
   boundary at 92% of the height out at the left and right walls. Solve the
   ellipse for dx = half the width and that is the number that comes back;
   move one and the other has to move with it.

   TWO THINGS CONTROL HOW HARD IT READS, and they are not the same thing. The
   radii set where the curve runs. The 78% stop sets how long the ramp is —
   opaque until 78% of the way out, then falling. A near stop makes a wide,
   heavy wash that eats the picture; a far one makes the veil this is. The
   first version had both wrong at once: a boundary through the middle of the
   box and a ramp starting at 62%, which drew the arc as a line instead of
   letting it disappear. */
.rel .menu-card--art .card-shot,
.rel .menu-card--art .card-store,
.rel .menu-card--art .card-orbit {
  -webkit-mask-image: radial-gradient(99% 118% at 50% -10%, #000 78%, transparent 100%);
  mask-image: radial-gradient(99% 118% at 50% -10%, #000 78%, transparent 100%);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* 23 over 16 is the same 1.44 the pair had at 20 over 14, scaled by 1.15. The
   ratio is the thing being held: it is what makes the title read as a title
   and the paragraph as its body, and moving one without the other closes or
   opens that gap. */
/* NO RULES INSIDE THE CARD. There were two — under the title and under the
   paragraph — and on a card that is already a surface on a ground, with a
   picture, a heading, a body and a button stacked down it, they were three
   horizontal lines competing to say the same thing the spacing says. The
   padding stays: what separated the parts was never the line. */
.rel .menu-card__name {
  font-size: 23px;
  padding-bottom: 14px;
}

/* Always readable. It woke on hover when it was one line under a title on a
   card that was mostly picture; on a card built to be read it cannot be the
   part you have to find. */
.rel .menu-card--art .menu-card__desc {
  opacity: 1;
  transform: none;
  font-size: 16px;
  color: var(--nix-text-soft);
  /* THE PARAGRAPH IS THE BAND BETWEEN THE TWO RULES, not a line of text with
     a gap under it. flex:1 hands it every pixel the picture and the button do
     not use, so its own bottom border lands directly above the button however
     long the copy runs — no second element to position and nothing to keep in
     sync when the text changes.

     Then display:flex centres the words inside that band. The text becomes an
     anonymous flex item, which is the whole trick: align-items has something
     to centre without wrapping the copy in a div. */
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  padding-block: 14px;
}

/* margin-top:auto has nothing left to claim now that the paragraph takes the
   slack, so the space above the button is stated rather than left over.

   text-decoration is the one thing that has to be said again here. The card
   was an <a> and is now a <div>, so this is the anchor, and an anchor
   underlines itself — .menu-card's text-decoration:none was covering it back
   when the card was the link. */
.rel .menu-card--art .menu-card__go {
  margin-top: 14px;
  text-decoration: none;
  transition: opacity var(--dur-2) var(--ease-out-expo);
}

/* Hovering the card no longer dims the button. It was the card that was
   clickable when that rule was written; now only the button is, and a whole
   card that responds to the pointer promises a click it does not take. */
.rel .menu-card:hover .menu-card__go { opacity: 1; }

.rel .menu-card .menu-card__go:hover { opacity: .85; }

/* margin-top:auto is what pins it: the card is a column, and the space that
   is left over goes above the button rather than under it. */
.menu-card__go {
  margin-top: auto;
  align-self: flex-start;
  /* Grown with the type around it. The padding moves too, not just the label:
     a bigger word in the same pill reads as a tighter button, not a larger
     one — the air around it is most of what "size" means here. */
  padding: 12px 24px;
  border-radius: var(--radius-pill);
  background: var(--nix-ink);
  color: var(--nix-on-ink);
  font-size: 15px;
  white-space: nowrap;
}

.menu-card:hover .menu-card__go { opacity: .85; }

/* ── The closing drop ───────────────────────────────────────────────────────
   The drop alone in a card of its own — the last thing on the page before the
   foot. It had a Let's Talk button beside it; without one the drop is centred
   in the card rather than sharing it, and the section says nothing, which is
   what a page that has already said everything should end on.

   It sits on the PAGE's ground, not the lifted one the releases and the
   writing share. Those two are one band of content; this is the end of it,
   and dropping back to the page ground is what separates the ask from the
   reading. The card inside it still steps up, so it reads as an object on
   the page rather than another band. */
.talk {
  margin-inline: calc(50% - 50vw);
  padding: clamp(40px, 7svh, 88px) max(calc(50vw - 590px), 4vw);
  /* No ground of its own. It was painting --nix-paper over whatever it sits
     on, which on the open page is the panel — a band the width of the window
     laid across a panel that is not. Inheriting means one surface. */
}

.talk__card {
  display: flex;
  align-items: center;
  justify-content: center;
  /* wrap instead of a breakpoint: on a phone the button drops under the drop
     and both stay centred, which is the only arrangement the two of them have
     at that width anyway */
  flex-wrap: wrap;
  gap: clamp(20px, 4vw, 56px);
  padding: clamp(24px, 4svh, 44px);
  border-radius: var(--radius-2xl);
  /* --nix-surface, the value every card carries. It was --nix-chip for as long
     as the panel under it was --nix-surface too — a card in the same value as
     its container has no edge at all, and the drop read as floating on a grey
     rectangle. The panel is --nix-panel now, a step darker, so the card can be
     a card again. */
  background: var(--nix-surface);
  box-shadow: var(--lift-2);
}

/* Scoped through .talk rather than set on .talk__orb, and that is deliberate:
   .card-orb declares --orb on the element itself, so a class of equal weight
   on the same element only wins by source order. Two classes always win. */
.talk .card-orb {
  /* The card has no height of its own — it is padding plus the taller of its
     two children — so this number is what sets it. The cap does the work on a
     desktop; the 24vw only takes over on a narrow window, where a fixed 300
     would be wider than the card holding it. */
  --orb: clamp(150px, 24vw, 300px);
  flex: none;
}

.rel__title {
  /* Ranged left with the cards it names. Centred, it would read as a title
     for the page rather than for the row directly under it. */
  font-size: clamp(22px, 3vw, 32px);
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--nix-text);
}

/* ── Writing ─────────────────────────────────────────────────────────────────
   A statement held on the left against a list on the right. The two columns
   are the point: the sentence is the reason to read and the rows are the way
   in, so they sit level rather than stacked.

   On the band's ground, so it shares the section above it — the writing and
   the releases are one half of the page and the foot is the other. */
.writing {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(24px, 5vw, 80px);
  align-items: start;
  margin-inline: calc(50% - 50vw);
  padding: clamp(48px, 8svh, 104px) max(calc(50vw - 590px), 4vw);
  /* On the page's ground, like the releases above it. The two used to share a
     lifted band; a band that only ever hid the cards standing on it. */
}

.writing__lede { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }

.writing__title {
  font-size: clamp(22px, 2.6vw, 34px);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--nix-text);
  /* Breaks on its own measure rather than the column's: a three-line
     statement reads as one thought, a six-line one as a paragraph. */
  max-width: 16ch;
}

.writing__all {
  color: var(--nix-text-soft);
  text-decoration: none;
  font-size: 14px;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
}

.writing__all:hover { color: var(--nix-text); }

.writing__list { list-style: none; margin: 0; padding: 0; }

/* The rule goes on the row, not between them: a border-bottom on every row
   plus one on the last leaves a line hanging under the list with nothing
   after it. First-child takes the top edge so the list is closed at both
   ends instead of open at the top. */
.writing__row { border-bottom: 1px solid var(--nix-rule); }

.writing__row:first-child { border-top: 1px solid var(--nix-rule); }

.writing__link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: clamp(14px, 2svh, 22px) 0;
  text-decoration: none;
  color: var(--nix-text);
}

.writing__link:hover .writing__name { opacity: .6; }

.writing__name { font-size: clamp(15px, 1.5vw, 19px); }

.writing__tag {
  flex: none;
  color: var(--nix-text-soft);
  font-size: 14px;
  white-space: nowrap;
}

@media (max-width: 900px) {
  /* One column: at this width the statement and the list each get about
     twenty characters, which is a sentence broken into a tower. */
  .writing { grid-template-columns: 1fr; gap: var(--space-4); }
  .writing__title { max-width: none; }
}

/* NO SHADOWS ON A DARK GROUND. A shadow is light being blocked, and on #101010
   there is none to block: what the browser draws instead is a black smear that
   reads as dirt around the card. The step between --nix-panel and --nix-surface
   is the whole edge in dark, and it is enough — the light theme keeps its
   shadows, where the two grounds are a per cent apart and the lift IS the edge.

   Listed rather than swept with a `*`: box-shadow is also how these sheets draw
   a few hairlines (a zero-blur shadow is a border wearing a moustache), and
   turning those off would take real lines with it. */
html[data-theme="dark"] .talk__card,
html[data-theme="dark"] .rel .menu-card__art { box-shadow: none; }

.index {
  /* the body no longer scrolls, so main is the scroller — the same
     arrangement .soon uses, and min-height:0 is what lets a flex item
     shrink far enough to actually scroll */
  flex: 1;
  min-height: 0;
  /* In the shell it is a grid item, where flex:1 means nothing — the height
     has to be claimed from the track instead. */
  height: 100%;
  box-sizing: border-box;
  position: relative;
  /* auto, not hidden: with a large system font or a short landscape
     viewport this content does not fit, and a page that clips its own
     cards is worse than one that scrolls. One screen is the target,
     not a promise kept by hiding things. */
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* NO SCROLL ANCHORING IN THIS BOX, and that is a bug fix rather than a
     preference. Sealed, the sphere is the only thing in it and sits mid
     screen; opening inserts the whole page around it, and the browser's
     anchoring then scrolls the container to keep the sphere where it was —
     so the first thing a reader did landed them in the middle of the page
     instead of at its top. The page is not a feed and nothing here depends on
     holding a position across a mutation. */
  overflow-anchor: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-3);
}

/* ── Overrides that have to stand last ────────────────────────────────────
   A media query adds no specificity, so a block written above a rule of the
   same selector loses to it at every width. These were higher up and every
   declaration in them was dead — including the reduced-motion opt-out, which
   is not a nicety: it is what a reader who asked their operating system for
   no motion gets. Position is the only thing that makes them apply. */

/* ── The phone ────────────────────────────────────────────────────────────
   The stage and the page come apart here. In a card this narrow the caption
   would sit on the sphere's brightest part with nowhere to go, so the two
   rows separate again and the page scrolls, which a phone does anyway. */
@media (max-width: 720px) {
  .index { gap: var(--space-4); padding: var(--space-4) var(--space-3); }
  .index__grid { grid-template-columns: 1fr; width: min(420px, 92vw); }
  .index__word { font-size: clamp(24px, 6vw, 30px); }
  .index__text { font-size: 15px; }
  /* The drop is measured against the screen it is on. The 42svh the desktop
     uses is a sliver on a tall phone. */
  .index__arch { --orb: min(74vw, 420px); }

  /* THE COLUMN STARTS AT THE TOP, AND THIS IS THE BUG THE PHONE HAD.
     ─────────────────────────────────────────────────────────────────────
     .index carries `justify-content: center` from its base rule, for the
     sealed state where it holds one sphere and centring it is the whole
     design. On a desktop the OPEN state is a grid, so that declaration is
     inert there and has been since the day it was written. Here the open
     state is this flex column — and a centred flex column whose content is
     taller than its box overflows EQUALLY IN BOTH DIRECTIONS.

     Overflow past the start edge of a scroller is unreachable. Measured on a
     430x932 phone: content 2453 tall in a 798 box, so the mark sat at
     top: -1635, the sphere at -1611 and the hero at -1251, with scrollTop
     already 0 and nowhere further to go. Every symptom reported came out of
     this one declaration — the page opening "in the middle", the hero never
     centred, and the top of the page simply not being reachable.

     flex-start, in the state that overflows. Sealed keeps the base's
     centring, which is what it was written for. */
  body.is-open .index {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    /* Declared HERE rather than on the arch, because the hero needs it too:
       it is a sibling, not a child, so an --orb set on .index__arch is not in
       scope for the calc that sizes the first screen. That calc silently
       resolved to nothing and the min-height was dropped — computed `auto`
       on a live page, which is how it was found. */
    --orb: min(78vw, 420px);
  }
  /* A stage, still — just a shorter one, and it keeps the bleed so the sphere
     is not boxed on the one screen where boxing it would hurt most. Open, it
     is as wide as the screen and no wider: wider than that and it is a curve
     running off both sides rather than a sphere. Cut at the equator, so the
     page ends on the face. */
  /* A WHOLE SPHERE, IN A BOX ITS OWN SIZE.
     This asked for a screen-wide orb in a box half as tall — the dome the
     page was meant to end on. Two things were wrong with it. The cut was
     never made (no clip), so the bottom half spilled over the word and the
     lede, invisible in dark and a pale disc across the hero in light. And
     clipping it, which is what that comment wanted, is not what the page
     wants: a sphere sawn in half reads as an image that failed to load, not
     as a horizon.

     So the box is the sphere's own height and the sphere is whole. Capped at
     420 so it does not eat a tall phone, and at 78vw so it keeps a margin
     either side rather than touching both edges. The hero follows it in the
     column, centred, with nothing behind it to read through. */
  body.is-open .index__arch {
    order: 1;
    width: 100vw;
    margin-inline: calc(50% - 50vw);
    /* INHERIT IT, DO NOT JUST STOP DECLARING IT. --orb is set on .index for
       the hero's sake, but the desktop rule above still declares its own on
       THIS box — `min(40vw, 48svh, 460px)` — and a media query only overrides
       the properties it names. So the arch went on resolving 160px while the
       hero subtracted 312, and the 152px difference was the hole between the
       sphere and the word. Measured in a browser; invisible in the file,
       because both rules read correctly on their own. */
    --orb: inherit;
    height: var(--orb);
    align-items: center;
    padding-top: 0;
    /* A DECLARED GAP, because the inherited one is 40.56px of some relative
       unit and the hero has to subtract it exactly. A number nobody can name
       cannot appear in the arithmetic that makes a screen a screen. */
    margin-bottom: var(--space-4);
    /* Belt and braces: nothing should exceed the box now, and if the drawing
       ever grows again it is cropped rather than laid over the words. */
    overflow: hidden;
  }

  /* CENTRED, AND BY ITS OWN MARGIN. Open, the mark is placed with grid-area
     and justify-self — and this block turns .index into a flex column, where
     both are inert. It fell back to the start of the cross axis, which is the
     left edge: a 24px mark in the corner of every phone. An auto inline
     margin centres a flex item whatever the parent's align-items says. */
  body.is-open .index__mark {
    margin-inline: auto;
    align-self: center;
  }
  /* THE FIRST SCREEN IS A SCREEN. Mark, sphere and hero were three items in a
     scrolling column that happened to be about a screen tall, so "New
     releases" crept above the fold on a short phone and the hero sat wherever
     the sum of three heights left it — never centred in what was left.

     The hero claims the rest of the viewport explicitly and centres its own
     contents in it: the screen is the mark, the sphere, and then everything
     under them balanced in the space that remains. The subtraction is spelt
     out because every term in it is already a number this block declares —
     the mark's band, the sphere's diameter, the two gaps between the three
     items, and the page's own padding top and bottom. */
  body.is-open .index__hero {
    order: 2;
    margin: 0;
    max-width: min(420px, 92vw);
    text-align: center;
    /* 100% OF THE SCROLLER, NOT 100svh. They are not the same number on a
       phone: the scroller's box is what the shell actually gave it, and svh
       is the viewport the browser promises when its toolbar is collapsed. Ask
       for the viewport and the first screen comes out taller than the box
       holding it — which is "New releases" peeking under the buttons, every
       time, by exactly the height of the address bar.

       The percentage resolves against .index's content box, which already has
       the page padding taken out of it, so the terms are the mark's band, the
       sphere, and the one gap under it — measured in a browser rather than
       assumed: this column's row-gap and its vertical padding both compute to
       zero here, so subtracting either put the fold in the wrong place. */
    min-height: calc(100% - var(--mark-h) - var(--orb)
                     - var(--space-4) - var(--arch-lead));
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    /* align-self, AND IT IS THE ANSWER TO "IT DRIFTS RIGHT AS THE WINDOW
       NARROWS". Open, this box is placed in a grid with `align-self: end`,
       which puts it at the end of the block axis — the bottom of its cell,
       exactly right there. Here the column is flex, the axes swap, and "end"
       becomes the RIGHT EDGE. The container's own align-items: center loses,
       because a child's align-self always wins.
       Measured at 523px wide: the hero sat at 222–499 in a 523 box.

       width, because without it the box is shrink-to-fit — 277px of the 420
       it is allowed — so even centred it would set the lede on a measure
       nobody chose. Full width up to its own max, centred, and the text
       inside it stays on the line it was written for. */
    align-self: center;
    width: 100%;
  }

  /* Air above the sphere, and the hero gives back exactly as much. The two
     terms are one variable so they cannot drift: the screen is a screen only
     while everything in it is accounted for once. */
  body.is-open .index { --arch-lead: var(--space-4); }

  /* margin, NOT padding. The arch's height IS the sphere's diameter and this
     sheet is border-box — padding there would come out of the drawing and
     `overflow: hidden` would crop the bottom of it. A margin moves the box
     without resizing what is in it. */
  body.is-open .index__arch { margin-top: var(--arch-lead); }
  body.is-open .index__word,
  body.is-open .index__text { text-align: center; }
  /* NEITHER BAND BLEEDS HERE. The flex fallback has no centred column for
     `50% - 50vw` to measure against, so both simply span what they are given —
     and the cards inside the band go back to their own width.

     Every section states its order: unnumbered is order 0, which is FIRST
     here rather than last, and the talk card would have opened the page. */
  body.is-open .rel {
    order: 3;
    width: 100%;
    margin-inline: 0;
    padding-inline: 5vw;
    align-items: center;
  }
  body.is-open .writing {
    order: 4;
    width: 100%;
    margin-inline: 0;
    padding-inline: 5vw;
  }
  body.is-open .talk {
    order: 5;
    width: 100%;
    margin-inline: 0;
    padding-inline: 5vw;
  }
  body.is-open .index-foot {
    order: 6;
    width: 100%;
    margin-inline: 0;
    padding-inline: 5vw;
  }
}
