/* ── DIE SCHRIFT LIEGT IM REPO ──────────────────────────────────────────────
   Vorher: IBM Plex Mono vom Google-CDN. Plex Mono ist fuer Buchstaben und
   Ziffern monospaced, hat aber KEINE Block- und Rahmenzeichen: ─ █ ░ ▌ kamen
   aus einer Rueckfallschrift und waren breiter als „0". Genau deshalb misst
   tuiResize() drei Zeichen statt einem, und deshalb stand in ui.js „█░ statt
   ▰▱: die Kaestchen fehlen in Plex Mono".
   GEMESSEN (scratchpad/font.js, liest hmtx/cmap direkt aus der TTF): Iosevka
   Fixed SS12 traegt alle 155 Zeichen, die wir malen — Latein, Ziffern, Rahmen,
   Bloecke, Pfeile, Umlaute — und ALLE mit derselben Laufweite 500/1000 em.
   100 % monospaced, ein Gitter, kein Rueckfall, kein CDN.
   ZWEI ECHTE SCHNITTE, EINE STUFE VERSETZT (06.08.: „nimm regular als fett und
   die light als default"). Im Ordner liegt kein Bold — also ist LIGHT die
   Grundschrift und REGULAR das Fett. Beide sind echte Dateien mit derselben
   Laufweite 500/1000: nichts wird synthetisch gefettet, das Gitter bleibt exakt.
   Der Schnitt entscheidet die Grenze bei 450 — `font-weight:400` (alles) faellt
   auf Light, `600` (`.bold`) auf Regular. */
@font-face{
  font-family:'Iosevka Fixed SS12';font-style:normal;font-weight:100 450;
  src:url('../fonts/IosevkaFixedSS12-Light.ttf') format('truetype');font-display:swap;
}
@font-face{
  font-family:'Iosevka Fixed SS12';font-style:normal;font-weight:451 900;
  src:url('../fonts/IosevkaFixedSS12-Regular.ttf') format('truetype');font-display:swap;
}
@font-face{
  font-family:'Iosevka Fixed SS12';font-style:italic;font-weight:100 900;
  src:url('../fonts/IosevkaFixedSS12-Oblique.ttf') format('truetype');font-display:swap;
}
/* PIXEL CODE (16.08.2026, "bau mal die pixelCode schrift ein") — an optional
   second face, same two-weight/one-italic shape as Iosevka above so the
   EINST font chip (screen-settings.js) and tui.js's `--mono` swap
   (fontFamilySet) have a matching pair to switch between. Only the three
   files this game actually uses are wired up; the other nineteen weights in
   fonts/ (Thin … Black, each with an italic) sit unused on purpose — one
   knob, not twenty. MEASURED (scratchpad/font.js): 100% monospaced for
   every glyph this game paints EXCEPT the solid block glyph and the Euro
   sign — see the long comment in tui.js next to FONT_FAMILY_STEPS. */
@font-face{
  font-family:'Pixel Code';font-style:normal;font-weight:100 450;
  src:url('../fonts/PixelCode-Light.ttf') format('truetype');font-display:swap;
}
@font-face{
  font-family:'Pixel Code';font-style:normal;font-weight:451 900;
  src:url('../fonts/PixelCode.ttf') format('truetype');font-display:swap;
}
@font-face{
  font-family:'Pixel Code';font-style:italic;font-weight:100 900;
  src:url('../fonts/PixelCode-Italic.ttf') format('truetype');font-display:swap;
}
/* DEPARTURE MONO (17.08.2026, "DepartureMono-Regular als font bitte
   einbauen") — fourth face, ONE cut (OTF/CFF): weights 100-900 map onto the
   same Regular file, `.bold` renders identical. OFL licence beside the file
   (fonts/DepartureMono-LICENSE.txt). MEASURED (scratchpad/font.js, otf path):
   100% monospaced (350/550), frames/blocks/suits all present — only 17 of
   197 painted glyphs missing (circles, check marks, braille ⣿, ▸ …), those
   borrow the Iosevka fallback; no bar PUA, bars keep the classic █/░.
   WOFF2, NOT THE OTF (20.08.2026, user: "DepartureMono-Regular muss online sein
   aber welche endung ist egal steht in der css"). `.otf` is not in deploy.py's
   ALLOWED_EXT and never was, so the face only worked online because somebody
   put the file there by hand — one FTP session away from being gone with
   nothing to say why. The woff2 is the same cut, deploys with everything else,
   and is 22 KB against 84. */
@font-face{
  font-family:'Departure Mono';font-style:normal;font-weight:100 900;
  src:url('../fonts/DepartureMono-Regular.woff2') format('woff2');font-display:swap;
}
/* BRAILLE (05.09.2026, "ich haette gerne die braille zeichen dazu"): Departure
   Mono carries 0 of the 256 braille cells, so U+2800..28FF fell through to
   Iosevka at 0.5 em against Departure's 0.6364 em - 21 % narrow, and the row
   holding one slipped. Generated on Departure's own master pixel
   (scratchpad/pixelbraille-font.py): dots 2 x 2 px, matrix 4 x 8 px, advance
   350/550. Since 07.09. it carries the six progress-bar glyphs (U+EE00..EE05)
   on the same grid; every other codepoint falls straight through to the next
   family in --mono. */
@font-face{
  font-family:'Pixel Braille';font-style:normal;font-weight:100 900;
  /* ?v= because the FILE changed (07.09.2026, the bar glyphs): the font URL is
     the one URL in this house that no cache generation touches otherwise. */
  src:url('../fonts/PixelBraille-Regular.woff2?v=2') format('woff2');font-display:swap;
}
/* THE FRAME SCALES WITH THE FONT (28.08.2026, user: "rahmen muss == font
   multiplyer sein ... sonst bricht es mit dem design"). In em, so one hairline
   at 9 px is the same hairline at 16 px - a fixed pixel edge grows heavy as
   the grid shrinks and vanishes as it grows. Every button and chip reads it. */
:root{
  --btnFrame:0.09em;
  /* CRT BLOOM, two steps (02.09.2026, user: "crt glow in der typo je heller
     desto mehr glow?" then "naja dann halt via css"). Radii, not colours -
     the glow is drawn in `currentColor` below, so one pair of numbers serves
     all twelve schemes and every class blooms in its own hue. */
  --glow-hi:0.35em;   /* the bright classes: lit, cyan, green, yellow, gold */
  --glow-op:30%;      /* how much of the glyph's own colour the halo carries */
  /* THE BLOOM SHIMMERS (05.09.2026, user: "geht darum das das glow so leicht
     schimmert"). ONE factor scales radius and halo together, so the glow only
     ever gets a little stronger or weaker - it never switches on and off, which
     is what the first cut (down to half strength) looked like. Two rates
     multiply into that factor: the slow swell and a fine tremble on top. They
     are separate properties on purpose - two animations on one element cannot
     share a property, but they can both feed one calc. */
  --glow-beat:2s;      /* 0.5 Hz - the swell */
  --glow-shimmer:0.1s; /* 10 Hz - the tremble riding on it */
  --glow-mul:1;        /* the still state: exactly the calibrated glow */
  --glow-shim:1;       /* the ends of both swings are literals in @keyframes */
  /* 0.10/0.05em was the first try and could not be seen: at a 14 px lattice
     that is 1.4 px of blur, which a dark ground swallows whole (02.09.2026,
     user: "und der css glow fuer helle schriften fehlt auch"). */
  --base03:#002b36; --base02:#073642; --base01:#586e75; --base00:#657b83;
  --base0:#839496;  --base1:#93a1a1;  --base2:#eee8d5;
  --yellow:#b58900; --orange:#cb4b16; --red:#dc322f; --magenta:#d33682;
  --violet:#6c71c4; --blue:#268bd2;   --cyan:#2aa198;  --green:#859900;
  /* Departure Mono ist die Werkschrift (20.08.2026, „der font departure den wir
     jetzt final nutzen werden") — dieselbe Vorgabe wie FONT_FAMILY_DEFAULT in
     tui.js, damit das erste Bild nicht in einer anderen Schrift steht als das
     zweite. Die anderen vier bleiben als Wahl unter html.font-* weiter unten. */
  --mono:'Departure Mono','Pixel Braille','Iosevka Fixed SS12',ui-monospace,monospace;
  /* Ground tone. Black looks right in a dark room and falls apart in daylight:
     a black screen mostly returns the reflection of whatever is behind you, so
     the picture competes with the room instead of the room with the picture.
     Lifting the floor to a working grey gives the reflection something to sit
     against. Two steps, so the panel reads as a surface over the scene rather
     than a hole in it — the sky is the darker of the two.
     ALLES OBEN IST NUR NOCH DIE VORGABE. Die laufende Ansicht setzt dieselben
     Properties von JS aus (js/theme.js `themeCss`) — auch im Dunkelmodus. Damit
     ist die Handarbeit erledigt, die hier stand: „--sky is duplicated in
     render.js, the two have to be kept in step by hand." Himmel, Loeschfarbe
     und Nebelboden holen sich ihre Zahl jetzt aus derselben Tabelle. */
  --sky:#1a1a1a; --ground:#1e1e1e; --panel:#252526;
  /* DIESELBEN FARBEN NOCHMAL, als nackte Kanaele. `rgba(var(--x),0.3)` kann
     keine Raute lesen, und jede Toenung hier traegt eine Alpha. Gesetzt werden
     sie von js/theme.js (`themeCss`) fuer JEDEN Namen der Palette; hier stehen
     nur die, die weiter unten wirklich vorkommen — als Vorgabe fuer „JS ist
     nie gelaufen", wie die Zeilen darueber. */
  --panel-rgb:37,37,38;   --cyan-rgb:42,161,152;  --base1-rgb:147,161,161;
  --blue-rgb:38,139,210;  --red-rgb:220,50,47;
}
/* EIN class-Umschalter, derselbe Griff wie `html.pixelate` (meshes.js) —
   tui.js `fontFamilySet` setzt ihn, jeder Verbraucher von var(--mono) folgt
   ohne eigene Regel (body, #tui, #labels, #console, #intro/#feedback/
   #hangar/#report). hud.js' Canvas-Font kann keine CSS-Variable lesen und
   fragt stattdessen dieselbe Wahl per JS ab (hudFontFamily). */
html.font-iosevka{--mono:'Iosevka Fixed SS12',ui-monospace,'Courier New',monospace}
/* Pixel Code needs a line of its own since Departure Mono took over :root
   (20.08.2026). Without it the chip switched the GLYPH language
   (glyphsFontApply: # bars, ASCII marks, the Fira bar PUA) while the DOM kept
   drawing Departure Mono, which has none of the PUA. */
html.font-pixelcode{--mono:'Pixel Code','Iosevka Fixed SS12',ui-monospace,monospace}
html.font-departure{--mono:'Departure Mono','Pixel Braille','Iosevka Fixed SS12',ui-monospace,monospace}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
/* KEIN KANTENGLAETTEN AUF DER SCHRIFT (20.08.2026, Nutzer: „da ist noch font
   alias mit dabei"). Eine Pixelschrift auf einem ganzen Vielfachen ihrer
   eigenen Groesse (11 px x Master-Pixel, tui.js) hat nichts zu glaetten — jeder
   Stamm liegt schon auf einer Pixelkante, der graue Saum des Rasterisierers ist
   also reine Unschaerfe ueber einer exakten Form. `-webkit-font-smoothing:none`
   schaltet Chromium/Safari auf reines Schwarzweiss, `font-smooth:never` ist die
   (nicht standardisierte) aeltere Schreibweise. Vererbt, deshalb steht es hier
   einmal und nicht an jedem Schirm.
   EHRLICH DAZU: Firefox hat keinen Schalter dafuer und glaettet weiter, und die
   Zahlen auf dem HUD-Canvas (hud.js fillText) kann kein CSS erreichen —
   Canvas-2D-Text ist immer geglaettet. */
html,body{height:100%;margin:0;overscroll-behavior:none;background:var(--ground);
  -webkit-font-smoothing:none;font-smooth:never}
/* touch-action:manipulation ist das Netz UNTER #view/#console (die schon auf
   `none` stehen, staerker als alles hier): Safaris eigener Doppel-Tap-Zoom
   ignoriert `user-scalable=no` und erreicht die vier Overlay-Schirme
   ausserhalb von #view/#console (intro/hangar/feedback/report), die selbst
   kein touch-action setzen. Ersetzt nicht den JS-Fang (input.js touchend) —
   manche Safari-Faelle respektieren nur den einen oder den anderen. */
body{overflow:hidden;user-select:none;-webkit-user-select:none;font-family:var(--mono);
  touch-action:manipulation}

/* ── WebGL: the whole screen, behind everything ─────────────────────────── */
/* Left of the console. Width is set from JS so the renderer, the projection
   and the label lattice all agree on where the middle is. */
#view{position:fixed;left:0;top:0;bottom:0;z-index:0;background:var(--sky);touch-action:none}
#view canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
/* The GL canvas deliberately overflows #view to the full window width. The part
   that ends up under the console is never looked at directly — it is what the
   panel blurs. #view keeps its strip width because that is what the framing,
   the HUD and the pointer handling are measured against (see resize()). */
/* qualified so it outranks the `#view canvas` rule above */
#view canvas#gl{z-index:1;right:auto;width:100vw}
/* PIXELATE (Nutzer 16.08.2026: „pixelate filter ... ordentlich in scharf und
   kein filtering"). meshes.js rendert bei aktivem Filter in einen kleineren
   Zeichenpuffer (`renderEffScale`, Pixelgroesse waehlbar via PIXELATE_SIZES,
   16.08.2026 „pixelsize solte einstellbar sein") und laesst den Browser
   auf CSS-Groesse hochziehen — `image-rendering` entscheidet nur noch, WIE:
   `crisp-edges` als Fallback (aeltere Firefox-Faelle), `pixelated` (der
   eigentliche Wert) danach, weil ein Browser, der beide kennt, die LETZTE
   verstandene Zeile gewinnen laesst.
   NUR DIE GL-SZENE. #hud zeichnet lesbare Zahlen (fillText — Distanz, Delta-v,
   hud.js) und bleibt deshalb IMMER glatt, dieselbe Regel wie TUI/DOM-Text
   (#labels, #console) — die tauchen hier gar nicht erst auf. #fx (Koernung)
   rendert unabhaengig von RENDER SCALE schon 1:1 mit CSS-Pixeln (siehe
   resize(), input.js: `fxCv.width=VW` ohne DPR/Scale) — ohne Hochskalieren
   aendert `image-rendering` dort nichts, also bleibt es hier unerwaehnt statt
   eine wirkungslose Zeile vorzutaeuschen. */
html.pixelate #view canvas#gl{
  image-rendering:crisp-edges;
  image-rendering:pixelated;
}
/* THE BLOCK LAYER IS A COMPOSITED ELEMENT, NOT A CPU BLIT (24.08.2026,
   measured in real headless Firefox at the user's own 3790x2072: even a BARE
   scaled drawImage of the block buffer onto the main canvas cost 13.6 ms of
   a 43 ms frame, the SVG-filter blit (23./24.08.) 27.2 ms — half the frame
   gone before a single mark was judged worth drawing, client/hud.js has the
   numbers). `#hudpix`'s CANVAS BUFFER stays at block resolution (hudPixW x
   hudPixH device px, client/hud.js hudPixSync) while its CSS box is the same
   100%/100% every `#view canvas` gets — `image-rendering:pixelated` is what
   turns that mismatch into a nearest-neighbour GPU upscale instead of a blur,
   the SAME move `#gl` makes above under html.pixelate, unconditionally here
   because the block edge is ALWAYS meant pixel-perfect (16.08.2026 decision),
   not gated behind the 3D scene's own PIXELATE toggle.
   Z-INDEX 3, between `#gl` (1) and `#hud` (4): hud.js used to composite the
   block content BEHIND its own already-drawn text with
   globalCompositeOperation="destination-over" on ONE canvas; as two elements
   that ordering is z-index, not a paint order — get it wrong and every HUD
   number ends up UNDER the blocks instead of on top of them. */
#view canvas#hudpix{
  z-index:3;
  image-rendering:crisp-edges;
  image-rendering:pixelated;
  /* IT DRAWS, IT DOES NOT CATCH (25.08.2026, measured in headless Firefox:
     document.elementFromPoint in the middle of the viewport answered `hudpix`,
     i.e. this layer sat between the player's finger and the 3D and swallowed
     every click on it - selecting a contact, the context menus, the whole
     picture. #fx above it has said `pointer-events:none` since it existed for
     exactly this reason; this layer forgot to. */
  pointer-events:none;
}
/* KEINE VIGNETTE MEHR (05.08.2026, „vignette kann raus").
   Hier stand ein Vollbild-Verlauf ueber der Szene: aussen abdunkeln, in der Mitte
   ein Hauch Cyan — gerechnet gegen einen SCHWARZEN Himmel, wo Abdunkeln fast
   nichts zeigt und der Lift die Einzige Wirkung war. Auf einem grauen Himmel
   drehte sich das um: gemessen zog der Verlauf den Rand von 148 auf 110 und
   damit genau auf die Tinte, die dort steht — die Fahrtzeile unten stand auf
   1,06:1, war also weg. Ein Rahmen, der die Schrift frisst, die er einrahmt,
   ist kein Rahmen; und ein Effekt, der je Ansicht neu ausgehandelt werden muss,
   kostet mehr als er zeigt. Ersatzlos gestrichen, samt dem <div id="vig"> in
   index.html — ein Vollbild-Composite je Bild weniger.
   Was bleibt: die Koernung (#fx, GRAIN_A in render.js). Die ist ein FILM, kein
   Rahmen, und sie liegt auf beiden Himmeln gleich.                          */

/* Strength of the grain lives in GRAIN_A in render.js, not in an opacity
   here — one knob, not two that multiply. */
/* DIE KOERNUNG LIEGT UEBER ALLEM (21.08.2026, Nutzer: „haette gerne das noise
   ueberall liegt"). Sie stand auf 3, also UNTER dem HUD (4) und unter Text und
   Panel (6/10) — ein Film, der nur die halbe Leinwand erwischt, ist ein
   Farbstich auf der 3D und sonst nichts. Jetzt ueber dem Panel (10) und unter
   den Vollbild-Schirmen (8995+): Hangar, Namenswahl und Bericht sind eigene
   Bilder, kein Blick durch die Scheibe. `pointer-events:none` traegt sie
   ohnehin, sie faengt also keinen Klick ab. */
#fx{z-index:11;pointer-events:none}
#hud{z-index:4;pointer-events:none}
/* THE LINE MASK (02.09.2026, user: "scanlines mal testen"). z 7 puts it over
   the picture (#view, z 0) and over the HUD lattice (#labels, z 6) and under
   the console (z 10) - measured: covering the console too would take its ink
   to 1.9:1 on hybrid, the value theme.js records as the stroke disappearing.
   Both custom properties come from JS (client/frame.js scanApply): the period
   is rounded to whole DEVICE pixels there, or the mask beats against the
   pixel grid and crawls. --scan-a at 0 is the off state and the element then
   paints nothing. */
/* THE WHOLE SCREEN, PAGES INCLUDED (02.09.2026, user: "page nicht drin").
   The console is background:transparent, so at z 7 the mask reaches through
   it and the pages carry the lines like everything else. The 12:50 inset
   against the console edge is gone with the instruction that asked for it.
   NO COLOUR FRINGE (same message: "der farbshift sieht auch sehr strange
   aus"). Two rows per period, black and clear. A band of red above a band of
   blue is not a shifted beam, it is two coloured stripes - a real RGB shift
   offsets the CHANNELS of what is underneath, which a background cannot do
   and the post pass already does for the picture (postfx.js uCA). */
/* ABOVE THE CONSOLE (02.09.2026, user: "flights header hat keine scanlines").
   At z 7 the mask sat UNDER #console, so it showed through wherever the page
   is transparent and vanished wherever a row has a filled ground - the page
   title bar being exactly that. A tube's lines run over the glass, not behind
   it. #fx lives inside #view and has its own stacking context, so 11 here does
   not collide with it. */
/* THE SECOND LAYER, PICTURE ONLY (12.09.2026, user: "30% fuer den 3d viewport
   sieht gut aus waere es moeglich das HUD nur mit 15% zu rendern? weil die
   schrift vertraegt das nicht so gut"). Inside #view at z 2 - above #gl (1),
   below #hudpix (3) - so it darkens the rendered picture and NOTHING else.
   #scan above keeps covering everything at the weaker alpha; the two compose
   over the picture back to the dial's own number (client/frame.js scanSplit).
   Same period and phase by construction: both read --scan-period/--scan-half.
   No colour wave here, that stays one layer. */
#scanpic{
  position:absolute;inset:0;z-index:2;pointer-events:none;
  background:repeating-linear-gradient(to bottom,
    rgba(0,0,0,var(--scan-a2,0)) 0 var(--scan-half,1px),
    rgba(255,255,255,var(--scan-lit2,0)) var(--scan-half,1px) var(--scan-period,2px));
}
#scan{
  position:fixed;inset:0;z-index:11;pointer-events:none;
  /* HORIZONTAL, AND NOTHING ELSE (02.09.2026, user: "scanlines horizontal
     bitte"). The vertical RGB grille that stood here had no clear column - red,
     green and blue filled the whole triad - so it was not a fringe on the
     lines, it was a full-screen wash with vertical structure over every pixel,
     and it is what made the picture read vertical. Third attempt at a colour
     shift, third time out; a real one offsets the channels of what is
     underneath, which the post pass can do for the picture and a background
     layer cannot. */
  /* Only the lines, and they do not move. The three colour bands that used to
     ride on top of them are drawn in the post pass now (client/postfx.js
     wave()), where the dither reaches them. */
  background:repeating-linear-gradient(to bottom,
    rgba(0,0,0,var(--scan-a,0)) 0 var(--scan-half,1px),
    rgba(255,255,255,var(--scan-lit,0)) var(--scan-half,1px) var(--scan-period,2px));
}
#glfail{
  position:absolute;inset:0;display:none;place-items:center;z-index:9;
  color:var(--orange);text-align:center;padding:20px;font-size:12px;
}

/* ── World labels: the same lattice, but no surface of their own ────────── */
/* REVERSED 02.09.2026 (user: "der header oben hat auch nix"): #labels carries
   the status header as well as the world marks, and the header is where he
   wants the glow. The rule now lives with the lattice glow further down; what
   stands here is why it once did not.
   No text-shadow. Piling glow around every glyph smeared the letterforms and
   cost more legibility than the separation from the background was worth —
   the labels sit over a mostly black scene anyway. */
#labels{
  position:fixed;inset:0;z-index:6;margin:0;
  font-family:var(--mono);white-space:pre;overflow:hidden;
  color:var(--base0);pointer-events:none;
}
#labels .k{pointer-events:auto;cursor:pointer}

/* DER VORHANG (08.08.: „dann wird das bild schwarz"). Über Bild und HUD (4),
   UNTER den Marken (6): die RUN-BEENDET-Karte wird ins Marken-Gitter gestempelt
   (drawGameOverHud) und steht AUF dem Schwarz, ihr NEUER-RUN-Chip bleibt
   tippbar. Die Deckkraft fährt deathTick (combat.js). Schwarz bleibt Schwarz,
   auch im Hellmodus — der Tod hat keine zweite Ansicht. */
#tod{position:fixed;inset:0;z-index:5;background:#000;opacity:0;
  pointer-events:none}

/* The zoom control is drawn in the character grid now (see drawRangeSlider),
   so this element is kept only as an anchor for sizing. */
#zoom{display:none}

/* Labels need something behind them. Over a lit planet or a bright star field
   bare glyphs disappear, and a glow shadow only smeared them. */
/* GRAU, HALB DURCHSICHTIG (05.08.: „hintergrund 50% opacity" · „hintergrund grau
   50% opacity im dark mode"). Vorher 0,78 in Tiefschwarz-Petrol — das war ein
   Kasten, der das Bild ausstanzte, und bei sechs Kopfzeilen stand ein Riegel
   ueber dem Schiff. Jetzt ein NEUTRALES Grau: die Platte soll eine Scheibe sein,
   durch die man das Bild sieht, kein zweiter Farbton neben dem Petrol der
   Konsole. Wer es dunkler braucht, dreht HIER, nicht je Element. */
#labels .lbl{background:rgba(80,80,80,0.5);box-decoration-break:clone}
/* UND JEDER KNOPF IM BILD AUCH (05.08.: „auf kontrast achten"). `.chip` ist ein
   10-%-Ton — auf dem Panel genug, um „ruhender Knopf" zu sagen, über einem
   beleuchteten Planeten aber kein Grund.
   ── EIN KNOPF AUF EINER PLATTE BRAUCHT SEINEN EIGENEN GRUND (11.08.2026) ────
   „auto fire button ist nicht als button erkennbar. buttons sollten immer einen
   hintergrund haben, vorallen wenn sie bereits auf einem hintergrund liegen …
   und er wird schwarz wenn aktiv."
   ZWEI FEHLER, EINE URSACHE — die Spezifitaet. `#labels .lbl` ist ID plus
   Klasse und schlaegt damit JEDE einfache Klasse: der ruhende Knopf bekam
   dieselbe graue Platte wie die Beschriftung daneben (also kein Knopf), und der
   AKTIVE bekam sie auch — samt `color:base03` aus `.auto`, also schwarze
   Schrift auf grauem Grund statt schwarz auf gruen. Genau davor warnt der
   Absatz weiter unten fuer `warnbg`/`dangerbg`; die Knopfklassen hatte niemand
   nachgezogen.
   RUHE IST JETZT CYAN GETOENT statt neutralgrau — auf der Platte ist ein
   grauer Kasten im grauen Kasten nichts, und Cyan ist die Tinte, die `.chip`
   auf dem Panel ohnehin traegt (dort mit 10 %, hier mit 32 %, weil darunter ein
   beleuchteter Planet liegen kann).
   AKTIV BEHAELT SEINE FARBE: gruen fuer `.on`/`.go`/`.auto` (DECISIONS §80),
   cyan fuer den Kopfbalken. Voll deckend, also schluckt die Platte nichts. */
#labels .chip{background:rgba(var(--cyan-rgb),0.32);
  box-shadow:inset 0 0 0 var(--btnFrame) rgba(var(--cyan-rgb),0.6)}
/* SQUARE, NOT ROUNDED (20.08.2026, Nutzer: „da ist noch font alias mit dabei /
   oder irgendwas anders" mit Bild der VIEW/FAV-Knoepfe). Die 2 px Radius waren
   die einzige KURVE im ganzen Bild, und eine Kurve rastert der Browser mit
   grauen Zwischentoenen — auf einem Raster aus ganzen Pixeln ist genau das der
   weiche Saum, den man dann sucht. Ecken sind Ecken. */
#labels .on,#labels .go,#labels .auto{background:var(--green)}
#labels .head{background:var(--cyan)}
/* UND DIE DREI GETOENTEN FLAECHEN AUCH (05.08.: „im dunkel modus brauchen die
   huds einen hintergrund damit man die gut sehen kann"). `warnbg`, `dangerbg`
   und `sel` sind fuer die KONSOLE gebaut, wo sie auf einem Panelgrund liegen —
   34 % Orange ueber einem beleuchteten Planeten ist kein Alarm mehr, sondern ein
   Schimmer. Im Bild dieselben Farben, kraeftiger gesetzt; `lbl` kann hier nicht
   aushelfen, weil eine Flaeche nur EINEN Hintergrund hat und die Platte die
   Farbe schluckt. Betrifft die LAGE-Zeile (WARNUNG/KRITISCH) und die gewaehlte
   Marke.
   DIESE DREI BLEIBEN KRAEFTIGER als die 0,5 der Platte: sie tragen ihre Bedeutung
   IN der Farbe, und eine halbdurchsichtige Warnung ueber einem orangen Planeten
   ist keine Warnung mehr. 0,7 ist der Kompromiss — die Szene schimmert durch,
   die Farbe bleibt erkennbar. */
#labels .warnbg{background:rgba(203,75,22,0.7)}
#labels .dangerbg{background:rgba(220,50,47,0.7)}
#labels .dozebg{background:rgba(var(--blue-rgb),0.7)}
/* One brighter hold instead of a blink - crewGateRecent() times it. */
#labels .dozebgHi{background:rgba(var(--blue-rgb),0.92);color:var(--base2)}
#labels .sel{background:rgba(38,139,210,0.7)}
/* Auftragsziel UND ausgewaehlt (18.08.2026): gelb schlaegt blau, sonst
   verliert ein Ziel seine Auftragsfarbe genau dann, wenn man es anfliegt. */
#labels .selye{background:rgba(181,137,0,0.7)}

/* ── The console: ONE continuous surface ────────────────────────────────────
   The background lives on this container, not on the individual rows. Putting
   it per-line left visible seams between rows and made the whole thing read
   as a stack of strips instead of a panel.
   The surface is translucent: the scene keeps running under the panel instead
   of being cut off at a hard edge. One flat tint, no gradient — a ramp across
   the panel only drew a seam where the console meets the view.

   KEIN WEICHZEICHNER MEHR, SONDERN ABDUNKELN (08.08. abends: „kein blur mehr
   als filter sondern einfach 75 % abdunkeln"). `backdrop-filter:blur(10px)`
   war ein Vollbild-Composite je Bild — der teuerste Posten der Seite, und die
   einzige Stelle, an der ein GPU-Filter über dem Zeichenraster lag. Die
   Trennung von Text und Sternenfeld macht jetzt die Deckkraft allein: 0,75
   statt 0,41. Wer weniger Abdunklung will, dreht HIER — und nirgends sonst,
   die Tönung steht einmal je Ansicht.                                        */
/* KEIN GRUND MEHR HINTER DER SEITE (Vorgabe 09.08.2026: „background hinter den
   pages raus"). Die Abdunklung stand da, um Text von Sternenfeld zu trennen —
   und um die HUD-Schrift zu verdecken, die darunter weiterlief. Das zweite
   erledigt seit heute `conBox` (tui.js): hinter der Seite steht kein Zeichen
   mehr, also braucht die Seite auch keine Platte, auf der sie liegt. Übrig
   bleibt die Kante, die sagt, wo die Seite anfängt.
   AUSGEFAHREN BLEIBT SIE UNDURCHSICHTIG (`body.full` weiter unten): dort IST
   die Konsole der Schirm. */
/* UND KEINE KANTE (09.08.2026: „das div hat immer noch den 1px border"). Der
   Strich markierte die Naht zwischen Fenster und Bild — mit dem Grund ist die
   Naht weg, und ein 1-px-Strich quer durchs Bild ist dann nur noch ein Strich,
   der nicht aus dem Zeichenraster kommt. Wo die Seite anfängt, sagt jetzt ihr
   eigener ASCII-Rahmen. */
#console{
  position:fixed;top:0;right:0;bottom:0;z-index:10;
  background:transparent;
  border:none;
  overflow:hidden;
  /* The console handles its own scrolling in whole rows (see the pointer
     handlers in tui.js); letting the browser also pan or zoom here would
     fight it and rubber-band the page. */
  touch-action:none;
  /* width is set from JS: half the screen, or all of it when expanded */
}
/* Belt and suspenders for the fullscreen scroll bug (13.08.2026, "fullscreen
   funktioniert aber dann das scrolling nicht mehr"): the actual break traced to
   a stale #console rect (js/tui.js fullscreenchange re-measure), not to a UA
   :fullscreen rule overriding these two — but the spec's own fullscreen style
   sheet DOES carry `!important` on properties near these, and re-asserting the
   ones this panel depends on costs nothing if it turns out a browser needs it. */
:fullscreen #console,:-webkit-full-screen #console{overflow:hidden;touch-action:none}
/* Alert tinting.
   The whole surface carries the state, not a line of text buried in a list —
   you should be able to tell how bad things are from the corner of your eye,
   without reading. Applied to the CONTAINER so the wash stays continuous
   across rows, the same reason the base gradient lives here.
   Transitions are slow on the way in and quick on the way out: an alarm that
   fades up reads as a condition, one that snaps reads as a glitch. */
/* TINTED, not DARKENED — and that distinction is the bug this fixes.
   These were written against a black panel, where rgba(10,8,0,0.90) reads as a
   faint amber wash. The base surface later became grey rgba(37,37,38,0.82) and
   these did not follow, so raising an alert dropped the console from grey to
   near-black: the panel visibly changed colour several times a run for no
   reason a player could connect to anything, and an alarm made the interface
   RECEDE instead of asserting itself.
   Each level now sits at roughly the base lightness and moves along hue only,
   so the change reads as a state and not as the lights going out. */
#console{transition:opacity 500ms ease,
  background 600ms ease, border-color 600ms ease, box-shadow 600ms ease}
/* DAS FENSTER FADET (08.08. abends, dritter Anlauf: „das reinschieben bitte
   komplett rausnehmen — wir faden das einfach ein mit dem ascii arts"): die
   Zeichen loesen sich ueber die Seitenwechsel-Bloecke auf (tuiFxCross, tui.js),
   hier fadet die FLAECHE — Grund, Blur, Rand — ueber dieselbe halbe Sekunde
   (FX_MS/CON_SLIDE_MS). pointer-events aus, sonst faengt die unsichtbare
   Flaeche weiter Tipper und Rad. */
body.conclose #console{opacity:0;pointer-events:none}
/* KEINE ALARMTOENUNG DER KONSOLE MEHR (05.08., §55). Hier standen vier Zustaende
   (al1…al4) mit Grund, Rand, Innenschein und Puls; angewandt wurden zuletzt nur
   noch die zwei lautesten, und die faerbten im Gefecht genau die Zeilen mit, die
   man dann liest. Der Alarm wohnt jetzt dort, wo er hingehoert: Schuetzenmarke im
   Bild, GEFECHT-Block oben, Alarmzeile unter dem Kopf, Kontaktfarben in der
   Tabelle. Die Regeln sind ersatzlos gestrichen, damit niemand sie wieder
   einschaltet, ohne diese Begruendung zu lesen. */

/* AUSGEFAHREN IST DIE KONSOLE UNDURCHSICHTIG (06.08.: „nur das wenn das os
   offen ist das man kein 3d viewport sieht"). Die Platte ist sonst mit Absicht
   durchscheinend — die Szene laeuft unter dem Panel weiter, statt an einer
   harten Kante aufzuhoeren. Genau das ist hier der Fehler: wer die Konsole ganz
   aufmacht, will sie lesen und nicht das Bild dahinter. `tuiResize` haengt die
   Klasse an <body>, `#view` ist dann ohnehin display:none — der volle Grund ist
   die zweite Haelfte davon, denn ein leerer Viewport ist immer noch der Himmel.
   Den `backdrop-filter:none` von hier braucht es seit dem 08.08. abends nicht
   mehr — es gibt keinen Weichzeichner mehr, den man abschalten koennte. */
body.full #console{
  background:var(--panel);
  border-left-color:transparent;border-top-color:transparent;
}
/* Hier stand `body.light.full #console{background:#fdf6e3}` — dieselbe Zahl,
   die `--panel` im Hellmodus ohnehin traegt, also eine Regel, die nur die
   Zeile darueber wiederholte. Weg damit: jetzt faellt der volle Grund fuer
   JEDE Ansicht richtig aus, auch fuer die Terminalschemata. */

/* Stacked topology (see tuiResize): the console moves under the view and takes
   the full width. Only the four edges and the border change here — the width
   and height still come from JS, because they are whole character cells and
   the layout is not allowed to disagree with the grid about where the panel
   ends. The border follows the console: it marks the seam against the view,
   so it has to be on the side the view is actually on. */
body.stack #console{left:0;right:0;bottom:0;top:auto;
  border-left:none;border-top:1px solid rgba(var(--cyan-rgb),0.28)}
body.stack #view{bottom:auto}
/* The GL canvas overflows the view sideways in the split layout so the panel
   has something to blur. Stacked there is nothing beside the view to cover,
   and 100vw on a full-width element would only cost fill rate. */
body.stack #view canvas#gl{width:100%}

#tui{
  margin:0;font-family:var(--mono);white-space:pre;
  color:var(--base0);
  /* font-size and line-height come from JS so the grid is exact */
}
/* Anything that cannot be done right now looks like it. A control that is
   present but inert, with no visual difference from a live one, is the single
   most common way an interface lies to you. */
/* One step below .dim, and no lower. On black, base02 was a legible "dead";
   on the grey panel it is a shade away from the surface itself, and a control
   you cannot read at all is worse than one that merely looks inert. */
#tui .off,#labels .off{color:var(--base01)!important;opacity:0.75}
/* Stamps in the viewport obey the same rule as rows in the panel: dead is
   visibly dead, and not clickable. */
#labels .off{pointer-events:none}
#tui .off .k{cursor:default}
/* Only rows that do something take the pointer; the rest lets the view
   underneath keep its drag and zoom. */
#tui .k{cursor:pointer}
#tui .k:active{background:rgba(var(--cyan-rgb),0.30)}

/* Channel split on a torn line. Text-shadow is the honest way to do an RGB
   offset on glyphs: one pixel of red one way, one of cyan the other, the
   character itself still where it belongs. */
#tui .sp{text-shadow:-1px 0 rgba(var(--red-rgb),.85),1px 0 rgba(var(--cyan-rgb),.85)}

/* palette — the Solarized set the rest of the game uses */
/* Was base01. Raising the ground costs the dimmest text its contrast — 3.0:1
   against the panel, where it had 3.9:1 against black — and dim text in
   daylight is exactly what this whole change is about. base00 buys it back. */
.dim{color:var(--base00)}
.mid{color:var(--base0)}
.lit{color:var(--base2)}
.cy{color:var(--cyan)}
.bl{color:var(--blue)}
.gr{color:var(--green)}
.ye{color:var(--yellow)}
.or{color:var(--orange)}
.rd{color:var(--red)}
.vi{color:var(--violet)}
/* GOLD — die mittlere Stufe des Kassen-Blinkens (06.08.: „das soll gelb gold
   weiss blinken"). Zwischen `ye` (Solarized-Gelb, satt) und `lit` (das stärkste
   Zeichen der Rampe) fehlte der warme Schritt; ohne ihn springt die Anzeige von
   Gelb auf Weiß und blinkt zweistufig.
   GEMISCHT STATT GESETZT: `base2` ist im Dunkelmodus fast weiß und im Hellmodus
   fast schwarz (js/theme.js spiegelt die Rampe), also ergibt dieselbe Mischung
   oben ein helles und unten ein dunkles Gold — eine feste Zahl wäre auf Papier
   unlesbar. Die Zeile davor ist der Rückfall für Browser ohne `color-mix`. */
.gold{color:#d9a441;color:color-mix(in srgb,var(--yellow) 60%,var(--base2))}
/* …AND THE PANEL TEXT BLOOMS, brighter classes wider (02.09.2026). Two steps
   and not a curve: CSS cannot ask a colour how light it is, and the ramp the
   eye reads on this palette is bright / normal / quiet anyway. `.dim` gets
   none - a glow on the quietest text is what turns a panel into mush, and it
   is the class that carries the most glyphs. In `em`, so the bloom keeps its
   proportion when the lattice is resized.
   The world labels keep their ban (see #labels above): those sit over the
   scene where the smear cost more legibility than it bought, these sit on a
   panel of their own. */
/* ON THE LATTICE, NOT ON A LIST OF CLASSES (02.09.2026, user: "naja aber der
   header oben hat auch nix und alle typo elemente in den pages auch nicht").
   The first cut named ten colour classes, so every glyph WITHOUT one - most of
   a page - stayed flat. text-shadow inherits, so the base goes on the two
   surfaces themselves and the classes only raise or clear it.
   #labels IS the header (both live in that one <pre>), which reverses the ban
   written above it: the smear that cost legibility over the scene is the same
   glow he is asking for on the status row, and CSS cannot tell the two apart.
   .dim and .off keep none - they carry the most glyphs and a glow there is
   what turns a panel into mush. */
/* ONLY THE BRIGHT ONES (02.09.2026, user: "glow nur bei hellen nicht bei
   dunkeln fonts"). The base glow on the whole lattice went with that: a dark
   glyph blooming in its own dark colour is a smudge, not light, and it was on
   every classless character on the page. Nothing inherits a shadow now - the
   five bright classes carry it and everything else is flat.
   TWO STOPS: a tight core and a wide halo, both in the glyph's own colour. One
   radius alone reads as a blurred letter; the pair reads as a letter giving off
   light. At --glow-op of it, because full-strength currentColor was a blob
   ("glow auf 30%"); the line above is the fallback where color-mix is missing. */
/* Registered, or the two never interpolate: an unregistered custom property
   jumps from one value to the next instead of sweeping. --glow-mul is a plain
   number and the em stays in the shadow, so the halo keeps scaling with the
   lattice font size tui.js sets from JS. Driven on the two surfaces and
   inherited down - two running animations, not one per glyph span. */
@property --glow-mul{syntax:'<number>';inherits:true;initial-value:1}
@property --glow-shim{syntax:'<number>';inherits:true;initial-value:1}
/* LITERALS, not var(): a keyframe value that has to be substituted first makes
   the browser fall back to a DISCRETE animation on a registered property, and
   the bloom then switches between two steps instead of sweeping - which is what
   "ist jetzt immer noch AN ... und AUS" was (05.09.2026).
   The swell is 95..105 % of the calibrated bloom and the tremble another +-5 %
   ON TOP of it (user: "unterschied glow 95 105 + 5% 10hz"), so the two together
   reach 90.3 % .. 110.3 %. */
@keyframes glowPulse{
  0%,100%{--glow-mul:1.05}
  50%    {--glow-mul:0.95}
}
@keyframes glowShimmer{
  0%,100%{--glow-shim:1.05}
  50%    {--glow-shim:0.95}
}
#tui,#labels{animation:glowPulse var(--glow-beat) ease-in-out infinite,
                       glowShimmer var(--glow-shimmer) ease-in-out infinite}
#tui .lit,#labels .lit,#tui .cy,#labels .cy,#tui .gr,#labels .gr,
#tui .ye,#labels .ye,#tui .gold,#labels .gold{
  text-shadow:0 0 calc(var(--glow-hi) * var(--glow-mul) * var(--glow-shim)) currentColor,
              0 0 calc(var(--glow-hi) * var(--glow-mul) * var(--glow-shim) * 2.4) currentColor;
  text-shadow:0 0 calc(var(--glow-hi) * var(--glow-mul) * var(--glow-shim)) color-mix(in srgb,currentColor calc(var(--glow-op) * var(--glow-mul) * var(--glow-shim)),transparent),
              0 0 calc(var(--glow-hi) * var(--glow-mul) * var(--glow-shim) * 2.4) color-mix(in srgb,currentColor calc(var(--glow-op) * var(--glow-mul) * var(--glow-shim)),transparent);
}
/* A breathing panel is still motion, and the shimmer on top runs at 10 Hz.
   Whoever asked the system for less of it gets the bloom held at its peak. */
@media (prefers-reduced-motion:reduce){
  #tui,#labels{animation:none}
}
/* Paper does not bloom. Under body.light the same shadow reads as a smudge
   around dark text on a bright ground, which is dirt, not a monitor. */
body.light #tui,body.light #labels,body.light #tui *,body.light #labels *{
  text-shadow:none;
}
body.light #tui,body.light #labels{animation:none}
/* FETT — die eine Auszeichnung, die keine Farbe verbraucht (05.08.: „die
   wichtigste info kasse … muss oben fett zu sehen sein"). `600` trifft den
   Regular-Schnitt, während alles andere in Light steht (siehe @font-face ganz
   oben) — ein echter Sprung, keine synthetische Verdickung, dieselbe Laufweite.
   Er trägt jetzt JEDE Zahl, auf die man handelt: Kasse, Tank, Hülle, Fracht,
   Zeiten, Entfernungen. Die Einheit dahinter bleibt dünn — fett ist die ZAHL,
   nicht die Zeile. */
.bold{font-weight:600}
/* JEDE ZAHL AUF DEM SCHIRM (06.08.: „mach bitte alle wichtigen werte bold").
   `<b>` setzt `tui.js` (`zahlFett`) um jeden Ziffernlauf — im Panel, in der
   Überblendung und in den Marken im Bild. `600` statt des Browser-`700` nur der
   Ordnung halber: beide treffen denselben Regular-Schnitt, und derselbe Wert
   steht in `.bold`. Farbe erbt, damit die Zahl in der Farbe ihrer Zeile bleibt —
   fett ist GEWICHT, nicht eine zweite Bedeutung. */
#tui b,#labels b{font-weight:600;color:inherit}
/* Invers, und beide Ansichten ohne eigene Regel: `--base03` ist im Dunkelmodus
   das dunkelste, im Hellmodus das hellste der Rampe (js/theme.js spiegelt sie).
   Damit steht der gewaehlte Chip hier einmal — vorher war die Schriftfarbe eine
   nackte Zahl (#00181c), und die haette der Hellmodus als zweite Regel gebraucht. */
/* AKTIV IST GRÜN, IMMER (08.08.2026: „aktive buttons haben immer einen grünen
   hintergrund"). Die Farbachsen vom 05./06.08. (Cyan = Zustand, Grün = Auslöser,
   Violett = Automat) sind damit für den GRUND eingeklappt — DECISIONS.md §80.
   Die Rangfolge von `.chip` bleibt: Ruhe schwach, Druck stark, Wahl invers. */
/* DER KOPFBALKEN — die eine Zeile, die durchgehend eingefaerbt ist (Vorgabe
   09.08.2026: „die komplette zeile durchgehend einfaerben"). Invers wie `.on`,
   aber in CYAN und nicht in Gruen: Gruen heisst in diesem Panel „aktiv,
   ausloesbar" (DECISIONS §80), und der Name des Geraets ist kein Knopf.
   Er steht auf der ZEILE (`tuiLine(...,"kopf")`) und nicht nur auf den Chunks,
   damit der Grund auch die Spalten hinter dem Text traegt. */
.head{color:var(--base03);background:var(--cyan);font-weight:600}
/* The chosen one is inverse, and it keeps a frame too - otherwise the row
   reads as one framed button beside one unframed patch of colour. */
.on{color:var(--base03);background:var(--green);
  box-shadow:inset 0 0 0 var(--btnFrame) rgba(0,0,0,0.45)}
.go{color:var(--base03);background:var(--green)}
.auto{color:var(--base03);background:var(--green)}
/* EIN CHIP IN RUHE (05.08.2026, Interface-Runde). Bis hierher sah ein nicht
   gewaehlter Chip aus wie grauer Text: sichtbar war nur der GEWAEHLTE (`.on`,
   invers) und der gedrueckte (`#tui .k:active`). Gemessen (scratchpad/panellast.js)
   waren im Leerlauf 94 Trefferzonen auf dem Schirm, davon 16 als ganze Zeile
   markiert — die anderen 78 waren nicht als tastbar zu erkennen, und genau das
   meinte „man sieht nicht was man machen muss".
   Eine RUHENDE Andeutung statt einer zweiten Farbe: dieselbe Tinte wie `:active`,
   nur ein Drittel so stark. Damit bleibt die Reihenfolge lesbar — Ruhe schwach,
   Druck stark, Wahl invers — und die Palette bekommt keine neue Farbe.
   `chip` steht NEBEN der Farbklasse (die Chunks tragen mehrere Klassen), also
   behalten farbige Knoepfe ihre Bedeutung. */
/* AND A CHIP IS A BUTTON, SO IT CARRIES A BUTTON'S FRAME (28.08.2026, user:
   "da fehlen die rahmen dann aber", after the overlay buttons got theirs).
   An INSET box-shadow, never a border: the console is a character lattice and
   a border would push every glyph after it out of its cell. Same ink as the
   plate, four steps stronger, so the edge reads without a second colour. */
.chip{background:rgba(var(--cyan-rgb),0.10);
  box-shadow:inset 0 0 0 var(--btnFrame) rgba(var(--cyan-rgb),0.45)}
/* Das FLUG-Fenster liegt einen Ton heller als der Panelgrund — zwei Bloecke,
   zwei Flaechen, ohne eine neue Farbe (derselbe base-Ton wie die Zebra-Idee). */
/* ZEBRA — der Grundton jeder zweiten Tabellenzeile. Halb so stark wie
   .blocklight: ein Streifen soll die Zeile fuehren, nicht sie einrahmen. */
.zebra{background:rgba(var(--base1-rgb),0.035)}
.blocklight{background:rgba(var(--base1-rgb),0.07)}
/* ROWBAR — JEDE ZEILE IHR EIGENER BALKEN (13.08.2026, vierter Anlauf: „der
   hintergrund ist immer noch fuer den ganzen bereich nicht fuer die einzelnen
   zeilen"). Die ersten drei Anlaeufe (siehe UEBERGABE) setzten auf die
   natuerliche Luecke aus `line-height` minus Zeichenhoehe — dieselbe Mechanik,
   die bei `#labels .lbl` sauber eine Reihe von Balken zeigt. GEMESSEN
   (scratchpad/rowbar-probe.js): die Luecke ist bei `.rowbar` GENAUSO gross wie
   bei `.lbl` (14 px Inhalt in einer 16-px-Zeile, beide Male). Der Unterschied
   ist nicht die Geometrie, sondern der GRUND DARUNTER: `#labels` schwebt direkt
   ueber der 3D-Szene (fast schwarz), also steht ein groauer Balken auf Schwarz —
   volle Kontrast. `#tui` liegt AUF `#console`, das selbst schon denselben
   Grauton traegt (siehe dort); Balken und Luecke unterscheiden sich dort nur um
   eine Alpha-Stufe DESSELBEN Grautons und gehen im Sternenrauschen dahinter
   unter. Deshalb hier ein Trenner, der nicht von dieser Alpha-Rechnung
   abhaengt: eine eigene, feste dunkle Linie an der Oberkante jeder Zeile
   (`box-shadow: inset`), sichtbar unabhaengig davon, wie aehnlich sich Balken
   und Untergrund sonst sind. MUSS VOR `.sel` STEHEN, aus demselben Grund wie
   `.planbg` weiter unten: die Auswahl ersetzt den Balken, sie mischt nicht mit
   ihm. */
.rowbar{background:rgba(var(--base1-rgb),0.12);
  box-shadow:inset 0 1px 0 rgba(0,0,0,0.35)}
/* DAS PLANUNGS-FENSTER (05.08.: „das planungs interface muss sich mehr abheben
   vom rest"). Es sitzt zwischen Zebra-Zeilen der Zieltabelle, also reicht der
   neutrale Ton von `blockhell` dort nicht — er ist derselbe Grau-Ton, nur doppelt
   so stark wie der Streifen. Dieselbe Tinte wie `.sel` (die Kopfzeile des Plans),
   auf ein Viertel gedämpft: die Auswahl leuchtet, ihr Fenster liegt darunter. */
.planbg{background:rgba(var(--blue-rgb),0.15)}
/* DER GEFECHTSBLOCK HAT JETZT EINEN GRUND (05.08.2026: „gefecht sektion mit
   rotem hintergrund oder irgendwas was auffällt"). Dritter Ton derselben
   Familie — grau ist der Flug, blau der Plan, rot das Gefecht —, also dieselbe
   Stärke wie `planbg` und keine neue Mechanik: `tuiRowBg` in drawGefecht.
   Gemessen (scratchpad/kampfgrund.js): rote Tinte auf diesem Grund steht bei
   2,95:1, also GENAU dort, wo sie auf `blockhell` schon stand (2,96) und besser
   als auf `planbg` (2,75). Der Block kostet keinen Kontrast, den die anderen
   beiden nicht auch kosten.
   MUSS VOR `.sel`/`.warnbg`/`.dangerbg` STEHEN: tuiRowBg haengt seine Klasse
   HINTEN an, und bei gleicher Spezifitaet gewinnt die spaetere REGEL, nicht das
   spaetere Wort — sonst faerbt der Blockton die gewaehlte Zeile und die
   Warnung mit. */
.fightbg{background:rgba(var(--red-rgb),0.15)}
.sel{background:rgba(38,139,210,0.32);color:var(--base2)}
.selye{background:rgba(181,137,0,0.42);color:var(--base2)}
.warnbg{background:rgba(203,75,22,0.34);color:var(--base2)}
.dangerbg{background:rgba(220,50,47,0.42);color:var(--base2)}
/* DIE OHNMACHT IST GELB, nicht grau (05.08.: „crew ausser gefecht warnung gelb
   hinterlegen"). Sie stand als `dim` da, weil sie kein Befehl ist — nur ist der
   Preis der groesste im Spiel (Waffen unbesetzt), und eine graue Zeile liest
   sich als abgeschaltet. Gelb heisst weiter nicht „jetzt handeln" (das ist
   Orange/Rot), aber es heisst „hier fehlt etwas". */
/* Crew-gate colour, blue not yellow (19.09.2026, user: "roter alarm nur blau
   und weniger nervig") - same weight as warnbg/dangerbg, its own hue. */
.dozebg{background:rgba(var(--blue-rgb),0.34);color:var(--base2)}
.dozebgHi{background:rgba(var(--blue-rgb),0.60);color:var(--base2)}

/* ── HELLMODUS ──────────────────────────────────────────────────────────────
   Die FARBEN stehen nicht hier, sondern in js/theme.js: die Rampe wird dort
   gespiegelt und als Custom Properties gesetzt, also fallen `--base0`, `--cyan`
   und die drei Flaechen von selbst richtig aus. Hier steht nur, was eine ALPHA
   traegt — eine Tinte auf einer Flaeche laesst sich nicht spiegeln, sie muss
   ihre Richtung wechseln: im Dunkelmodus hellen diese Toenungen den Grund auf,
   hier dunkeln sie ihn ab.
   Alles mit `body.light` davor. Wer eine Regel ohne Alpha hier ergaenzt, hat sie
   am falschen Ort — sie gehoert in die Palette.                              */


/* DIE KONSOLE IST PAPIER, nicht Himmel — aber duenneres Papier als bisher
   (05.08.: „bright mode bg muss auf 50% opacity runter, das ist zu hell").
   Hier stand 0,90 mit der Begruendung, die gespiegelte Tinte brauche den
   Papiergrund. Der Preis steht jetzt dabei, gemessen (scratchpad/panelalpha.js,
   Papier ueber 58 % Grau):

     alpha   Grund     dim base00   mid base0   lit base2
      0,90   #f3ecdb      2,68:1       3,78:1     11,0:1
      0,50   #c9c5bc      1,84:1       2,59:1      7,6:1

   Also: `lit` traegt weiter, `mid` liegt an der Grenze, und `.dim` ist auf
   diesem Grund keine dritte Stufe mehr, sondern ein Hauch. Wer die drei Stufen
   im Hellmodus zurueckwill, zieht die Rampe im Panel eine Stufe nach unten
   (wie es `body.light #labels` fuer den Himmel schon tut) — NICHT die Alpha
   wieder hoch. Unter 0,50 nicht weiter: dort faellt auch `mid` unter 2,5:1.
   UND SEIT DEM 08.08. ABENDS 0,75 (mitgezogen mit „kein blur mehr … einfach
   75 % abdunkeln"). Das ist keine zweite Meinung zur Helligkeit, sondern der
   Ersatz fuer den weggefallenen Weichzeichner: die 0,50 standen NUR, weil der
   Blur die Trennung von Text und Szene machte. In derselben Tabelle liegt 0,75
   zwischen den beiden gemessenen Zeilen, also besser als der Stand von gestern
   und noch deutlich unter den 0,90, die zu hell waren. */
/* Der Grund ist auch hier weg (09.08.2026, siehe #console): geblieben ist die
   kräftigere Kante, die der Hellmodus braucht. */
body.light #console{
  background:transparent;
  border-left-color:rgba(var(--cyan-rgb),0.45);
}
body.light.stack #console{border-top-color:rgba(var(--cyan-rgb),0.45)}
/* AUSGEFAHREN HAT SIE AUCH GESTAPELT KEINE KANTE (09.08.2026, mit „mobile ui
   wechselt zwischen fullscreen 3d mit hud und fullscreen reiter"): der Strich
   markiert die Naht zum Bild, und auf dem Telefon gibt es im offenen Reiter kein
   Bild mehr — er lag als cyanfarbene Linie auf der obersten Bildschirmkante.
   `body.full #console` weiter oben setzt ihn schon auf transparent, verliert
   aber gegen die zwei `stack`-Regeln (gleiche bzw. höhere Spezifität), also
   stehen beide Fälle hier noch einmal. */
body.full.stack #console,body.light.full.stack #console{border-top-color:transparent}

/* Die Marken im Bild brauchen weiter etwas hinter sich — jetzt Papier statt
   Tiefschwarz, sonst steht ein dunkles Kaestchen ueber dem hellen Himmel. */
body.light #labels .lbl{background:rgba(var(--panel-rgb),0.5)}
body.light #labels .chip{background:rgba(var(--panel-rgb),0.55)}

/* Ruhender Knopf, gedrueckter Knopf, Zebra, Block: dieselbe Ordnung wie im
   Dunkelmodus (Ruhe schwach, Druck stark), nur dunkelt die Tinte jetzt. */
body.light .chip{background:rgba(var(--cyan-rgb),0.13)}
body.light #tui .k:active{background:rgba(var(--cyan-rgb),0.32)}
body.light .zebra{background:rgba(var(--base1-rgb),0.05)}
body.light .blocklight{background:rgba(var(--base1-rgb),0.10)}
/* Papier statt Bildschirm: der Trenner braucht dort weniger Schwaerze, um
   sichtbar zu sein (siehe `.rowbar` weiter oben). */
body.light .rowbar{background:rgba(var(--base1-rgb),0.18);
  box-shadow:inset 0 1px 0 rgba(0,0,0,0.15)}
/* DIE ZWEI BLEIBEN GESETZTE ZAHLEN, und das ist Absicht: `--blue` und `--red`
   sind im Hellmodus halbiert (THEME_ACC), ein Block-Grund aber nicht Tinte —
   halbiert waere er auf Papier nicht mehr zu sehen. Der Dunkelmodus nimmt
   dieselben Toene aus der Palette (`.planbg`/`.fightbg` oben), fuer ihn und
   fuer jedes dunkle Terminalschema stimmt das von selbst. */
body.light .planbg{background:rgba(38,139,210,0.18)}
body.light .fightbg{background:rgba(220,50,47,0.18)}

/* Der Kanalversatz auf einer gerissenen Zeile: dieselbe Idee, dunklere Tinte —
   auf Papier verschwindet ein helles Rot-Cyan-Paar im Weiss. */
body.light #tui .sp{text-shadow:-1px 0 rgba(var(--red-rgb),.85),1px 0 rgba(var(--cyan-rgb),.85)}

/* DIE BESCHRIFTUNG IM BILD LIEGT AUF DEM HIMMEL, nicht auf Papier — und ein
   mittleres Grau traegt die MITTE der Rampe nicht (gemessen: base00 hat auf
   58 % Grau 1,1:1, base0 1,5:1, base2 3,3:1). Also wird die Tinte im
   Bildgitter um zwei Stufen nach unten gezogen. Nur dort: im Panel steht
   dieselbe Rampe auf Papier, dort stimmt sie.
   Der Preis steht dabei: aus drei Stufen (dim/mid/lit) werden zweieinhalb, weil
   unter base2 keine Tinte mehr kommt. Ein grauer Himmel kann keine Rampe
   tragen — er liegt in ihrer Mitte. Wer drei Stufen braucht, macht den Himmel
   dunkler (js/theme.js, THEME_TAB.light.sky). */
body.light #labels{color:var(--base2)}
body.light #labels .dim{color:var(--base1)}
body.light #labels .mid{color:var(--base2)}
body.light #labels .off{color:var(--base0)}

/* EIN TRENNER ZWISCHEN DEN WAFFENSYSTEMEN (06.08.2026: „zwischen jeden
   waffensystem block eine zeile mit dunkel rot als bg", dann: „bzw nen dunkel
   grau irgendwas was nicht schwarz ist oder zumindest leere zeile · wir haben
   genug pixel"). Ein Waffenstand ist sechs bis acht Zeilen, und zwei davon
   untereinander lasen sich als eine — TAKT, Graph, ANGRIFF, WIRKUNG, dann
   wieder TAKT, ohne Kante dazwischen.
   UND SIE IST ROT WIE DER BLOCK, MIT EINEM STRICH DRIN (06.08.2026: „jetzt ist
   die dunkel cyan, mach eine rote bg wie die andern aber mit ascii trenner").
   `--base02` war der Griff davor und in dieser Palette ein dunkles CYAN — auf
   einem roten Block eine dritte Farbe, die nichts bedeutet. Jetzt derselbe rote
   Ton wie die anderen Grunde, nur etwas dichter als `.fightbg` (0,28 gegen
   0,15), und die Trennung macht der STRICH: `─` über die Panelbreite, dieselbe
   Glyphe wie in jedem Balken dieser Konsole (CLAUDE.md — nur gemessene Zeichen).
   STEHT HINTER `.fightbg`, weil `tuiRowBg` seine Klasse anhaengt und bei
   gleicher Spezifitaet die spaetere REGEL gewinnt (siehe dort). */
.sysrule{background:rgba(220,50,47,0.28)}
body.light .sysrule{background:rgba(220,50,47,0.34)}

/* DAS FUNKFELD — unsichtbar, aber fokussierbar (COMM, js/screen-comm.js).
   Es ist das erste <input> in diesem Projekt und darf NICHT gezeichnet
   werden: die Konsole ist ein Zeichengitter, und ein Browser-Eingabefeld
   mitten darin haette eine eigene Schrift, eine eigene Zeilenhoehe und einen
   eigenen Cursor. Gezeichnet wird die Zeile in `commEingabe()`, hier steht
   nur der Empfaenger der Tastatur.
   WARUM NICHT `display:none` ODER `visibility:hidden`: beides nimmt das Feld
   aus der Fokusreihenfolge, `focus()` tut dann nichts, und auf dem Telefon
   kommt keine Bildschirmtastatur. `opacity:0` mit 1 px laesst es
   fokussierbar. `position:fixed` unten links, damit der Browser beim
   Fokussieren nicht scrollt — ein Sprung des ganzen Bildes beim Antippen von
   SENDEN waere der sichtbare Fehler, den dieses Feld sonst macht.
   16 px Schriftgroesse ist kein Geschmack: darunter zoomt iOS Safari die
   Seite beim Fokussieren automatisch hinein, und das Raster stimmt danach
   nicht mehr. */
/* `#fbInput` stand hier mit drin, solange die Fehlermeldung im Zeichengitter
   getippt wurde (unsichtbares Fokusfeld, wie `#funk`). Seit dem 14.08.2026 ist
   sie eine eigene Seite mit einem SICHTBAREN Textfeld — siehe `#feedback`. */
#funk{
  position:fixed; left:0; bottom:0;
  width:1px; height:1px; padding:0; margin:0; border:0;
  opacity:0; font-size:16px;
  background:transparent; color:transparent; caret-color:transparent;
  z-index:-1;
}


/* ══ DER ERSTE SCHIRM ═══════════════════════════════════════════════════════
   Vorgabe 09.08.2026: „ein schwarzer screen mit ship.os typo oben und dann
   name und id und einen start button" — und, nachgereicht: „der login screen
   darf keine unterschiedlich großen fonts haben."

   EINE SCHRIFTGRÖSSE, und zwar wörtlich: `#intro` setzt sie, alles darunter
   erbt `1em`. Das gilt auch für die Eingabefelder und die Knöpfe, die vom
   Browser sonst ihre eigene bekämen (`font:inherit`). Der Schirm ist damit ein
   Zeichengitter wie der Rest des Spiels und keine Webseite mit Überschriften.

   DIE BREITE IST DIE WORTMARKE: 44 Zeichen. `#introBox` steht auf `44ch`, also
   ist jede Zeile darunter an derselben Kante ausgerichtet, und die Marke
   braucht keine eigene Behandlung.
   Damit sie auf ein Telefon passt, WÄCHST die eine Größe mit dem Fenster
   (`clamp`) — das ist keine zweite Größe, sondern dieselbe, gemessen am
   Schirm. 44ch brauchen rund 26,4 Zeichenbreiten Prozent; 2,05vw trifft das
   mit Rand.

   EIN SCHLEIER, KEIN VORHANG (23.08.2026, user: "schau dir mal launchpage
   an - hangar bitte da reinbauen aber ohne scrollen also hangar overlay"):
   the login, the hangar and CONTINUE RUN all float over the LIVE scene the
   way landing.html's hero does - the space behind the box is the pitch,
   not a black wall. The scrim keeps the 44ch grid readable over stars. */
#intro{
  position:fixed; inset:0; z-index:9000;
  display:flex; align-items:center; justify-content:center;
  background:transparent; color:var(--base1);  /* kein Scrim - der Landing-Hero bleibt hell (user 23.08.2026) */
  font-family:var(--mono);
  /* NUR DER RUECKFALL. Die echte Groesse holt js/start.js aus dem
     Zeichengitter des Spiels (#tui) — eine Typo-Groesse in der ganzen
     Anwendung, siehe PLAN 1.4. Diese Zeile gilt nur, solange tuiResize noch
     nicht gemessen hat. */
  font-size:clamp(9px,2.05vw,16px);
  line-height:1.4;
}
#intro[hidden]{display:none}
#intro *{font:inherit; letter-spacing:0}
/* 44ch until 28.08.2026, which the three login buttons overran by two
   characters (9 + 2 + 20 + 2 + 13 = 46) - the row wrapped and a lone
   bracket sat under it. 30 % wider (user), and the buttons have room. */
#introBox{width:57ch; max-width:100%; padding:1.5em 2ch; background:rgba(0,0,0,.35)}   /* landing.html #login's panel */
/* THE LANDING WORDMARK OVER EVERY MENU (23.08.2026, user: "landingpage
   design ... dann wechselt nur das fenster"): one fixed yellow HAULER.RUN,
   type + stacked extrusion - no image, no second scene. Shown only while a
   boot menu stands (body.menuUp, frame.js), the window below it swaps. */
#heroMark{
  position:fixed; left:0; right:0; top:7vh; z-index:9400;
  display:none; text-align:center; pointer-events:none;
  font-family:var(--mono); font-weight:700; letter-spacing:.06em;
  font-size:clamp(28px,6.5vw,84px); line-height:1;
  color:#ffd94a;
  text-shadow:
    1px 1px 0 #7a6420, 2px 2px 0 #6e5a1d, 3px 3px 0 #62501a,
    4px 4px 0 #564617, 5px 5px 0 #4a3c14, 6px 6px 0 #3e3211,
    7px 7px 0 #32280d, 8px 8px 0 #261e0a, 9px 9px 1px #1a1407;
}
/* #heroMark is the CSS wordmark over the boot menus. It steps aside once the
   menu scene has its own 3D one (client/menuscene.js sets body.logo3d when the
   FBX has landed) - the same division as before 25.08.2026, when the wordmark
   the player saw was the one inside landing.html's iframe. */
body.logo3d #heroMark{display:none}
/* THE FADE TO BLACK between START/cast-off and the boot run. bootFade()
   (client/start.js) raises the opacity, waits, hands over, drops it. */
#fadeBlack{
  position:fixed; inset:0; z-index:9600; background:#000;
  opacity:0; pointer-events:none; transition:opacity .6s ease;
  display:grid; place-items:center; color:var(--cyan); font-size:1.1em;
  white-space:pre; text-align:center; line-height:1.8;
}
#fadeBlack b{font-weight:normal; letter-spacing:.35em}
#introMark,#hangarMark{
  margin:0 0 2em; white-space:pre; color:var(--cyan);
  /* 44 Zeichen breit. Passt das Gitter des Spiels nicht darauf, laeuft die
     Marke seitlich — kleiner wird sie NICHT (eine Groesse, PLAN 1.4). */
  overflow-x:auto;
  /* Die Marke ist ein Bild aus Zeichen: keine Zeilenhöhe dazwischen, sonst
     reissen die Blockgrafiken senkrecht auf. */
  line-height:1;
}
/* Same 44ch mark, centered in the 60ch hangar box. */
#hangarMark{width:44ch; max-width:100%; margin-left:auto; margin-right:auto}
.introRow{display:flex; align-items:baseline; margin:0 0 .6em}
.introRow>span{width:10ch; flex:0 0 auto; color:var(--base01)}
.introRow>input{
  flex:1 1 auto; min-width:0;
  background:transparent; color:var(--base2);
  border:0; border-bottom:1px solid var(--base02);
  padding:0 0 .15em; outline:none;
}
.introRow>input:focus{border-bottom-color:var(--cyan)}
.introRow>input:disabled{color:var(--base01)}
/* DIE ERKLÄRUNG STEHT UNTER DEM FELD, an derselben Kante wie die Eingabe
   (10ch, wie `.introRow>span` breit ist). Ein Satz je Feld, keine Überschrift,
   keine zweite Größe — der Schirm bleibt ein Zeichengitter. */
.introWhat{margin:-.3em 0 1.1em 10ch; white-space:pre-wrap; color:var(--base01)}
#introHint{margin:1.6em 0 0; white-space:pre-wrap; color:var(--base01)}
/* Die Zeile, auf der steht, was gerade passiert (Bootlauf, Kontowechsel,
   Ablehnung). Sie ist meistens leer und hält trotzdem ihre Höhe — sonst
   springen die Knöpfe, sobald der erste Satz darin steht. */
#introStatus{margin:.8em 0 1.2em; min-height:1.4em; white-space:pre-wrap;
  color:var(--cyan)}
#introStatus.bad{color:var(--orange)}
/* A BUTTON THAT LOOKS LIKE ONE, AND ALL OF THEM (28.08.2026, user: "die
   buttons sehen nicht aus wie buttons", then "das muss dann aber fuer alle
   buttons gelten"). Until today they were bare text in square brackets - the
   brackets WERE the button, and over the scene they read as a label. One
   frame in the button's own colour, one dark plate, one hover, for every
   overlay: login, CONTINUE RUN, the bug report, the hangar and the
   while-you-were-away note. The brackets are gone from every label with it
   (index.html, client/start.js) - a box and a bracket say it twice. */
.ovBtn,#introButtons,#contButtons,#fbButtons,#hangarButtons,#reportButtons{
  display:flex; gap:2ch; flex-wrap:wrap;
}
#introButtons>button,#contButtons>button,#fbButtons>button,
#hangarButtons>button,#reportButtons>button{
  background:rgba(0,0,0,.45); border:var(--btnFrame) solid currentColor;
  padding:.15em 1.5ch; cursor:pointer;
  color:var(--cyan);
}
#introButtons>button:disabled,#contButtons>button:disabled,
#fbButtons>button:disabled,#hangarButtons>button:disabled,
#reportButtons>button:disabled{
  color:var(--base01); background:transparent; cursor:default;   /* base02 is black on black */
}
#introSolo,#fbCancel{color:var(--base01)}
#introButtons>button:hover:not(:disabled),#contButtons>button:hover:not(:disabled),
#fbButtons>button:hover:not(:disabled),#hangarButtons>button:hover:not(:disabled),
#reportButtons>button:hover:not(:disabled){
  color:var(--base2); background:rgba(0,0,0,.7);
}

/* ══ DER FEHLERMELDE-SCHIRM ═════════════════════════════════════════════════
   Vorgabe 14.08.2026: „NEIN DER BUTTON MUSS UNTEN LINKS und dann geht ne page
   auf wo man was reintippen kann." Gebaut wie `#intro` und bewusst nicht als
   Zeile im Zeichengitter: ein Fehlerbericht ist ein Absatz und braucht
   Zeilenumbruch, Auswahl und Einfuegen — alles, was ein echtes <textarea>
   mitbringt und eine gestempelte HUD-Zeile nicht.
   z-index UNTER dem Namensschirm (9000) und ueber allem anderen: waehrend der
   Anmeldung gibt es noch nichts zu melden. Kein schwarzer Vollgrund wie beim
   Intro, sondern eine Schleier-Platte — man soll noch sehen, worueber man sich
   gerade beschwert. */
#feedback{
  position:fixed; inset:0; z-index:8996;
  display:flex; align-items:center; justify-content:center;
  background:rgba(0,0,0,0.82); color:var(--base1);
  font-family:var(--mono);
  font-size:clamp(9px,2.05vw,16px);
  line-height:1.4;
}
#feedback[hidden]{display:none}
#feedback *{font:inherit; letter-spacing:0}
#fbBox{width:60ch; max-width:92vw}
#fbMark{margin:0 0 1.2em; white-space:pre; color:var(--cyan)}
/* Das Feld traegt den Rahmen, nicht den Hintergrund des Schirms: es ist die
   einzige Flaeche hier, in die man etwas HINEIN tut. */
#fbInput{
  display:block; width:100%; box-sizing:border-box;
  padding:.6em .8em; resize:vertical;
  background:var(--bg); color:var(--base1);
  border:1px solid var(--base02); outline:none;
}
#fbInput:focus{border-color:var(--cyan)}
#fbHint{margin:1.2em 0 0; white-space:pre-wrap; color:var(--base01)}
#fbStatus{margin:.8em 0 1.2em; min-height:1.4em; white-space:pre-wrap;
  color:var(--green)}
#fbStatus.bad{color:var(--orange)}
/* Look and hover live in the shared overlay-button rule above. */

/* ══ DER BOOTLAUF ═══════════════════════════════════════════════════════════
   Vorgabe 11.08.2026: „nach dem starten möchte ich das das system bootet, so
   schön 386 mit bios logo und so", mehrfach nachgeschärft — zuletzt: „das muss
   mehr nach linux boot aussehen." Aus dem BIOS-POST (Award-Kopf, PCI-Tabelle,
   DOS-„mem /c") ist ein dmesg/systemd-Log geworden (js/bios.js): Kernelzeilen
   mit Zeitstempel, dann `[  OK  ]`/`[FAILED]` vor jeder Unterkomponente.

   FARBE STEHT AN DEN SPANS, NICHT AM BLOCK — anders als vorher (dort war
   `#biosText` komplett gruen). Ein Linux-Boot ist ueberwiegend HELLGRAU
   (`--base1`, wie der Rest der Konsole) mit genau zwei farbigen Woertern je
   Zeile: `.gr` fuer OK, `.rd` fuer FAILED — dieselben globalen Klassen, die
   auch `wValue()` im Spiel benutzt, hier nur wiederverwendet.

   VOLLE BREITE HEISST HIER: kein zentrierter Kasten, keine 60ch-Kappung.
   `white-space:pre` statt `pre-wrap` — genau wie `#introMark` (js/start.js):
   ein Zeichengitter wird nicht umgebrochen, es LÄUFT seitlich, wenn eine
   Zeile nicht passt (`overflow:auto` traegt beide Achsen). NUR BREIT
   (29.08.2026, DECISIONS §296): auf dem Telefon (`body.stack`) bricht
   `#biosText` um - die Begruendung schuetzt `#introMark`s ASCII-Kunst, nicht
   dieses Log, das seit dem Systemd-Umbau keine Tabelle mehr traegt.

   Z-INDEX 8995, EINE STUFE UNTER DEM NAMENSSCHIRM (9000) UND DER WERFT (8990),
   ueber der Szene: `hangarZu()` (js/hangar.js) loest ihn beim Ablegen aus,
   und er deckt die schon laufende Szene fuer seine Laufzeit ab. */
#bios{
  position:fixed; inset:0; z-index:8995;
  background:#000; color:var(--base1);
  font-family:var(--mono);
  font-size:clamp(9px,2.05vw,16px);     /* nur der Rueckfall, siehe #intro */
  line-height:1.3;
  overflow:auto; cursor:pointer;
}
#bios[hidden]{display:none}
#bios *{font:inherit; letter-spacing:0}
#biosText{
  display:inline-block; min-width:100%; width:max-content;
  padding:1.5em 2ch; white-space:pre; color:var(--base1);
}
/* PHONE ONLY: THE LOG WRAPS (29.08.2026, DECISIONS §296). The grid rule above
   protects #introMark's ASCII art and the console's per-column hit zones; #bios
   has neither - one whole-overlay click (biosDone) and dmesg prose. */
body.stack #biosText{
  width:100%; white-space:pre-wrap;
}
/* DIE ENTHÜLLUNG (js/bios.js `biosRevealStart`) — derselbe Platz wie #bios,
   löst ihn ab statt ihn zu überlagern: erst wird #bios versteckt, dann dieser
   Canvas gezeigt, randlos, damit die Kacheln bis an den Rand reichen. */
#biosReveal{
  position:fixed; inset:0; z-index:8995;
  width:100%; height:100%; display:block; cursor:pointer;
}
#biosReveal[hidden]{display:none}

/* ══ DER HANGAR ═════════════════════════════════════════════════════════════
   Vorgabe 09.08.2026: „bei einem neuen run möchte ich das man sein schiff
   ausrüsten kann und eine start region festlegt."

   DIESELBEN REGELN WIE OBEN, und deshalb steht er direkt darunter: schwarz,
   eine Schriftgröße (js/hangar.js holt sie aus #tui), alles darunter erbt
   `1em`. Der einzige Unterschied ist die BREITE — 60 statt 44 Zeichen, weil
   eine Modulzeile Name, Preis und Wirkung nebeneinander trägt und eine
   abgeschnittene Wirkung keine Entscheidung ist.

   Z-INDEX 8990, EINE STUFE UNTER #intro: beim allerersten Start liegen beide
   Schirme da, der Namensschirm deckt ab, und mit seinem START gibt er den
   Hangar frei. Ohne Zeitgeber, ohne dass eine Datei die andere ruft.

   DIE SEITE SCROLLT NIE (23.08.2026, user: "aber ohne scrollen also hangar
   overlay"): der Hangar ist ein Overlay ueber der laufenden Szene, wie der
   Login. Passt die Liste nicht in die Hoehe (iPhone, 19 Zeilen), scrollt
   der KASTEN innen - der Schirm dahinter steht fest. */
#hangar{
  position:fixed; inset:0; z-index:8990;
  display:flex; align-items:flex-start; justify-content:center;
  /* Below the wordmark. The CSS one (#heroMark) is 7vh plus its own height;
     the 3D one (client/menuscene.js, body.logo3d) stands lower and wider and
     gets its own step below. One name, so the box height follows it. */
  --hangarTop:calc(9vh + clamp(28px,6.5vw,84px));
  /* Ink per ROW (user 25.08.2026): no scrim over the whole screen - the
     planet stays - each line carries its own black, so the wordmark's
     extrusion cannot run through the text. */
  --hangarInk:rgba(0,0,0,.72);
  padding-top:var(--hangarTop);
  background:transparent; color:var(--base1);  /* kein Scrim - der Landing-Hero bleibt hell (user 23.08.2026) */
  font-family:var(--mono);
  font-size:clamp(9px,1.5vw,16px);     /* nur der Rueckfall, siehe #intro */
  line-height:1.4;
  overflow:hidden;
}
#hangar[hidden]{display:none}
#hangar *{font:inherit; letter-spacing:0}
/* A flat vh number is the right kind of constant here: a vertical-FOV
   camera's screen footprint does not scale with window width. The wordmark is
   placed in the scene by MENU_LOGO.pos (client/menuscene.js) and 40vh clears
   it. */
body.logo3d #hangar{--hangarTop:40vh}
#hangarBox{width:78ch; max-width:100%; padding:0 0 1.5em;   /* 60ch until 28.08. */
  max-height:calc(100svh - var(--hangarTop) - 1vh); overflow-y:auto}
/* RUN IN PROGRESS (23.08.2026, user: "reload sollte dann continue run als
   nachricht stehen") - the one overlay between login and space when a run
   is already under way. Same family as #intro, one step below it, one
   above the hangar it replaces in that case. */
#continue{
  position:fixed; inset:0; z-index:8995;
  display:flex; align-items:center; justify-content:center;
  background:transparent; color:var(--base1);  /* kein Scrim - der Landing-Hero bleibt hell (user 23.08.2026) */
  font-family:var(--mono);
  font-size:clamp(9px,2.05vw,16px);
  line-height:1.4;
}
#continue[hidden]{display:none}
/* WHILE A BOOT MENU STANDS the game's own surfaces hold their breath
   (23.08.2026, playtest screenshot: the FLIGHT console drew straight
   through the translucent hangar). The scene stays - it is the backdrop
   the menus float over - the terminal does not. frame.js toggles the
   class once per frame off bootScreenOn()/the hangar flag. */
body.menuUp #console,body.menuUp #hud,body.menuUp #labels,body.menuUp #zoom{
  visibility:hidden}
/* AND ON BLACK WHILE THE MENU IS NOT BUILT YET (28.08.2026, user: "zur not
   einfach schwarz blende"): the page waits for the world to say WHICH menu
   this boot gets, and the backdrop can be a few frames behind that. #view
   carries the sky of the theme - black is what belongs under a menu. */
body.bootBlack #view{background:#000}
#continue *{font:inherit; letter-spacing:0}
#contBox{width:44ch; max-width:100%}
#contMark{margin:0 0 2em; white-space:pre; color:var(--cyan); line-height:1}
#contInfo{margin:0 0 1.6em; white-space:pre-wrap; color:var(--base01)}
/* Eine Zeile ist ein Zeichengitter: kein Umbruch, keine zweite Höhe. Was nicht
   hineinpasst, hat js/hangar.js schon mit „…" gekürzt. */
.hz{
  display:block; width:100%; text-align:left; white-space:pre;
  background:var(--hangarInk); border:0; padding:0; margin:0;
  color:var(--base1); font:inherit;
}
/* Eine leere Zeile ist eine ZEILE: ohne Höhe faellt sie zusammen, und dann
   stehen Revier und Werft ohne Luft uebereinander. */
.hz.empty{height:1.4em; background:transparent}
.hz.head{color:var(--base2)}
.hz.tit{color:var(--base01)}
.hz.foot{color:var(--base01)}
/* `regal` ist die Zeile aus dem eigenen Lager — anfassbar wie Revier und Modul.
   `fern` ist derselbe Regal-Eintrag, nur aus einem fremden Revier (TODO 13.08.). */
.hz.region,.hz.module,.hz.regal,.hz.fern,
.hz.dopen,.hz.dpart,.hz.dmount,.hz.dsize,.hz.dpos,.hz.dturn,
.hz.dface,.hz.dpart2,.hz.dadd,.hz.dmadd,.hz.dmpos,.hz.dend,.hz.dkit,
.hz.dact,.hz.dslot,.hz.dbox,.hz.ditem,.hz.ddock,.hz.drem{cursor:pointer}
.hz.region:hover,.hz.module:hover,.hz.regal:hover,.hz.fern:hover,
.hz.dopen:hover,.hz.dpart:hover,.hz.dmount:hover,.hz.dsize:hover,
.hz.dpos:hover,.hz.dturn:hover,.hz.dface:hover,.hz.dpart2:hover,
.hz.dadd:hover,.hz.dmadd:hover,.hz.dmpos:hover,.hz.dend:hover,
.hz.dkit:hover,.hz.dact:hover,.hz.dslot:hover,.hz.dbox:hover,
.hz.ditem:hover,.hz.ddock:hover,.hz.drem:hover{color:var(--base2)}
/* WHILE THE DESIGNER STANDS, THE ROWS TAKE ONE SIDE and the bay takes the
   rest (03.09.2026). The camera does the other half of this with a lens
   shift (client/designscene.js designShift) - these two numbers and that
   one are the same statement, so they move together. */
body.designUp #hangar{--hangarTop:10vh; pointer-events:auto}
body.designUp #hangarBox{width:52ch; margin-left:auto; margin-right:2vw;
  background:var(--hangarInk)}
@media (max-aspect-ratio: 115/100){
  body.designUp #hangarBox{width:100%; margin:0; --hangarTop:52vh}
  body.designUp #hangar{--hangarTop:52vh}
}
/* Gewählt ist HELL, gesperrt ist stumpf — dieselbe Reihenfolge wie in der
   Konsole (Ruhe schwach · Wahl invers, TODO §2.1). */
.hz.an{color:var(--cyan)}
.hz.locked{color:var(--base02); cursor:default}
#hangarButtons{margin:1.6em 0 0}   /* look: the shared overlay-button rule */
#hangarButtons>button{background:var(--hangarInk)}   /* its own ink over the scene */

/* ══ DIE PAUSE ══════════════════════════════════════════════════════════════
   Vorgabe 09.08.2026: „wenn game nicht aktiv passiert nix · nach 5 min das
   bild schwarz · beim zurück kommen die option zu wählen."

   DIESELBEN REGELN WIE OBEN — schwarz, eine Schriftgröße (js/pause.js holt sie
   aus #tui), alles darunter erbt `1em`. Breite 44ch wie der Namensschirm: die
   Wortmarke ist 41 Zeichen und die zwei Sätze darunter passen daneben.

   Z-INDEX 8980, EINE STUFE UNTER DEM HANGAR und zwei unter dem Namensschirm.
   Die Reihenfolge ist die des Spiels: wer noch keinen Namen hat, hat keine
   Abwesenheit, und wer im Hangar steht, fliegt noch gar nicht. */
/* …and #afk is the same plate (01.09.2026, client/afk.js): black, one type
   size, 44ch. One step lower, because a report has something to say and the
   hold has not. */
#report,#afk{
  position:fixed; inset:0; z-index:8980;
  display:flex; align-items:center; justify-content:center;
  background:#000; color:var(--base1);
  font-family:var(--mono);
  font-size:clamp(9px,2.05vw,16px);     /* nur der Rueckfall, siehe #intro */
  line-height:1.4;
}
#afk{z-index:8979}
#report[hidden],#afk[hidden]{display:none}
#report *,#afk *{font:inherit; letter-spacing:0}
#reportBox,#afkBox{width:44ch; max-width:100%}
#reportMark,#afkMark{
  margin:0 0 2em; white-space:pre; color:var(--base01);
  overflow-x:auto; line-height:1;
}
#reportTime,#afkWhy{margin:0 0 1.6em; white-space:pre; color:var(--cyan)}
/* DER BERICHT. Gescrollt wird er NICHT: was nicht mehr hineinpasst, hat
   js/report.js schon weggelassen und sagt es in der letzten Zeile. */
#reportLog{margin:0 0 1.6em; white-space:pre-wrap; color:var(--base1)}
#reportLog[hidden]{display:none}
#reportLog .warn{color:var(--yellow)}
#reportLog .err{color:var(--red)}
#reportLog .ok,#reportLog .hl{color:var(--cyan)}
#reportLog .dim{color:var(--base01)}
#reportButtons,#afkButtons{display:flex; gap:2ch}
#reportButtons>button,#afkButtons>button{
  background:transparent; border:0; padding:0; cursor:pointer;
  color:var(--cyan);
}
#reportButtons>button:hover,#afkButtons>button:hover{color:var(--base2)}
