/* landing.css — layout and the one control. Nothing here draws the scene:
   the brass, the marble, the wall and the light are geometry in landing/,
   rendered by the same engine the editor uses. CSS places a canvas and a
   link, which is all a landing page's stylesheet should ever do. */

/* ── THE TWO ROLES, AND WHY THE ACCENT IS NOT THE TEXT COLOUR ──────────────
   Cyan carries STRUCTURE — rules, the world's name, the door — and grey-white
   carries CONTENT. That division is not a style preference, it is what keeps
   the page readable: --accent on #05080a is around 7:1, fine for the large
   name and the one link, while the figures and their labels are read in a
   6-point mono at a glance and get the full-contrast neutral instead. An
   accent used for body text is an accent nobody can see past. */
:root {
  color-scheme: dark;
  --ink: #e8f4f8;                          /* content: near-white, faintly cool */
  --dim: #7c8c94;                          /* secondary content: neutral grey */
  --accent: #4fd8ea;                       /* structure: the one cyan */
  --edge: rgba(79, 216, 234, 0.34);
  --edge-hot: rgba(126, 234, 248, 0.92);
  /* ── A CONTROL'S EDGE IS NOT A PANEL'S EDGE ──────────────────────────────
     --panel-edge below is a hairline on a panel: it separates two dark
     surfaces and it is allowed to be almost nothing, because nothing depends
     on finding it. It was also being used for the stepper's buttons, where it
     composited to 1.01:1 against the room — a control whose boundary is
     invisible, which WCAG 1.4.11 asks to clear 3:1 for exactly this reason.
     This is the same hue at the alpha that gets there (≈3.4:1 on the room,
     ≈3.0:1 on the panel), and it is used ONLY where a control's own extent has
     to be findable.

     (--glass, --glass-edge and --graphite-edge stood here and are gone: the
     panel they described was a pane bled out behind the type, and the three
     values that drew it were replaced by the three below. A token nothing
     references is a value the next person will match a new component to.) */
  --control-edge: rgba(150, 172, 182, 0.60);
  /* --dim, for type that sits on the rendered room rather than on a panel.
     #7c8c94 is a 5:1 grey on near-black glass and a 2:1 grey on the lit part
     of the wall, and the masthead crosses both. */
  --dim-lit: #a3b4bc;

  /* ── THE CONSOLE'S OWN THREE VALUES ──────────────────────────────────────
     The readout used to be a block of type with a pane bled out behind it and
     one rule down its left edge. It is an INSTRUMENT now — a bounded surface
     with a drawn boundary and drawn divisions — and an instrument needs its
     edges to be three different weights, not one:

       --panel        the surface. Darker and less transparent than the old
                      --glass: it now covers the plinth rather than floating
                      clear of it, and a panel you can read the marble through
                      is a panel that is not a panel.
       --panel-edge   the OUTLINE. One pixel, graphite, and the strongest of
                      the three, because it is what says where the instrument
                      stops and the room starts.
       --rule         the DIVISIONS. One pixel and much fainter: they separate
                      rows that are already aligned, so they only have to be
                      found by an eye that is following a row, never by one
                      that is looking for them.

     All three are the same hue at three alphas, so the panel reads as one
     material ruled at three weights instead of as three borrowed greys. None
     of them is cyan: the cyan on this block is the world's name, the door and
     the current dot, and an accent spent on furniture is an accent that has
     stopped naming anything. */
  --panel: rgba(6, 10, 12, 0.80);
  --panel-edge: rgba(126, 148, 158, 0.26);
  --rule: rgba(126, 148, 158, 0.115);

  /* ── THE ROOM'S CYAN, AS THE PANEL CATCHES IT ────────────────────────────
     Three values, all of them the same hue as --accent and none of them used
     as a colour anybody reads. They exist because the instrument is standing
     in a lit room and until now it was not: a flat dark rectangle with square
     corners, sitting ON the render rather than IN it.

       --lip     the light along the panel's top edge. A horizontal surface
                 turned toward a room whose light comes from above catches a
                 line on its leading edge; that is the single cheapest cue that
                 says an object has thickness. Drawn as a gradient rather than
                 as a border-top, so it fades along the edge the way a
                 reflection does and does not read as a coloured outline.
       --glow    the cyan the housing spills onto the room behind it, as the
                 outermost of the panel's three shadows.
       --inner   one pixel of light INSIDE the top edge, which is the same
                 highlight seen on the panel's own surface rather than on its
                 lip. Together they make the edge read as a chamfer. */
  --lip: rgba(120, 226, 244, 0.52);
  --glow: rgba(60, 190, 214, 0.13);
  --inner: rgba(150, 214, 232, 0.075);

  /* The housing's own radius. One token, because the card, the stepper and the
     door are three elements drawing one object and a radius that disagreed
     across the seam would be the first thing to give that away. */
  --housing: 10px;
}

html, body { margin: 0; height: 100%; background: #000; }
body { overflow: hidden; }

main { position: relative; width: 100%; height: 100%; display: grid; place-items: center; }

/* ── THE CANVAS IS THE VIEWPORT NOW ───────────────────────────────────────
   It used to render at the plate's 3:2 and let `object-fit: contain` fit that
   inside the window. On a desktop that cost two invisible bars; on a phone it
   cost sixty per cent of the screen, and it left this stylesheet's own card
   and door — which are positioned against the VIEWPORT — floating in the
   black under a picture they were describing.

   The drawing buffer is now the viewport's own size and the camera is solved
   for the viewport's own aspect (landing/framing.js), so the picture reaches
   every edge at every size and nothing is stretched or cropped to make it.

   `cover` rather than `contain` for the one frame a drag-resize can leave the
   buffer a size behind the element: cover holds the edges and loses a few
   pixels off one axis, contain would flash a black bar. */
#view {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

/* ══ THE WORDMARK, AS TYPE ═════════════════════════════════════════════════
   Present to the accessibility tree at every size; drawn only where the
   rendered lockup has stopped being readable. Clipped rather than
   `display: none`, which would take it out of both.

   In portrait it stands at the top of the frame, above the masthead, at the
   size a mark is set at rather than the size a headline is — the picture
   underneath is still doing the shouting. Tracked wide and set in the same
   mono as everything else on this page, because the one thing it must not do
   is look like a different product from the metal behind it. */
.mark {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
body.is-portrait .mark {
  position: fixed;
  left: clamp(14px, 4vw, 28px);
  top: clamp(18px, 3.4vh, 40px);
  z-index: 10;
  width: auto; height: auto;
  margin: 0;
  clip-path: none;
  overflow: visible;
  font: 600 1.05rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.30em;
  text-transform: uppercase;
  color: var(--ink);
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.9);
}
/* The masthead moves down to sit under it. */
body.is-portrait #lede { top: calc(clamp(18px, 3.4vh, 40px) + 1.9rem); }

/* ══ THE MASTHEAD ══════════════════════════════════════════════════════════
   Top left, in the quarter the key light leaves empty, at the size a caption
   is set at rather than the size a headline is: this is the sentence that
   says what the page is, and the picture underneath it is still the argument.

   The second clause is a shade dimmer than the first, because "what this is"
   and "what to do" are two different jobs and reading them as one run of grey
   type is how a visitor ends up reading neither. */
/* ── AND IT HAS A RULE BESIDE IT AGAIN ─────────────────────────────────────
   One hairline of cyan down the left of the masthead, and it is the same mark
   the console's own signal rail is: the thing on this page that says "a piece
   of type has been placed here deliberately" as against "a piece of type has
   ended up here".

   Both corners of this frame now carry one. That pairing is the whole reason
   to have it — the console in the lower right is an instrument with a lit
   marker beside its primary line, and without a matching mark the masthead in
   the upper left was the only text block on the page with no relationship to
   anything else on it.

   Drawn as a ::before rather than as a border-left so it can be INSET from the
   type's own cap line and stop short of its descender: a border runs the full
   height of the box including the leading, which puts cyan above the first
   letter and below the last. It is 2px, the rail's own width, and it carries
   the rail's own spill so the two read as the same mark at two sizes.

   `padding-left` moves the text off it. The block's own left inset is
   unchanged, so the masthead still begins where it always did and the rule
   stands in the margin the text used to start at. */
#lede {
  position: fixed;
  left: clamp(18px, 3.2vw, 46px);
  top: clamp(18px, 3.4vh, 40px);
  z-index: 10;
  padding-left: 17px;
  /* 42ch, not 30: `ch` is the width of a "0" and every character here also
     carries 0.13em of tracking, so a 30-character line needs about 36ch of
     box. At 30 the first clause broke after "WORLD" and left ENGINE. alone on
     a line — a two-line masthead where a one-line one was intended. */
  max-width: 42ch;
  margin: 0;
  font: 400 0.66rem/1.75 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink);
  /* Never over a lit slab: the room's top-left corner is the darkest area of
     both compositions, and this is the one piece of type that is allowed to
     be read at a glance without a panel behind it. */
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.85);
}
#lede::before {
  content: "";
  position: absolute;
  left: 0;
  /* 0.30em down from the box's top and 0.36em up from its bottom: the mono's
     line-height here is 1.75, so that is very nearly the cap line of the first
     clause and the baseline of the last. */
  top: 0.30em; bottom: 0.36em;
  width: 2px;
  border-radius: 1px;
  background: var(--accent);
  box-shadow: 0 0 9px -1px rgba(79, 216, 234, 0.5);
}

/* Two clauses, two line boxes, so each wraps on its own terms rather than
   flowing into one paragraph that breaks wherever the width runs out. */
#lede span { display: block; }
/* Balanced, so the second clause splits evenly instead of leaving "YOU SEE
   IT." alone on a third line — which is what a 42ch measure does to a
   45-character sentence. */
#lede .lede-do { color: var(--dim-lit); text-wrap: balance; }

/* CLASS, NOT `nav`. This rule was written when the page had exactly one <nav>
   in it, and the shelf's stepper is a second one — a named landmark, which is
   what a group of five world buttons should be. A bare `nav` selector reached
   inside the console and pinned the stepper to the bottom-left corner of the
   window, on top of this very link. An element selector that is really naming
   one instance is a rule waiting for its second instance. */
.standing-door {
  position: fixed;
  left: clamp(18px, 3.2vw, 46px);
  bottom: clamp(20px, 4vh, 42px);
  z-index: 10;
}

/* The one door. RESTRAINED means the cyan is the colour of the type and its
   rule and nothing else — no glow, no fill, no box. Its position, padding,
   size, tracking and case are exactly what they were; only the hue moved. */
.enter {
  display: inline-block;
  padding: 10px 18px 10px 0;
  border-bottom: 1px solid var(--edge);
  color: var(--accent);
  text-decoration: none;
  font: 500 0.68rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  /* The only type on the page with no plate behind it and no shadow under it,
     standing on a render whose luminance under it changes with the viewport.
     The same shadow the masthead carries, for the same reason. */
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.85);
  transition: border-color 0.3s ease, color 0.3s ease;
}
.enter:hover { border-bottom-color: var(--edge-hot); color: #b4f2fc; }
.enter:focus-visible { outline: 2px solid var(--edge-hot); outline-offset: 4px; }

/* The progress line, and the failure line: same element, because a visitor
   watching one is the visitor who needs to be told about the other. Sized for
   a sentence rather than for two words, since it now carries "this browser
   would not give a 3D context" as well as "eroding five worlds". */
#boot {
  position: absolute;
  margin: 0;
  max-width: min(34ch, 78vw);
  text-align: center;
  font: 400 0.68rem/1.7 ui-monospace, Menlo, monospace;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--dim-lit);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.85);
}

/* A visitor who has asked for less motion gets a shelf that does not drift —
   that is enforced in landing/main.js, where the turntable is, because no
   media query reaches a WebGL loop. This block is the DOM's half of the same
   answer: nothing in the interface travels either. */
@media (prefers-reduced-motion: reduce) {
  .enter { transition: none; }
}

/* ══ THE CONSOLE ═══════════════════════════════════════════════════════════
   The card used to be the whole of this corner and it used to place itself.
   It is now the first row of a block that also holds the shelf's controls and
   the door that opens the world it is describing, and the block places itself
   instead — because those three things have to move together. A readout in one
   corner and the control for it in another is the failure this page had:
   everything the shelf could do was reachable only by guessing.

   Bottom right, which is where the card already was, and still the quarter of
   both compositions the key light leaves darkest and quietest: the lockup is
   at the left and the shelf sits above the plinth, so the corner under it is
   the only place a block of type can stand without covering something lit.

   The rows stretch, so the door is exactly as wide as the readout above it —
   a primary action narrower than the block it belongs to reads as an
   afterthought pinned to the corner of one. */
/* ── THE READOUT AND THE STEPPER ARE ONE INSTRUMENT ────────────────────────
   `gap: 0`, and that single line is most of the rebuild. The card and the
   stepper are two elements because they are built by two modules with two
   jobs, and they were being DRAWN as two: a pane behind the figures, sixteen
   pixels of room, and then a row of unhoused controls standing on the render
   underneath it. The stepper acts on the world the card is naming, so a
   visitor reading the figures and a visitor pressing › are looking at one
   object, and the picture should say so.

   They now share one surface, one outline and one internal division. The DOOR
   does not join them, deliberately: it leaves this page, and a control that
   leaves is a different kind of thing from a control that changes what the
   instrument is showing. It keeps the gap and its own plate. */
#ui {
  position: fixed;
  /* ── NEARER THE CORNER, AND THAT IS A COMPOSITION DECISION ────────────────
     The console used to float a full gutter in from both edges, which reads as
     a floating card. An instrument that is PART OF the frame sits against the
     frame: the insets come in from 3.2vw/4vh to 2.2vw/2.6vh, so at 1600×1000
     it stands 35px from the right and 26px from the bottom instead of 46 and
     40. The picture gains the strip the panel was hovering over, and the panel
     gains the corner it now belongs to.

     Still an inset rather than flush. tools/frame-check.mjs asserts every
     control's box is inside the viewport at five device sizes, and a panel
     whose shadow reaches the edge on a phone with a rounded display is a panel
     with a corner cut off it. The floor of 16px is what holds that. */
  right: clamp(16px, 2.2vw, 34px);
  bottom: clamp(16px, 2.6vh, 30px);
  z-index: 10;
  display: grid;
  gap: 0;
  justify-items: stretch;
  /* Wider than the 17.5rem it was. The panel has real padding now rather than
     a pane bled out past its type, and the readout's widest row — a 14-glyph
     seed against its label — was the thing setting the old width from the
     inside.

     ── AND WIDER AGAIN, BECAUSE THE PICTURE BEHIND IT GREW ──
     19.5rem against a 1600px frame is 12% of the width, which was right beside
     a lockup at 70% of the frame's height and is thin beside one at 80%. 21.5
     keeps the readout at the same relative weight as the type it stands under.
     The 48vw cap is what stops that from being a change on a phone, where the
     block is full width and this rule does not apply. */
  width: min(21.5rem, 48vw);
  /* ── VISIBILITY, NOT JUST OPACITY ────────────────────────────────────────
     At opacity 0 a control is invisible and still focusable: eight buttons and
     a link sat in the tab order for the six seconds the worlds take to erode,
     so the first Tab on this page went to a stepper nobody could see, for a
     shelf that did not exist yet. `visibility: hidden` takes them out of the
     tab order and out of the accessibility tree, and it is transitioned with a
     delay so the fade still plays: opacity animates over 0.5s, visibility
     flips at the end of it going out and at the start coming in. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.5s ease, visibility 0s linear 0.5s;
}
body.has-shelf #ui {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.5s ease, visibility 0s linear 0s;
}
body.shelf-down #ui { opacity: 0; visibility: hidden; }

/* ── THE SURFACE, SHARED ───────────────────────────────────────────────────
   Declared once for both halves of the instrument so they cannot drift into
   two slightly different darks. Each half then draws only the borders that are
   ITS OWN edges, below — the card takes the top, the stepper takes the bottom,
   and the seam between them is one hairline rather than two abutting outlines.

   `backdrop-filter` is progressive: a browser without it gets the same panel
   at the same opacity, slightly less separated from the room behind it. The
   `--panel` alpha is high enough that nothing depends on the blur, which is
   the whole reason it was raised from the old 0.66. */
.card,
.shelf-nav {
  background: var(--panel);
  border-left: 1px solid var(--panel-edge);
  border-right: 1px solid var(--panel-edge);
  /* More blur and less saturation than before. The room behind the panel is
     three stops darker than it was, so what the blur is averaging is nearly
     black — and at 18px the terrain slab's cyan waterline was still legible
     through it as a coloured smudge. 26px turns that into a wash, which is
     what "controlled transparency" means: the panel takes the room's LIGHT and
     not its picture. */
  backdrop-filter: blur(26px) saturate(0.74);
  -webkit-backdrop-filter: blur(26px) saturate(0.74);
}

/* ── THE HOUSING ───────────────────────────────────────────────────────────
   The card and the stepper are two elements drawing ONE object, and the object
   now has an outside: rounded at all four of its outer corners and square at
   the seam between them, which is how a two-part housing is actually made.
   Stated as one rule over both halves so the two radii cannot drift. */
.card { border-radius: var(--housing) var(--housing) 0 0; }
.shelf-nav { border-radius: 0 0 var(--housing) var(--housing); }

.card {
  position: relative;
  /* GENEROUS, and measured against the type rather than chosen: 18px is a
     little over one line of the 0.68rem figures, which is the smallest gutter
     at which a bounded panel stops looking like a box drawn tightly round some
     text. The old block had none at all — its pane was bled out past the type
     on all four sides precisely so that nothing had to move. */
  padding: 20px 20px 11px;
  border-top: 1px solid var(--panel-edge);
  color: var(--ink);
  /* ── THREE SHADOWS, WHICH IS WHAT "PHYSICALLY INTEGRATED" COSTS ───────────
     One shadow lifts an object off a surface. Three of them put it IN a room,
     and each of the three is a different physical fact:

       the contact   0 4px 12px at −8px spread. Tight and directly under the
                     housing: the darkness a body makes where it is nearest the
                     thing behind it. Without it the panel floats however deep
                     the other two go.
       the cast      0 30px 64px at −28px. The long soft one this file already
                     had, deepened with the room — the wall behind the console
                     is three stops darker than it was, so a shadow sized
                     against the old wall had nothing left to darken.
       the spill     0 0 40px −6px in --glow. The one COLOURED shadow on this
                     page, and it is not a glow round a HUD: it is the cyan the
                     room is full of, gathering on the surface immediately
                     around a piece of dark glass standing in it. At 13% alpha
                     over a near-black render it is felt at the edge and
                     invisible at arm's length, which is the test.

     Drawn on the card alone and reaching past the bottom of it, so the shadow
     belongs to the whole panel rather than to each half of it. */
  box-shadow:
    0 4px 12px -8px rgba(0, 0, 0, 0.9),
    0 30px 64px -28px rgba(0, 0, 0, 0.98),
    0 0 40px -6px var(--glow);
}

/* ── THE LIT EDGE ──────────────────────────────────────────────────────────
   The single change that stops this reading as a rectangle pasted over a
   photograph. A dark object standing in a room lit from above catches a line
   of that light on its top edge, and the eye reads that line as THICKNESS —
   it is the same cue the lockup's chamfers carry and the same one the terrain
   slabs' perimeter lines carry, applied to the third object in the frame.

   A gradient rather than a border, and the difference is the whole point: a
   border-top in cyan is a coloured outline, which is decoration; a gradient
   that is brightest a third of the way along and gone at both ends is a
   REFLECTION, which is light. It fades out before the rounded corners so the
   line never has to turn one, which is where a lit edge would give itself away
   as a stroke.

   `inset 0 1px 0 --inner` under it is the same highlight on the panel's own
   face rather than on its lip. Together the two read as a chamfered edge one
   pixel wide. */
   It is drawn as the first layer of the card's own paper (.card::before,
   below) rather than as an element of its own: that pseudo-element is already
   inset to the panel's box, already rounded to the housing, and already paints
   between the panel's background and its type, which is exactly where a
   reflection on the surface belongs. A fourth DOM node for one pixel would be
   a node tools/frame-check.mjs has to walk past. */

/* ══ THE CARD ══════════════════════════════════════════════════════════════
   The readout for the world at the front of the shelf, and the console's
   first row. A scientific instrument panel, not a HUD: near-black glass, a
   graphite edge, contour paper, and exactly one lit mark on it. It sits in
   the room's own palette because it is a label on a lit object rather than a
   panel floating over a photograph, and a bright UI card would be the first
   thing to break the illusion that the worlds are IN the room. */

/* ── THE PAPER ─────────────────────────────────────────────────────────────
   The panel's surface is a solid on `.card` above; this is the FIGURE in it.
   It was a pane bled out past the type on all four sides, which was the whole
   trick that let a panel appear without the readout moving — and that trick is
   what is being undone here, because a bled pane cannot have a border, and a
   1px technical border is what an instrument is.

   So it is inset to the panel's own box now and carries only the texture and
   the inner highlight. `z-index: -1` against the card's own stacking context,
   so it never covers the rows standing over it. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--housing) var(--housing) 0 0;
  /* One pixel of light along the top edge, which is what stops a flat dark
     rectangle from reading as a hole cut in the picture. */
  box-shadow: inset 0 1px 0 var(--inner);
  background:
    /* THE LIT EDGE — see the note above. Inset by the housing's own radius at
       both ends so the line never has to turn a corner, and sized to exactly
       one pixel of height. */
    linear-gradient(90deg,
      transparent 0%, var(--lip) 24%, var(--lip) 50%, transparent 86%)
      var(--housing) 0 / calc(100% - 2 * var(--housing)) 1px no-repeat,
    /* ── DELICATE TOPOGRAPHIC CONTOURS ──
       Two repeating conic sweeps at incommensurate angles and periods. Where
       they cross they neither line up nor repeat on any short cycle, so what
       falls out is an irregular set of curved hairlines — contour-shaped
       rather than plaid, which two linear repeats would have been.

       ── SPARSE, AND WHY THE FIRST SET WAS NOT ──
       At 2.5°/3.1° periods the two sweeps crossed often enough to read as
       cross-hatch — a mesh, not a contour map. Widened to 6.4° and 8.3° and
       dropped to 2%, so each sweep contributes a handful of curved hairlines
       across the panel and they rarely meet. Roughly one 8-bit value step
       over near-black: legible as texture when you look for it, invisible as
       pattern when you are reading the figures over it. A cartographic panel
       has paper; this is the paper, not the map. */
    repeating-conic-gradient(from 18deg at 128% 138%,
      rgba(150, 205, 220, 0.022) 0deg 0.30deg, transparent 0.30deg 6.4deg),
    repeating-conic-gradient(from -47deg at -34% 118%,
      rgba(150, 205, 220, 0.018) 0deg 0.26deg, transparent 0.26deg 8.3deg),
    linear-gradient(155deg, rgba(24, 40, 46, 0.34) 0%, rgba(6, 10, 12, 0.0) 52%);
}

/* ── THE SIGNAL RAIL ──
   The one lit mark on the instrument, and it points at the world's NAME —
   the panel's primary line and the only thing on it that is naming rather
   than reporting. A rail running the panel's full height would read as a box
   edge; one that stops reads as a marker.

   It used to hang off the card's own left border at `left: -1px`. The card has
   padding now, so it stands inside the panel where the name begins, and the
   name is set off it rather than butting against the outline. */
.card::after {
  content: "";
  position: absolute;
  left: 20px; top: 20px;
  width: 2px; height: 1.22rem;
  background: var(--accent);
  border-radius: 1px;
  /* The rail is the one LIT mark on the instrument, and until now it was the
     one lit mark that cast no light. Two pixels of cyan on near-black glass in
     a room where every other cyan edge blooms is a mark that has been drawn
     rather than illuminated. */
  box-shadow: 0 0 9px -1px rgba(79, 216, 234, 0.55);
}
/* ── THE HEADER ──
   The name and the hour, set off the rail and separated from the figures by
   the first of the panel's divisions. The world's name is the one piece of
   type in the block that NAMES rather than reports, so it takes the accent and
   the largest size on the panel; the hour is a caption under it and reads as
   one. */
/* ── THE TOP OF THE LADDER ─────────────────────────────────────────────────
   The world's name is the brightest thing on this panel and it should look
   like it. Two changes and both are about hierarchy rather than size: the
   colour goes from --accent to the hot accent the door's focus ring uses, so
   the name is a full step above every other cyan on the block; and it carries
   the same faint spill the rail beside it does, so it reads as lit rather than
   as printed. A world's name in a launcher is a title card, not a label. */
.card-name {
  margin: 0 0 0 15px;
  font: 600 1.22rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: #8ceaf8;
  text-shadow: 0 0 16px rgba(79, 216, 234, 0.32);
}
/* And the metadata under it goes the other way. It is the most subdued line on
   the instrument by design — an hour and a weather word are context for the
   figures, never a second heading — so it loses a little size and takes the
   panel's own dim rather than the lit one the room's type uses. */
.card-mood {
  margin: 9px 0 16px 15px;
  font: 400 0.58rem/1 ui-monospace, Menlo, monospace;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--dim);
}
/* ── THE FIGURES ──
   `gap: 0` and a rule per row rather than five pixels of air per row. Ruled
   rows are how an instrument prints a table: the hairline is what a reader's
   eye tracks from a label to a figure four centimetres away, and the space it
   replaces was doing that job by asking the reader to hold a horizontal
   position across a gap with nothing in it. */
.card-rows {
  margin: 0;
  display: grid;
  gap: 0;
  border-top: 1px solid var(--rule);
}
/* ── PRIMARY AND SECONDARY, SAID THREE WAYS ──
   A label and its figure are not the same kind of information, and on the
   first pass they were set at nearly the same size, weight and colour with
   2.5rem between them — a row of eight near-identical greys that had to be
   read left to right to be understood. The split is now stated in size (0.56
   against 0.68), in weight (400 against 500) and in colour (--dim against
   --ink), so a reader takes the column of VALUES in at a glance and consults
   the labels only when they need to. */
.card-row {
  display: flex; justify-content: space-between; align-items: baseline;
  /* 4rem, not 2.5. The gap IS the hierarchy: too little and the pair reads as
     one run of text, too much and they stop reading as a pair at all. */
  gap: 4rem;
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
  font: 500 0.68rem/1.6 ui-monospace, Menlo, monospace;
  letter-spacing: 0.10em;
}
/* The last figure keeps no rule of its own. The stepper below already draws
   the division between the readout and the controls, and with both the panel
   carried two hairlines four pixels apart — which is not a heavier division,
   it is a mistake that looks like one. */
.card-row:last-child { border-bottom: 0; }
.card-row dt {
  color: var(--dim); text-transform: uppercase;
  letter-spacing: 0.18em; font-size: 0.56rem; font-weight: 400;
  /* Never wraps and never shrinks: a two-line label would break the baseline
     grid the value column is read down. */
  white-space: nowrap;
}
/* Figures line up in a column, so they are read as a set rather than as five
   unrelated strings.

   ── AND THEY ARE THE CRISPEST TYPE ON THE PANEL ──
   A measurement is the thing this instrument is FOR, so the value column takes
   the full-contrast neutral, the tightest tracking on the block and the
   heaviest weight the mono has at this size. Against a label set at 0.56rem /
   400 / 0.18em, that is three separate statements of which of the two is the
   information — which is what lets a reader take the column in without
   consulting the labels at all. */
.card-row dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  color: #f2fbfe;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.card-note {
  margin: 11px 0 8px;
  max-width: 26ch;
  font: 400 0.58rem/1.5 ui-monospace, Menlo, monospace;
  letter-spacing: 0.08em;
  color: var(--dim);
}
/* A provisional figure is dimmed rather than hidden: the visitor should see
   the number arrive and settle, not watch a blank become a fact. */
.card.is-provisional .card-rows { opacity: 0.62; }
/* THE SAME VALUE the stepper's busy state uses. They are one panel now, and
   two halves of one surface fading to two different greys is the giveaway
   that they were never really one. */
.card.is-busy { opacity: 0.42; }

/* ── THE READOUT CHANGED ───────────────────────────────────────────────────
   The world at the front is a different world, and the figures under its name
   are different figures. Without this the panel's content is REPLACED between
   two frames while its box does not move — the numbers are simply other
   numbers now, and a reader who was mid-row has no cue that anything happened
   beyond the fact that they are now reading something else.

   One 260 ms settle on the figures, started by the same call that writes them
   (landing/card.js). It is a value change and not a movement: nothing travels,
   nothing reflows, and the panel is the same size throughout — which is what
   lets it run at all on a block anchored to a corner. */
.card-rows { transition: opacity 0.26s ease; }
/* `transition: none` on the way IN is the half that makes this a readout
   refreshing rather than a panel fading: the dip is instant and only the
   recovery is timed. See landing/card.js, flash(). */
.card.is-turning .card-rows { opacity: 0.22; transition: none; }

/* ══ THE SHELF'S CONTROLS ══════════════════════════════════════════════════
   The turntable's whole vocabulary, in elements that can be pressed, focused
   and read out loud. See landing/controls.js for what each one is for. */
.shelf-controls { display: grid; gap: 12px; }
.shelf-controls.is-busy { opacity: 0.42; pointer-events: none; }

/* The instrument's lower half: the same surface as the readout, its own two
   edges, and ONE hairline where the two meet. `border-top` in --rule rather
   than --panel-edge, because this is a division inside the panel and not the
   panel's boundary — drawn at the outline's weight it would read as two
   stacked boxes touching. */
.shelf-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 11px 14px;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--panel-edge);
}

/* ── HIT AREA AND MARK ARE DIFFERENT SIZES, DELIBERATELY ───────────────────
   A chevron drawn at the size a chevron should be drawn at is a 12px target,
   and 12px is not a target on a phone. So the button is 40px square and the
   mark inside it is small — the whole square is pressable and only the middle
   of it is inked, which is what every well-built stepper does and what the
   first pass of this one did not. */
.shelf-arrow {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  padding: 0;
  /* A FILL, faint, where there was none. Standing on the room these buttons
     were an outline round a hole; standing on the instrument they are keys cut
     into its face, and a key is a slightly different surface from the panel it
     is set in. Two per cent of the same graphite the outline is drawn in, so
     the difference is felt at the edge and invisible in the middle. */
  background: rgba(126, 148, 158, 0.045);
  border: 1px solid var(--control-edge);
  border-radius: 2px;
  color: var(--dim-lit);
  cursor: pointer;
  transition: color 0.18s ease, border-color 0.18s ease, background-color 0.18s ease;
}
.shelf-chev { font: 400 1.0rem/1 ui-monospace, Menlo, monospace; }
.shelf-arrow:hover { color: var(--accent); border-color: var(--edge); background: rgba(79, 216, 234, 0.09); }
.shelf-arrow:active { background: rgba(79, 216, 234, 0.16); }

/* ── THE DOTS ──
   Same rule: a 6px dot in a 30x40 button. The list is a list because there
   are five of them and that count is information — it is the only place on
   screen, other than one line of the readout, that says how many worlds there
   are, and it is the only one you can press. */
.shelf-dots {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  margin: 0; padding: 0;
  list-style: none;
}
.shelf-dot {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 30px; height: 38px;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
}
/* The mark itself is a pseudo-element so the button can be large and the dot
   can be 6px, without a wrapper element per dot. */
.shelf-dot::before {
  content: "";
  display: block;
  width: 6px; height: 6px;
  margin: 0 auto;
  border-radius: 50%;
  /* 0.72, not 0.45. An unselected dot is still a control and still has to be
     findable — at 0.45 the four of them composited to 2.1:1 and read as
     specks. The selected one is separated by hue AND by half again the
     diameter, so brightening the set does not blunt the distinction. */
  background: var(--dim-lit);
  opacity: 0.72;
  transition: opacity 0.22s ease, background-color 0.22s ease, transform 0.22s ease;
}
/* A hover that only brightens is a hover you have to be looking at to see. The
   dot grows a little as well, so a pointer crossing the row is felt at the
   edge of vision — and it stops well short of the selected dot's own 1.5, so
   "under the pointer" and "in front" stay two different states. */
.shelf-dot:hover::before { opacity: 1; transform: scale(1.2); }
/* The selected world is stated in COLOUR and in SIZE, not in colour alone:
   a reader who cannot separate cyan from grey still has a dot that is half
   again as large as its neighbours.

   ── AND IN A HALO, WHICH IS THE THIRD STATEMENT ──
   A 9px cyan dot on a panel is a small mark to carry "this is the world every
   other control on this instrument is pointing at". The ring is a box-shadow
   rather than a border so the dot's own diameter is unchanged and the two
   scales stay comparable — it reads as the mark being LIT rather than as a
   sixth, larger dot. */
.shelf-dot.is-front::before {
  background: var(--accent);
  opacity: 1;
  transform: scale(1.5);
  box-shadow: 0 0 0 3px rgba(79, 216, 234, 0.13);
}

/* ══ THE DOOR ══════════════════════════════════════════════════════════════
   The page's primary action, and until now it did not exist: the only way to
   open the world you were looking at was to click a picture of it.

   It is a PLATE and the standing door below is a rule of type, and that
   difference is the hierarchy. The cyan is still rationed — a hairline, the
   type, and a wash at 8% that is nearer to black than to the accent — so this
   reads as the one lit control in the room rather than as a button pasted
   over a photograph. */
.door {
  display: flex;
  align-items: baseline;
  gap: 0.55em;
  /* 14px, matching the stepper's own row height above it, so the door reads as
     the same instrument's last key rather than as a button that happened to
     land under one. */
  /* ── AND IT IS THE ONE ILLUMINATED CONTROL IN THE ROOM ───────────────────
     Everything above is still the argument and the level was set against a
     brighter frame. With the room three stops down, an 8% cyan wash inside a
     34%-alpha hairline reads as another dark rectangle — the primary action
     was the quietest element on its own panel.

     The rationing does not change; what it buys does. Still no fill anybody
     would call a colour, still a hairline, still no glow round the type. What
     is added is the same lit top edge the housing above it carries and the
     same cyan spill into the room, so the door reads as a piece of the
     instrument that is switched ON rather than as a button drawn on one. The
     gradient in the background is a hand's-breadth of light falling from the
     top of a slightly proud surface, which is what the housing's own lip is;
     it is 5% of cyan at its brightest.

     The type goes to the hot accent with it, because a control's label and its
     housing have to agree about whether it is live. */
  padding: 15px 15px;
  border: 1px solid rgba(79, 216, 234, 0.46);
  border-radius: var(--housing);
  background:
    linear-gradient(180deg, rgba(79, 216, 234, 0.13) 0%, rgba(79, 216, 234, 0.05) 62%),
    rgba(9, 20, 24, 0.72);
  color: #9df0fc;
  text-decoration: none;
  font: 500 0.70rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  /* The same blur the panel above it carries. Without it the one control on
     this page that is NOT on the instrument sits directly on the render, and
     an 8%-cyan wash over a lit plinth is a different colour in every viewport.
     With it the door has a surface, and the surface is the panel's. */
  backdrop-filter: blur(26px) saturate(0.74);
  -webkit-backdrop-filter: blur(26px) saturate(0.74);
  box-shadow:
    0 3px 10px -7px rgba(0, 0, 0, 0.9),
    0 20px 44px -28px rgba(0, 0, 0, 0.98),
    0 0 34px -8px rgba(60, 190, 214, 0.22),
    inset 0 1px 0 rgba(140, 234, 248, 0.30);
  transition: background-color 0.22s ease, border-color 0.22s ease, color 0.22s ease, transform 0.12s ease;
}
.door-verb { color: var(--dim); flex: 0 0 auto; }
/* The world's name, at the weight and colour the card gives it, so the door
   and the readout are visibly naming the same thing. */
.door-name { flex: 1 1 auto; font-weight: 600; letter-spacing: 0.20em; }
.door-arrow { flex: 0 0 auto; transition: transform 0.22s ease; }
.door:hover {
  background:
    linear-gradient(180deg, rgba(79, 216, 234, 0.24) 0%, rgba(79, 216, 234, 0.11) 62%),
    rgba(11, 26, 31, 0.76);
  border-color: var(--edge-hot);
  color: #d3f7ff;
}
.door:hover .door-arrow { transform: translateX(3px); }
.door:active { transform: translateY(1px); background: rgba(79, 216, 234, 0.22); }

/* ── ONE FOCUS RING, EVERYWHERE ────────────────────────────────────────────
   Stated once for every control on the page rather than per component, so a
   control added later cannot quietly ship without one. Offset outward and in
   the hot accent, which is the only value on this page with enough contrast
   against both the near-black room and the glass panels. */
.shelf-arrow:focus-visible,
.shelf-dot:focus-visible,
.door:focus-visible,
.enter:focus-visible {
  outline: 2px solid var(--edge-hot);
  outline-offset: 3px;
  border-radius: 2px;
}

/* The shelf is draggable, so the canvas says so — and a world you are over is
   a world you can OPEN, which is a different verb and gets a different cursor.
   `body.on-world` is set by the same raycast that decides what a click hits,
   so the pointer cannot promise something the click will not do. */
/* The canvas is a gesture surface, not a scroll surface. `body` is
   overflow:hidden and the document is exactly one viewport tall, so there is
   no scroll to preserve — but without this, Android's pull-to-refresh and the
   iOS rubber-band can still claim a slightly diagonal drag, and a gesture the
   browser takes back arrives as `pointercancel`, which endDrag treats as
   cancelled: the shelf force-settles mid-swipe and never flings.
   css/editor.css already declares this for #viewport; the landing was the one
   WebGL surface on this origin without it. */
#view { touch-action: none; }

body.has-shelf #view { cursor: grab; }
body.has-shelf.on-world #view { cursor: pointer; }
body.has-shelf #view:active { cursor: grabbing; }

/* ══ LEAVING ═══════════════════════════════════════════════════════════════
   The picture goes and the DOOR STAYS. It used to be that everything went —
   canvas, masthead, console — and for the 420 ms before the navigation the
   page was a black rectangle that said nothing at all, which is indistinguishable
   from a page that has crashed. What a visitor needs in that gap is the one
   sentence naming what they just asked for, so the door holds its place and
   changes its own label to say it (landing/controls.js, setOpening). */
body.leaving #view { opacity: 0; transition: opacity 0.42s ease; }
body.leaving .standing-door, body.leaving #lede,
body.leaving .card, body.leaving .shelf-nav { opacity: 0; transition: opacity 0.3s ease; }
.door.is-opening {
  background: rgba(79, 216, 234, 0.20);
  border-color: var(--edge-hot);
  color: #d3f7ff;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  #ui, .shelf-dot::before, .door, .door-arrow, .shelf-arrow, .card-rows,
  body.leaving #view, body.leaving .standing-door, body.leaving #lede,
  body.leaving .card, body.leaving .shelf-nav { transition: none; }
  /* The readout's change cue goes with the rest. It is a value change and not
     a movement, so it is the mildest thing on this list — but a visitor who
     has asked for less motion has asked for less of exactly this, and the
     figures still change, which is the information. */
  .card.is-turning .card-rows { opacity: 1; }
}

/* ══ PORTRAIT ══════════════════════════════════════════════════════════════
   `is-portrait` is set by landing/main.js from the SAME ramp that moves the
   camera, not by a width in this file. A media query and a camera solver are
   two opinions about one question, and they were always going to disagree:
   720px is portrait on a phone and landscape on a phone lying down, and the
   picture is the thing that knows which.

   The layout stacks, because the composition stacks. The worlds are centred
   in the upper half; the console reads across the lower quarter under them,
   full width, so the readout, the stepper and the door are one block instead
   of a column squeezed into a corner that no longer exists. */
body.is-portrait #lede {
  right: clamp(14px, 4vw, 28px);
  /* The viewport's own width, less the insets: at 360 px that is 304 px, and
     the first clause sets in 231. The second is longer than one line at any
     phone width and is balanced rather than left to orphan "IT." under it. */
  max-width: none;
}
body.is-portrait #ui {
  left: clamp(14px, 4vw, 28px);
  right: clamp(14px, 4vw, 28px);
  /* Clear of the standing door, which stays at the bottom edge. */
  bottom: calc(clamp(16px, 2.6vh, 30px) + 42px);
  /* ── FULL WIDTH IS NOT THE SAME AS EVERY WIDTH ────────────────────────
     A phone is 360 px and the console wants all of it. A tablet in portrait
     is 834, and stretched across all of THAT the readout put its labels
     against the left edge and its figures against the right — a 700 px gap
     between "RELIEF" and "1470 m", which is a row you have to read twice —
     and the stepper's two arrows ended up further apart than a thumb can
     span. The block caps, and centres on the axis the picture is centred on. */
  width: auto;
  max-width: 30rem;
  margin-inline: auto;
  /* NOT 14px. The readout and the stepper are one panel at every size — see
     `#ui` — and this rule predates that; left as it was it would cut the
     instrument in half on exactly the screens where the block is widest and
     the seam most obvious. */
  gap: 0;
}
body.is-portrait .card-row { gap: 1.5rem; }
/* The panel is edge to edge on a phone, so its padding is the screen's own
   margin as well as its own gutter, and 18px against a 14px window inset would
   set the figures further from the left edge than the masthead above them. */
body.is-portrait .card { padding: 16px 16px 3px; }
body.is-portrait .card::after { left: 16px; top: 16px; }
body.is-portrait .shelf-nav { padding: 8px 9px; }
/* Aligned to the console's left edge rather than the window's, so the two
   doors stack rather than staggering. */
body.is-portrait .standing-door {
  left: clamp(14px, 4vw, 28px);
  right: clamp(14px, 4vw, 28px);
  max-width: 30rem;
  margin-inline: auto;
  bottom: clamp(16px, 2.6vh, 30px);
}
/* One finger, one target: the two arrows go to the ends of a full-width row
   and the dots take the middle, so nothing on a phone is a 30px button in the
   centre of a 40px gap. */
body.is-portrait .shelf-arrow { width: 44px; height: 44px; }
body.is-portrait .shelf-dot { width: 40px; height: 44px; }
body.is-portrait .door { padding: 14px; }

/* ══ SHORT AND WIDE — A PHONE LYING DOWN ═══════════════════════════════════
   740×360 is the shape this layout had no answer for. The composition there is
   the landscape one, so the shelf is in the room's right third — and the
   console, a 270 px column pinned to the bottom right, stood on top of it. Two
   thirds of the height spent on chrome, over the only thing the page is for.

   So at this shape the console STOPS BEING A COLUMN. It lays out along the
   bottom edge as one instrument bar: the readout's figures set as labelled
   columns rather than as rows with the label at one end, the stepper and the
   door beside them. It comes to about 70 px, which is a fifth of the height
   instead of three quarters, and the worlds are visible above it again.

   The masthead loses its second clause and the standing door moves to the top
   right — the two corners a bottom bar leaves free. */
@media (max-height: 540px) and (orientation: landscape) {
  .standing-door {
    top: clamp(14px, 3vh, 24px);
    bottom: auto;
    left: auto;
    right: clamp(18px, 3.2vw, 46px);
  }
  #lede .lede-do { display: none; }
  .card-note { display: none; }

  /* Sized to its content and hugging the bottom-right corner, rather than
     stretched edge to edge: a bar across all 740 px would lie over the whole
     lower half of the lockup to hold 380 px of type. */
  #ui {
    left: auto;
    right: clamp(18px, 3.2vw, 46px);
    bottom: clamp(14px, 3vh, 22px);
    width: auto;
    max-width: min(34rem, 76vw);
    /* ── AND THE INSTRUMENT COMES APART HERE, ON PURPOSE ──────────────────
       Everywhere else the readout and the stepper are one panel with one
       division, because they are stacked and adjacent. At this shape they are
       not: the stepper moves to sit BESIDE the door, on the far side of the
       readout, so a shared surface would have to be a shape with a corner cut
       out of it. Two bounded panels is the honest drawing of the layout that
       is actually there, and each one closes its own box below. */
    gap: 10px;
  }
  /* The housing closes on all four corners here, because the two halves come
     apart (see above) and each one is a whole object. Shallower than the
     stacked panel's 10px: this bar is 60-odd pixels tall, and a radius sized
     for a 300px column eats a tenth of its height. */
  .card {
    padding: 12px 14px 3px;
    border-bottom: 1px solid var(--panel-edge);
    border-radius: 7px;
  }
  .card::before {
    border-radius: 7px;
    /* The lit edge is inset by the housing, and the housing is 7 here. Both
       lists name ALL FOUR layers: a single value applies to every layer, which
       would give the two contour sweeps and the corner wash a height of one
       pixel and delete the panel's paper. */
    background-position: 7px 0, 0 0, 0 0, 0 0;
    background-size: calc(100% - 14px) 1px, auto, auto, auto;
  }
  .card::after { left: 14px; top: 12px; height: 0.95rem; }
  .card-name { font-size: 0.86rem; margin-left: 12px; }
  .card-mood { margin: 4px 0 8px 12px; }
  /* Label ABOVE figure, in a row of columns: the pairing survives, and four
     stacked rows become one 24 px band. The rules turn with the layout — a
     table set as columns is divided by VERTICAL hairlines, and leaving the
     horizontal ones on would underline four unrelated figures. */
  .card-rows {
    grid-auto-flow: column;
    justify-content: start;
    gap: 0;
    border-top: 0;
  }
  .card-row {
    display: block;
    padding: 2px 0.9rem 4px;
    border-bottom: 0;
  }
  .card-row:first-child { padding-left: 0; }
  .card-row + .card-row { border-left: 1px solid var(--rule); }
  .card-row dt { display: block; }
  /* A figure that wraps is a figure read twice — "4.1 × / 4.1 km" over two
     lines is not a measurement, it is two fragments. The column widens
     instead. */
  .card-row dd { white-space: nowrap; }

  /* The stepper and the door go side by side, so the console is two shallow
     bands rather than three. */
  .shelf-controls { grid-auto-flow: column; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 10px; }
  .shelf-nav {
    gap: 2px;
    padding: 5px 6px;
    border-top: 1px solid var(--panel-edge);
    border-radius: 7px;
  }
  .shelf-arrow { width: 32px; height: 32px; }
  .shelf-dot { width: 22px; height: 32px; }
  .door { padding: 11px 14px; }
}
