/* ===========================================================================
   FERROCADE — one stylesheet, no dependencies, no external requests.
   ---------------------------------------------------------------------------
   Every colour below is the game's own token, converted 1:1 from
   crates/wz-render/src/components.rs (`Color::srgb(r,g,b)` * 255, rounded).
   The page is meant to read as the title screen continued into a browser, so
   when in doubt the answer is "what does the HUD do?", not "what do websites do?".
   =========================================================================== */

/* ---- The one self-hosted face -------------------------------------------
   DejaVu Sans Bold — the face the game installs over Bevy's default (see
   flow::install_font), subset to uppercase + numerals + a little punctuation
   (2.7 KB). The `unicode-range` is not an optimisation here, it is a SAFETY
   NET: anything outside the subset (lowercase, umlaut-lowercase, quotes)
   silently falls back to the system stack instead of rendering tofu. Which is
   also why this family is only ever applied to text that is uppercase by
   construction. -------------------------------------------------------- */
@font-face {
  font-family: "Ferrocade Display";
  src: url("/fonts/ferrocade-display.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020, U+0025, U+002B, U+002C-002F, U+0030-0039, U+003A,
    U+0041-005A, U+00B7, U+00C4, U+00D6, U+00D7, U+00DC, U+2013;
}

:root {
  /* --- Palette: components.rs tokens, verbatim --- */
  --accent:         #4fc3f7; /* TEAL / ACCENT — interaction, the one brand mark */
  --accent-dim:     #2e6180; /* TEAL_DIM — rules, quiet chrome */
  --accent-bright:  #75dbff; /* TEAL_BRIGHT / FOCUS_RING */
  --amber:          #ffbf59; /* AMBER / WARN */
  --good:           #57c780; /* SUCCESS / GOOD */
  --danger:         #e64d52; /* DANGER / CRITICAL */
  --hint:           #99d9ff; /* HINT */

  --surface:        #0d1520; /* SURFACE — the page floor */
  --surface-alt:    #0f1b29; /* SURFACE_ALT — banded sections */
  --surface-raised: #162334; /* SURFACE_RAISED — cards */
  --backdrop:       #04080b; /* SCREEN_BACKDROP — deepest, used in scrims */

  --border:         #1e2b3a; /* BORDER — hairlines */
  --border-strong:  #31455c; /* BORDER_STRONG / BTN_BORDER */

  --text:           #e0f0ff; /* TEXT */
  --text-muted:     #9fb0c0; /* TEXT_MUTED */
  --text-dim:       #73879e; /* TEXT_DIM */

  --btn-idle:       #1b2a3c; /* BTN_IDLE */
  --btn-hover:      #2b435c; /* BTN_HOVER */
  --btn-press:      #0e1824; /* BTN_PRESS */

  /* --- Spacing: the game's PAD_XS..PAD_XL ladder, extended upward for a page
         (a HUD never needs 96 px of air; a landing page does). --- */
  --pad-xs: 3px;
  --pad-sm: 6px;
  --pad-md: 10px;
  --pad-lg: 16px;
  --pad-xl: 24px;
  --pad-2xl: 40px;
  --pad-3xl: 72px;
  --pad-4xl: 112px;

  /* --- Radii: RADIUS_SM / RADIUS / RADIUS_MD / RADIUS_LG --- */
  --r-sm: 4px;
  --r: 6px;
  --r-md: 8px;
  --r-lg: 12px;

  /* --- Type: the 11/13/15/18/24 ramp, scaled ~1.15x because 13 px body copy
         is a HUD decision, not a reading decision. --- */
  --fs-micro: 12px;
  --fs-body: 16px;
  --fs-label: 17px;
  --fs-head: 21px;
  --fs-title: clamp(1.6rem, 1.1rem + 1.6vw, 2.35rem);
  /* The wordmark tops out at 4.5rem = 72 px, a hair over the game's own
     FS_WORDMARK (68 px) — the browser viewport is wider than the menu's lockup
     column, so the mark needs the extra size to hold the same presence. */
  --fs-display: clamp(2rem, 1rem + 3.6vw, 4.5rem);

  /* HOVER_LERP_RATE is 14/s in-game — about 70 ms to settle. Same here, so the
     page's buttons feel like the menu's buttons. */
  --t: 70ms;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);

  --measure: 68ch;
  --shell: 1180px;

  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans", sans-serif;
  --font-display: "Ferrocade Display", var(--font-body);
}

/* ---------------------------------------------------------------- reset -- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; }
}

body {
  margin: 0;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  /* The page floor is never pure black; the game's SCREEN_BACKDROP sits under
     the fold so an over-scroll on iOS shows the world, not white. */
  overscroll-behavior-y: none;
}
img { max-width: 100%; display: block; }
/* The UA stylesheet gives <figure> `margin: 1em 40px`. Every gallery tile is a
   figure, so without this each screenshot sat 80 px narrower than its grid cell —
   77 % of the column on a phone, which read as "the images are the wrong size". */
figure { margin: 0; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-bright); }
h1, h2, h3 { line-height: 1.15; margin: 0; text-wrap: balance; }
p { margin: 0 0 var(--pad-lg); }
p:last-child { margin-bottom: 0; }

:focus-visible {
  /* FOCUS_RING / FOCUS_RING_WIDTH, straight off the token ladder. */
  outline: 2px solid var(--accent-bright);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.shell { width: min(100% - 2 * var(--pad-xl), var(--shell)); margin-inline: auto; }
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--surface-raised); color: var(--text);
  padding: var(--pad-md) var(--pad-lg); border: 1px solid var(--accent);
}
.skip-link:focus { left: var(--pad-lg); top: var(--pad-lg); }

/* ------------------------------------------------------------- lockup ---- */
/* The wordmark, rebuilt from menu.rs `wordmark()`:
     accent bar (0.9em wide, 3 px)  ->  letter-spaced name  ->  2 px ACCENT_DIM
     rule  ->  tagline.
   In Bevy each glyph is its own node and the row's `column_gap` IS the
   tracking; on the web `letter-spacing: 0.16em` is the same 0.16 em, with a
   negative right margin to eat the trailing space CSS adds after the last
   glyph (Bevy's gap sits only BETWEEN glyphs). */
/* `width: fit-content` is load-bearing, not cosmetic: menu.rs notes that the rule
   "stretches to the lockup's own width (the glyph row is the widest child)". As a
   block-level flex container the lockup would be as wide as the page, and the rule
   — which is `align-self: stretch` — would run right across the hero. */
.lockup { display: flex; flex-direction: column; align-items: flex-start; gap: var(--pad-sm); width: fit-content; max-width: 100%; }
.lockup__bar { width: 0.9em; height: 3px; background: var(--accent); font-size: var(--fs-wordmark, inherit); }
.lockup__name {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.16em;
  margin-right: -0.16em;
  line-height: 1;
  color: var(--text);
  /* Drop shadow, not a glow — menu.rs is explicit about why: the backdrop is
     busy key art and the mark must survive both the bright haze and the dark
     facade. offset = 0.05em, colour = near-black with a blue cast. */
  text-shadow: 0 0.05em 0.14em rgba(0, 5, 13, 0.92);
  white-space: nowrap;
}
.lockup__rule { align-self: stretch; height: 2px; background: var(--accent-dim); }
.lockup__tagline {
  color: var(--text-muted);
  letter-spacing: 0.02em;
  margin-top: var(--pad-xs);
  text-shadow: 0 2px 6px rgba(0, 5, 13, 0.9);
}

/* ---------------------------------------------------------------- topbar - */
/* ALWAYS ON SCREEN, including over the key art.
   It used to start translated off the top and slide in once the hero was behind you — the
   reasoning being that a bar on the title art is the one element that says "website" instead of
   "title screen". That cost more than it bought: the landing page is where the nav has to do its
   job, and every link in it (Werkstatt, Bauen, Tester-Login) was invisible until a visitor
   happened to scroll. A menu you have to discover by scrolling is not a menu.
   PANEL_BG is rgba(8,14,21,.95) — the same plate every HUD panel uses. */
.topbar {
  position: fixed; inset: 0 0 auto 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--pad-lg);
  padding: var(--pad-md) max(var(--pad-xl), calc((100vw - var(--shell)) / 2));
  background: rgba(8, 14, 21, 0.92);
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(10px);
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease);
}
/* `is-stuck` is now a NO-OP. Every page still carries it in its markup (and src/builds.ts and
   tools/gen-catalog.mjs still write it), so it stays a valid selector rather than becoming a
   silent typo — but it no longer decides whether the bar exists. Nothing needs to add it, and a
   page that forgets it still shows its menu, which is the failure mode that started this. */
.topbar.is-stuck { }
.topbar__mark {
  font-family: var(--font-display); font-size: var(--fs-micro);
  letter-spacing: 0.28em; margin-right: -0.28em; color: var(--text);
}
.topbar__nav { display: flex; gap: var(--pad-lg); align-items: center; }
.topbar__nav a {
  color: var(--text-muted); font-size: var(--fs-micro);
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: var(--pad-xs) 0; border-bottom: 1px solid transparent;
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease);
}
.topbar__nav a:hover { color: var(--text); border-bottom-color: var(--accent); }
/* Narrow screens drop the in-page anchors, tighten the row, and — if it still does not fit — let it
   wrap onto a second line rather than running off the edge. Wrapping is the important part: the bar
   is permanent now, so an overflowing nav is not something a visitor can scroll past, and the next
   link anyone adds must not be able to push the language switch off screen. */
@media (max-width: 640px) {
  .topbar { padding-block: var(--pad-sm); }
  .topbar__nav a.is-optional { display: none; }
  .topbar__nav { flex-wrap: wrap; justify-content: flex-end; gap: var(--pad-xs) var(--pad-md); }
  .topbar__nav a { letter-spacing: 0.08em; }
  .topbar .langswitch { padding-left: var(--pad-md); margin-left: 0; }
}

/* ---- language toggle ----
   Two three-letter marks with a hairline between them — the same shape as a HUD mode
   switch, not a flag icon and not a dropdown. The current language is not a link, so
   there is never a control that does nothing when you press it. */
.langswitch {
  display: inline-flex; align-items: center; gap: var(--pad-sm);
  font-size: var(--fs-micro); letter-spacing: 0.14em; text-transform: uppercase;
  padding-left: var(--pad-lg); margin-left: var(--pad-xs);
  border-left: 1px solid var(--border);
}
.langswitch a, .langswitch span { padding: var(--pad-xs) 2px; border-bottom: 1px solid transparent; }
.langswitch a { color: var(--text-dim); transition: color var(--t) var(--ease), border-color var(--t) var(--ease); }
.langswitch a:hover { color: var(--text); border-bottom-color: var(--accent); }
.langswitch [aria-current="true"] { color: var(--accent); }
.langswitch .sep { color: var(--border-strong); padding: 0; }
/* In the footer the switch sits in a link row that already has its own rhythm. */
.footer__links .langswitch { border-left-color: var(--border); }

/* ----------------------------------------------------------------- hero -- */
.hero {
  position: relative;
  min-height: min(100svh, 900px);
  display: flex; flex-direction: column; justify-content: center;
  isolation: isolate;
  padding: var(--pad-4xl) 0 var(--pad-3xl);
  overflow: clip;
}
/* Positive z-indices, not negative ones. A child at `z-index: -2` paints behind its
   parent's CONTENT but in front of its background, which is correct per spec and
   still fragile in practice — chromium-headless-shell dropped the plate entirely and
   rendered a black hero. Art 0 / scrim 1 / copy 2 says the same thing with no edge case. */
.hero__art { position: absolute; inset: 0; z-index: 0; }
.hero__art img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 42%; }
.hero__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    /* left wash — carries the lockup over the ruined facade */
    linear-gradient(100deg, rgba(4, 8, 11, 0.9) 0%, rgba(4, 8, 11, 0.55) 34%,
      rgba(4, 8, 11, 0.12) 62%, rgba(4, 8, 11, 0) 82%),
    /* Top hood + the bottom melt INTO the page floor. The last three stops are the
       melt: it has to reach full --surface a few percent BEFORE the edge, otherwise
       the hero ends on a visible tonal step where the art meets the section below. */
    linear-gradient(to bottom, rgba(4, 8, 11, 0.72) 0%, rgba(4, 8, 11, 0.2) 22%,
      rgba(4, 8, 11, 0.05) 42%, rgba(13, 21, 32, 0.5) 68%,
      rgba(13, 21, 32, 0.92) 88%, var(--surface) 97%);
}
/* Scanline tissue at 2 % — the HUD's own texture. Any stronger and it reads as
   a broken screen instead of a display. */
.hero__scrim::after {
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom,
    rgba(224, 240, 255, 0.02) 0 1px, transparent 1px 4px);
  mix-blend-mode: overlay;
}
.hero__inner { position: relative; z-index: 2; }

/* `.hero__lang` used to live here — a corner language switch, needed only because the topbar was
   absent over the key art. The topbar is permanent now and carries the switch itself, so the hero
   markup and these rules are gone rather than kept as a second control doing the same job. */
.hero .lockup__name { font-size: var(--fs-display); }
.hero .lockup__bar { width: calc(0.9 * var(--fs-display)); }
.hero .lockup__tagline { font-size: clamp(1rem, 0.85rem + 0.6vw, 1.28rem); }
.hero__lede {
  max-width: 46ch; margin-top: var(--pad-xl); color: var(--text-muted);
  text-shadow: 0 2px 10px rgba(0, 5, 13, 0.85);
}
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--pad-md); margin-top: var(--pad-xl); }
.hero__meta {
  margin-top: var(--pad-2xl); display: flex; flex-wrap: wrap; gap: var(--pad-lg);
  font-size: var(--fs-micro); color: var(--text-dim);
  letter-spacing: 0.14em; text-transform: uppercase;
}
.hero__meta span { display: inline-flex; align-items: center; gap: var(--pad-sm); }
.hero__meta span::before {
  content: ""; width: 6px; height: 6px; background: var(--accent-dim); transform: rotate(45deg);
}

/* ------------------------------------------------------------- buttons --- */
/* BTN_IDLE fill + BTN_BORDER outline, hover lifts to BTN_HOVER with an ACCENT
   border and a BTN_GLOW_ALPHA (0.34) halo, press drops to BTN_PRESS and
   BTN_PRESS_SCALE (0.97). Same four states, same numbers as button_chrome. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--pad-sm);
  padding: var(--pad-md) var(--pad-xl);
  min-height: 46px;
  font: inherit; font-size: var(--fs-micro);
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text);
  background: var(--btn-idle);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  cursor: pointer;
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease),
    box-shadow var(--t) var(--ease), transform var(--t) var(--ease), color var(--t) var(--ease);
}
.btn:hover { background: var(--btn-hover); border-color: var(--accent); box-shadow: 0 0 0 1px rgba(79, 195, 247, 0.22), 0 6px 24px rgba(79, 195, 247, 0.13); }
.btn:active { background: var(--btn-press); transform: scale(0.97); }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; transform: none; }

/* The ONE primary per screen — a filled-teal pill with near-white text, exactly
   as MenuButtonStyle::Primary describes it. */
.btn--primary {
  background: var(--accent); border-color: var(--accent-bright);
  color: #06121b; font-weight: 700;
}
.btn--primary:hover { background: var(--accent-bright); border-color: #ffffff; color: #04080b; }
.btn--primary:active { background: #3fa9dc; }
.btn--ghost { background: rgba(13, 21, 32, 0.45); }
.btn--small { min-height: 34px; padding: var(--pad-sm) var(--pad-md); letter-spacing: 0.1em; }
.btn--good:hover { border-color: var(--good); box-shadow: 0 0 0 1px rgba(87, 199, 128, 0.28); }
.btn--danger:hover { border-color: var(--danger); box-shadow: 0 0 0 1px rgba(230, 77, 82, 0.28); }

/* ------------------------------------------------------------ sections --- */
.section { padding: var(--pad-4xl) 0; position: relative; }
.section--alt { background: var(--surface-alt); border-block: 1px solid var(--border); }
.eyebrow {
  display: flex; align-items: center; gap: var(--pad-md);
  font-family: var(--font-display); font-size: var(--fs-micro);
  letter-spacing: 0.24em; color: var(--accent);
  margin-bottom: var(--pad-lg);
  font-variant-numeric: tabular-nums;
}
.eyebrow::after { content: ""; height: 1px; flex: 1; background: linear-gradient(to right, var(--accent-dim), transparent); }
.section__title { font-size: var(--fs-title); font-weight: 700; letter-spacing: -0.01em; }
.section__lede { color: var(--text-muted); max-width: var(--measure); margin-top: var(--pad-lg); }

/* pitch -------------------------------------------------------------------- */
.pitch { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: var(--pad-3xl); align-items: start; }
@media (max-width: 860px) { .pitch { grid-template-columns: 1fr; gap: var(--pad-2xl); } }
.pitch__body { color: var(--text-muted); }
.pitch__body strong { color: var(--text); font-weight: 600; }
.triad { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--pad-lg); }
.triad li { border-left: 2px solid var(--accent-dim); padding-left: var(--pad-lg); }
.triad b {
  display: block; font-family: var(--font-display); font-size: var(--fs-head);
  letter-spacing: 0.16em; color: var(--text); font-weight: 700;
}
.triad span { color: var(--text-dim); font-size: var(--fs-micro); line-height: 1.5; display: block; margin-top: var(--pad-xs); }

/* panels / feature cards --------------------------------------------------- */
/* The corner ticks are the HUD's panel chrome (PANEL_HI/PANEL_LO bevel, reduced
   to two L-shaped marks). They are what stops these cards reading as generic
   SaaS tiles. */
.panel {
  position: relative;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--pad-xl);
}
.panel::before, .panel::after {
  content: ""; position: absolute; width: 11px; height: 11px;
  border: 1px solid var(--accent-dim); pointer-events: none;
  transition: border-color var(--t) var(--ease);
}
.panel::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; border-top-left-radius: var(--r-md); }
.panel::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; border-bottom-right-radius: var(--r-md); }

.features { display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); gap: var(--pad-lg); margin-top: var(--pad-2xl); }
.feature { transition: border-color var(--t) var(--ease), transform var(--t) var(--ease), background var(--t) var(--ease); }
.feature:hover { border-color: var(--border-strong); background: #18283a; transform: translateY(-2px); }
.feature:hover::before, .feature:hover::after { border-color: var(--accent); }
.feature__idx {
  font-family: var(--font-display); font-size: var(--fs-micro); color: var(--accent-dim);
  letter-spacing: 0.2em; font-variant-numeric: tabular-nums;
}
.feature h3 { font-size: var(--fs-head); margin: var(--pad-md) 0 var(--pad-md); }
.feature p { color: var(--text-muted); font-size: 15px; margin: 0; }

/* fact strip --------------------------------------------------------------- */
.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px; margin-top: var(--pad-2xl);
  background: var(--border); border: 1px solid var(--border); border-radius: var(--r-md);
  overflow: hidden;
}
.fact { background: var(--surface); padding: var(--pad-xl) var(--pad-lg); text-align: center; }
.fact b {
  display: block; font-family: var(--font-display); font-size: 2.1rem; line-height: 1;
  color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: 0.04em;
}
.fact span {
  display: block; margin-top: var(--pad-sm); font-size: var(--fs-micro);
  color: var(--text-dim); letter-spacing: 0.14em; text-transform: uppercase;
}

/* gallery ------------------------------------------------------------------ */
/* No lightbox, no carousel: the first shot runs full width as the establishing
   plate, the rest sit in a two-up grid, and a click opens the 2560 px file in a
   new tab. Fewer moving parts than a modal, and it survives JavaScript being off. */
.gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--pad-lg); margin-top: var(--pad-2xl); }
.gallery > .shot--wide { grid-column: 1 / -1; }
@media (max-width: 720px) { .gallery { grid-template-columns: 1fr; } }

.shot {
  position: relative; display: block; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--backdrop);
  transition: border-color var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.shot img { width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover; transition: transform 260ms var(--ease); }
.shot:hover { border-color: var(--accent); box-shadow: 0 0 0 1px rgba(79, 195, 247, 0.2), 0 10px 40px rgba(0, 0, 0, 0.45); }
.shot:hover img { transform: scale(1.015); }
.shot figcaption {
  position: absolute; inset: auto 0 0 0;
  padding: var(--pad-2xl) var(--pad-lg) var(--pad-md);
  font-size: var(--fs-micro); color: var(--text-muted); line-height: 1.5;
  background: linear-gradient(to top, rgba(4, 8, 11, 0.94) 0%, rgba(4, 8, 11, 0.72) 45%, rgba(4, 8, 11, 0) 100%);
}
.shot__zoom {
  position: absolute; top: var(--pad-md); right: var(--pad-md);
  font-family: var(--font-display); font-size: 10px; letter-spacing: 0.18em;
  color: var(--text); background: rgba(8, 14, 21, 0.8);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  padding: 3px var(--pad-sm);
  opacity: 0; transition: opacity var(--t) var(--ease), border-color var(--t) var(--ease);
}
.shot:hover .shot__zoom, .shot:focus-visible .shot__zoom { opacity: 1; border-color: var(--accent); }

/* ------------------------------------------------------------------ beta -- */
.beta { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: var(--pad-3xl); align-items: start; }
@media (max-width: 900px) { .beta { grid-template-columns: 1fr; gap: var(--pad-2xl); } }

.form { display: grid; gap: var(--pad-lg); }
.field { display: grid; gap: var(--pad-sm); }
.field > label {
  font-size: var(--fs-micro); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text-dim);
}
.field .opt { color: var(--border-strong); letter-spacing: 0.08em; }
input[type="email"], input[type="text"], input[type="password"], textarea, select {
  width: 100%; font: inherit; font-size: 15px; color: var(--text);
  background: var(--btn-press);
  border: 1px solid var(--border-strong); border-radius: var(--r);
  padding: var(--pad-md) var(--pad-lg);
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
input::placeholder, textarea::placeholder { color: #4a5b70; }
input:hover, textarea:hover, select:hover { border-color: #3d5470; }
input:focus, textarea:focus, select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(79, 195, 247, 0.35);
  background: #0b1622;
}
textarea { resize: vertical; min-height: 88px; }
select {
  appearance: none;
  /* A real file, not a `data:` URI: the CSP is `img-src 'self'` with no `data:`
     escape hatch, and keeping it that way is worth one 150-byte request that is
     cached with everything else. (This is also the only icon on the site.) */
  background-image: url("/img/chevron.svg");
  background-repeat: no-repeat; background-position: right var(--pad-lg) center;
  padding-right: var(--pad-3xl);
}
select option { background: var(--surface-raised); color: var(--text); }

/* The honeypot. Off-screen rather than display:none — some bots skip hidden
   inputs but happily fill anything the DOM still lays out. aria-hidden +
   tabindex -1 keep it away from humans and screen readers. */
.hp { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; overflow: hidden; }

.formnote { font-size: var(--fs-micro); color: var(--text-dim); line-height: 1.55; }
.formnote a { color: var(--hint); text-decoration: underline; text-underline-offset: 2px; }

/* Result banner: tone comes from the token set — GOOD for done, WARN for
   "already on the list", CRITICAL for a real error. */
.result { display: none; border: 1px solid var(--border-strong); border-left-width: 3px; border-radius: var(--r); padding: var(--pad-lg); font-size: 15px; }
.result.is-shown { display: block; }
.result[data-tone="good"] { border-left-color: var(--good); background: rgba(87, 199, 128, 0.07); }
.result[data-tone="warn"] { border-left-color: var(--amber); background: rgba(255, 191, 89, 0.07); }
.result[data-tone="bad"] { border-left-color: var(--danger); background: rgba(230, 77, 82, 0.07); }
.result[data-tone="info"] { border-left-color: var(--accent); background: rgba(79, 195, 247, 0.07); }
.result h3 { font-size: var(--fs-label); margin-bottom: var(--pad-sm); }
.result p { color: var(--text-muted); margin: 0; }
.result p + p { margin-top: var(--pad-md); }

.channels { display: grid; gap: var(--pad-md); margin-top: var(--pad-xl); }
.channel { display: flex; gap: var(--pad-lg); align-items: baseline; border-top: 1px solid var(--border); padding-top: var(--pad-md); }
.channel b { font-family: var(--font-display); font-size: var(--fs-micro); letter-spacing: 0.2em; color: var(--accent); min-width: 90px; }
.channel span { color: var(--text-muted); font-size: 15px; }

/* ---------------------------------------------------------------- footer -- */
.footer { background: var(--backdrop); border-top: 1px solid var(--border); padding: var(--pad-3xl) 0 var(--pad-2xl); }
.footer__grid { display: flex; flex-wrap: wrap; gap: var(--pad-xl); justify-content: space-between; align-items: flex-end; }
.footer .lockup__name { font-size: 1.15rem; }
.footer .lockup__bar { width: calc(0.9 * 1.15rem); height: 2px; }
.footer .lockup__tagline { font-size: var(--fs-micro); }
.footer__links { display: flex; flex-wrap: wrap; gap: var(--pad-lg); font-size: var(--fs-micro); letter-spacing: 0.14em; text-transform: uppercase; }
.footer__links a { color: var(--text-dim); }
.footer__links a:hover { color: var(--accent); }
.footer__fine { margin-top: var(--pad-xl); font-size: var(--fs-micro); color: #4a5b70; letter-spacing: 0.04em; }

/* ------------------------------------------------------------- subpages --- */
/* Impressum / Datenschutz / Status / Admin share one narrow document shell. */
.page { padding: calc(var(--pad-4xl) + 20px) 0 var(--pad-4xl); }
.page--narrow .shell { width: min(100% - 2 * var(--pad-xl), 760px); }
.doc h2 { font-size: var(--fs-head); margin: var(--pad-2xl) 0 var(--pad-md); }
.doc h2:first-of-type { margin-top: var(--pad-xl); }
.doc p, .doc li { color: var(--text-muted); }
.doc ul { padding-left: 1.15rem; margin: 0 0 var(--pad-lg); }
.doc li { margin-bottom: var(--pad-sm); }
.doc dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--pad-sm) var(--pad-xl); margin: 0 0 var(--pad-lg); }
.doc dt { color: var(--text-dim); font-size: var(--fs-micro); letter-spacing: 0.12em; text-transform: uppercase; padding-top: 3px; }
.doc dd { margin: 0; color: var(--text-muted); }
/* Unfilled legal fields are marked, not faked. */
.todo {
  color: var(--amber); background: rgba(255, 191, 89, 0.09);
  border: 1px dashed rgba(255, 191, 89, 0.4); border-radius: var(--r-sm);
  padding: 1px var(--pad-sm); font-size: var(--fs-micro); letter-spacing: 0.06em;
}
.backlink { display: inline-flex; align-items: center; gap: var(--pad-sm); font-size: var(--fs-micro); letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-dim); }
.backlink:hover { color: var(--accent); }

/* -------------------------------------------------------------- admin ----- */
.admin-bar { display: flex; flex-wrap: wrap; gap: var(--pad-lg); align-items: center; justify-content: space-between; margin-bottom: var(--pad-xl); }
/* The action banner sits between the toolbar and the table; without this it butts
   straight against the table head and reads as part of it. */
#adminresult.is-shown { margin-bottom: var(--pad-lg); }
.counts { display: flex; gap: var(--pad-lg); flex-wrap: wrap; }
.count { border: 1px solid var(--border); border-radius: var(--r); padding: var(--pad-sm) var(--pad-lg); background: var(--surface-raised); }
.count b { font-family: var(--font-display); font-variant-numeric: tabular-nums; font-size: var(--fs-head); display: block; line-height: 1.1; }
.count span { font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-dim); }
.count[data-k="pending"] b { color: var(--amber); }
.count[data-k="approved"] b { color: var(--good); }
.count[data-k="rejected"] b { color: var(--text-dim); }

.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-raised); }
table { border-collapse: collapse; width: 100%; font-size: 14px; min-width: 860px; }
th, td { text-align: left; padding: var(--pad-md) var(--pad-lg); border-bottom: 1px solid var(--border); vertical-align: top; }
th { font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-dim); font-weight: 600; background: var(--surface-alt); position: sticky; top: 0; }
tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: #18283a; }
td.num, td .when { font-variant-numeric: tabular-nums; color: var(--text-dim); white-space: nowrap; }
td .note { color: var(--text-dim); font-size: 13px; display: block; max-width: 34ch; }
.pill { display: inline-block; font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; padding: 2px var(--pad-sm); border-radius: 999px; border: 1px solid var(--border-strong); color: var(--text-muted); }
.pill[data-s="pending"] { color: var(--amber); border-color: rgba(255, 191, 89, 0.45); }
.pill[data-s="approved"] { color: var(--good); border-color: rgba(87, 199, 128, 0.45); }
.pill[data-s="rejected"] { color: var(--text-dim); }
.rowacts { display: flex; gap: var(--pad-sm); }

/* ----------------------------------------------------------- utilities --- */
/* A deliberately tiny set. They exist because the CSP is `style-src 'self'` with
   no 'unsafe-inline', which means a `style="…"` attribute anywhere in the markup
   is silently DROPPED by the browser — the layout would quietly differ from what
   the source says. Every one-off spacing decision therefore has to be a class. */
/* Contact-address slots (see js/contact.js) are empty until the script fills them.
   `:empty` hides the placeholder in the meantime, so the footer does not carry a
   phantom flex gap and the Impressum does not show a stray space before its <noscript>. */
[data-mail]:empty { display: none; }

.u-mt-lg { margin-top: var(--pad-xl); }
.u-mt-xl { margin-top: var(--pad-2xl); }
.u-mb-xl { margin-bottom: var(--pad-2xl); }
.u-prose { color: var(--text-muted); max-width: var(--measure); }
.u-btnrow { display: flex; gap: var(--pad-md); flex-wrap: wrap; margin-top: var(--pad-xl); }
.u-form-panel { max-width: 460px; }
/* The lockup at document scale (footer of a subpage, 404, /danke). */
.lockup--sm .lockup__name { font-size: 1.6rem; }
.lockup--sm .lockup__bar { width: calc(0.9 * 1.6rem); }
.dl-btn { margin-top: var(--pad-sm); align-self: flex-start; }

/* ---------------------------------------------------------------- /builds -- */
/* The release feed. Rendered by the Worker (src/builds.ts), not shipped as a file — so these
   are the only styles on the site whose markup lives in TypeScript. Everything reuses the
   existing tokens; nothing here introduces a new colour or a new spacing value. */
.builds__note { font-size: var(--fs-micro); color: var(--text-dim); margin-top: var(--pad-md); }
.builds__hint { font-size: var(--fs-micro); color: var(--text-dim); max-width: 460px; }

/* Truncated access code in the admin table — recognisable, not readable off a screen. */
.codefrag {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--fs-micro);
  color: var(--text-dim); background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 2px var(--pad-sm); align-self: center;
}

.release__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--pad-md) var(--pad-lg);
  border-bottom: 1px solid var(--border); padding-bottom: var(--pad-md);
}
/* The build number is the thing a tester is scanning for — display face, tabular figures,
   the same treatment every count in the HUD gets. */
.release__num {
  font-family: var(--font-display); font-size: var(--fs-title); font-weight: 700;
  letter-spacing: 0.06em; color: var(--accent); font-variant-numeric: tabular-nums; margin: 0;
}
.release__date { color: var(--text-dim); font-size: var(--fs-micro); margin: 0; }
.release__date time { font-variant-numeric: tabular-nums; }

.release__notes { color: var(--text-muted); max-width: var(--measure); margin-top: var(--pad-lg); }
.release__notes p { margin: var(--pad-md) 0 0; }
.release__notes strong { color: var(--text); font-weight: 600; }
.release__notes ul { margin: var(--pad-md) 0 0; padding-left: var(--pad-xl); }
.release__notes li { margin-top: var(--pad-xs); }
.release__notes code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 1px var(--pad-xs);
}
/* The CHANGELOG's "everyone must update" warning. Amber, because that is what WARN means
   everywhere else in this product. */
.release__notes blockquote {
  margin: var(--pad-lg) 0 0; padding: var(--pad-md) var(--pad-lg);
  border-left: 2px solid var(--amber); background: var(--surface);
}
.release__notes blockquote p { margin: 0; color: var(--text); }

.release__dl { margin-top: var(--pad-xl); padding-top: var(--pad-lg); border-top: 1px solid var(--border); }
.release__dlhead {
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--text-dim); margin: 0;
}
.release__soon { color: var(--text-dim); font-size: var(--fs-micro); margin: var(--pad-sm) 0 0; }
.release__mac { color: var(--text-muted); font-size: 15px; margin: var(--pad-md) 0 0; }

/* --------------------------------------------------------- narrow phones -- */
@media (max-width: 560px) {
  :root { --pad-3xl: 52px; --pad-4xl: 72px; }
  .shell { width: min(100% - 2 * var(--pad-lg), var(--shell)); }
  /* The key art is a wide landscape plate; on a portrait phone `cover` throws
     away the sides, so bias the crop toward the convoy instead of the empty
     left-hand ruin. */
  .hero__art img { object-position: 58% 46%; }
  .hero { min-height: min(94svh, 760px); padding-top: 96px; }
  .hero__actions .btn { flex: 1 1 100%; }
  .hero__meta { gap: var(--pad-sm) var(--pad-lg); }
  .footer__grid { align-items: flex-start; }
}

/* ----------------------------------------------------------------- radar -- */
/* Discovery view in /admin. No chart library — 14 bars and three lists do not justify a
   dependency, and a CDN would need a CSP exception this site does not grant. */
/* `[hidden]` is only a UA rule (`display: none`), so ANY class selector that sets `display`
   silently beats it and the element stays on screen. The wave banner did exactly that: with no
   wave active it rendered as an empty red box. This one line is the fix, and the `*[hidden]`
   rule under it stops the next component from repeating the mistake. */
[hidden] { display: none !important; }

.wave {
  display: flex; flex-direction: column; gap: var(--pad-xs);
  border: 1px solid var(--danger); border-left-width: 3px; background: rgba(230, 77, 82, 0.08);
  padding: var(--pad-lg); border-radius: var(--r); margin-bottom: var(--pad-xl);
}
.wave b { color: var(--danger); font-size: var(--fs-head); }
.wave span { color: var(--text-muted); font-size: var(--fs-micro); }

.radar { display: grid; gap: var(--pad-xl); margin-bottom: var(--pad-2xl); }
.radar__chart {
  display: flex; align-items: flex-end; gap: var(--pad-xs); height: 120px;
  border-bottom: 1px solid var(--border); padding-bottom: var(--pad-lg);
}
.spark__col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; position: relative; }
.spark__fill { background: var(--accent); border-radius: var(--r-sm) var(--r-sm) 0 0; min-height: 0; transition: height var(--t) var(--ease); }
.spark__day { position: absolute; bottom: -18px; width: 100%; text-align: center; font-size: 10px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

.radar__breakdowns { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--pad-xl); margin-top: var(--pad-lg); }
.radar__col h3, .radar__first h3 {
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--text-dim); margin: 0 0 var(--pad-md);
}
.radar__hint { text-transform: none; letter-spacing: 0; color: var(--text-dim); font-weight: 400; }

.bar { display: grid; grid-template-columns: minmax(70px, 34%) 1fr auto; gap: var(--pad-sm); align-items: center; margin-top: var(--pad-xs); }
.bar__label { font-size: var(--fs-micro); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar__track { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm); height: 12px; overflow: hidden; }
.bar__fill { background: var(--accent-dim); height: 100%; }
.bar__n { font-size: var(--fs-micro); color: var(--text); font-variant-numeric: tabular-nums; min-width: 3ch; text-align: right; }

.firstrow { display: flex; flex-wrap: wrap; gap: var(--pad-sm) var(--pad-lg); align-items: baseline; padding: var(--pad-sm) 0; border-bottom: 1px solid var(--border); }
.firstrow b { color: var(--accent); }
.firstrow span { color: var(--text-dim); font-size: var(--fs-micro); }

/* ------------------------------------------------------- catalog pages --- */
/* /werkstatt and /bauen and their English mirrors. The markup is GENERATED —
   tools/gen-catalog.mjs writes it from the sim's own catalog — so nothing here may depend on
   hand-authored structure. Everything reuses the existing tokens; no new colour, no new spacing. */

/* ---- the bar ladder ----
   These 21 classes exist because of the CSP. `style-src 'self'` has no 'unsafe-inline', so a
   `style="width:73%"` attribute in generated markup is DROPPED by the browser and every bar would
   render empty — the one failure mode that looks like a design choice instead of a bug. The admin
   radar gets away with inline widths because a script sets them through the CSSOM, which the policy
   does not cover; a static page has no script to do that. 5 % steps, which is finer than the eye
   reads off a 120 px track. */
.lvl-0  { width: 0; }
.lvl-1  { width: 5%; }
.lvl-2  { width: 10%; }
.lvl-3  { width: 15%; }
.lvl-4  { width: 20%; }
.lvl-5  { width: 25%; }
.lvl-6  { width: 30%; }
.lvl-7  { width: 35%; }
.lvl-8  { width: 40%; }
.lvl-9  { width: 45%; }
.lvl-10 { width: 50%; }
.lvl-11 { width: 55%; }
.lvl-12 { width: 60%; }
.lvl-13 { width: 65%; }
.lvl-14 { width: 70%; }
.lvl-15 { width: 75%; }
.lvl-16 { width: 80%; }
.lvl-17 { width: 85%; }
.lvl-18 { width: 90%; }
.lvl-19 { width: 95%; }
.lvl-20 { width: 100%; }

/* The catalog bar is the radar's bar minus its number column — these pages show WHERE a part sits,
   never what it costs. */
.bars { display: grid; gap: var(--pad-xs); margin-top: var(--pad-md); }
.bars .bar { grid-template-columns: minmax(84px, 46%) 1fr; margin-top: 0; }
.bars .bar__label { font-size: 11px; color: var(--text-dim); letter-spacing: 0.04em; }
.bars .bar__track { height: 8px; }
/* `display: block` is load-bearing, not tidiness. The generator writes the fill as a `<span>` inside
   the track, and an INLINE box ignores width — every bar rendered at the same hairline length, which
   reads as a styling choice rather than as the bug it is. The track itself escapes this because it
   is a grid item and therefore blockified for free; its child is not. */
.bars .bar__fill { display: block; background: linear-gradient(to right, var(--accent-dim), var(--accent)); }
/* A recon or supply vehicle genuinely has no gun. An empty track reads as a broken bar, so that one
   row says so in words instead. */
.bar--none .bar__none { font-size: 11px; color: var(--border-strong); letter-spacing: 0.04em; }

/* ---- part cards ---- */
.parts { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: var(--pad-lg); margin-top: var(--pad-xl); }
.part {
  display: flex; flex-direction: column;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: border-color var(--t) var(--ease), transform var(--t) var(--ease);
}
.part:hover { border-color: var(--border-strong); transform: translateY(-2px); }
/* The plate is --backdrop, not --surface-raised: the renders sit on their own dark studio sweep and
   the deepest token is the one that disappears behind them. */
.part__plate { position: relative; aspect-ratio: 1; background: var(--backdrop); border-bottom: 1px solid var(--border); }
.part__plate img { width: 100%; height: 100%; object-fit: cover; }
/* Parts whose every candidate render is clipped or broken (see gen-parts.sh). An honest empty plate,
   marked as such, beats a photo that misrepresents the model. */
.part__plate--pending {
  display: flex; align-items: center; justify-content: center;
  background: repeating-linear-gradient(135deg, var(--backdrop) 0 8px, #071019 8px 16px);
}
.part__plate--pending span {
  font-family: var(--font-display); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--border-strong); border: 1px dashed var(--border-strong); border-radius: var(--r-sm);
  padding: var(--pad-xs) var(--pad-sm); text-align: center;
}
.part__body { padding: var(--pad-lg); display: flex; flex-direction: column; flex: 1; }
.part__name { font-size: var(--fs-label); line-height: 1.25; }
.part__tags { display: flex; flex-wrap: wrap; gap: var(--pad-sm); margin: var(--pad-sm) 0 0; }
.part__tags span {
  font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-dim);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px var(--pad-sm);
}
.part__blurb { color: var(--text-muted); font-size: 14px; line-height: 1.5; margin: var(--pad-md) 0 auto; }
.part__blurb + .bars { margin-top: var(--pad-lg); }

/* A shelf heading inside a section — the turret groups are the designer's own role tabs. */
.shelf {
  font-family: var(--font-display); font-size: var(--fs-micro); letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--text-dim);
  margin: var(--pad-2xl) 0 0; padding-bottom: var(--pad-sm);
  border-bottom: 1px solid var(--border);
}
.shelf--tight { margin-top: 0; margin-bottom: var(--pad-md); }

/* The honesty line under every section of concept art. Amber-free on purpose: it is a note about
   provenance, not a warning. */
.refnote {
  color: var(--text-dim); font-size: var(--fs-micro); line-height: 1.55;
  max-width: var(--measure); margin-top: var(--pad-md);
  border-left: 2px solid var(--accent-dim); padding-left: var(--pad-md);
}

/* ---- the three worked designs ---- */
.designs { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--pad-lg); margin-top: var(--pad-2xl); }
.design { display: flex; flex-direction: column; }
.design__kicker { font-family: var(--font-display); font-size: var(--fs-micro); letter-spacing: 0.2em; color: var(--accent); margin: 0; }
.design__name { font-size: var(--fs-head); margin: var(--pad-sm) 0 var(--pad-lg); }
.design__blurb { color: var(--text-muted); font-size: 14px; line-height: 1.55; margin: var(--pad-lg) 0 auto; }
/* Hull + drive + turret, spelled out as the sum it is. */
.recipe { display: flex; align-items: flex-start; gap: var(--pad-sm); }
.recipe__part { flex: 1; min-width: 0; text-align: center; }
.recipe__part img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  background: var(--backdrop); border: 1px solid var(--border); border-radius: var(--r);
}
.recipe__part span {
  display: block; margin-top: var(--pad-sm); font-size: 10px; line-height: 1.35;
  color: var(--text-dim); letter-spacing: 0.06em;
}
.recipe__plus { color: var(--border-strong); font-size: var(--fs-head); line-height: 1; padding-top: 22%; }
.design .bars { margin-top: var(--pad-lg); }

/* ---- modules + hardpoint components: fittings, not silhouettes, so no art ---- */
.fittings { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--pad-lg); margin-top: var(--pad-2xl); align-items: start; }
.fitting { display: grid; gap: 2px; padding: var(--pad-md) 0; border-top: 1px solid var(--border); }
.fitting:first-of-type { border-top: 0; padding-top: 0; }
.fitting b { font-size: 15px; color: var(--text); font-weight: 600; }
.fitting span { color: var(--text-muted); font-size: 13px; line-height: 1.5; }
.fitting__class {
  display: inline-block; margin-right: var(--pad-sm);
  font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent-dim);
}

.analyse__title { font-size: var(--fs-head); }
.analyse__body { color: var(--text-muted); max-width: var(--measure); margin: var(--pad-md) 0 0; }

/* The closing beta pitch, shared by both catalog pages. */
.cta { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--pad-2xl); align-items: center; }
@media (max-width: 860px) { .cta { grid-template-columns: 1fr; gap: var(--pad-xl); } }
.cta .u-btnrow { margin-top: 0; }

/* The nav entry for the page you are already on: present, but not a control that does nothing —
   the same reasoning as the language switch's current-language mark. */
.topbar__nav .is-here {
  color: var(--accent); font-size: var(--fs-micro); letter-spacing: 0.14em; text-transform: uppercase;
  padding: var(--pad-xs) 0; border-bottom: 1px solid var(--accent-dim);
}

@media (max-width: 560px) {
  .parts { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--pad-md); }
  .part__body { padding: var(--pad-md); }
  .part__blurb { font-size: 13px; }
  /* Two cards across a phone leaves ~80 px for a label column, and "Panzerung therm." ellipsises to
     "Panzerung th…" in it. Stack the label over its own track instead — taller, but every row still
     says what it measures. */
  .bars .bar { grid-template-columns: 1fr; gap: 3px; }
  .bars .bar__label { overflow: visible; white-space: normal; }
  .bar--none { display: flex; gap: var(--pad-sm); align-items: baseline; }
}

/* ---------------------------------------------------------- title music -- */
/* Fixed HUD chip, same plate as the topbar. The three bars are the playing
   state — they freeze when the bed is off, so the control never needs a
   second word and never needs an icon file (CSP: no data: URIs). */
.music {
  position: fixed;
  z-index: 40;
  left: max(var(--pad-lg), env(safe-area-inset-left, 0px));
  bottom: max(var(--pad-lg), env(safe-area-inset-bottom, 0px));
  display: inline-flex;
  align-items: center;
  gap: var(--pad-md);
  margin: 0;
  padding: var(--pad-sm) var(--pad-lg);
  min-height: 36px;
  font: inherit;
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-muted);
  background: rgba(8, 14, 21, 0.92);
  border: 1px solid var(--border);
  border-radius: var(--r);
  cursor: pointer;
  backdrop-filter: blur(10px);
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease),
    box-shadow var(--t) var(--ease), background var(--t) var(--ease);
}
.music:hover {
  color: var(--text);
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(79, 195, 247, 0.22);
}
.music:active { transform: scale(0.97); }
.music.is-on { color: var(--text); border-color: var(--accent-dim); }
.music__eq {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
  width: 12px;
}
.music__eq span {
  display: block;
  width: 2px;
  height: 3px;
  background: var(--text-dim);
  transform-origin: bottom;
}
.music.is-on .music__eq span {
  background: var(--accent);
  animation: musicEq 0.9s ease-in-out infinite;
}
.music.is-on .music__eq span:nth-child(2) { animation-delay: -0.3s; }
.music.is-on .music__eq span:nth-child(3) { animation-delay: -0.6s; }
@keyframes musicEq {
  0%, 100% { height: 3px; }
  50% { height: 12px; }
}
@media (prefers-reduced-motion: reduce) {
  .music.is-on .music__eq span { animation: none; height: 8px; }
}
