/* The breach. Violet shell, same family as the green one at /apply.
 *
 * ── FIVE VERSIONS, AND THE ONE MISTAKE UNDER ALL OF THEM ─────────────
 * v1 rounded glass card            "too ai generated"
 * v2 chrome piled on: brackets, kanji, coordinates, a status line
 * v3 tasteful minimal list         "doesn't look like the netrunners screen"
 * v4 right structure, violet FILL, grotesk    "this is a powerpoint"
 * v5 dark body, neon jagged frame  "now it looks like a postcard"
 *
 * ★ Every one of those was A PANEL. Framed, centred, floating, however
 * it was decorated. And a framed rectangle in the middle of a page is a
 * card -- that is what "postcard" was naming, and it was true of all
 * five. The frame itself was the problem, not the paint on it.
 *
 * A terminal has no frame. It is text on black, filling what it is
 * given, with a cursor at the bottom. So this page is now built the way
 * /apply is built -- the SAME SHELL, in violet instead of green, so the
 * two read as one system with two modes: violet is the deck you choose
 * on, green is the shell you are asked to prove yourself in.
 *
 * ── DARKER, SCARIER, MORE CONTRAST ───────────────────────────────────
 * The previous version sat in a narrow band of mid-violet -- everything
 * roughly equally lit, which is exactly why it read as decorative. This
 * one is nearly all pure black, with the light concentrated:
 *
 *   - the page vignettes to #000 at the corners, not to a dark violet
 *   - unlit text sits at ~25% and is genuinely dim
 *   - only the LIVE row blows out to white
 *   - a slow luminance flicker on the whole shell, so it reads as
 *     something powered and unstable rather than printed
 *
 * ⚠️ The flicker is opacity on one already-composited layer, which is a
 * GPU blend and free. It is NOT a filter and NOT a blend mode: a
 * full-viewport filter measured at ~40% of the frame budget on the hero
 * of this same site.
 */

/* ★ DOTGOTHIC16, for the four names. Her description was "the flat
   computer 0 1 type font" -- a BITMAP face, not a smooth vector one.
   The faces tried before it were angular but still modern and DRAWN;
   this is a dot-matrix gothic built out of square units, so it
   reads as something a machine PRINTED rather than something a designer
   set. Its lineage is the old Japanese bitmap gothics used for horror
   and serious games, which is where the menace comes from -- nothing
   had to be added to make it frightening, the face already is.
   10.3 KB, latin subset, one weight. */
@font-face {
  font-family: 'DotGothic16';
  src: url('../assets/dotgothic16.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

/* ★ RAJDHANI. Not an approximation of the look -- it is the typeface
   Cyberpunk 2077 sets its entire in-game UI in (Regular through Bold,
   with Orbitron as secondary). Her note was that the chrome labels
   looked "too ai-y": they were mono with wide tracking, which is the
   generic solution every generated interface reaches for. Rajdhani is
   squared-off, condensed and technical by design, and it carries the
   genre without costing a word of decoration.
   ⚠️ ONE weight, latin subset, 15 KB. Bold, because that is where its
   angular character actually shows -- at 600 it reads merely narrow. */
@font-face {
  font-family: 'Rajdhani';
  src: url('../assets/rajdhani-700.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}


:root {
  --void: #05030A;
  /* ★ DEEPER, AND ALONE. #c46bff was pink-violet and read as candy.
     This is shifted toward indigo -- more blue, less rose -- which is
     the difference between neon-as-decoration and neon-as-voltage.
     ⚠️ And the real move is not the violet getting deeper, it is
     EVERYTHING ELSE GETTING DRAINED. Horror palettes are desaturated;
     dread comes from one living colour alone in a dead field, not from
     a brighter one. So the dim tone below is nearly grey, the room is
     nearly monochrome, and the violet is the only saturated thing left
     on the page. */
  --neon:     #a23cff;
  --neon-hot: #e6b4ff;
  --dim:      #4a3a5c;   /* was violet. Now near-grey, deliberately. */
  --cyan:     #6fd0ff;
  --blood:    #ff2d55;   /* the Daedra in the room. Danger only. */
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* THREE faces, one job each, and the split is what keeps it from
     looking like fonts that happened to be loaded:
       --cut   the four things you choose. A bitmap face.
       --disp  labels that NAME parts of the system (Rajdhani).
       --mono  everything the machine emits: hex, buffer, prompt. */
  --disp: 'Rajdhani', 'Arial Narrow', ui-sans-serif, system-ui, sans-serif;
  --cut:  'DotGothic16', 'Courier New', ui-monospace, monospace;

  /* ── THE PANEL AND THE LIGHT ON IT ─────────────────────────────────
     ★ Measured, because "it reads flat" turned out to be measurable.
     On the previous build, at 1440x900:

        the header label   mean  70, peaking at pure 255
        the four names     mean  12, peaking at 105
        the room           mean   4, peaking at  32
        the void           mean   3.5

     58% of the page was literally pure black and 0.15% of it was above
     half brightness. Two faults in that, both structural:

     1. INVERTED HIERARCHY. The brightest thing on the page was a
        LABEL, by 2.4x. The four things you came to click never got
        past 41% grey. Draining everything else is right -- that is
        where the violet gets its force -- but the content has to be
        what survives the drain, not the chrome.
     2. NO SURFACE ANYWHERE. Every element was emissive text on void:
        no gradient across anything, no edge catching light, nothing
        with a top and a bottom. An image with no gradation is the
        definition of flat, and glow cannot fix it -- a glow IS a soft
        gradient, which is the opposite of contrast.

     So the names now sit in a milled channel on a panel that has a lit
     top edge and a dark bottom, and the page has one hard specular
     travelling across it. Material, not more light. */
  --name:      #9c86b8;   /* at rest: luma ~142, was 105 */
  --name-off:  #5b4d70;   /* ...and what it drops to while another is live */
  --panel-hi:  rgba(226,206,255,0.085);  /* the lit top edge of the slab */
  --panel-lo:  rgba(0,0,0,0.42);         /* the shadow it sits in */
  --lip:       rgba(214,186,255,0.22);   /* light caught in the groove */
}

* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; background: #000; }

body {
  opacity: 0;
  transition: opacity 460ms ease;
  color: var(--neon);
  font-family: var(--mono);
  font-size: clamp(0.78rem, 1.02vw, 0.92rem);
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
}
body.is-in { opacity: 1; }
@media (prefers-reduced-motion: reduce) { body { opacity: 1; transition: none; } }

/* ── The backdrop: the room, and the people still in it ───────────────
   ★ It was crushed to a black rectangle -- safe, and wasting the one
   genuinely frightening thing available. That plate has a Daedra in
   black armour with red eyes, a figure wired with lightning, and a
   table of people who are all still sitting there while you pick an
   option. A dim nothing behind the letters throws all of that away.
   So the room comes back and gets POSTERISED instead of hidden:
   contrast hard enough that it breaks into slabs of light and dark,
   mapped onto the violet. Recognisable as the room, but rendered --
   which is the difference between a photo behind text and a system
   drawing what it can see.
   ⚠️ Blur stays low now (5px). It was doing the hiding; the contrast
   and the scrim do it instead, and blurring a posterised image just
   turns the slabs back into mush.
   One filter chain on a static image, so it rasterises ONCE. Filters
   are only expensive here where the thing underneath moves -- which is
   why the hero cannot have one and this page can. */
.nav__room { position: fixed; inset: 0; z-index: 0; overflow: hidden; background: #000; }
.nav__room img {
  position: absolute; inset: -4%; width: 108%; height: 108%;
  object-fit: cover;
  /* ★ DUOTONE, not a dim photograph. The room was blurred and drained
     until it was effectively a black rectangle -- safe, and doing
     nothing. This crushes it instead: grayscale first, then contrast
     hard so it posterises into slabs of light and dark, then sepia +
     hue-rotate + saturate, which is the standard CSS duotone chain and
     maps the whole image onto the violet.
     The result is GRAPHIC rather than photographic -- it reads as
     something the system is rendering, not a picture someone took.
     ⚠️ One filter chain on a static image, so it rasterises once and
     the compositor holds a single texture. Filters are only expensive
     on this site where the thing underneath moves, which is why the
     hero cannot have one and this page can. */
  /* ⚠️ Blur almost off. At 5px the posterised slabs smeared back into
     soft blobs and the backdrop read as dust on the lens rather than
     as a room -- worse than the black rectangle it replaced, because
     speckle looks like a defect. The contrast has to act on actual
     edges to break the image into slabs, so the blur has to get out of
     its way and the SCRIM does the hiding instead. */
  filter: blur(1.5px) grayscale(1) contrast(2.3) brightness(0.86)
          sepia(1) hue-rotate(234deg) saturate(4.6);
  /* ★ THE ROOM MOVES WITH THE POINTER, by a few pixels. This is the
     cheapest honest depth cue there is and it is what a flat page is
     missing: move your head in front of a pane and the scene behind it
     shifts, because it is further away than the glass. Nothing else on
     this page is parallel to anything, so nothing else conveys that
     there are two planes at all.
     ⚠️ transform only, on a layer that is already composited and whose
     filter has already rasterised -- so this moves a finished texture
     and costs nothing. A `filter` that recomputed per frame here would
     be the hero's 40%-of-budget mistake all over again.
     ⚠️ NO transition: js/nav.js eases this itself, and a CSS transition
     fighting per-frame JS writes is a visible stutter on direction
     changes (learned on krystallisjr's red crossfade).
     The element is inset -4% and 108% wide, which exists exactly so
     there is somewhere to travel to without exposing an edge. */
  transform: translate3d(var(--rx, 0px), var(--ry, 0px), 0);
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .nav__room img { transform: none; }
}
/* ★ The scrim is DIRECTIONAL now, not a centred vignette. It has to do
   two jobs that pull against each other: keep the text column
   unambiguously readable, and let the room survive everywhere else.
   A centred vignette darkens exactly the middle -- which is where the
   room is most interesting and where the text is not. So this is
   heaviest on the left, over the column, and releases toward the right
   where the table and the figures are.
   Still ends at pure #000 at the edges: that is where the dark comes
   from, and a screen in a black room is scarier than a grey one. */
.nav__room::after {
  content: ''; position: absolute; inset: 0;
  background:
    /* Heaviest over the column, releasing to the right where the table
       and the figures are. The text never has to fight the picture. */
    /* ★ OPENED UP ON THE RIGHT, 0.74 -> 0.42. The room was measuring a
       peak luma of 32 -- effectively gone, and it is the only element
       on this page that HAS gradation in it. A page made of emissive
       text on void has no mid-tones at all, which is most of what
       "flat" was. The glass also needs something behind it worth
       bending; a pane in front of a black rectangle is a black
       rectangle.
       The left stays at 0.97 so the column keeps its true black and
       the drain still does its work -- the gradient does both jobs
       precisely because it is directional.

       ⚠️⚠️ AND THE SCRIM IS NOT THE REAL LEVER HERE. Four variants were
       rendered and measured, 0.42 down to 0.26 at the right edge, and
       the whole range moves the right third of the page by 7.47 -> 8.15
       mean luma -- under 10%, for a number that looks like a big change.
       The reason is the filter chain above: `contrast(2.3)` pivots
       around mid-grey, and this plate lives at 0.05-0.20, so everything
       below ~0.28 is crushed to pure black before the scrim ever sees
       it. Only the windows and the lanterns survive the crush; the
       seated figures sit at ~0.07 and are mathematically gone.

       That crush IS the posterised look she asked for and liked ("i
       love that"), so it stays. But it means the figures cannot be
       brought up by touching the scrim at all -- it would take a
       pre-contrast brightness of ~4x, which would blow the windows to
       white and lose the whole effect. If "bring the people up" ever
       comes back as an ask, the answer is a different PLATE (a lighter
       grade baked in tools/glass_plate.py), not a different scrim. */
    linear-gradient(97deg,
      rgba(0,0,0,0.966) 0%,
      rgba(0,0,0,0.93) 42%,
      rgba(0,0,0,0.68) 66%,
      rgba(0,0,0,0.30) 100%),
    radial-gradient(ellipse 110% 100% at 38% 50%,
      rgba(0,0,0,0) 0%, rgba(0,0,0,0.30) 72%, #000 100%);
}
/* Scanlines, as on the terminal, and harder. */
.nav__room::before {
  content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background:
    repeating-linear-gradient(to bottom,
      rgba(162,60,255,0.11) 0px, rgba(162,60,255,0.11) 1px,
      transparent 1px, transparent 3px),
    /* a coarser band on top of the fine one, so the surface is not an
       even screen door -- unevenness is what stops it reading as a
       texture swatch */
    repeating-linear-gradient(to bottom,
      rgba(0,0,0,0.26) 0px, rgba(0,0,0,0.26) 2px,
      transparent 2px, transparent 9px);
}

/* ── The glass ────────────────────────────────────────────────────────
   ★ Her ask: "glassier". ⚠️ NOT a glass CARD -- that was v1, "too ai
   generated", and a panel of frosted glass holding the content would be
   the postcard again in a new material. The glass here is the PANE YOU
   ARE LOOKING THROUGH, which the bezel has already implied all along: a
   bezel is the edge of a display, and a display has glass in it. Until
   now that glass was a hole. Nothing said there was a sheet between you
   and the room.

   What separates expensive glass from the cheap kind is not blur --
   blur is the part every glassmorphism tutorial does. It is: a
   SPECULAR (one hard highlight where light catches the surface), a
   FRESNEL EDGE (the boundary brightening at a grazing angle),
   REFRACTION (the backdrop physically displaced, not merely blurred),
   and CHROMATIC ABERRATION (red and blue bending by different amounts).
   Apple's Liquid Glass is exactly those four on top of the blur
   everyone already had.

   Which of the four lives where, and why:
     specular   here, as a travelling highlight. Transform-only.
     fresnel    here, static, and it carries the chromatic split --
                cyan down one edge and violet down the other, which is
                what a lens does at opposite edges.
     refraction tools/glass_plate.py, baked into the plate. It cannot
                be done live: `backdrop-filter: url(#svg)` is Chromium
                only, and Safari PARSES it, passes @supports, then
                renders nothing. A feature query that lies is worse
                than no feature.
     parallax   js/nav.js, on the room itself -- see .nav__room img. */
.glass { position: fixed; inset: 0; z-index: 5; pointer-events: none; }

/* ★ THE SPECULAR. A room light crossing a pane as you move past it.
   This is the single element that proves the glass exists: a surface
   you cannot see is not a surface, and one hard moving highlight says
   "there is something between you and that" more plainly than any
   amount of frost.
   ⚠️ Deliberately a long cycle. At 22s it is never "an animation
   playing" -- most visitors see one pass. A 3s sweep would read as a
   loading shimmer, which is the opposite of menacing.
   ⚠️ Plain alpha over a dark page, NOT mix-blend-mode: screen. The
   band moves, so a blend mode would re-evaluate its region every
   frame; white at 5% on near-black is visually the same thing for
   free. Transform-only, one composited layer. */
.glass__sweep {
  position: absolute;
  top: -30%; bottom: -30%;
  left: 0; width: 46vw;
  background: linear-gradient(90deg,
    rgba(228,210,255,0)     0%,
    rgba(228,210,255,0.016) 36%,
    rgba(236,222,255,0.052) 50%,   /* the bright line itself */
    rgba(228,210,255,0.014) 62%,
    rgba(228,210,255,0)    100%);
  transform: rotate(-13deg) translate3d(-120vw, 0, 0);
  animation: sweep 22s linear infinite;
  will-change: transform;
  /* ⚠️ The individual `translate` property, NOT `transform` -- the
     animation owns transform, and the two compose instead of fighting.
     js/nav.js writes this so the reflection counter-moves against the
     room: a reflection is on the near face of the glass and the room is
     behind it, so they cannot travel together without collapsing into
     one plane again. */
  translate: var(--gx, 0px) 0;
}
@keyframes sweep {
  0%   { transform: rotate(-13deg) translate3d(-70vw, 0, 0); }
  100% { transform: rotate(-13deg) translate3d(190vw, 0, 0); }
}

/* ★ THE FRESNEL EDGE, with the chromatic split on it. A pane returns
   almost all the light at a grazing angle, which is why the edges of
   glass are always the brightest part of it -- and why the bezel has
   until now looked stuck ON the page rather than seated in it. The
   cyan/violet split across opposite edges is the colour fringing a
   lens produces; it is in CSS rather than in the plate because the
   room's own filter chain opens with grayscale(1) and would have
   thrown a baked fringe away. */
.glass__fresnel {
  position: absolute; inset: 0;
  background:
    linear-gradient(90deg,  rgba(111,208,255,0.085) 0%, rgba(111,208,255,0) 8%),
    linear-gradient(270deg, rgba(198,110,255,0.10)  0%, rgba(198,110,255,0) 9%),
    linear-gradient(180deg, rgba(214,186,255,0.055) 0%, rgba(214,186,255,0) 5%),
    linear-gradient(0deg,   rgba(214,186,255,0.045) 0%, rgba(214,186,255,0) 5%);
}

@media (prefers-reduced-motion: reduce) {
  .glass__sweep { animation: none; opacity: 0.5; transform: rotate(-13deg) translate3d(30vw,0,0); }
}

/* ── The bezel ────────────────────────────────────────────────────────
   ★ Her ask: "put some sort of neon purple border around all this."
   ⚠️ NOT a border round the content -- that is the card that got called
   a postcard. This is a border round the VIEWPORT: the edge of the
   display you are looking through, fixed to the window and sitting over
   everything. A bezel is not a frame around a thing, it is the thing
   you are looking through, and that distinction is the whole reason
   this one is allowed to exist.
   Corners are cut out of it so the four sides read as separate lit
   runs rather than one closed rectangle -- a closed rectangle is a box
   again. */
.bezel {
  position: fixed; inset: 0; z-index: 6; pointer-events: none;
  /* ★ THE BEZEL LOSES SYNC WITH THE CONTENT. It was sitting perfectly
     still while the shell jolted, which is the giveaway that the jolt
     is an effect applied to some text rather than a display failing.
     The frame of a screen that drops its horizontal drops it too.
     Same 11s clock, so they fault together.
     ⚠️ TRANSFORM-ONLY keyframes here, not the shell's `powered`. That
     one animates opacity as well, and a running animation beats a
     normal declaration -- so the power-draw dim below silently did
     nothing while the jolt was driving the same property. Measured:
     is-drawing applied, computed opacity still 1. The two effects have
     to own different properties. */
  animation: fault 11s ease-in-out infinite;
}

/* The jolt, as transform alone, leaving opacity free for the draw.
   ★ TONED DOWN, her call 2026-10-01, and toned down properly rather
   than nudged: amplitude 3px -> 1px (-67%), and the cycle stretched
   7.3s -> 11s so it also happens less often. At the old size it had
   started to read as an effect being played at you; at this size you
   are not sure you saw it, which is the only setting at which a fault
   is frightening rather than decorative. */
@keyframes fault {
  0%, 46.5%, 49%, 53%, 100% { transform: translate3d(0,0,0); }
  47%   { transform: translate3d(-1px,0,0); }
  48%   { transform: translate3d(1px,0,0); }
  51%   { transform: translate3d(1px,0,0); }
  52%   { transform: translate3d(-0.5px,0,0); }
}

/* ★ POWER DRAW. Light a daemon and the bezel DIMS -- the system has a
   finite budget and your choice is spending it. The frame recovers the
   moment you let go.
   This is the whole argument for the page in one behaviour: nothing is
   drawn on screen to say the machine is strained, the machine simply
   behaves as though it is. */
body.is-drawing .bezel { opacity: 0.42; }
.bezel { transition: opacity 180ms ease; }

/* ★★ THE FRAME TAKES THE COLOUR OF WHATEVER DOOR IS LIVE. Her call, and it
   is the right extension of the hover-previews-the-destination idea: if the
   row is telling you where it goes, the DISPLAY agreeing with it makes the
   whole screen commit to the answer rather than one line of it. The edge of
   the thing you are looking through is the largest surface on the page, so
   it is also the cheapest way to make the preview unmissable.
   ⚠️ The frame changes SLOWER than the row (380ms against the row's
   instant). The row is what you touched; the frame is the system noticing.
   Matching them would read as one CSS rule firing -- staggering them reads
   as cause and effect, which is the entire difference.
   ⚠️ Both faces move together, because the comment below already says they
   are one object -- but the far face takes a DEEPER version of the hue
   rather than the same one, so the depth stays carried by value. Same
   reasoning as the far bezel on /founder/. */
.bezel span {
  position: absolute;
  background: var(--edge, var(--neon));
  box-shadow: 0 0 4px rgb(var(--edge-soft, 230 180 255) / 0.95),
              0 0 14px var(--edge, var(--neon)),
              0 0 40px rgb(var(--edge-rgb, 162 60 255) / 0.55);
  transition: opacity 90ms linear,
              background-color 380ms ease, box-shadow 380ms ease;
}

/* ★ ONE SOURCE OF TRUTH FOR WHICH ROOM IS LIVE. 01 and 02 open the green
   terminal; 03 and 04 open a red room. Everything on the page that reacts
   -- the frame, the far face, the matrix -- reads these three tokens, so
   the mapping is written once and a fourth daemon would not need it
   repeating anywhere.
   ⚠️ :has() for the pointer AND .is-on for the keyboard cursor. This nav
   can be driven entirely by digits and arrows, so a preview that existed
   only for the mouse would be one most keyboard users never see -- the
   same gap `:has(:focus-visible)` left on /our-vision/. */
body:has(.daemon--term:hover),
body:has(.daemon--term.is-on) {
  --dest: #35ff8d; --dest-rgb: 53 255 141; --dest-soft: 190 255 215;
  --dest-far: #0f9d58; --dest-far-rgb: 15 157 88;
}
body:has(.daemon--house:hover),
body:has(.daemon--house.is-on) {
  --dest: #ff1a08; --dest-rgb: 255 26 8; --dest-soft: 255 170 150;
  --dest-far: #a8160a; --dest-far-rgb: 168 22 10;
}
body:has(.daemon--term:hover)  .bezel,
body:has(.daemon--term.is-on)  .bezel,
body:has(.daemon--house:hover) .bezel,
body:has(.daemon--house.is-on) .bezel {
  --edge: var(--dest); --edge-rgb: var(--dest-rgb); --edge-soft: var(--dest-soft);
}
body:has(.daemon--term:hover)  .bezel--far,
body:has(.daemon--term.is-on)  .bezel--far,
body:has(.daemon--house:hover) .bezel--far,
body:has(.daemon--house.is-on) .bezel--far {
  --edge: var(--dest-far); --edge-rgb: var(--dest-far-rgb); --edge-soft: var(--dest-soft);
}
/* One run cutting out, briefly. Set from js/nav.js at irregular
   intervals -- a frame that is reliably whole is a frame nobody
   worries about. */
.bezel span.is-cut { opacity: 0.06; }
/* top and bottom runs, inset from the corners */
.bezel span:nth-child(1) { top: 14px; left: 74px;  right: 32px; height: 1px; }
.bezel span:nth-child(2) { bottom: 14px; left: 32px; right: 108px; height: 1px; }
/* left and right runs */
.bezel span:nth-child(3) { left: 14px;  top: 74px; bottom: 32px; width: 1px; }
.bezel span:nth-child(4) { right: 14px; top: 32px; bottom: 92px; width: 1px; }
/* the diagonal that closes the cut top-left corner */
.bezel span:nth-child(5) {
  top: 30px; left: 6px; width: 86px; height: 1px;
  transform: rotate(45deg); transform-origin: left center;
}

/* ── The far surface ──────────────────────────────────────────────────
   ★ Her ask, 2026-10-01: "a second border of like light blue around the
   stuff." ⚠️ Around the VIEWPORT, not around the stuff -- a rectangle
   drawn around the content is a card in any colour, and that is the
   failure five versions of this page shared. This is the second face of
   the same pane.
   Three things keep it from reading as a decorative double-stroke:
     - it is CYAN, because the second reflection off glass is the colder
       one, and the fresnel edge already splits cyan from violet for the
       same physical reason;
     - it is DIMMER and THINNER, because it is further away;
     - it breaks in different places, so the two edges never parallel
       each other into one thick frame.
   And it does the one thing a second line can do that a first cannot:
   it DRIFTS against the near edge as you move (js/nav.js), which is
   exactly how a real pane tells you how thick it is. */
.bezel--far {
  z-index: 5;                    /* under the near edge, over the glass */
  animation: none;               /* steady. The near edge is the one that faults. */
  translate: var(--bx, 0px) var(--by, 0px);
}
/* ★ BROUGHT UP TO THE WEIGHT OF THE NEAR EDGE, her call: "thicken it so
   its like the other one". Three steps, not one, because weight is not
   only width -- at 1px and 0.66 alpha against a 1px line at FULL --neon
   with a three-layer glow, the blue was always going to read as the
   thin one however wide it got:
     line   1px -> 2px
     alpha  0.66 -> 1
     glow   matched to the violet's three-layer stack, one step wider
   ⚠️ The far face is the one that gets THICKER rather than the near one
   getting thinner. You are seeing it through more material, so a softer,
   broader edge is the physically right way round -- and it leaves the
   near edge the sharper of the two, which is what keeps them reading as
   two faces of one pane rather than as a double rule. */
.bezel--far span {
  background: var(--edge, var(--cyan));
  box-shadow: 0 0 4px rgb(var(--edge-soft, 200 240 255) / 0.95),
              0 0 16px var(--edge, var(--cyan)),
              0 0 44px rgb(var(--edge-rgb, 111 208 255) / 0.5);
}
/* Different breaks from the violet run, and no diagonal. */
.bezel--far span:nth-child(1) { top: 27px;    left: 36px;  right: 96px;  height: 2px; }
.bezel--far span:nth-child(2) { bottom: 27px; left: 112px; right: 36px;  height: 2px; }
.bezel--far span:nth-child(3) { left: 27px;   top: 110px;  bottom: 44px; width: 2px; }
.bezel--far span:nth-child(4) { right: 27px;  top: 44px;   bottom: 128px; width: 2px; }
/* The power draw takes both faces down together -- they are one object. */
body.is-drawing .bezel--far { opacity: 0.42; }

/* ── The shell ────────────────────────────────────────────────────────
   No frame, no card. A column of output on black, left-aligned, the way
   /apply is. */
.nav {
  position: relative; z-index: 1;
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding: clamp(28px, 7svh, 76px) clamp(18px, 6vw, 90px);
  /* ★ Unstable power AND unstable sync. The opacity dip was already
     there; the horizontal jolt is new, and it is the one that makes it
     feel broken rather than merely dim -- a picture that loses hold of
     its own horizontal for two frames is a signal fault, and signal
     faults are frightening in a way that dimming is not.
     ⚠️ transform + opacity only, both free on an already-composited
     layer. Kept to 3px and to two instants in a 7.3s cycle: any more
     and it stops reading as a fault and starts reading as an effect. */
  animation: powered 11s ease-in-out infinite;
}
/* ★ TONED DOWN with the bezel, same clock, same reasoning. The dip went
   0.06 -> 0.028 (-53%) and the shift 3px -> 1px (-67%). Both halves had
   to come down together: cutting the jolt alone would have left a page
   that visibly dims for no reason, which reads as a fade rather than as
   a fault. */
@keyframes powered {
  0%, 100% { opacity: 1;     transform: translate3d(0,0,0); }
  46.5%    { opacity: 1;     transform: translate3d(0,0,0); }
  47%      { opacity: 0.978; transform: translate3d(-1px,0,0); }
  48%      { opacity: 1;     transform: translate3d(1px,0,0); }
  49%      { opacity: 1;     transform: translate3d(0,0,0); }
  51%      { opacity: 0.972; transform: translate3d(1px,0,0); }
  52%      { opacity: 1;     transform: translate3d(-0.5px,0,0); }
  53%      { opacity: 1;     transform: translate3d(0,0,0); }
}
@media (prefers-reduced-motion: reduce) {
  .nav, .bezel { animation: none; }
  .bezel, .bezel span { transition: none; }
}

.shell { width: min(940px, 100%); }

/* ── The header line ──────────────────────────────────────────────────
   One line of output, the way a shell announces itself. */
.hdr {
  display: flex; align-items: baseline; gap: 1ch; flex-wrap: wrap;
  margin: 0 0 1.9em;
  letter-spacing: 0.06em;
}
.hdr__mark { color: rgba(162,60,255,0.72); }
/* ★ PULLED WAY DOWN, from pure white with a triple glow to this.
   Measured, it was the brightest thing on the page by 2.4x -- a label
   outshining the four things you came to click. It is the shell
   announcing itself, which is worth one dim line and nothing more.
   The Wallace principle from Blade Runner 2049 is the general version:
   Territory built the richest interface in that film with no labels and
   no iconography at all, on the reasoning that it is his own machine
   and does not have to explain itself to anyone. Street tech is the
   stuff covered in chrome. */
.hdr__t {
  font-family: var(--disp);
  font-size: 1.24em;
  color: #6f6186; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
}
/* The one warm thing on the page. The Daedra sits in that room with red
   eyes; this is the only place that colour appears, and it appears on
   the word that means "this system is live". */
.hdr__state {
  margin-left: auto;
  font-family: var(--disp);
  color: var(--blood);
  font-weight: 700;
  font-size: 1.1em; letter-spacing: 0.14em; text-transform: uppercase;
  text-shadow: 0 0 10px rgba(255,45,85,0.85);
  animation: pulse 2.6s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 0.55 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .hdr__state { animation: none; opacity: .9 } }

/* ── Buffer ───────────────────────────────────────────────────────────
   ⚠️ THE LABEL IS GONE, along with "code matrix" and "daemons // select
   one to upload". Three labels naming the parts of a system that does
   not need naming -- and by the Wallace rule above, labelling every
   component is what reads street-tier rather than owned-by-something-
   powerful. The slots still fill on hover, so the thing still operates;
   it just no longer introduces itself. */
.buffer { display: flex; align-items: center; gap: 0.7ch; margin-bottom: 1.25em; }
.buffer__slot {
  min-width: 3.2ch; text-align: center;
  color: var(--dim);
  border-bottom: 1px solid rgba(74,58,92,0.9);
}
.buffer__slot::before { content: '__'; }
/* ★ The buffer is holding the live row's OWN sequence, so it is the most
   direct readout on the page of which door is selected -- which makes it
   the one place the destination colour is least decorative. Cyan stays as
   the default, for the moment before anything is chosen. */
.buffer__slot.is-set {
  color: var(--dest, var(--cyan));
  border-bottom-color: var(--dest, var(--cyan));
  text-shadow: 0 0 5px #fff, 0 0 14px var(--dest, var(--cyan));
  transition: color 300ms ease, border-color 300ms ease, text-shadow 300ms ease;
}
.buffer__slot.is-set::before { content: none; }

/* ── Two columns of output ────────────────────────────────────────────*/
.deck__body {
  display: grid;
  grid-template-columns: minmax(120px, 0.5fr) 1.5fr;
  gap: clamp(16px, 3vw, 46px);
}

/* ── Code matrix ──────────────────────────────────────────────────────
   Raw text on black. No cells, no boxes, no background -- boxing it was
   part of what made the whole thing a panel. aria-hidden; never needed. */
.matrix { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0 0.6ch; }
.cell {
  color: rgba(74,58,92,0.95);
  text-align: center;
  letter-spacing: 0.04em;
}
/* Neon: white core inside a violet halo. One coloured shadow is a
   smudge; a blown-out centre with gas glowing around it is a tube. */
/* The ghost: something else moving in the matrix. Deliberately WEAKER
   than a real match -- if it looked the same you would read it as the
   page highlighting something, rather than as something you were not
   supposed to see. */
.cell.is-ghost {
  color: var(--neon-hot);
  text-shadow: 0 0 6px var(--neon), 0 0 18px rgba(162,60,255,0.6);
}

.cell.is-lit {
  color: #fff;
  text-shadow: 0 0 4px #fff,
               0 0 12px var(--dest, var(--neon)),
               0 0 30px rgb(var(--dest-rgb, 162 60 255) / 0.9);
  transition: text-shadow 300ms ease;
}
/* ★ THE MATCHED CELLS TAKE THE DESTINATION COLOUR TOO, her call -- and the
   matrix is the right place for it, because it is the one element on this
   page that is already PERFORMING the lookup rather than decorating it.
   The cells that light are literally the row's own sequence being found,
   so them arriving in the colour of the room that row opens is the lookup
   reporting its answer, not a second thing agreeing with the first.

   ⚠️ THE GHOST STAYS VIOLET, deliberately. It is "something else moving in
   the matrix" -- not part of the lookup, and the whole reason it is drawn
   weaker than a real match is so it does not read as the page highlighting
   something. Giving it the destination colour would make it a second
   answer, and there is only one. Violet is the machine's own colour, which
   is exactly what the ghost is.
   ⚠️ 300ms, slower than the cells themselves light. The lookup happens,
   then the colour resolves -- a readout settling rather than a stylesheet
   firing. Same stagger as the frame. */

/* ── Daemons: the navigation ──────────────────────────────────────────
   Output rows, not buttons. A `>` marks the live one, exactly the way a
   cursor marks the live line in a shell. */
.daemons { display: block; }

/* ★ EACH ROW IS NOW A SLAB, not a line of text with a rule under it.
   One vertical gradient across it -- lighter at the top edge, darker at
   the bottom -- plus a hard 1px specular on that top edge and a dark
   groove under it. That gradient is the entire difference between a
   surface and a line: a thing with a lit top and a shadowed bottom has
   a normal, and a thing with a normal has somewhere for light to come
   from. Every element on the previous build was emissive text on void,
   which is why no amount of glow made it stop reading flat.
   ⚠️ It bleeds wider than the text (negative margin, matching padding)
   and has NO left or right edge. The moment a surface closes on four
   sides it is a card again, whatever it is made of -- that is the
   postcard, and this is the whole reason a slab is allowed where a
   panel was not. */
.daemon {
  position: relative;
  display: grid;
  grid-template-columns: 2ch 3ch 1fr auto;
  align-items: baseline;
  gap: 0 1ch;
  padding: 0.68em 1.2ch 0.76em;
  margin: 0 -1.2ch;
  color: var(--dim);
  text-decoration: none;
}

/* ⚠️ THE SURFACE IS A PSEUDO-ELEMENT WITH A FADED RIGHT EDGE, and that
   is not a detail. Painted as a plain background it was a visible grey
   RECTANGLE with a hard left and right edge -- which is a card, which is
   the postcard, which is the thing this whole page exists to not be.
   A surface is allowed here only as long as it has no closed edge: it
   begins at the cursor column and runs out into the dark, so it reads
   as light falling ACROSS the row rather than as a box drawn around it.
   The mask is a plain alpha gradient, not a luminance mask on an image
   file -- that is the one that silently failed to clip on real Safari
   and cost eleven wrong fixes on krystallisjr. If this one were ever
   ignored the slab would be a full-width rectangle: worse, not broken. */
.daemon::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg,
    var(--panel-hi) 0%,
    rgba(255,255,255,0.012) 34%,
    rgba(0,0,0,0.10) 62%,
    var(--panel-lo) 100%);
  box-shadow:
    inset 0 1px 0 rgba(236,222,255,0.13),   /* the milled top edge */
    inset 0 -1px 0 rgba(0,0,0,0.85);        /* the groove under it */
  /* ⚠️ FADED AT BOTH ENDS AT REST. One hard edge is all it takes: with
     a square left edge the row was still a grey rectangle sitting next
     to the matrix, just one with a soft side. At rest nothing on this
     page may have a closed edge -- the surface has to arrive out of the
     dark and leave into it.
     On hover it gets a left edge back (below), and that is the point:
     an edge appears at the moment the light does, so the edge reads as
     the light arriving rather than as a box that was always there. */
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0%, #000 9%, #000 30%, rgba(0,0,0,0.42) 62%, transparent 92%);
          mask-image: linear-gradient(90deg,
    transparent 0%, #000 9%, #000 30%, rgba(0,0,0,0.42) 62%, transparent 92%);
  transition: background 150ms ease, box-shadow 150ms ease,
              -webkit-mask-image 150ms ease, mask-image 150ms ease;
}

.daemon__cur { color: var(--neon-hot); opacity: 0; }
.daemon__n {
  font-family: var(--cut); font-weight: 400;
  font-size: 1.18em; letter-spacing: 0.08em;
}

.daemon__name {
  display: block;
  font-family: var(--cut);
  font-weight: 400;
  /* Bigger, and tracked OUT rather than in. A bitmap face needs air
     between its letters: the square units of adjacent glyphs otherwise
     merge into one block and the word turns to mush. */
  /* ⚠️⚠️ THE FLOOR IS SET TO PRESERVE A RELATIONSHIP, NOT PICKED ALONE.
     Found by tools/audit_proportions.py, which measures how much every
     element shrinks from laptop to phone and flags the ones that do not
     shrink with the rest. Measured 35.8 -> 21.1px (x0.59) against a page median of x0.85, so
     the row name stopped dominating: 2.44x its chrome on a laptop, 1.69x
     on a phone. 1.9rem is 30.4px, which is x0.85. Checked what actually
     fits first -- the longest name clears 34px even at 360, so the old
     1.32rem floor was far more cautious than the width ever required.
     ★ The rule: on these pages the chrome is already near its clamp floor
     at BOTH sizes, so it only shrinks ~0.8. Display type set with a low
     floor shrinks 0.5-0.6 and the hierarchy collapses on a phone -- the
     big thing stops being big relative to everything round it. Floors are
     a proportion decision; they are not a safety net. */
  /* ⚠️ 1.9rem -> 1.55rem. The proportions audit wanted 1.9 to hold the
     laptop hierarchy (x0.85 like everything else on the page) and she
     looked at it and said "make the letters on the nav smaller so they
     dont look as big". Her eye wins: a ratio that is arithmetically
     right can still be too loud for a four-row list that is the whole
     screen. 1.55rem is 24.8px -- still well above the 21.1 it was before
     the audit, so the hierarchy is repaired without the shouting.
     ★ audit_proportions.py will flag this as x0.69 against a x0.85
     median. That is a DELIBERATE exception, not a regression. */
  font-size: clamp(1.55rem, 2.65vw, 2.24rem);
  line-height: 1.08;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  /* ⚠️ NO skew. Shearing a bitmap face destroys the one thing that
     makes it a bitmap face -- the square units stop being square and
     the edges turn to grey mush under antialiasing. It stands upright,
     which is also simply scarier: a slanted word is going somewhere, an
     upright one is just there. */
  /* ★ MILLED INTO THE PANEL. A dark wall above each glyph and a lit lip
     below it, which is the letterpress stack inverted for a dark
     surface: light is coming from above, so the top inner wall of a
     cut channel is in shadow and the bottom edge catches it.
     It stays LIGHTER than the panel rather than the same colour as it,
     because this is also a lit display and the names have to be legible
     from across a room -- so it reads as a channel milled into metal
     with something glowing in the bottom of it, rather than as a pure
     engraving. On hover the channel floods. */
  color: var(--name);
  text-shadow:
    0 -1px 0 rgba(0,0,0,0.92),
    0 1px 0 var(--lip),
    0 2px 5px rgba(0,0,0,0.6);
  transition: color 140ms ease, text-shadow 140ms ease;
}

.daemon__note {
  display: block;
  margin-top: 0.15em;
  font-size: 0.8em;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(74,58,92,0.95);
}

.daemon__seq { color: rgba(74,58,92,0.95); letter-spacing: 0.1em; white-space: nowrap; }

/* ★ THE CONTRAST. Unlit rows are genuinely dim -- a dark violet you can
   read and no more. The live row blows to white and takes the cursor,
   the index and the sequence with it. One row lit against four dark is
   where "contrast" and "scarier" actually come from; evenly-lit rows
   are what made the last version decorative. */
.daemon:hover, .daemon:focus-visible, .daemon.is-on { outline: none; }

/* ★★ EACH DOOR LIGHTS IN THE COLOUR OF THE ROOM BEHIND IT. The nav rests
   violet; /apply/ is a green terminal and /founder/ and /our-vision/ are
   red, and until now the handoff into one of those was a jolt -- you left
   a violet page and arrived somewhere else entirely with no warning.
   Now the row tells you where it goes before you open it. The colour stops
   being decoration and becomes INFORMATION, which is the same move as
   hovering a daemon performing the matrix lookup rather than merely
   highlighting a row.
   ⚠️ Colour is never the ONLY signal -- the slab floods, the cursor
   appears, the other three dim, and the lookup runs. That matters because
   red and green are the one pairing ~8% of men cannot separate, and this
   page uses state to say which door is live. Anyone who cannot see the hue
   still gets the state from four other channels.
   ⚠️ Violet stays the resting colour. If every row were red the page would
   lose its own identity AND the red of "the Daedra in the room" would stop
   reading as a warning, which is the only job that colour has here. */
.daemon { --lit: var(--neon); --lit-hot: var(--neon-hot); --lit-rgb: 162 60 255; }
.daemon--term  { --lit: #35ff8d; --lit-hot: #c9ffdf; --lit-rgb: 53 255 141; }
.daemon--house { --lit: #ff1a08; --lit-hot: #ffd9d2; --lit-rgb: 255 26 8; }

/* ★ THE CHANNEL FLOODS. The light enters from the left edge of the slab
   and runs out across it, so the row lights the way a channel fills
   rather than the way a button highlights. The top specular goes from a
   hint to a hard white line at the same moment -- that line is the one
   genuinely blown-out edge on the page, and a single hard specular is
   what "high contrast" means on a material. More glow would just be a
   softer gradient. */
.daemon:hover::before, .daemon:focus-visible::before, .daemon.is-on::before {
  background:
    linear-gradient(90deg,
      rgb(var(--lit-rgb) / 0.26) 0%,
      rgb(var(--lit-rgb) / 0.07) 38%,
      rgb(var(--lit-rgb) / 0.014) 68%,
      transparent 100%),
    linear-gradient(180deg,
      rgba(236,222,255,0.11) 0%,
      rgba(255,255,255,0.02) 34%,
      rgba(0,0,0,0.10) 62%,
      var(--panel-lo) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,250,255,0.46),
    inset 0 -1px 0 rgba(0,0,0,0.9),
    inset 2px 0 0 var(--lit);             /* the edge the light came in */
  /* The left edge comes back the instant it is live, so the neon bar
     has something to sit on. */
  -webkit-mask-image: linear-gradient(90deg,
    #000 0%, #000 34%, rgba(0,0,0,0.5) 66%, transparent 94%);
          mask-image: linear-gradient(90deg,
    #000 0%, #000 34%, rgba(0,0,0,0.5) 66%, transparent 94%);
}

/* ★ AND THE OTHER THREE GO DOWN. This is the same power-draw argument
   as the bezel dimming: the system has a finite budget and lighting one
   daemon spends it. It is also the only way to keep the live row a
   genuine event now that the resting state is bright enough to read --
   contrast is a ratio, so if the floor comes up, the floor has to drop
   again at the moment it matters. Nothing is printed to say any of
   this; the machine simply behaves as though it is straining. */
body.is-drawing .daemon:not(.is-on) .daemon__name { color: var(--name-off); }
body.is-drawing .daemon:not(.is-on)::before { background:
  linear-gradient(180deg, rgba(255,255,255,0.016) 0%, rgba(0,0,0,0.12) 60%, var(--panel-lo) 100%); }
body.is-drawing .daemon:not(.is-on) .daemon__note,
body.is-drawing .daemon:not(.is-on) .daemon__seq { opacity: 0.45; }
.daemon:hover .daemon__cur, .daemon:focus-visible .daemon__cur, .daemon.is-on .daemon__cur { opacity: 1; }
.daemon:hover .daemon__name, .daemon:focus-visible .daemon__name, .daemon.is-on .daemon__name {
  color: #fff;
  text-shadow:
    -2px 0 rgba(255,45,85,0.75),          /* the Daedra's red, split left  */
     2px 0 rgba(111,208,255,0.75),        /* cyan, split right             */
     0 0 5px #fff, 0 0 18px var(--lit), 0 0 46px rgb(var(--lit-rgb) / 0.95);
}
.daemon:hover .daemon__n, .daemon:focus-visible .daemon__n, .daemon.is-on .daemon__n,
.daemon:hover .daemon__seq, .daemon:focus-visible .daemon__seq, .daemon.is-on .daemon__seq {
  color: var(--lit-hot);
}
.daemon:hover .daemon__note, .daemon:focus-visible .daemon__note, .daemon.is-on .daemon__note {
  color: var(--lit);
}
.daemon:focus-visible .daemon__name { text-decoration: underline; text-underline-offset: 6px; }

/* ── Decode on arrival ────────────────────────────────────────────────
   ★ The four names resolve out of garbage when the page loads: the
   breach actually breaching, rather than a signifier of hacking bolted
   onto a page that is already finished.

   ⚠️ TWO LAYERS, and this is the whole reason it is safe. The REAL text
   is in the DOM from first parse and never altered -- it only fades up.
   The garbage lives in a separate aria-hidden layer stacked on top and
   is removed when it finishes. So a screen reader and a crawler only
   ever see the true name; nothing reads scrambled text aloud, and
   nothing indexes it. Rewriting textContent in place would have put the
   garbage into the accessibility tree. */
.daemon__wrap { position: relative; display: block; }

.daemon__name { opacity: 0; transition: opacity 180ms linear; }
.is-decoded .daemon__name,
.no-js .daemon__name { opacity: 1; }

.daemon__scram {
  position: absolute; inset: 0;
  pointer-events: none;
  color: var(--neon);
  /* Fully opaque. The real name is held at 0 underneath while this
     resolves, so there is nothing to blend with -- a translucent layer
     would only soften the garbage for no reason. */
  opacity: 1;
  /* Matches the name's metrics exactly so nothing shifts when they
     swap -- a jump at the handover would read as a bug, not an effect. */
  font-family: var(--cut);
  font-size: clamp(1.32rem, 2.65vw, 2.24rem);
  line-height: 1.08;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
}

/* ── Prompt ───────────────────────────────────────────────────────────
   The page ends on a live prompt, because that is how a shell ends. */
.prompt {
  display: flex; align-items: center; gap: 0.8ch;
  margin-top: 2.1em;
  color: var(--dim);
  letter-spacing: 0.1em;
}
.prompt__ps { color: var(--neon); }
.prompt__cur {
  display: inline-block;
  width: 0.62em; height: 1.05em;
  background: var(--neon-hot);
  box-shadow: 0 0 8px var(--neon);
  animation: blink 1.05s step-end infinite;
}
@keyframes blink { 0%,50% { opacity: 1 } 51%,100% { opacity: 0 } }
@media (prefers-reduced-motion: reduce) { .prompt__cur { animation: none } }

.prompt__back {
  margin-left: auto;
  color: var(--dim); text-decoration: none;
  letter-spacing: 0.16em; text-transform: uppercase;
  border-bottom: 1px solid rgba(74,58,92,0.8); padding-bottom: 2px;
  /* ⚠️ Carries its own hit area. Hit-tested it is 25px tall, against a
     44px floor for a finger, and it is the only way back off this page.
     An absolutely-positioned transparent ::after rather than padding,
     because padding-bottom would push the underline away from the word
     and padding-top would shift the whole prompt row -- the baseline
     this sits on is shared with the live cursor beside it. 46 rather
     than 44: the hit area is measured outward from the centre in 2px
     steps, so a control exactly on the floor is a rounding error from
     being under it. Not gated on `pointer: coarse` -- this project's
     headless browser reports `pointer: none` at every viewport, so such
     a gate cannot be verified, and the area costs a mouse nothing. */
  position: relative;
}
.prompt__back::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 46px;
  translate: 0 -50%;
}
.prompt__back:hover, .prompt__back:focus-visible {
  color: #fff; border-color: var(--neon);
  text-shadow: 0 0 5px #fff, 0 0 14px var(--neon);
  outline: none;
}

/* ── ✗ THE MACHINED FACE LOST. Michroma, built and compared side by
   side on the real page, then deleted along with its toggle.
   It is genuinely the more expensive-looking face -- wide, flat-sided,
   every terminal cut square, closer to milled than printed. And that is
   exactly why it lost: it reads TECH BILLIONAIRE and drops the DEMON.
   Set against a page whose whole fiction is a breach, an intrusion and
   four names resolving out of garbage, a clean wide face argues with
   the content.
   The deciding evidence was already in the history: Chakra Petch was
   rejected here as "too modern", and Michroma is further that way, not
   less. A thing that has not bothered to upgrade its terminal is more
   frightening than a thing with a nice font.
   Do not re-propose a wide geometric face for the four names. */

@media (max-width: 720px) {
  /* The matrix goes first: atmosphere, and the names need the width. */
  .deck__body { grid-template-columns: 1fr; }
  .matrix__wrap, .daemon__seq { display: none; }
  .daemon { grid-template-columns: 2ch 3ch 1fr; }
  .hdr__state { margin-left: 0; width: 100%; }
  /* The sweep is a window-light reflection; on a phone held upright it
     would cross the whole screen in one pass and read as a flash. */
  .glass__sweep { width: 86vw; animation-duration: 30s; }

  /* ⚠️⚠️ THE FRAME AND THE TEXT WERE OVERLAPPING, and it is a units bug
     rather than a spacing one. Both bezel faces are pinned in FIXED
     PIXELS -- the near runs at 14px from each edge, the far at 27px with
     a 2px rule -- because they are the edge of a display and a display's
     edge does not scale with its content. The shell's gutter is
     `clamp(18px, 6vw, 90px)`, which is ~86px on a laptop and 23.4px at
     390. So on a phone the content box extends to within 23px of the
     screen while the cyan rail sits at 27px, and the two cross.
     Caught on screen: the final E of "← BACK HOME" painting under the
     right-hand rail.

     ★ The frame comes IN rather than the text going in further, and that
     is the faithful fix rather than the convenient one. 27px is 1.9% of a
     1440px window and 6.9% of a 390px one -- at phone size the original
     inset is not the same frame, it is a much fatter one eating a
     seventh of the screen. Scaling it down is what keeps it reading as
     the edge of the glass; pushing the content in would have cost ~70px
     of the width the four names need, and they are the page.
     Near 14 -> 9, far 27 -> 16 (rule still 2px, so it occupies 16..18).
     ⚠️ The gutter still needs a floor: 6vw is 21.6px at 360, which would
     leave only 3.6px of air over the rail. 28px clears it by 10. The
     daemon slabs bleed 1.2ch (~9px) wider than the text, so their edge
     lands at 19px -- still clear, and the slab's mask is transparent for
     its first 9% anyway. */
  .bezel span:nth-child(1) { top: 9px;    left: 48px;  right: 21px; }
  .bezel span:nth-child(2) { bottom: 9px; left: 21px;  right: 70px; }
  .bezel span:nth-child(3) { left: 9px;   top: 48px;   bottom: 21px; }
  .bezel span:nth-child(4) { right: 9px;  top: 21px;   bottom: 60px; }
  .bezel span:nth-child(5) { top: 20px;   left: 4px;   width: 56px; }
  .bezel--far span:nth-child(1) { top: 16px;    left: 23px; right: 62px; }
  .bezel--far span:nth-child(2) { bottom: 16px; left: 72px; right: 23px; }
  .bezel--far span:nth-child(3) { left: 16px;   top: 71px;  bottom: 28px; }
  .bezel--far span:nth-child(4) { right: 16px;  top: 28px;  bottom: 83px; }
  .nav { padding: clamp(28px, 7svh, 76px) max(28px, 6vw); }
}

/* ── Short ──────────────────────────────────────────────────────────────
   A landscape phone, or a short laptop window. Measured at 844x390 the
   page was 415px tall against a 390px viewport -- 25px of scroll on a
   screen that is `min-height: 100svh` and has no business scrolling at
   all. It is not one thing: 56px of block padding, four 61px daemon rows
   and a 2.1em gap before the prompt were each sized for a tall window.
   ⚠️ The DAEMON ROWS keep their height. They are the navigation and they
   are already only 61px; the padding and the gaps are chrome and go
   first. Measured after: 390x390, yov 0. */
@media (max-height: 560px) {
  .nav { padding-block: clamp(14px, 3svh, 24px); }
  .hdr { margin-bottom: 1.1em; }
  .buffer { margin-bottom: 0.8em; }
  .prompt { margin-top: 1.1em; }
  .daemon { padding-top: 0.5em; padding-bottom: 0.56em; }
}
