/* ============================================
   ECHOBORN — Warframe Clan Site
   Dark / Gold theme
   ============================================ */

/* ═══════════════════════════════════════════════════════════════════════════
   RELAY PLATING — the design system (2026-08-30)
   Concept approved before porting. The UX did not change; this is the skin.

   The problem was never the colours, it was the COUNT: 84 distinct font-sizes,
   63 transitions, 31 letter-spacings, 28 shadows, 11 breakpoints, and
   --font-head identical to --font-body. Premium is mostly the ABSENCE of
   ad-hoc values. Everything below is one scale, one rhythm, one motion
   vocabulary — use these tokens and never invent a neighbouring value.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  /* ── ground: TRUE NEUTRAL (2026-09-07) ───────────────────────────────────
     ⚠️ THIS COMMENT USED TO ARGUE THE OPPOSITE, and the argument was not
     stupid — it read: "the neutrals carry a faint blue-violet bias on purpose;
     a pure grey next to this gold goes muddy, while a cool dark makes #d4af37
     read as warm metal." Simultaneous contrast is real and that holds on a page
     where the ONLY colour is the accent.

     ⛔ IT STOPPED HOLDING WHEN THE SITE GREW ITS OWN ART. Eleven drawn banners
     and five cycle cards now put ice blue, void blue, Zariman purple and
     Duviri rose on the same screens. Against those, a blue-violet ground is not
     a foil — it JOINS them, and everything reads as one muddled wash. Stephan,
     looking at the real page rather than the theory: "it's kind of a purple,
     dark, bluish-black, and it's breaking too much with the other colours…
     it's an issue across the board."

     So the ground is hue-free and the CHROMA IS SPENT DELIBERATELY: gold for
     the accent, and the drawn environments for themselves. A neutral ground is
     what lets both of those read as chosen instead of competing.

     ⚠️ EVERY VALUE BELOW HOLDS ITS OLD RELATIVE LUMINANCE to five decimal
     places — the hue moved and nothing else did, so not one contrast ratio on
     the site changed and no text became harder to read. That is what made a
     site-wide colour migration a safe edit rather than a redesign. */
  --bg:        #060606;   /* void  — page ground        (was #06060a) */
  --bg-alt:    #0f0f0f;   /* hull  — section bands      (was #0e0e15) */
  --panel:     #171717;   /* plate — cards and panels   (was #16161f) */
  --panel-2:   #1e1e1e;   /* raised plate               (was #1d1d28) */

  --gold:        #d4af37;
  --gold-bright: #f4d97a;
  --gold-dim:    #8a6f28;

  --panel-border: rgba(212, 175, 55, 0.20);
  /* ⛔ --etch MUST DERIVE FROM THE ACCENT, NOT HARDCODE IT. Six colour themes
     ([data-theme="corpus"] etc., an Operator perk) each override --gold and
     --panel-border. A literal rgba(212,175,55,…) here would have left every
     hairline and plate edge GOLD on a Corpus-blue or Grineer-orange account —
     the accent changing everywhere except the one device the whole design is
     built from. Aliasing --panel-border means every theme carries it for free.
     color-mix gets a plain-rgba fallback declared first, so a browser without
     it keeps the gold default rather than losing the colour entirely. */
  --etch:      var(--panel-border);
  --etch-soft: rgba(212, 175, 55, 0.09);
  --etch-soft: color-mix(in srgb, var(--gold) 9%, transparent);
  --etch-lit:  rgba(212, 175, 55, 0.55);
  --etch-lit:  color-mix(in srgb, var(--gold) 55%, transparent);
  --etch-mid:  rgba(212, 175, 55, 0.45);
  --etch-mid:  color-mix(in srgb, var(--gold) 45%, transparent);
  --hairline:  rgba(255, 255, 255, 0.06);

  --text:     #eaeaea;
  --text-dim: #969696;
  /* ⛔ --muted was used 24 times in this file and NEVER DEFINED. `color:
     var(--muted)` with no fallback is invalid at computed-value time, so those
     24 places silently inherited full body colour instead of dimming — text
     that should recede simply did not, on every page, for months. Defined now.
     CLAUDE.md documented it as a token the whole time, which is why nobody
     went looking. */
  --muted:    #636363;

  /* ── type ───────────────────────────────────────────────────────────────
     Three voices where there was one. Real fallback stacks: if the font host
     is blocked the site degrades to exactly the old system look. */
  --font-head: "Chakra Petch", "Segoe UI", "Helvetica Neue", system-ui, -apple-system, Arial, sans-serif;
  --font-body: "IBM Plex Sans", "Segoe UI", "Helvetica Neue", system-ui, -apple-system, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas, monospace;

  /* ── the scale — eight steps, and nothing between them ───────────────── */
  --t-micro: 0.6875rem;  /* 11 — MR, timers, stamps */
  --t-label: 0.75rem;    /* 12 — // KICKERS, buttons */
  --t-sm:    0.875rem;   /* 14 — card copy, comments */
  --t-base:  1rem;       /* 16 — body */
  --t-lead:  1.25rem;    /* 20 — standfirst */
  --t-h3:    1.75rem;    /* 28 */
  --t-h2:    2.5rem;     /* 40 */
  --t-h1:    3.75rem;    /* 60 */

  /* ── rhythm — 4px base ──────────────────────────────────────────────── */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px;
  --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px;

  /* ── motion — two curves, not sixty-three ───────────────────────────── */
  --swift: 160ms cubic-bezier(0.4, 0, 0.2, 1);
  --glide: 320ms cubic-bezier(0.22, 0.61, 0.36, 1);

  /* ── tracking — four steps. Uppercase labels need air; body does not. ── */
  --track-tight: 0.05em;   /* headings, large display */
  --track:       0.09em;   /* default label */
  --track-wide:  0.14em;   /* // KICKERS, buttons */
  --track-max:   0.20em;   /* the widest stamps */

  /* the Orokin notch — the one corner treatment on the site */
  --notch: 14px;

  --max-width: 1440px;
}

/* ── Color themes (Operator+ perk) ─────────────────────── */
[data-theme="silver"] {
  --gold: #b8b5ac;
  --gold-bright: #d4d1c8;
  --gold-dim: #7e7b74;
  --panel-border: rgba(184,181,172,0.18);
}
[data-theme="corpus"] {
  --gold: #4ab0cc;
  --gold-bright: #6ccce8;
  --gold-dim: #2a8099;
  --panel-border: rgba(74,176,204,0.18);
}
[data-theme="grineer"] {
  --gold: #d45c18;
  --gold-bright: #f07840;
  --gold-dim: #9a3e0e;
  --panel-border: rgba(212,92,24,0.18);
}
[data-theme="void"] {
  --gold: #8866d8;
  --gold-bright: #aa88f5;
  --gold-dim: #5c44a0;
  --panel-border: rgba(136,102,216,0.18);
}
[data-theme="steel"] {
  --gold: #7898b0;
  --gold-bright: #98b8cc;
  --gold-dim: #507088;
  --panel-border: rgba(120,152,176,0.18);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: 400;
  line-height: 1.6;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

img { max-width: 100%; display: block; }

a { color: var(--gold); text-decoration: none; transition: color var(--swift); }
a:hover { color: var(--gold-bright); }

::selection { background: var(--gold-dim); color: #000; }

/* ============ Section heads ============ */
.section-head {
  text-align: center;
  max-width: 860px;
  margin: 0 auto clamp(2.5rem, 4vw, 4rem);
  padding: 0 1.5rem;
}

.section-kicker {
  font-family: var(--font-head);
  color: var(--gold);
  letter-spacing: var(--track-max);
  font-size: clamp(0.72rem, 0.66rem + 0.18vw, 0.85rem);
  margin-bottom: 0.85rem;
  text-transform: uppercase;
}

.section-title {
  font-family: var(--font-head);
  font-weight: 700;
  /* Fluid: grows through laptop → hi-res instead of freezing at 2.8rem */
  font-size: clamp(1.9rem, 0.9rem + 2.6vw, 3.6rem);
  line-height: 1.12;
  letter-spacing: var(--track);
  text-transform: uppercase;
  margin-bottom: 0.9rem;
}

.section-desc {
  color: var(--text-dim);
  font-size: clamp(1rem, 0.92rem + 0.4vw, 1.28rem);
  font-weight: 300;
  line-height: 1.65;
}

/* ============ Buttons ============ */
.btn {
  display: inline-block;
  font-family: var(--font-head);
  font-size: var(--t-sm);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  padding: 0.95rem 2.2rem;
  border: 1px solid var(--gold);
  border-radius: 2px;
  transition: all var(--glide);
  position: relative;
}

.btn-primary {
  background: var(--gold);
  color: #0a0a0a;
  font-weight: 700;
}

.btn-primary:hover {
  background: var(--gold-bright);
  color: #000;
  box-shadow: 0 0 24px rgba(212, 175, 55, 0.45);
}

.btn-ghost {
  background: transparent;
  color: var(--gold);
}

.btn-ghost:hover {
  background: rgba(212, 175, 55, 0.08);
  box-shadow: 0 0 16px rgba(212, 175, 55, 0.2);
  color: var(--gold-bright);
}

.btn-large {
  /* Fluid: smaller on phones/tablets, gently capped on MacBook / hi-res displays */
  padding: clamp(0.7rem, 0.5rem + 0.5vw, 1rem) clamp(1.6rem, 1rem + 2.5vw, 2.8rem);
  font-size: clamp(0.76rem, 0.66rem + 0.35vw, 0.9rem);
}

.btn-sm {
  padding: 0.45rem 1rem;
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
}

/* ============ Header / Nav ============ */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  background: transparent;
  backdrop-filter: none;
  border-bottom: 1px solid transparent;
  transition: background var(--glide), border-color var(--glide), backdrop-filter var(--glide);
}

.site-header.scrolled {
  background: rgba(7,7,7, 0.55);
  backdrop-filter: blur(10px);
  border-bottom-color: var(--panel-border);
}

.nav-wrap {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  transition: padding var(--glide);
}

/* Shrink-on-scroll is a DESKTOP-only effect (>860px). On mobile the bar keeps a
   constant height, so the address-bar show/hide near the scroll threshold can't
   make it flicker or jump, and the fullscreen menu text never shrinks. */
@media (min-width: 861px) {
  .site-header.scrolled .nav-wrap {
    padding: 0.4rem 1.5rem;
  }
  .site-header.scrolled .logo {
    font-size: 1rem;
  }
  .site-header.scrolled .nav-links a {
    font-size: 0.68rem;
  }
  .site-header.scrolled .nav-login {
    padding: 4px 12px !important;
  }
}

.logo {
  font-family: var(--font-head);
  font-weight: 900;
  font-size: var(--t-lead);
  letter-spacing: var(--track-wide);
  color: var(--text);
  transition: font-size var(--glide);
}

.logo span { color: var(--gold); }

/* ⚜️ THE NAV IS TWO GROUPS (2026-09-08): primary = pages that CHANGE while you
   are away, secondary = static destinations behind one click. See the comment
   above the array in includes/header.php for why that axis and not importance. */
.nav-links {
  display: flex;
  align-items: center;
  gap: 1.6rem;
}
.nav-primary,
.nav-end { display: flex; align-items: center; gap: 1.6rem; }
.nav-secondary { position: relative; display: flex; align-items: center; }

/* the disclosure — a button, styled to sit level with the links beside it */
.nav-more-btn {
  font-family: var(--font-head); font-size: var(--t-label);
  letter-spacing: var(--track-max); text-transform: uppercase;
  color: var(--text-dim); background: none; border: 0; padding: 0;
  cursor: pointer; display: inline-flex; align-items: center; gap: .4em;
  transition: color var(--swift), font-size var(--glide);
}
.nav-more-btn::after {
  content: ""; width: 5px; height: 5px; border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor; transform: rotate(45deg) translate(-1px, -1px);
  transition: transform var(--swift);
}
.nav-more-btn:hover { color: var(--gold); }
.nav-more-btn:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 4px; }
.nav-more-btn[aria-expanded="true"] { color: var(--gold); }
.nav-more-btn[aria-expanded="true"]::after { transform: rotate(-135deg) translate(-2px, -2px); }

/* ⚠️ ABSOLUTE, NOT FIXED. The scrolled header carries `backdrop-filter`, which
   makes it the containing block for any FIXED descendant — that is exactly what
   once collapsed the fullscreen mobile menu into the thin bar. An absolutely
   positioned panel is contained by the header either way, so the filter cannot
   move it. Nothing on the path sets `overflow`, so it is not clipped. */
.nav-more-panel {
  display: none;
  position: absolute; top: calc(100% + 12px); right: 0;
  min-width: 12rem; padding: var(--s3);
  background: var(--panel); border: 1px solid var(--etch); border-radius: 2px;
  flex-direction: column; gap: var(--s3);
}
.nav-more-panel.open { display: flex; }
.nav-more-panel a { white-space: nowrap; }

/* ⛔ THE LIVE CHIP: default `display:none` in OUR OWN rule, revealed by a class.
   Never the `hidden` attribute — that is one user-agent declaration and any
   author `display` beats it by origin, which is how marsdeck.net sold "Sign up"
   to a member who had already paid. */
.nav-live { display: none; }
.nav-links.nav-is-live .nav-live {
  display: inline-flex; align-items: center; gap: .45em;
  color: var(--gold-bright);
}
.nav-live-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--gold-bright);
  box-shadow: 0 0 0 3px var(--etch-soft);
  animation: navLiveBreath 2.8s var(--glide) infinite;
}
@keyframes navLiveBreath { 0%,100% { opacity: 1 } 50% { opacity: .45 } }
@media (prefers-reduced-motion: reduce) { .nav-live-dot { animation: none; } }

.nav-links a {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--text);
  position: relative;
  transition: font-size var(--glide);
}

/* ⛔ THE SLIDING UNDERLINE IS FOR TEXT LINKS, NOT FOR BUTTONS. `.nav-login` and
   `.nav-cms` draw their own gold border, so the hover rule was adding a second
   gold line INSIDE the box — a stroke crossing a button, which reads as a
   rendering fault rather than an affordance. Stephan spotted it on Log in; it
   was doing the same thing on CMS, which only an admin ever sees.
   ⚠️ EXCLUDED BY A NAME, NOT BY A LIST. `:not(.nav-login):not(.nav-cms)` would
   have to grow every time a boxed item is added, and the one that gets
   forgotten is the bug. `.nav-btn` says the true thing — "I already draw my own
   edge" — so the next boxed item is covered by wearing the class.

   ⛔ AND `.nav-marked` IS THE SECOND CATEGORY, NOT THE START OF THE LIST THE
   NOTE ABOVE WARNS ABOUT. The account link is not a button and draws no edge —
   it carries a SIGIL, and the underline spans the whole flex row, so a gold
   1px rule was being drawn under a circular avatar. A line crossing a mark
   reads as a rendering fault, which is the same complaint that produced the
   `.nav-btn` exclusion in the first place.
   ⚠️ The distinction that keeps this honest: these two name KINDS of item —
   "I draw my own edge", "I carry my own mark" — where `:not(.nav-login)` named
   one item. A kind covers everything that joins it; a name covers one thing and
   then silently stops. The rule underneath both is that the sliding underline
   belongs to links whose ONLY affordance is the word. */
.nav-links a:not(.nav-btn):not(.nav-marked)::after {
  content: '';
  position: absolute;
  left: 0; bottom: -4px;
  width: 0%; height: 1px;
  background: var(--gold);
  transition: width var(--glide);
}
/* a boxed item hovers the way every other plate on the site does: the EDGE
   brightens. Same device as .plate:hover and the link rows on a Tenno page. */
.nav-links a.nav-btn:hover { border-color: var(--gold-bright); }

.nav-links a:hover {
  color: var(--gold);
}

.nav-links a:hover::after { width: 100%; }

.nav-links a.active {
  color: var(--gold-bright);
}

.nav-login {
  border: 1px solid var(--gold);
  padding: 6px 14px !important;
  border-radius: 2px;
  transition: padding var(--glide);
}

.nav-login:hover {
  background: var(--gold);
  color: var(--bg) !important;
}

/* Officer-only entry point to the EchoBorn CMS. */
.nav-cms {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold) !important;
  border: 1px solid var(--gold);
  border-radius: 2px;
  padding: 5px 11px !important;
  transition: background var(--glide), color var(--glide);
}

.nav-cms:hover,
.nav-cms.active {
  background: var(--gold);
  color: var(--bg) !important;
}

.nav-account {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.nav-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
}

.nav-baro {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  transition: color var(--glide), text-shadow var(--glide);
}

.nav-baro--here {
  color: var(--gold-bright) !important;
  text-shadow: 0 0 10px rgba(212,175,55,0.5);
}

.nav-baro-time {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-tight);
  color: var(--gold);
}

.nav-stream {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  transition: color var(--glide), text-shadow var(--glide);
}

.nav-stream--live {
  color: var(--gold-bright) !important;
  text-shadow: 0 0 10px rgba(212,175,55,0.5);
}

.nav-stream-status {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-tight);
  color: var(--text-dim);
  text-transform: uppercase;
}

.nav-stream-status--live {
  color: #ff4757;
  animation: stream-live-pulse 1.6s ease-in-out infinite;
}

@keyframes stream-live-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  width: 32px;
  height: 32px;
}

.nav-toggle span {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--gold);
  transition: transform var(--glide), opacity var(--glide);
}

.nav-toggle.open span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.nav-toggle.open span:nth-child(2) {
  opacity: 0;
}

.nav-toggle.open span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ============ Hero ============ */
.hero {
  position: relative;
  height: 100vh;
  min-height: 600px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.hero-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 35%;
}

/* ── Generic cinematic background crossfade (reusable across pages) ── */
.cinematic-bg { position: absolute; inset: 0; }

.cinematic-bg-layer {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 35%;
  background-color: var(--bg-alt);
  opacity: 0;
  transition: opacity 2.5s ease;
}

.cinematic-bg-layer.active {
  opacity: 1;
  animation: cinematic-kenburns 9s ease-in-out forwards;
}

@keyframes cinematic-kenburns {
  from { transform: scale(1); }
  to   { transform: scale(1.07); }
}

.hero .cinematic-bg-layer {
  filter: saturate(0.85) brightness(0.55);
}

.hero .cinematic-bg-layer:first-child {
  background-image: url('/Images/hero-railjack.png');
}

@media (max-width: 720px) {
  .hero .cinematic-bg-layer:first-child {
    background-image: url('/Images/dojo-orb.png') !important;
  }
}

.hero-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(7,7,7,0.35) 0%, rgba(7,7,7,0.55) 55%, rgba(7,7,7,0.98) 100%),
    radial-gradient(ellipse at center, rgba(7,7,7,0.1) 0%, rgba(7,7,7,0.7) 100%);
}

.hero-content {
  position: relative;
  z-index: 2;
  text-align: center;
  max-width: min(1100px, 92vw);
  overflow: visible;
  padding: 0 1.5rem;
}

.hero-kicker {
  font-family: var(--font-head);
  color: var(--gold);
  letter-spacing: var(--track-max);
  font-size: var(--t-label);
  margin-bottom: 1.2rem;
  text-transform: uppercase;
  animation: flicker 4s infinite;
}

.hero-title {
  font-family: var(--font-head);
  font-weight: 900;
  font-size: clamp(3rem, 10vw, 6rem);
  letter-spacing: var(--track-wide);
  white-space: nowrap;
  overflow: visible;
  background: linear-gradient(135deg, var(--gold-bright) 0%, var(--gold) 60%, var(--gold) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 0 40px rgba(212, 175, 55, 0.25);
  margin-bottom: 1.2rem;
}

@media (max-width: 480px) {
  .hero-title { font-size: clamp(2.4rem, 13vw, 3.4rem); white-space: normal; }
}

.hero-sub {
  font-size: var(--t-lead);
  font-weight: 300;
  color: var(--text-dim);
  margin-bottom: 2.5rem;
  line-height: 1.7;
}

.hero-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
}

/* Hero live status bar */
.hero-status {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 3;
  display: flex;
  justify-content: flex-start;
  align-items: stretch;
  gap: 0;
  background: rgba(7,7,7, 0.6);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--panel-border);
  overflow-x: auto;
  scrollbar-width: none;
}

.hero-status::-webkit-scrollbar { display: none; }

.hero-status-inner {
  display: flex;
  align-items: stretch;
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
}

.hs-item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  padding: 0.85rem 1.5rem;
  border-right: 1px solid var(--panel-border);
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 9.5rem;
}

.hs-item:last-child { border-right: none; }

.hs-icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hs-icon svg { width: 100%; height: 100%; }

.hs-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
}

.hs-label {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--text-dim);
}

.hs-value {
  display: block;
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-tight);
  font-variant-numeric: tabular-nums;
  color: var(--gold-bright);
  text-align: center;
  min-width: 7rem;
}

.hs-value.loading {
  color: var(--text-dim);
  font-style: italic;
  font-weight: 300;
  font-family: var(--font-body);
}

@keyframes flicker {
  0%, 100% { opacity: 1; }
  92% { opacity: 1; }
  93% { opacity: 0.4; }
  94% { opacity: 1; }
  96% { opacity: 0.5; }
  97% { opacity: 1; }
}

/* ============ Focus Areas ============ */
.focus {
  max-width: var(--max-width);
  margin: 0 auto;
  /* Fluid vertical air (more breathing room on big screens) + fluid gutters */
  padding: clamp(5rem, 6vw, 9rem) clamp(1.5rem, 3vw, 3rem);
}

.focus-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(1.2rem, 1.6vw, 2rem);
}

.focus-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  overflow: hidden;
  transition: transform var(--glide), box-shadow var(--glide), border-color var(--glide);
}

.focus-card:hover {
  transform: translateY(-6px);
  border-color: rgba(200, 168, 75, 0.5);
  box-shadow: 0 16px 44px rgba(0, 0, 0, 0.4);
}

.focus-img {
  height: clamp(170px, 13vw, 220px);
  background-size: cover;
  background-position: center;
  position: relative;
}

.focus-img::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 40%, var(--panel) 100%);
}

.focus-body {
  padding: 1.4rem 1.5rem 1.8rem;
}

.focus-body h3 {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.6rem;
}

.focus-body p {
  color: var(--text-dim);
  font-size: var(--t-base);
  font-weight: 300;
  line-height: 1.55;
  margin-bottom: 1rem;
}

/* Homepage feature grid — deliberate, balanced columns (never auto-fit 3+1) */
.home-features .focus-grid { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1000px) { .home-features .focus-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .home-features .focus-grid { grid-template-columns: 1fr; } }
/* card title white, gold reserved for the link (restraint) */
.home-features .focus-body h3 { color: var(--text); }

/* ── Clan page "What We Run" — bigger, punchier 2-col cards ── */
#focus .focus-grid {
  grid-template-columns: repeat(2, 1fr);
  gap: 1.75rem;
}

#focus .focus-img {
  height: 300px;
  overflow: hidden;
  transition: transform var(--glide);
}

#focus .focus-card:hover .focus-img {
  transform: scale(1.06);
}

#focus .focus-card:hover {
  border-color: var(--panel-border);
}

#focus .focus-body {
  padding: 1.6rem 1.7rem 2rem;
}

#focus .focus-body h3 {
  font-size: var(--t-lead);
}

#focus .focus-body p {
  font-size: var(--t-base);
}

@media (max-width: 900px) {
  #focus .focus-grid { grid-template-columns: 1fr; }
  #focus .focus-img { height: 260px; }
}

@media (max-width: 480px) {
  #focus .focus-img { height: 220px; }
  #focus .focus-body { padding: 1.3rem 1.4rem 1.6rem; }
}

.focus-link {
  display: inline-block;
  margin-top: 0.8rem;
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track);
  color: var(--gold);
  opacity: 0.8;
  transition: opacity var(--swift), color var(--swift);
}

.focus-card:hover .focus-link {
  opacity: 1;
  color: var(--gold-bright);
}

/* ============ Market ticker (home) ============ */
.market-ticker {
  position: relative;
  background: rgba(7,7,7, 0.9);
  border-bottom: 1px solid var(--panel-border);
  overflow: hidden;
  white-space: nowrap;
  padding: 0.7rem 0;
}

.market-ticker-track {
  display: inline-flex;
  align-items: center;
  animation: market-scroll 38s linear infinite;
  will-change: transform;
}

.market-ticker:hover .market-ticker-track {
  animation-play-state: paused;
}

.market-ticker-item {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 2rem;
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-tight);
  color: var(--text-dim);
  border-right: 1px solid var(--panel-border);
}

.market-ticker-plat {
  color: var(--gold-bright);
  font-weight: 700;
}

@keyframes market-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ============ Home pitch (3-pillar explainer) ============ */
.home-pitch-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem;
}

.home-pitch-item {
  text-align: center;
  padding: 1.5rem;
}

.home-pitch-icon {
  width: 52px;
  height: 52px;
  margin: 0 auto 1.1rem;
}

.home-pitch-icon svg {
  width: 100%;
  height: 100%;
}

.home-pitch-item h3 {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.6rem;
}

.home-pitch-item p {
  color: var(--text-dim);
  font-size: var(--t-sm);
  font-weight: 300;
  line-height: 1.5;
}

@media (max-width: 720px) {
  .home-pitch-grid {
    grid-template-columns: 1fr;
    gap: 2.5rem;
  }
}

/* ============ Scroll reveal ============ */
.scroll-reveal-item {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity var(--glide), transform var(--glide);
}

.scroll-reveal-item.in-view {
  opacity: 1;
  transform: translateY(0);
}

.home-pitch-grid .scroll-reveal-item:nth-child(2) { transition-delay: .12s; }
.home-pitch-grid .scroll-reveal-item:nth-child(3) { transition-delay: .24s; }
.focus-grid .scroll-reveal-item:nth-child(2) { transition-delay: .08s; }
.focus-grid .scroll-reveal-item:nth-child(3) { transition-delay: .16s; }
.focus-grid .scroll-reveal-item:nth-child(4) { transition-delay: .24s; }
.support-tiers .scroll-reveal-item:nth-child(2) { transition-delay: .1s; }
.support-tiers .scroll-reveal-item:nth-child(3) { transition-delay: .2s; }

/* ============ THE STORY — what the app does, told down the page ============
   ⛔ A SPINE WITH BEATS ON IT, NOT A GRID OF CARDS. A grid says "here are five
   equal things"; this page needs to say "here is what happens, in order", and
   the run-up to a purchase is a sequence. The spine is a 1px rule, which is the
   language the whole site is drawn in.
   ⚠️ IT REUSES .scroll-reveal-item RATHER THAN INVENTING A SECOND SYSTEM.
   That class already honours prefers-reduced-motion — which it did NOT when it
   shipped, and that was fixed rather than re-broken here. */
.home-story { position: relative; }
.story-beats {
  list-style: none; margin: 0 auto; padding: 0;
  max-width: 1040px; position: relative;
}
/* the spine: one hairline the marks sit on */
.story-beats::before {
  content: ""; position: absolute; left: 50%; top: 0; bottom: 0;
  width: 1px; background: var(--panel-border); transform: translateX(-.5px);
}
.story-beat {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: var(--s6);
  padding: var(--s6) 0;
}
/* ⛔ ALTERNATING, AND THE ORDER IS SET BY grid-column, NEVER BY `direction` or
   `row-reverse`. Reversing the writing direction reverses the TEXT too, which
   is how an alternating layout quietly breaks a screen reader and anything with
   punctuation at the end of a line. */
/* ⛔ BOTH ITEMS ARE PINNED TO ROW 1, AND WITHOUT THAT HALF THE BEATS BREAK.
   Grid auto-placement is SPARSE: the cursor only moves forward. The mark comes
   first in the DOM and takes row 1 column 2, so a body then assigned column 1
   cannot go BACKWARDS — it is pushed down to row 2, landing up to 226px below
   its own mark. The even beats looked perfect throughout, because column 3 is
   ahead of the cursor, which is exactly what made it look like a styling
   accident rather than a placement rule. */
.story-beat .story-mark { grid-column: 2; grid-row: 1; }
.story-beat .story-body { grid-column: 1; grid-row: 1; text-align: right; }
.story-beat:nth-child(even) .story-body { grid-column: 3; text-align: left; }

.story-mark {
  width: 72px; height: 72px; flex: 0 0 auto;
  display: grid; place-items: center;
  background: var(--bg); border: 1px solid var(--etch-soft);
  border-radius: 50%;
  color: var(--gold);
}
.story-mark svg { width: 42px; height: 42px; display: block; }
.story-mark .sm-ring, .story-mark .sm-tick, .story-mark .sm-hand,
.story-mark .sm-plate {
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.story-mark .sm-ring--2 { opacity: .6; }
.story-mark .sm-ring--3 { opacity: .35; }
.story-mark .sm-plate--2 { opacity: .55; }
.story-mark .sm-tick { opacity: .5; }
.story-mark .sm-hand--2 { opacity: .6; }
.story-mark .sm-core { fill: currentColor; stroke: none; }

.story-body { min-width: 0; }
.story-n {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); color: var(--gold-dim);
  margin: 0 0 var(--s2);
}
.story-body h3 {
  font-family: var(--font-head); font-size: var(--t-h3);
  letter-spacing: var(--track-tight); margin: 0 0 var(--s3);
  color: var(--text);
}
/* ⛔ A MEASURE ON THE PROSE. At 1440 a half of this section is ~470px, which is
   fine — but the section grows with the page and unmeasured copy is how the Hub
   ended up setting 212 characters to a line. */
.story-body p { margin: 0 0 var(--s3); color: var(--text-dim); line-height: 1.6; max-width: 46ch; }
.story-beat .story-body p, .story-beat .story-body .story-n { margin-left: auto; }
.story-beat:nth-child(even) .story-body p,
.story-beat:nth-child(even) .story-body .story-n { margin-left: 0; margin-right: auto; }
.story-body strong { color: var(--text); }
.story-body em { font-style: normal; color: var(--gold); }
.story-code {
  font-family: var(--font-mono); font-size: var(--t-sm); color: var(--gold);
  white-space: nowrap;
}
.story-go {
  display: inline-block; margin-top: var(--s1);
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--gold); text-decoration: none;
  transition: color var(--swift);
}
.story-go:hover { color: var(--gold-bright); }

/* each beat arrives a little after the one above it */
.story-beats .scroll-reveal-item:nth-child(2) { transition-delay: .06s; }
.story-beats .scroll-reveal-item:nth-child(3) { transition-delay: .12s; }
.story-beats .scroll-reveal-item:nth-child(4) { transition-delay: .18s; }
.story-beats .scroll-reveal-item:nth-child(5) { transition-delay: .24s; }

/* ⛔ ONE COLUMN BELOW 820px, AND THE SPINE MOVES TO THE EDGE. Alternating sides
   needs room for two columns of prose; at phone width it becomes a single
   column with the marks running down the left, which is the same story told in
   the shape a thumb can read. */
@media (max-width: 820px) {
  .story-beats::before { left: 36px; }
  .story-beat {
    grid-template-columns: 72px 1fr; gap: var(--s4);
    align-items: start; padding: var(--s5) 0;
  }
  .story-beat .story-mark,
  .story-beat:nth-child(even) .story-mark { grid-column: 1; grid-row: 1; }
  .story-beat .story-body,
  .story-beat:nth-child(even) .story-body { grid-column: 2; grid-row: 1; text-align: left; }
  .story-beat .story-body p, .story-beat .story-body .story-n,
  .story-beat:nth-child(even) .story-body p,
  .story-beat:nth-child(even) .story-body .story-n { margin-left: 0; margin-right: 0; }
  .story-body p { max-width: none; }
}

/* ============ Network — operative dossiers ============ */
.page-content--top { justify-content: flex-start; padding-top: 6.5rem; padding-bottom: 4rem; }
.network-page { width: 100%; max-width: 1280px; margin: 0 auto; padding: 0 1.5rem; }

/* ⛔ THE OLD .net-masthead FLEX RULE IS GONE (2026-09-07), AND LEAVING IT WAS
   THE WHOLE BUG. The masthead was rebuilt at the end of this file and I ADDED a
   rule instead of REPLACING this one — so `display:flex` survived, the plate
   became a flex container, `.plate-in` became a flex ITEM sizing to its
   content, and the plate's gold edge-background was left showing as a large
   olive rectangle across the right half of the header.
   ⚠️ Second time in one session: the dead `.op-banner-tag` rule was removed for
   exactly this reason hours earlier. A rule that outlives its markup does not
   sit quietly — it wins a cascade nobody is looking at. The masthead now lives
   in ONE block, further down. */
.net-title { font-family: var(--font-head); font-weight: 700; font-size: clamp(2rem, 5vw, 3.4rem); color: var(--text); margin: 0.4rem 0 0.6rem; letter-spacing: var(--track-tight); }
/* .net-desc / .net-stats / .net-stat REMOVED 2026-09-07 with the markup that
   used them — the standfirst and the old three-number chip row. A rule that
   matches nothing is not harmless: it is how the next reader concludes the
   element still exists and styles around a ghost. Third such clean-up today,
   and the second one caused a visible bug before it was found. */
.net-empty { text-align: center; color: var(--text-dim); margin-top: 2rem; }

/* Roster filter / sort toolbar */
.net-toolbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.8rem; }
.net-filters { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.net-chip {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--text-dim); background: var(--panel); border: 1px solid var(--panel-border); border-radius: 20px;
  padding: 0.42rem 0.9rem; cursor: pointer; transition: color var(--swift), border-color var(--swift), background var(--swift);
}
.net-chip:hover { color: var(--text); border-color: rgba(212, 175, 55, 0.4); }
.net-chip.active { background: var(--gold); color: #0a0a0a; border-color: var(--gold); font-weight: 700; }
.net-toolbar-right { display: flex; align-items: center; gap: 0.8rem; }
.net-search {
  background: var(--panel); border: 1px solid var(--panel-border); border-radius: 20px;
  padding: 0.45rem 0.95rem; color: var(--text); font-family: var(--font-body); font-size: var(--t-sm); width: 190px;
}
.net-search:focus { outline: none; border-color: var(--gold); }
.net-sort { display: inline-flex; gap: 0.2rem; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 20px; padding: 0.18rem; }
.net-sort button {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track-tight); text-transform: uppercase;
  color: var(--text-dim); background: none; border: none; border-radius: 16px; padding: 0.32rem 0.7rem; cursor: pointer;
  transition: color var(--swift), background var(--swift);
}
.net-sort button:hover { color: var(--text); }
.net-sort button.active { background: var(--gold); color: #0a0a0a; font-weight: 700; }
.net-no-results { text-align: center; color: var(--text-dim); font-style: italic; margin: 1rem 0; }

@media (max-width: 640px) {
  .net-toolbar { flex-direction: column; align-items: stretch; }
  .net-toolbar-right { justify-content: space-between; }
  .net-search { flex: 1; width: auto; }
}

.network-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.5rem;
}

.network-card {
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.025), transparent 55%), var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  padding: 1.6rem 1.5rem 1.3rem;
  transition: transform var(--glide), box-shadow var(--glide), border-color var(--glide);
}
.network-card::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--tier-accent); opacity: 0.9;
}
.network-card:hover {
  transform: translateY(-5px);
  border-color: rgba(212, 175, 55, 0.4);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.45);
}


.network-card-head {
  display: flex; align-items: center; gap: 0.9rem;
  margin-bottom: 1.1rem; padding-bottom: 1.1rem; border-bottom: 1px solid var(--panel-border);
}
.network-avatar {
  width: 54px; height: 54px; border-radius: 50%; flex-shrink: 0; object-fit: cover;
  border: 2px solid var(--tier-accent);
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.25), 0 0 16px -3px var(--tier-accent);
}
.network-card-id { min-width: 0; }
.network-name { font-family: var(--font-head); font-size: var(--t-base); letter-spacing: var(--track-tight); color: var(--text); margin: 0 0 0.5rem; }
.network-tags { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
/* ⚠️ the `.network-tags .patron-badge { margin: 0 }` override that used to
   live here is gone: the component no longer declares a margin, so it was
   zeroing nothing. It was the tell — the leak had already been patched at
   one call site instead of at the source, which is why it kept reappearing
   everywhere else the badge sits beside text. */
.network-platform {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--text-dim); border: 1px solid var(--panel-border); border-radius: 20px; padding: 0.2rem 0.55rem;
}

.network-card-body { display: flex; flex-direction: column; gap: 0.55rem; margin-bottom: 1rem; }
.network-row { display: flex; justify-content: space-between; align-items: baseline; gap: 0.75rem; font-size: var(--t-sm); }
.network-row span:first-child { color: var(--muted); font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); text-transform: uppercase; }
.network-row span:last-child { color: var(--text); text-align: right; }

.network-joined {
  font-size: var(--t-micro); color: var(--muted); letter-spacing: var(--track-tight); text-transform: uppercase;
  font-family: var(--font-head); margin: 0; padding-top: 0.95rem; border-top: 1px solid var(--panel-border);
}

/* Operator+ members showcase their chosen profile-card background on the roster. */
.network-card-bg {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
  filter: brightness(0.62) saturate(0.95);
}
.network-card-scrim {
  position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(180deg, rgba(8,8,8, 0.4) 0%, rgba(8,8,8, 0.72) 62%, rgba(8,8,8, 0.93) 100%);
}
.network-card.has-bg::before { z-index: 2; }
.network-card.has-bg > :not(.network-card-bg):not(.network-card-scrim) { position: relative; z-index: 1; }
.network-card.has-bg .network-name,
.network-card.has-bg .network-row span:last-child { text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85); }
.network-card.has-bg .network-card-head { border-bottom-color: rgba(255, 255, 255, 0.14); }
.network-card.has-bg .network-joined { border-top-color: rgba(255, 255, 255, 0.14); }

/* Warlord-exclusive backgrounds — locked preview for Operator-tier members. */
.bg-option--locked { position: relative; cursor: not-allowed; }
.bg-option--locked .bg-label { display: none; }
.bg-option--locked::after {
  content: '\1F451 WARLORD';
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); color: #e8940a;
  background: rgba(6,6,6, 0.74); -webkit-backdrop-filter: blur(1px); backdrop-filter: blur(1px);
  border-radius: inherit;
}

/* ===== Staff / team role badge — IDENTICAL box model to .patron-badge (so it
   never shifts the layout), just steel (role/authority) instead of gold (paid tier) ===== */
/* ⛔ THIS BOX MODEL IS THE SAME AS `.patron-badge` BY DESIGN — a staff role
   REPLACES the tier badge in one slot, so any difference in padding, size, gap
   or tracking makes the name beside it jump when the role is set or cleared.
   Guarded in tools/verify-banners.php; change both or neither. */
.staff-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* ⚠️ QUIETER AGAIN, 2026-09-06. Stephan, looking at nine cards at once:
     "they should be there and seen, but not the first thing I look at".
     At 700 weight in the display face they out-shouted the member's NAME,
     which is the thing a roster card is actually about — an annotation had
     become the headline. Lighter, tighter, and the tracking does the work
     of making it legible at this size instead of the weight.
     ⛔ BOTH BADGES MOVE TOGETHER. They share one box model on purpose so a
     role and a tier never shift the layout when one replaces the other in
     the single badge slot; verify-banners fails the build on any drift. */
  padding: 0.12rem 0.42rem;
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  font-weight: 500;
  background: rgba(108, 204, 232, 0.12);
  color: #6ccce8;
  border: 1px solid rgba(108, 204, 232, 0.4);
}
.staff-badge--founder {
  border-color: rgba(108, 204, 232, 0.55);
  box-shadow: 0 0 12px rgba(108, 204, 232, 0.2);
}

/* ⚙️ DEVELOPER — the one who builds it, in the house colour.
   ⛔ LITERAL VALUES, NOT var(--gold). The six colour themes are an Operator
   perk and they override --gold for the VIEWER, so a Corpus-blue account would
   render this badge blue — straight into the cyan the other staff roles wear,
   which is the collision this badge exists to end. The same trap as --etch
   hardcoding gold, pointed the other way: there the literal was wrong, here it
   is the only correct choice.
   ⚠️ And it is deliberately BRIGHTER and cooler than the paid Founder badge
   (#e8940a amber). Two gold-ish badges that a glance cannot separate would put
   the collision back in a new costume. */
/* ⚙️ DEVELOPER — green, his call.
   ⚠️ #43e08f, NOT the #6fd17a of `.status-accepted`. Both can sit on the
   Command Hub at once (the dossier badge above, the clan-application pill
   below), and two greens a glance cannot separate would read as one state
   rendered twice. This one is cooler and more saturated; the pill stays soft.
   ⛔ LITERAL, NEVER var(--gold): the six colour themes override --gold for the
   VIEWER, so a themed account would recolour a badge that exists precisely to
   be distinguishable. Same trap as --etch, pointed the other way. */
.staff-badge--developer {
  background: rgba(67, 224, 143, 0.12);
  color: #43e08f;
  border-color: rgba(67, 224, 143, 0.45);
  box-shadow: 0 0 12px rgba(67, 224, 143, 0.18);
}

/* ===== Operative-card popover (comment avatars: hover desktop / tap mobile) ===== */
.uc-trigger { cursor: pointer; }
.user-card-pop {
  position: fixed; z-index: 200; width: 250px; max-width: calc(100vw - 16px);
  background: var(--panel); border: 1px solid var(--panel-border); border-radius: 10px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55); overflow: hidden; animation: ucfade 0.12s ease;
}
@keyframes ucfade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.uc { position: relative; padding: 1rem; }
.uc::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--tier-accent); z-index: 2; }
.uc-bg { position: absolute; inset: 0; z-index: 0; background-size: cover; background-position: center; filter: brightness(0.5) saturate(0.95); }
.uc-scrim { position: absolute; inset: 0; z-index: 0; background: linear-gradient(180deg, rgba(8,8,8, 0.5) 0%, rgba(8,8,8, 0.9) 100%); }
.uc-head { position: relative; z-index: 1; display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.85rem; }
.uc-avatar { width: 46px; height: 46px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.uc-id { min-width: 0; }
.uc-name { font-family: var(--font-head); font-size: var(--t-base); color: var(--text); margin: 0 0 0.4rem; word-break: break-word; }
.uc-badge { display: flex; }
.uc-badge .patron-badge, .uc-badge .staff-badge { margin: 0; }
.uc-stats { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 0.4rem; }
.uc-row { display: flex; justify-content: space-between; gap: 0.6rem; font-size: var(--t-label); }
.uc-row span:first-child { color: var(--muted); font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); text-transform: uppercase; }
.uc-row span:last-child { color: var(--text); text-align: right; }
.uc-joined { position: relative; z-index: 1; margin: 0.85rem 0 0; padding-top: 0.7rem; border-top: 1px solid var(--panel-border); font-size: var(--t-micro); font-family: var(--font-head); letter-spacing: var(--track-tight); text-transform: uppercase; color: var(--muted); }


@media (max-width: 600px) {
  .net-masthead { gap: 1.2rem; }
}

/* ============ Supporter tier showcase (home) ============ */
.support-tiers {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 1.4vw, 1.5rem);
  max-width: 1040px;
  margin: 0 auto 3.5rem;
  align-items: stretch;
}

.support-tier-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 2.2rem 1.75rem 1.9rem;
  text-align: center;
  position: relative;
  transition: transform var(--glide), box-shadow var(--glide), border-color var(--glide);
}

.support-tier-card:hover {
  transform: translateY(-8px);
  border-color: var(--tier-accent);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4), 0 0 28px rgba(212, 175, 55, 0.16);
}

.support-tier-card--featured {
  border-color: var(--tier-accent);
  background: linear-gradient(160deg, var(--panel) 0%, rgba(255, 255, 255, 0.05) 100%);
  transform: translateY(-12px);
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.4);
}
.support-tier-card--featured:hover { transform: translateY(-18px); }

.support-tier-badge-flag {
  position: absolute;
  top: -0.65rem;
  left: 50%;
  transform: translateX(-50%);
  background: var(--tier-accent);
  color: #0a0a0a;
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  padding: 0.3rem 0.8rem;
  border-radius: 10px;
  white-space: nowrap;
}

.support-tier-name {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--tier-accent);
  margin-bottom: 0.5rem;
}

.support-tier-price {
  font-family: var(--font-head);
  font-size: var(--t-h2);
  font-weight: 700;
  color: var(--text);
  margin-bottom: 1.4rem;
}

.support-tier-price span {
  font-size: var(--t-sm);
  font-weight: 400;
  color: var(--text-dim);
}

.support-tier-perks {
  list-style: none;
  margin: 0 0 1.8rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  text-align: left;
}

.support-tier-perks li {
  font-size: var(--t-sm);
  color: var(--text-dim);
  padding-left: 1.4rem;
  position: relative;
}

.support-tier-perks li::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: var(--tier-accent);
  font-weight: 700;
}

.support-tier-card .btn {
  display: block;
}

@media (max-width: 900px) {
  .support-tiers { grid-template-columns: 1fr; max-width: 440px; }
  .support-tier-card--featured { transform: none; }
}

/* ============ Install / push callout (home) ============ */
.home-install-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 2rem;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 2.2rem 2.5rem;
}

.home-install-icon {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
}

.home-install-icon svg { width: 100%; height: 100%; }

.home-install-text .section-kicker,
.home-install-text .section-title {
  text-align: left;
}

.home-install-text .section-title {
  font-size: clamp(1.4rem, 2.4vw, 1.9rem);
  margin-bottom: 0.5rem;
}

.home-install-text .section-desc {
  font-size: var(--t-sm);
  text-align: left;
}

.home-install-actions {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  flex-shrink: 0;
}

.home-install-actions .btn { white-space: nowrap; text-align: center; }

#pwaInstallBtn {
  animation: install-pulse 2.6s ease-in-out infinite;
}

@keyframes install-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(212, 175, 55, 0.35); }
  50%      { box-shadow: 0 0 0 9px rgba(212, 175, 55, 0); }
}

.home-install-hint {
  grid-column: 1 / -1;
  font-size: var(--t-label);
  color: var(--gold-dim);
  text-align: center;
  min-height: 1.2rem;
}

@media (max-width: 820px) {
  .home-install-card {
    grid-template-columns: 1fr;
    text-align: center;
    padding: 2rem 1.5rem;
  }
  .home-install-icon { margin: 0 auto; }
  .home-install-text .section-kicker,
  .home-install-text .section-title,
  .home-install-text .section-desc {
    text-align: center;
  }
  .home-install-actions { width: 100%; }
}

/* ============ Safehouse Lab spotlight (home) ============ */
.home-lab {
  position: relative;
  width: 100%;
  min-height: 78vh;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
}

.home-lab-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  /* ⚠️ COMPUTED, not nudged. The band is ~2.28:1 on a 1080p screen against a
     1.79:1 image, so `cover` scales to width and crops VERTICALLY: 1072px of
     image into an 842px band leaves only 230px of travel. The visible window is
     78.5% of the image height.

     ⚠️ RECOMPUTED FOR THE SECOND FRAME (2026-09-02, lab-stream-2). It is a
     tighter, darker shot and the face sits much HIGHER — around 22% rather than
     32%. Anchoring a 78.5% window on 34% wants a top edge below zero, so it
     clamps: show from the top and let the foreground desk be what gets cut.
     Carrying the old 36% forward would have pushed the face toward the frame
     edge. A focal point belongs to an image, not to an element.

     ⚠️ Honest limit: with only 230px of travel, Y can move the frame by at most
     ~21% of the image. If a genuinely tighter frame is wanted, re-crop the
     SOURCE at full resolution — zooming with background-size would upscale a
     2000px file past native and go soft on anything above 1080p. */
  background-position: center 0%;
  transition: transform 8s ease;
}

@media (hover: hover) and (pointer: fine) {
  .home-lab:hover .home-lab-bg {
    transform: scale(1.04);
  }
}

/* ⚠️ On a phone the geometry INVERTS. A 390x660 band against a 1.79:1 image
   crops HORIZONTALLY and hard — only ~33% of the image width is visible — so X
   is the value that matters and Y stops mattering at all. Centre it on the
   subject (who spans roughly 33–58% of the frame) rather than on the image. */
@media (max-width: 700px) {
  .home-lab-bg { background-position: 30% center; }
}

.home-lab-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(7,7,7, 0.05) 0%, rgba(7,7,7, 0.2) 45%, rgba(7,7,7, 0.97) 100%),
    linear-gradient(90deg, rgba(7,7,7, 0.55) 0%, rgba(7,7,7, 0) 45%);
}

.home-lab-content {
  position: relative;
  z-index: 1;
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  padding: 0 1.5rem 5.5rem;
}

.home-lab-content .section-kicker,
.home-lab-content .section-title,
.home-lab-content .section-desc {
  text-align: left;
  margin: 0 0 0.7rem;
  max-width: 640px;
}

.home-lab-content .section-title {
  font-size: clamp(1.9rem, 4.4vw, 3.2rem);
}

.home-lab-content .section-desc {
  margin-bottom: 2rem;
}

.home-lab-actions {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

@media (max-width: 720px) {
  .home-lab { min-height: 88vh; }
  .home-lab-bg { background-position: center top; }
  .home-lab-content { padding: 0 1.25rem 4.5rem; }
}

/* ============ Final CTA band (home) ============ */
.home-final-cta {
  position: relative;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 6rem 1.5rem 7rem;
  text-align: center;
}

.home-final-cta::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 70%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--panel-border), transparent);
}

.home-final-cta-title {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  color: var(--text);
  text-shadow: 0 0 36px rgba(212, 175, 55, 0.18);
  margin-bottom: 0.9rem;
}

.home-final-cta-sub {
  color: var(--text-dim);
  font-size: var(--t-base);
  font-weight: 300;
  margin-bottom: 2.2rem;
}

.home-final-cta-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
}

/* ============ Teaser Grid (home dashboard) ============ */
.teasers {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 6rem 1.5rem 7rem;
}

.teaser-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
}

.teaser-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.8rem 1.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  text-decoration: none;
  transition: transform var(--glide), box-shadow var(--glide), border-color var(--glide);
}

.teaser-card:hover {
  transform: translateY(-6px);
  border-color: var(--gold);
  box-shadow: 0 12px 40px rgba(212, 175, 55, 0.12);
}

.teaser-icon {
  width: 40px;
  height: 40px;
}

.teaser-icon svg { width: 100%; height: 100%; }

.teaser-card h3 {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--gold);
}

.teaser-card p {
  color: var(--text-dim);
  font-size: var(--t-sm);
  font-weight: 300;
  flex: 1;
}

.teaser-link {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--gold-bright);
}

.teaser-card:hover .teaser-link {
  text-decoration: underline;
}

/* ============ Page Background ============ */
.page-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  background-color: var(--bg);
}

.page-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(7,7,7,0.88) 0%, rgba(7,7,7,0.94) 50%, var(--bg) 100%);
}

/* ⛔ orbiter-bg.jpg (1,780 KB) removed from this class 2026-09-02 — the ground
   is a drawn graticule now, further down this file. ⚠️ The FILE is still in
   use: `.page-bg--baro` loads it as the Baro page's ground. */
/* ⛔ Dashboard.png (2,316 KB) REMOVED 2026-09-04 — the heaviest single image
   left outside /blog/, sitting behind a scrim on the one page a member is meant
   to leave OPEN on a second monitor for hours. The ground is DRAWN now, at the
   end of this file. Declared in ONE place; there is deliberately no url() here
   to override, and the file is no longer referenced anywhere. */

/* ⛔ SafehouseLab-Brickwall.webp (488 KB) removed from this class 2026-09-02
   — the ground is a drawn broadcast carrier now, at the end of this file.
   Declared in ONE place; there is deliberately no url() here to override. */

/* ⛔ Market-Wallpaper.png (2,067 KB) removed from this class 2026-09-03 — the
   ground is a drawn ORDER-BOOK LADDER now, at the end of this file. It was the
   heaviest single image left outside /blog/, sitting behind a 78% scrim where
   nobody could see it, on the page most likely to be opened mid-mission on a
   phone. Declared in ONE place; there is deliberately no url() here to
   override, and the file is no longer referenced anywhere. */

.page-content {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.page-content > section {
  width: 100%;
}

/* ============ Open World Cycles ============ */
.cycles {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 6rem 1.5rem 0;
}

.cycles-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 1.25rem;
}

.cycles-grid .cycle-card {
  grid-column: span 2;
}

.cycles-grid #cycle-duviri,
.cycles-grid #cycle-zariman {
  grid-column: span 3;
}

@media (max-width: 900px) {
  .cycles-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .cycles-grid .cycle-card,
  .cycles-grid #cycle-duviri,
  .cycles-grid #cycle-zariman {
    grid-column: span 1;
  }
}

@media (max-width: 560px) {
  .cycles-grid {
    grid-template-columns: 1fr;
  }
}

.cycle-card {
  position: relative;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.4rem 1.5rem;
  min-height: 132px;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  transition: border-color var(--glide);
}


.cycle-card-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  opacity: 0.6;
  transition: opacity 0.8s ease, filter 1.4s ease;
}

.cycle-card-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg,
    rgba(11,11,11,0.88) 0%,
    rgba(11,11,11,0.48) 48%,
    rgba(11,11,11,0.08) 100%
  );
}

/* ── Background images ──────────────────────── */




/* ⛔ cambion.webp removed 2026-09-02 — the state is DRAWN now (see the art
   block at the end of this file). Left as a note rather than a dead rule. */



/* ── State-based color filters ──────────────── */

/* Cetus & Vallis */



/* Cambion — FASS is orange/hot, VOME is cool/blue */
/* ⛔ The two hue-rotates that WERE the difference between Fass and Vome. This
   is the whole diagnosis in two lines: one photograph, two tints, and no way
   for the states to have different atmospheres. Both drawn now. */

/* Zariman — Corpus is blue-white, Grineer is amber-red */



/* Duviri moods */






/* ============ Cycle Card Ambient FX ============ */
.cycle-fx {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  display: block;
}

/* Rain — Plains of Eidolon */
.cycle-fx--rain .drop {
  position: absolute;
  top: -10%;
  left: calc(var(--i) * 6% - 5%);
  width: 1px;
  height: 50px;
  background: linear-gradient(180deg, rgba(190, 210, 230, 0) 0%, rgba(190, 210, 230, 0.45) 100%);
  animation: cycle-rain linear infinite;
  animation-duration: calc(0.7s + (var(--i) * 0.07s));
  animation-delay: calc(var(--i) * -0.21s);
}

@keyframes cycle-rain {
  from { transform: translate(0, 0); opacity: 0; }
  10%  { opacity: 0.8; }
  to   { transform: translate(-14px, 170px); opacity: 0; }
}

/* Snow — Orb Vallis */
.cycle-fx--snow .flake {
  position: absolute;
  top: -8%;
  left: calc(var(--i) * 6.2% - 4%);
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.75);
  box-shadow: 0 0 4px rgba(255, 255, 255, 0.4);
  animation: cycle-snow linear infinite;
  animation-duration: calc(4s + (var(--i) * 0.6s));
  animation-delay: calc(var(--i) * -0.9s);
}

@keyframes cycle-snow {
  from { transform: translate(0, 0) scale(0.6); opacity: 0; }
  10%  { opacity: 0.9; }
  50%  { transform: translate(10px, 80px) scale(1); }
  to   { transform: translate(-6px, 170px) scale(0.6); opacity: 0; }
}

/* Embers — Cambion Drift */
.cycle-fx--embers .ember {
  position: absolute;
  bottom: -8%;
  left: calc(var(--i) * 6% - 3%);
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #ffb15e;
  box-shadow: 0 0 6px 2px rgba(255, 140, 40, 0.55);
  animation: cycle-ember ease-in-out infinite;
  animation-duration: calc(3.4s + (var(--i) * 0.6s));
  animation-delay: calc(var(--i) * -0.83s);
}

/* Five distinct wobble paths, cycled across the embers for a more
   chaotic "flying around" feel instead of a uniform rise. */
.cycle-fx--embers .ember:nth-child(5n+1) { --dx1: 16px;  --dx2: -12px; --dx3: 18px;  --dx4: -6px;  --sc: 1.3; }
.cycle-fx--embers .ember:nth-child(5n+2) { --dx1: -14px; --dx2: 20px;  --dx3: -10px; --dx4: 12px;  --sc: 0.7; }
.cycle-fx--embers .ember:nth-child(5n+3) { --dx1: 9px;   --dx2: -18px; --dx3: 14px;  --dx4: -16px; --sc: 1.1; }
.cycle-fx--embers .ember:nth-child(5n+4) { --dx1: -20px; --dx2: 7px;   --dx3: -12px; --dx4: 9px;   --sc: 0.85; }
.cycle-fx--embers .ember:nth-child(5n)   { --dx1: 11px;  --dx2: 15px;  --dx3: -20px; --dx4: 5px;   --sc: 1.2; }

@keyframes cycle-ember {
  0%   { transform: translate(0, 0) scale(0.7); opacity: 0; }
  12%  { opacity: 1; }
  35%  { transform: translate(var(--dx1, 12px), -42px) scale(var(--sc, 1)); }
  60%  { transform: translate(var(--dx2, -8px), -85px) scale(1); opacity: 0.9; }
  85%  { transform: translate(var(--dx3, 14px), -125px) scale(var(--sc, 1)); opacity: 0.6; }
  100% { transform: translate(var(--dx4, -4px), -155px) scale(0.5); opacity: 0; }
}

/* Stardust — Zariman */
.cycle-fx--stardust .mote {
  position: absolute;
  top: calc(var(--i) * 6% + 4%);
  left: calc(var(--i) * 6.5% - 5%);
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: rgba(210, 228, 240, 0.7);
  filter: blur(0.4px);
  animation: cycle-stardust ease-in-out infinite;
  animation-duration: calc(7s + (var(--i) * 0.9s));
  animation-delay: calc(var(--i) * -1.3s);
  --driftX: 16px;
}

.cycle-fx--stardust .mote:nth-child(even) {
  --driftX: -16px;
}

.cycle-fx--stardust .mote:nth-child(3n) {
  width: 1px;
  height: 1px;
}

.cycle-fx--stardust .mote:nth-child(4n) {
  width: 3px;
  height: 3px;
  background: rgba(210, 228, 240, 0.5);
  filter: blur(0.8px);
}

@keyframes cycle-stardust {
  0%, 100% { opacity: 0.2; transform: translate(0, 0); }
  50% { opacity: 0.7; transform: translate(var(--driftX), -10px); }
}

/* ============ Duviri mood-reactive FX ============ */
.cycle-fx--duviri .duviri-mood {
  position: absolute;
  inset: 0;
  display: none;
}

.cycle-fx--duviri[data-mood="joy"] .duviri-mood--joy,
.cycle-fx--duviri[data-mood="anger"] .duviri-mood--anger,
.cycle-fx--duviri[data-mood="envy"] .duviri-mood--envy,
.cycle-fx--duviri[data-mood="sorrow"] .duviri-mood--sorrow,
.cycle-fx--duviri[data-mood="fear"] .duviri-mood--fear {
  display: block;
}

/* Joy — gold dust */
.duviri-mood--joy .spark {
  position: absolute;
  top: calc(var(--i) * 6% + 4%);
  left: calc(var(--i) * 6.5% - 5%);
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: rgba(244, 217, 122, 0.75);
  filter: blur(0.4px);
  animation: cycle-stardust ease-in-out infinite;
  animation-duration: calc(6s + (var(--i) * 0.8s));
  animation-delay: calc(var(--i) * -1.2s);
  --driftX: 14px;
}

.duviri-mood--joy .spark:nth-child(even) {
  --driftX: -14px;
}

.duviri-mood--joy .spark:nth-child(3n) {
  width: 1px;
  height: 1px;
}

.duviri-mood--joy .spark:nth-child(4n) {
  width: 3px;
  height: 3px;
  background: rgba(244, 217, 122, 0.55);
  filter: blur(0.8px);
}

/* Anger — red embers */
.duviri-mood--anger .ember {
  position: absolute;
  bottom: -8%;
  left: calc(var(--i) * 6% - 3%);
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #ff5a3c;
  box-shadow: 0 0 6px 2px rgba(255, 90, 60, 0.55);
  animation: cycle-ember ease-in-out infinite;
  animation-duration: calc(2.8s + (var(--i) * 0.5s));
  animation-delay: calc(var(--i) * -0.7s);
}

.duviri-mood--anger .ember:nth-child(5n+1) { --dx1: 16px;  --dx2: -12px; --dx3: 18px;  --dx4: -6px;  --sc: 1.3; }
.duviri-mood--anger .ember:nth-child(5n+2) { --dx1: -14px; --dx2: 20px;  --dx3: -10px; --dx4: 12px;  --sc: 0.7; }
.duviri-mood--anger .ember:nth-child(5n+3) { --dx1: 9px;   --dx2: -18px; --dx3: 14px;  --dx4: -16px; --sc: 1.1; }
.duviri-mood--anger .ember:nth-child(5n+4) { --dx1: -20px; --dx2: 7px;   --dx3: -12px; --dx4: 9px;   --sc: 0.85; }
.duviri-mood--anger .ember:nth-child(5n)   { --dx1: 11px;  --dx2: 15px;  --dx3: -20px; --dx4: 5px;   --sc: 1.2; }

/* Envy — green dust */
.duviri-mood--envy .mote {
  position: absolute;
  top: calc(var(--i) * 6% + 4%);
  left: calc(var(--i) * 6.5% - 5%);
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: rgba(127, 232, 154, 0.7);
  filter: blur(0.4px);
  animation: cycle-stardust ease-in-out infinite;
  animation-duration: calc(7s + (var(--i) * 0.7s));
  animation-delay: calc(var(--i) * -1.4s);
  --driftX: 18px;
}

.duviri-mood--envy .mote:nth-child(even) {
  --driftX: -18px;
}

.duviri-mood--envy .mote:nth-child(3n) {
  width: 1px;
  height: 1px;
}

.duviri-mood--envy .mote:nth-child(4n) {
  width: 3px;
  height: 3px;
  background: rgba(127, 232, 154, 0.5);
  filter: blur(0.8px);
}

/* Sorrow — blue rain */
.duviri-mood--sorrow .drop {
  position: absolute;
  top: -10%;
  left: calc(var(--i) * 6% - 5%);
  width: 1px;
  height: 50px;
  background: linear-gradient(180deg, rgba(111, 195, 255, 0) 0%, rgba(111, 195, 255, 0.5) 100%);
  animation: cycle-rain linear infinite;
  animation-duration: calc(1.4s + (var(--i) * 0.12s));
  animation-delay: calc(var(--i) * -0.35s);
}

/* Fear — purple mist */
.duviri-mood--fear .wisp {
  position: absolute;
  top: calc(var(--i) * 14% + 4%);
  left: -15%;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: rgba(176, 124, 240, 0.18);
  filter: blur(6px);
  animation: cycle-mist linear infinite;
  animation-duration: calc(10s + (var(--i) * 1.4s));
  animation-delay: calc(var(--i) * -1.7s);
}

@keyframes cycle-mist {
  from { transform: translateX(0); opacity: 0.25; }
  50%  { opacity: 0.55; }
  to   { transform: translateX(420px); opacity: 0.1; }
}

.cycle-icon {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  width: 52px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cycle-icon svg { width: 100%; height: 100%; }

.cycle-info { position: relative; z-index: 1; flex: 1; min-width: 0; }

.cycle-info h4 {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.6rem;
}

.cycle-body.loading {
  color: var(--text-dim);
  font-style: italic;
  font-weight: 300;
  font-size: var(--t-sm);
}

.cycle-state-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.2rem 0.6rem;
  font-size: var(--t-sm);
  margin-bottom: 0.55rem;
}

.cycle-state-row .cycle-state {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  letter-spacing: var(--track-tight);
}

.cycle-state-row .cycle-time {
  font-family: var(--font-head);
  color: var(--gold-bright);
  font-size: var(--t-sm);
  white-space: nowrap;
}

.cycle-track {
  position: relative;
  height: 5px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
}

.cycle-fill {
  position: absolute;
  inset: 0;
  width: 0%;
  border-radius: 2px;
  transition: width var(--glide);
}

.cycle-fill.tone-warm { background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright)); }
.cycle-fill.tone-cold { background: linear-gradient(90deg, #2d6e8c, #6fd3ff); }
.cycle-fill.tone-fass { background: linear-gradient(90deg, #7a2e2e, #ef5d5d); }
.cycle-fill.tone-vome { background: linear-gradient(90deg, #2e5a7a, #5dc3ef); }
.cycle-fill.tone-joy { background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright)); }
.cycle-fill.tone-anger { background: linear-gradient(90deg, #7a2e2e, #ef5d5d); }
.cycle-fill.tone-envy { background: linear-gradient(90deg, #2e6e44, #6fcf8e); }
.cycle-fill.tone-sorrow { background: linear-gradient(90deg, #2d6e8c, #5ec2e8); }
.cycle-fill.tone-fear { background: linear-gradient(90deg, #5a2e7a, #b07cf0); }
.cycle-fill.tone-corpus { background: linear-gradient(90deg, #2e5a7a, #7ec8ff); }
.cycle-fill.tone-grineer { background: linear-gradient(90deg, #7a2e2e, #e8543f); }

/* ============ World State ============ */
.worldstate {
  position: relative;
  background: var(--bg-alt);
  padding: 4.5rem 1.5rem 7rem;
  border-bottom: 1px solid var(--panel-border);
}

.worldstate::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -160px;
  height: 160px;
  background: linear-gradient(180deg, transparent, var(--bg-alt));
  z-index: -1;
  pointer-events: none;
}

.worldstate .section-head { max-width: 1100px; }

/* ============ World State Hero ============ */
.ws-hero {
  padding-top: 8.5rem;
  padding-bottom: 1.5rem;
}

.ws-hero .section-head {
  margin-bottom: 0;
}

.ws-hero .section-kicker { margin-bottom: 0.6rem; }

.ws-hero .section-title {
  font-size: clamp(1.8rem, 3.5vw, 2.6rem);
  margin-bottom: 0;
}

@media (max-width: 720px) {
  .ws-hero {
    padding-top: 6.5rem;
    padding-bottom: 1.25rem;
  }

  .ws-hero .section-title {
    font-size: clamp(1.6rem, 7vw, 2rem);
  }
}

.worldstate-grid {
  max-width: var(--max-width);
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.25rem;
}

@media (max-width: 720px) {
  .worldstate-grid {
    grid-template-columns: 1fr;
  }
}

.ws-card {
  position: relative;
  min-width: 0;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.4rem 1.5rem;
  transition: border-color var(--glide);
}

@media (max-width: 900px) {
  .ws-card { padding: 1.2rem 1.25rem; }
}

@media (max-width: 480px) {
  .ws-card { padding: 1rem 1.1rem; }
  .ws-card h4 { font-size: 0.78rem; }
}


.ws-card h4 {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.9rem;
  padding-bottom: 0.7rem;
  padding-right: 2.2rem;
  border-bottom: 1px solid var(--panel-border);
}

/* World State notification controls */
.ws-watch-btn {
  position: absolute;
  top: 0.9rem;
  right: 0.9rem;
  z-index: 2;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  color: var(--text-dim);
  cursor: pointer;
  transition: color var(--swift), border-color var(--swift), background-color var(--swift);
}

.ws-watch-btn svg {
  width: 15px;
  height: 15px;
}

.ws-watch-btn:hover {
  color: var(--gold-bright);
  border-color: var(--gold-dim);
}

.ws-watch-btn.active {
  color: var(--gold-bright);
  border-color: var(--gold);
  background: rgba(212, 175, 55, 0.12);
}

.ws-watch-hint {
  position: absolute;
  top: 0.95rem;
  right: 0.95rem;
  z-index: 2;
  font-family: var(--font-body);
  font-size: var(--t-micro);
  letter-spacing: var(--track-tight);
  color: var(--text-dim);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-dim);
  transition: color var(--swift), border-color var(--swift);
}

.ws-watch-hint:hover {
  color: var(--gold-bright);
  border-color: var(--gold-bright);
}

.ws-body { font-size: var(--t-base); color: var(--text); }

.ws-body.loading {
  color: var(--text-dim);
  font-style: italic;
  font-weight: 300;
}

.ws-body.error {
  color: #d47373;
}

.ws-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 0.4rem;
  font-size: var(--t-sm);
  flex-wrap: wrap;
}

.ws-row span:first-child {
  color: var(--text-dim);
  flex-shrink: 0;
}

.ws-row span:last-child {
  font-family: var(--font-head);
  color: var(--gold-bright);
  text-align: right;
  font-size: var(--t-sm);
  min-width: 0;
  overflow-wrap: break-word;
}

.ws-tag {
  display: inline-block;
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  text-transform: uppercase;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--gold-dim);
  border-radius: 2px;
  color: var(--gold);
  margin: 0.15rem 0.25rem 0.15rem 0;
}

/* Relic tier accents */
:root {
  --c-lith: #5ec2e8;
  --c-meso: #6fcf8e;
  --c-neo: #e2546b;
  --c-axi: #e8e4d8;
  --c-requiem: #b07cf0;
  --c-omnia: var(--gold-bright);
}

.relic-grid {
  display: grid;
  gap: 0.5rem;
  margin: 0.7rem 0 0.9rem;
}

.relic-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  min-width: 0;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 0.3rem 0.6rem;
  font-size: var(--t-label);
}

.relic-icon {
  flex-shrink: 0;
}

.relic-chip .relic-name {
  font-family: var(--font-head);
  letter-spacing: var(--track);
  color: var(--text);
  text-transform: uppercase;
  font-size: var(--t-label);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 560px) {
  .relic-grid {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}

.relic-chip .relic-count {
  color: var(--text-dim);
}

.fissure-extra {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

.fissure-extra .ws-tag {
  margin: 0;
}

.fissure-soonest {
  margin-top: 0.9rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--panel-border);
}

.fissure-soonest-label {
  font-size: var(--t-label);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
  font-family: var(--font-head);
  margin-bottom: 0.5rem;
}

.fissure-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}

.fissure-row:last-child { margin-bottom: 0; }

.fissure-row .relic-icon {
  margin-top: 1px;
}

.fissure-row-body { flex: 1; min-width: 0; }

.fissure-row-text {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: var(--t-sm);
  margin-bottom: 0.3rem;
}

.fissure-row-text .fissure-node {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.fissure-row-text .fissure-time {
  font-family: var(--font-head);
  color: var(--gold-bright);
  font-size: var(--t-label);
  flex-shrink: 0;
}

.fissure-track {
  position: relative;
  height: 3px;
  border-radius: 2px;
  background: rgba(255,255,255,0.06);
  overflow: hidden;
}

.fissure-fill {
  position: absolute;
  inset: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright));
}

.fissure-fill.urgent {
  background: linear-gradient(90deg, #7a2e2e, #ef5d5d);
}

.ws-mission-list { margin-top: 0.4rem; }
.ws-mission-list li {
  list-style: none;
  font-size: var(--t-sm);
  color: var(--text-dim);
  padding: 0.3rem 0;
  border-bottom: 1px dashed rgba(212,175,55,0.08);
}
.ws-mission-list li:last-child { border-bottom: none; }
.ws-mission-list li span { color: var(--text); }
/* ⛔ A NIGHTWAVE ROW IS A FLEX ROW, NOT A FLOAT. The standing used to be an
   inline `float: right`, which drops onto its own line as soon as the challenge
   title is long enough to fill the row — i.e. on a phone, which is where this
   card is mostly read. Flex keeps the pair on one line and lets the name wrap.
   ⚠️ `.nw-name` restates the dim colour on purpose: the title used to be bare
   li text and is now a span, and the rule above brightens every span — so
   wrapping it for layout would silently have restyled every challenge name. */
.ws-mission-list li.nw-challenge {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3);
}
.nw-challenge .nw-name { min-width: 0; color: var(--text-dim); }
.nw-challenge .nw-rep  { flex: 0 0 auto; white-space: nowrap; }

.sortie-mission-row { color: var(--text-dim); }

.sortie-modifier {
  margin-top: 0.2rem;
  font-size: var(--t-label);
  font-style: italic;
  color: var(--gold-dim);
}

.ws-credit {
  text-align: center;
  margin-top: 2.5rem;
  font-size: var(--t-label);
  color: var(--text-dim);
}

/* ── Varzia — Primed Resurgence featured card ── */
.ws-card--varzia {
  min-height: 210px;
}

.ws-featured-kicker {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  color: var(--gold);
  opacity: .75;
  margin-bottom: .2rem;
}

.varzia-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: .7rem;
  gap: .5rem;
}

.varzia-loc {
  font-size: var(--t-label);
  color: var(--text-dim);
}

.varzia-timer {
  font-family: var(--font-head);
  font-size: var(--t-label);
  color: var(--gold-bright);
  white-space: nowrap;
}

.varzia-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .28rem;
}

.varzia-item {
  font-size: var(--t-sm);
  color: var(--text);
  padding-left: 1rem;
  position: relative;
  line-height: 1.35;
}

.varzia-item::before {
  content: '◈';
  position: absolute;
  left: 0;
  color: var(--gold);
  font-size: var(--t-micro);
  top: .12em;
}

.varzia-more {
  font-size: var(--t-label);
  color: var(--text-dim);
  margin-top: .5rem;
  opacity: .6;
}

/* ── Baro Ki'Teer featured card ── */
.ws-card--baro {
  min-height: 210px;
  padding: 0;
  overflow: hidden;
}

.ws-baro-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 30%;
  opacity: 0;
  transition: opacity 1.4s ease, transform 1.2s ease;
  transform: scale(1) translate(0, 0);
}

@media (hover: hover) and (pointer: fine) {
  .ws-card--baro:hover .ws-baro-bg {
    animation: baro-kenburns 9s ease-in-out infinite;
  }
}







.ws-card--baro::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, rgba(10,10,10,.72) 25%, rgba(10,10,10,.08) 100%);
  pointer-events: none;
  z-index: 1;
}

.ws-baro-inner {
  position: absolute;
  inset: 0;
  z-index: 2;
  padding: 1.4rem 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 1.2rem;
}

.ws-baro-top, .ws-baro-bottom { flex-shrink: 0; }

.ws-baro-kicker {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  color: var(--gold);
  margin-bottom: .25rem;
}

.ws-card--baro h4 {
  border-bottom: none;
  margin-bottom: .6rem;
  padding-bottom: 0;
}

.ws-baro-status {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 .55rem;
}

.ws-baro-status--here { color: var(--gold-bright); }

.ws-baro-bigtime {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  margin-bottom: .65rem;
}

.ws-baro-bigtime-label {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  color: var(--text-dim);
}

.ws-baro-bigtime-value {
  font-family: var(--font-head);
  font-size: var(--t-h3);
  font-weight: 700;
  color: var(--gold-bright);
  letter-spacing: var(--track-tight);
  line-height: 1.1;
  transition: font-size var(--swift);
}

.ws-baro-loc {
  font-size: var(--t-sm);
  color: var(--text-dim);
  margin: 0;
}





/* Tablet and up — Varzia's full item list grows taller than Baro's
   content. Title stays top-left, status/timer block stays bottom —
   the space-between gap absorbs the extra height. Scale typography
   up too so it fills the stretched card. */
@media (min-width: 700px) {
  .ws-card--baro,
  .ws-card--varzia { min-height: 260px; }
  .ws-baro-kicker { font-size: .72rem; }
  .ws-card--baro h4 { font-size: 1.05rem; }
  .ws-baro-status { font-size: .8rem; }
  .ws-baro-bigtime-label { font-size: .68rem; }
  .ws-baro-bigtime-value { font-size: 2.6rem; }
  .ws-baro-loc { font-size: .95rem; }
}

@media (min-width: 1024px) {
  .ws-card--baro,
  .ws-card--varzia { min-height: 300px; }
  .ws-baro-bigtime-value { font-size: 3.2rem; }
  .ws-baro-loc { font-size: 1rem; }
  
}

@media (min-width: 1440px) {
  .ws-baro-bigtime-value { font-size: 3.6rem; }
}

@media (max-width: 480px) {
  .ws-card--baro { min-height: 250px; }
  .ws-baro-inner { padding: 1.5rem 1.3rem; gap: 1.4rem; }
}

/* ============ Twitch Stream ============ */
.stream {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 5rem 1.5rem 8rem;
}

.stream .section-head {
  margin-bottom: 1.5rem;
}

.stream .section-title {
  font-size: clamp(1.8rem, 3.5vw, 2.6rem);
  margin-bottom: 0;
}

.stream-wrap {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 1rem;
  aspect-ratio: unset;
}

.stream-player {
  aspect-ratio: 16/9;
  background: #000;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  overflow: hidden;
}

.stream-chat {
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  overflow: hidden;
  min-height: 300px;
}

.stream-player iframe,
.stream-chat iframe {
  border: none;
  display: block;
}

.info-card-cta {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.4rem;
}

.stream-note {
  text-align: center;
  margin-top: 1.5rem;
  font-size: var(--t-label);
  color: var(--text-dim);
}

.stream-note code {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  padding: 0.1rem 0.4rem;
  border-radius: 2px;
  color: var(--gold);
}

/* ============ Market ============ */
.market {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 6.5rem 1.5rem 3rem;
  width: 100%;
}

.market-section-label {
  font-family: var(--font-head);
  color: var(--gold);
  letter-spacing: var(--track-max);
  font-size: var(--t-micro);
  text-transform: uppercase;
  margin-bottom: 0.9rem;
  opacity: 0.85;
  text-align: center;
}

/* --- Shared market animations --- */
@keyframes market-shimmer {
  0%   { background-position: -600px 0; }
  100% { background-position:  600px 0; }
}

@keyframes market-card-in {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: translateY(0);   }
}

/* --- Featured Spotlight --- */
.market-featured {
  margin-top: 3.5rem;
  margin-bottom: 3.5rem;
}

/* ⛔ A DELIBERATE COLUMN COUNT, NOT auto-fill. Eight spotlight items in
   `auto-fill(minmax(210px,1fr))` laid out as SIX then TWO on a wide screen —
   a full row and an orphaned pair, which reads as a loading state that never
   finished. This is the identical fault the homepage feature grid was fixed
   for (it was doing 3+1); the lesson did not travel, so it is written down
   here too. Eight items divide as 4→2→1 and every row is full at every width. */
.market-featured-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.85rem;
  transition: opacity var(--glide);
}
@media (max-width: 1100px) { .market-featured-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .market-featured-grid { grid-template-columns: 1fr; } }

.market-featured-grid.fading { opacity: 0; pointer-events: none; }

.featured-skeleton {
  height: 70px;
  border-radius: 2px;
  border: 1px solid var(--panel-border);
  background: linear-gradient(90deg,
    var(--panel) 25%,
    rgba(212, 175, 55, 0.07) 50%,
    var(--panel) 75%);
  background-size: 600px 100%;
  animation: market-shimmer 1.5s ease-in-out infinite;
}

.featured-card {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 0.85rem 1rem;
  cursor: pointer;
  text-align: left;
  transition: border-color var(--swift);
  min-width: 0;
  opacity: 0;
  animation: market-card-in 0.35s ease forwards;
  animation-delay: var(--delay, 0s);
}

.featured-card:hover { border-color: var(--gold-dim); }

.featured-card img {
  width: 38px;
  height: 38px;
  object-fit: contain;
  flex-shrink: 0;
  border-radius: 2px;
}

.featured-no-img {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  background: var(--bg-alt);
  border-radius: 2px;
}

.featured-card-info { flex: 1; min-width: 0; }

.featured-card-name {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 0.25rem;
}

.featured-card-price {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  color: var(--gold-bright);
  letter-spacing: var(--track-tight);
}

.featured-card-vol {
  font-size: var(--t-micro);
  color: var(--text-dim);
  margin-top: 0.1rem;
}

/* --- Category Tabs --- */
.market-cats { margin-bottom: 1.5rem; margin-top: 0.5rem; }

.market-cat-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.market-cat {
  flex: 1 1 0;
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  padding: 0.38rem 0.9rem;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
  transition: color var(--swift), border-color var(--swift), background var(--swift);
}

.market-cat:hover  { color: var(--gold); border-color: var(--gold-dim); }
.market-cat.active { color: var(--gold); border-color: var(--gold-dim); background: rgba(212, 175, 55, 0.07); }

/* Mobile — tighter text to fit all 5 in one row */
@media (max-width: 560px) {
  .market-cat-tabs { gap: 0.3rem; }
  .market-cat {
    font-size: 0.58rem;
    letter-spacing: var(--track);
    padding: 0.42rem 0.25rem;
  }
}

/* --- Category Browse Grid (fixed height — never causes layout shift) --- */
.market-cat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 0.5rem;
  height: 360px;
  overflow-y: auto;
  padding-right: 0.25rem;
  transition: opacity var(--glide);
}

.market-cat-grid.fading { opacity: 0; pointer-events: none; }

.cat-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 0.5rem 0.7rem;
  cursor: pointer;
  text-align: left;
  font-size: var(--t-label);
  color: var(--text-dim);
  min-width: 0;
  height: fit-content;
  transition: color var(--swift), border-color var(--swift);
  opacity: 0;
  animation: market-card-in 0.28s ease forwards;
  animation-delay: var(--delay, 0s);
}

.cat-item:hover { color: var(--text); border-color: var(--gold-dim); }

.cat-item img {
  width: 26px;
  height: 26px;
  object-fit: contain;
  flex-shrink: 0;
}

.cat-item span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cat-skeleton {
  height: 42px;
  border-radius: 2px;
  border: 1px solid var(--panel-border);
  background: linear-gradient(90deg,
    var(--panel) 25%,
    rgba(212, 175, 55, 0.05) 50%,
    var(--panel) 75%);
  background-size: 600px 100%;
  animation: market-shimmer 1.5s ease-in-out infinite;
}

.market-search {
  position: relative;
  max-width: 640px;
  margin: 0 auto 1rem;
}

.market-search-icon {
  position: absolute;
  top: 50%;
  left: 1.2rem;
  width: 20px;
  height: 20px;
  transform: translateY(-50%);
  color: var(--text-dim);
  pointer-events: none;
  transition: color var(--swift);
}

.market-search:focus-within .market-search-icon {
  color: var(--gold);
}

.market-input {
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.15rem 1.4rem 1.15rem 3.1rem;
  font-family: var(--font-body);
  font-size: var(--t-base);
  color: var(--text);
  transition: border-color var(--swift), box-shadow var(--swift);
}

.market-input::placeholder {
  color: var(--text-dim);
}

.market-input:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.12), 0 0 28px rgba(212, 175, 55, 0.18);
}

.market-results {
  position: absolute;
  top: calc(100% + 8px);
  left: 0; right: 0;
  background: var(--bg-alt);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  max-height: 320px;
  overflow-y: auto;
  z-index: 10;
  display: none;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}

.market-results.open { display: block; }

.market-result {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  width: 100%;
  padding: 0.7rem 1rem;
  background: none;
  border: none;
  border-bottom: 1px solid var(--panel-border);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--t-sm);
  text-align: left;
  cursor: pointer;
  transition: background var(--swift);
}

.market-result:last-child { border-bottom: none; }
.market-result:hover, .market-result:focus { background: var(--panel); color: var(--gold); }

.market-result img {
  width: 32px;
  height: 32px;
  object-fit: contain;
  flex-shrink: 0;
}

/* --- Market Modal --- */
.market-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--glide);
}

.market-modal-overlay.open {
  opacity: 1;
  pointer-events: all;
}

.market-modal {
  position: relative;
  background: var(--bg-alt);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  width: 100%;
  max-width: 780px;
  max-height: 88vh;
  overflow-y: auto;
  padding: 2rem;
  transform: translateY(18px) scale(0.97);
  transition: transform var(--glide);
}

.market-modal-overlay.open .market-modal {
  transform: translateY(0) scale(1);
}

.market-modal-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 32px;
  height: 32px;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  background: transparent;
  color: var(--text-dim);
  font-size: var(--t-base);
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color var(--swift), border-color var(--swift);
  flex-shrink: 0;
}

.market-modal-close:hover { color: var(--gold); border-color: var(--gold-dim); }

.market-modal-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.5rem;
  padding-bottom: 1.2rem;
  padding-right: 2.5rem;
  border-bottom: 1px solid var(--panel-border);
}

.market-modal-head img {
  width: 54px;
  height: 54px;
  object-fit: contain;
  border-radius: 2px;
  flex-shrink: 0;
}

.market-modal-title { flex: 1; min-width: 0; }

.market-modal-title h3 {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.3rem;
}

.market-modal-title a {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-dim);
}

.market-modal-title a:hover { color: var(--gold-bright); }

.market-status {
  text-align: center;
  color: var(--text-dim);
  font-size: var(--t-sm);
  margin: 1rem 0 0;
}

.market-status.error { color: #ef5d5d; }

.market-orders {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
}

.market-orders h4 {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 0.7rem;
}

.order-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-sm);
}

.order-table th {
  text-align: left;
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 0.4rem 0.5rem;
  border-bottom: 1px solid var(--panel-border);
}

.order-table td {
  padding: 0.5rem;
  border-bottom: 1px dashed rgba(212,175,55,0.08);
}

.order-table td:last-child,
.order-table th:last-child { text-align: right; }

.order-table .plat {
  color: var(--gold-bright);
  font-family: var(--font-head);
  white-space: nowrap;
}

.order-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 0.5rem;
}

.order-dot.ingame  { background: #4ade80; }
.order-dot.online  { background: #facc15; }
.order-dot.offline { background: var(--text-dim); opacity: 0.4; }

.copy-cell { width: 32px; text-align: right; }

.copy-trade-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid transparent;
  border-radius: 2px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  transition: color var(--swift), border-color var(--swift), background var(--swift);
  flex-shrink: 0;
}

.copy-trade-btn svg { width: 13px; height: 13px; }

.copy-trade-btn:hover {
  color: var(--gold);
  border-color: var(--gold-dim);
  background: rgba(212, 175, 55, 0.07);
}

.copy-trade-btn.copied {
  color: #4ade80;
  border-color: rgba(74, 222, 128, 0.4);
  background: rgba(74, 222, 128, 0.06);
}

/* ============ Blog ============ */
.blog {
  max-width: 880px;
  margin: 0 auto;
  padding: 7rem 1.5rem;
}

.blog-list {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  margin-top: 2.5rem;
}

.blog-card {
  display: block;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.6rem 1.8rem;
  text-decoration: none;
  transition: border-color var(--glide), transform var(--glide);
}

.blog-card:hover {
  border-color: var(--gold);
  transform: translateX(4px);
}

.blog-date {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--gold-dim);
  margin-bottom: 0.5rem;
}

.blog-card h2 {
  font-family: var(--font-head);
  font-size: var(--t-lead);
  letter-spacing: var(--track-tight);
  color: var(--gold);
  margin-bottom: 0.6rem;
}

.blog-card p {
  color: var(--text-dim);
  font-size: var(--t-base);
  font-weight: 300;
}

.blog-empty {
  text-align: center;
  color: var(--text-dim);
  margin-top: 2.5rem;
}

/* ===== Blog index — full-bleed cinematic magazine ===== */
.blog-index { position: relative; width: 100%; padding: 5rem 0 6rem; overflow-x: clip; }

/* contained body below the full-bleed hero */
.blog-body { max-width: 1340px; margin: 0 auto; padding: 0 2rem; }

.blog-card-tag {
  display: flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-head);
  font-size: var(--t-micro); letter-spacing: var(--track-max); text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 0.7rem;
}
.blog-tag-type { color: var(--gold-bright); }
.blog-tag-dot { color: var(--gold); opacity: 0.55; }

/* Full-bleed hero — latest post */
.blog-hero {
  position: relative; display: block; overflow: hidden; width: 100%;
  min-height: clamp(440px, 74vh, 780px); text-decoration: none; background: #070707;
  border-top: 1px solid var(--panel-border); border-bottom: 1px solid var(--panel-border);
}
.blog-hero-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; animation: kenburns 22s ease-in-out infinite alternate; }
@keyframes kenburns { from { transform: scale(1.03); } to { transform: scale(1.12); } }
.blog-hero-grad { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(6,6,6, 0) 0%, rgba(6,6,6, 0.2) 40%, rgba(6,6,6, 0.94) 100%), linear-gradient(90deg, rgba(6,6,6, 0.6), transparent 60%); }
.blog-hero--noimg { background: radial-gradient(circle at 28% 30%, rgba(212, 175, 55, 0.16), transparent 58%), #0c0c0c; }
.blog-hero--noimg .blog-hero-grad { display: none; }
.blog-hero-inner { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; max-width: 1340px; margin: 0 auto; padding: 0 2rem 3.4rem; }
.blog-hero-index { font-family: var(--font-head); color: var(--gold-bright); margin-right: 0.15rem; }
.blog-hero-title {
  font-family: var(--font-head); font-weight: 700;
  font-size: clamp(2.6rem, 6.5vw, 5rem); line-height: 0.98; letter-spacing: -0.015em;
  color: #fff; margin: 0 0 1rem; text-shadow: 0 4px 34px rgba(0, 0, 0, 0.55); max-width: 900px;
}
.blog-hero-excerpt { color: rgba(255, 255, 255, 0.85); font-size: var(--t-base); font-weight: 300; line-height: 1.6; margin: 0 0 1.5rem; max-width: 620px; }
.blog-read {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--gold-bright);
}
.blog-read span { transition: transform var(--swift); }
.blog-hero:hover .blog-read span, .blog-feat:hover .blog-read span { transform: translateX(4px); }

/* Masthead row */
.blog-masthead {
  display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: 1.2rem;
  margin: 3.2rem 0 2.4rem; padding-bottom: 1.4rem; border-bottom: 1px solid var(--panel-border);
}
.blog-masthead-title { font-family: var(--font-head); font-weight: 700; font-size: clamp(1.9rem, 4vw, 3rem); color: var(--text); margin: 0.4rem 0 0.6rem; letter-spacing: var(--track-tight); }
.blog-masthead-desc { color: var(--text-dim); font-size: var(--t-base); font-weight: 300; max-width: 560px; margin: 0; line-height: 1.6; }
.blog-masthead-count { font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted); white-space: nowrap; }
.blog-masthead-count span { color: var(--gold-bright); font-size: var(--t-lead); margin-right: 0.2rem; }

/* Featured — 3 columns, indexed */
.blog-featured { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
.blog-feat {
  position: relative; display: block; overflow: hidden;
  border-radius: 14px; border: 1px solid var(--panel-border);
  min-height: clamp(300px, 32vh, 380px); text-decoration: none; background: #070707;
  transition: border-color var(--glide), box-shadow var(--glide), transform var(--glide);
}
.blog-feat:hover { border-color: rgba(212, 175, 55, 0.45); box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45); transform: translateY(-3px); }
.blog-feat::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; z-index: 3;
  background: var(--gold); transform: scaleX(0); transform-origin: left; transition: transform var(--glide);
}
.blog-feat:hover::before { transform: scaleX(1); }
.blog-feat-index { position: absolute; top: 0.9rem; left: 1.1rem; z-index: 3; font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track-wide); color: var(--gold-bright); text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6); }
.blog-feat-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform var(--glide); }
.blog-feat:hover .blog-feat-img { transform: scale(1.06); }
.blog-feat-grad { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(6,6,6, 0.05), rgba(6,6,6, 0.4) 50%, rgba(6,6,6, 0.94)); }
.blog-feat--noimg { background: radial-gradient(circle at 70% 20%, rgba(212, 175, 55, 0.1), transparent 60%), #0c0c0c; }
.blog-feat--noimg .blog-feat-grad { display: none; }
.blog-feat-inner { position: absolute; left: 0; right: 0; bottom: 0; padding: 1.5rem 1.6rem; z-index: 2; }
.blog-feat-title { font-family: var(--font-head); font-weight: 700; font-size: var(--t-lead); line-height: 1.12; color: #fff; margin: 0 0 0.5rem; text-shadow: 0 2px 14px rgba(0, 0, 0, 0.5); }
.blog-feat-excerpt {
  color: rgba(255, 255, 255, 0.8); font-size: var(--t-sm); font-weight: 300; line-height: 1.5; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

@media (max-width: 1000px) { .blog-featured { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .blog-featured { grid-template-columns: 1fr; } }

/* scroll-reveal entrance for blog cards */
.blog-feat.scroll-reveal-item, .blog-arch-row.scroll-reveal-item { opacity: 0; transform: translateY(24px); transition: opacity var(--glide), transform var(--glide); }
.blog-feat.in-view, .blog-arch-row.in-view { opacity: 1; transform: none; }

/* tags over imagery read light */
.blog-hero-inner .blog-card-tag, .blog-feat-inner .blog-card-tag, .post-hero-text .blog-card-tag { color: rgba(255, 255, 255, 0.72); }

/* Archive — indexed terminal list */
.blog-archive { margin-top: 4.5rem; }
.blog-archive-head {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track-max); text-transform: uppercase;
  color: var(--gold); opacity: 0.8; margin-bottom: 0.4rem; padding-bottom: 0.9rem; border-bottom: 1px solid var(--panel-border);
}
.blog-archive-list { display: flex; flex-direction: column; }
.blog-arch-row {
  display: grid; grid-template-columns: auto auto auto 1fr auto; align-items: center; gap: 1.1rem;
  padding: 1.15rem 0.5rem; border-bottom: 1px solid var(--panel-border);
  text-decoration: none; transition: background var(--swift), padding var(--swift);
}
.blog-arch-row:hover { background: rgba(212, 175, 55, 0.06); padding-left: 0.9rem; }
.blog-arch-num { font-family: var(--font-head); font-size: var(--t-label); color: var(--gold); opacity: 0.55; }
.blog-arch-date { font-family: var(--font-head); font-size: var(--t-label); color: var(--gold-dim); letter-spacing: var(--track-tight); }
.blog-arch-sep { color: var(--gold); opacity: 0.5; }
.blog-arch-title { color: var(--text); font-size: var(--t-base); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.blog-arch-row:hover .blog-arch-title { color: var(--gold-bright); }
.blog-arch-type {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track-wide); color: var(--muted);
  border: 1px solid var(--panel-border); border-radius: 20px; padding: 0.2rem 0.6rem; white-space: nowrap;
}

/* ===== Post view — full-bleed cinematic ===== */
.post-view { width: 100%; padding: 0 0 6rem; overflow-x: clip; }

/* contained content shell */
.post-shell { max-width: 1140px; margin: 0 auto; padding: 0 2rem; }
/* no-image posts: clear the fixed nav, keep the inline back link */
.post-shell--nohero { padding-top: 6.5rem; }
.post-shell--nohero .post-back { display: inline-block; margin-bottom: 1.5rem; }
/* floating back-link pill over the full-bleed hero */
.post-back--over {
  position: absolute; top: 5.4rem; left: 2rem; z-index: 5; margin: 0;
  background: rgba(8,8,8, 0.5); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(212, 175, 55, 0.3); border-radius: 20px; padding: 0.42rem 0.95rem;
}
.post-back--over:hover { background: rgba(8,8,8, 0.72); border-color: var(--gold); }

/* full-bleed hero — runs up behind the transparent fixed nav */
.post-hero-wrap { width: 100%; margin-bottom: 2.6rem; }
.post-hero {
  position: relative; overflow: hidden; width: 100%;
  min-height: clamp(480px, 74vh, 780px); background: #070707;
  border-bottom: 1px solid var(--panel-border);
}
.post-hero-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; animation: kenburns 24s ease-in-out infinite alternate; }
.build-post .post-hero-img { cursor: zoom-in; }
.post-hero-grad { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(6,6,6, 0.5) 0%, rgba(6,6,6, 0.12) 14%, rgba(6,6,6, 0.28) 50%, rgba(6,6,6, 0.95) 100%), linear-gradient(90deg, rgba(6,6,6, 0.55), transparent 60%); pointer-events: none; }
.post-hero-text { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; }
.post-hero-text-inner { max-width: 1140px; margin: 0 auto; padding: 0 2rem 3rem; }
.post-hero-title {
  font-family: var(--font-head); font-weight: 700;
  font-size: clamp(2.4rem, 6.5vw, 4.6rem); line-height: 0.98; letter-spacing: -0.015em; color: #fff; margin: 0;
  text-shadow: 0 4px 30px rgba(0, 0, 0, 0.6); max-width: 900px;
}
.post-hero-wrap .bg-thumbs { margin-top: 1rem; }

/* premium reading */
.post-view .post-body { font-size: var(--t-base); line-height: 1.85; }
.post-view .post-body p { margin-bottom: 1.45rem; }
.post-body--article { max-width: 720px; margin: 0 auto; }
.post-body--article > p:first-of-type::first-letter {
  float: left; font-family: var(--font-head); font-weight: 700;
  font-size: var(--t-h1); line-height: 0.78; padding: 0.1rem 0.55rem 0 0; color: var(--gold-bright);
}

/* comments — narrower, centered reading column */
.post-shell .comments { max-width: 760px; margin: 4rem auto 0; }

/* Two-column post grid: body+comments (left) | widgets+conversation card (right) */
.post-grid { display: grid; grid-template-columns: 1fr 320px; gap: 2.4rem; margin-top: 2rem; align-items: start; }
.post-col-main { min-width: 0; }
.post-col-main .post-body { margin-top: 0; }
.post-col-main .comments { max-width: none; margin: 3.6rem 0 0; }
.post-col-side { position: sticky; top: 90px; display: flex; flex-direction: column; gap: 1.2rem; align-self: start; }

/* Conversation card (sidebar, below widgets) */
.conversation-card {
  background: var(--panel); border: 1px solid rgba(212, 175, 55, 0.22); border-radius: 12px;
  padding: 1.3rem 1.3rem 1.4rem; text-align: center;
}
.conv-kicker { font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track-max); text-transform: uppercase; color: var(--gold); opacity: 0.8; margin: 0 0 0.7rem; }
.conv-count { font-family: var(--font-head); font-size: var(--t-sm); letter-spacing: var(--track-tight); text-transform: uppercase; color: var(--text-dim); margin: 0 0 0.9rem; }
.conv-count strong { color: var(--gold-bright); font-size: var(--t-h3); display: block; line-height: 1; margin-bottom: 0.25rem; }
.conv-avatars { display: flex; justify-content: center; align-items: center; margin-bottom: 0.55rem; }
.conv-avatars img { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; border: 2px solid var(--panel); margin-left: -8px; box-shadow: 0 0 0 1px rgba(212, 175, 55, 0.45); }
.conv-avatars img:first-child { margin-left: 0; }
.conv-more { font-family: var(--font-head); font-size: var(--t-micro); color: var(--gold-bright); margin-left: 0.4rem; }
.conv-sub { font-size: var(--t-label); color: var(--muted); margin: 0 0 1.1rem; }
.conv-cta { width: 100%; box-sizing: border-box; text-align: center; }

/* Article body fills the grid column (not the 720 centered measure) */
.post-col-main .post-body--article { max-width: none; margin: 0; }

/* Table of Contents widget */
.toc-widget { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; padding: 1.2rem 1.3rem; }
.toc-title { font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track-max); text-transform: uppercase; color: var(--gold); opacity: 0.8; margin: 0 0 0.7rem; padding-bottom: 0.6rem; border-bottom: 1px solid var(--panel-border); }
.toc-widget ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.toc-widget li { line-height: 1.4; }
.toc-widget a {
  display: block; color: var(--text-dim); font-size: var(--t-sm); text-decoration: none;
  padding: 0.35rem 0.6rem; border-left: 2px solid transparent; border-radius: 0 4px 4px 0;
  transition: color var(--swift), border-color var(--swift), background var(--swift);
}
.toc-widget a:hover { color: var(--text); background: rgba(212, 175, 55, 0.05); }
.toc-widget a.active { color: var(--gold-bright); border-left-color: var(--gold); background: rgba(212, 175, 55, 0.08); }
.toc-l3 a { padding-left: 1.5rem; font-size: var(--t-label); }

/* heading anchor offset under the fixed nav */
.post-body h2[id], .post-body h3[id] { scroll-margin-top: 90px; }

/* Comment cards — dividers + hover */
.comment-list { gap: 0; }
.comment { padding: 0.7rem 0.6rem 1.4rem; margin-bottom: 1.4rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); border-radius: 8px; transition: background var(--swift); }
.comment:hover { background: rgba(255, 255, 255, 0.018); }
.comment:last-child { margin-bottom: 0; padding-bottom: 0.6rem; border-bottom: none; }

@media (prefers-reduced-motion: reduce) {
  .blog-hero-img, .post-hero-img { animation: none; }
}

@media (max-width: 760px) {
  .blog-index { padding-top: 4rem; }
  .blog-hero { min-height: clamp(360px, 64vh, 520px); }
  .blog-hero-inner { padding: 0 1.3rem 2.2rem; }
  .blog-masthead { margin-top: 2.4rem; }
  .blog-arch-row { grid-template-columns: auto auto 1fr; gap: 0.7rem; }
  .blog-arch-sep, .blog-arch-type { display: none; }
  .post-shell { padding-left: 1.3rem; padding-right: 1.3rem; }
  .post-shell--nohero { padding-top: 5.5rem; }
  .post-hero { min-height: clamp(340px, 62vh, 520px); }
  .post-hero-text-inner { padding: 0 1.3rem 2rem; }
  .post-back--over { top: 4.6rem; left: 1.3rem; }
}

/* ===== Comments ===== */
.comments { max-width: 820px; margin: 3.5rem 0 0; }
.comments-head {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.8rem;
  margin-bottom: 1.4rem; padding-bottom: 0.8rem; border-bottom: 1px solid var(--panel-border);
}
.comments-title { font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--gold); margin: 0; }
.comments-count { color: var(--gold-bright); }
.comment-sort { display: inline-flex; gap: 0.2rem; background: var(--bg); border: 1px solid var(--panel-border); border-radius: 20px; padding: 0.18rem; }
.comment-sort button {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--text-dim); background: none; border: none; border-radius: 16px; padding: 0.3rem 0.75rem;
  cursor: pointer; transition: background var(--swift), color var(--swift);
}
.comment-sort button:hover { color: var(--text); }
.comment-sort button.active { background: var(--gold); color: #0a0a0a; font-weight: 700; }

.comment-form { display: flex; gap: 0.9rem; margin-bottom: 2rem; }
.comment-form-main { flex: 1; }
.comment-form textarea {
  width: 100%; box-sizing: border-box; background: var(--panel);
  border: 1px solid var(--panel-border); border-radius: 8px;
  padding: 0.8rem 0.9rem; color: var(--text); font-family: var(--font-body);
  font-size: var(--t-base); line-height: 1.5; resize: vertical;
}
.comment-form textarea:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.15); }
.comment-form-actions { display: flex; justify-content: flex-end; margin-top: 0.6rem; }

.comment-login {
  display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; justify-content: space-between;
  background: var(--panel); border: 1px solid var(--panel-border);
  border-radius: 8px; padding: 1.1rem 1.3rem; margin-bottom: 2rem;
}
.comment-login p { margin: 0; color: var(--text-dim); }

.comment-list { display: flex; flex-direction: column; gap: 1.4rem; }
.comments-empty { color: var(--text-dim); font-style: italic; }

.comment { display: flex; gap: 0.9rem; }
.comment-avatar {
  width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; object-fit: cover;
}
.comment-main { flex: 1; min-width: 0; }
.comment-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.35rem; }
.comment-author { font-family: var(--font-head); font-size: var(--t-sm); color: var(--text); letter-spacing: var(--track-tight); }
.comment-time { font-size: var(--t-label); color: var(--muted); }

.comment-badge {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); text-transform: uppercase;
  padding: 0.15rem 0.45rem; border-radius: 20px; border: 1px solid; font-weight: 700;
}
.comment-badge--officer  { color: #ff6b5b; border-color: rgba(255, 107, 91, 0.5); background: rgba(255, 107, 91, 0.1); }
.comment-badge--member   { color: #5dade2; border-color: rgba(93, 173, 226, 0.45); background: rgba(93, 173, 226, 0.1); }
.comment-badge--tenno    { color: var(--gold-bright); border-color: rgba(212, 175, 55, 0.45); background: rgba(212, 175, 55, 0.1); }
.comment-badge--operator { color: #c0bdb0; border-color: rgba(192, 189, 176, 0.45); background: rgba(192, 189, 176, 0.1); }
.comment-badge--warlord  { color: #e8940a; border-color: rgba(232, 148, 10, 0.5); background: rgba(232, 148, 10, 0.12); }

.comment-del { margin-left: auto; }
.comment-del button { background: none; border: none; color: var(--muted); cursor: pointer; font-size: var(--t-base); padding: 0 0.2rem; line-height: 1; }
.comment-del button:hover { color: #e74c3c; }

.comment-text { color: var(--text-dim); font-size: var(--t-base); line-height: 1.6; font-weight: 300; margin: 0; overflow-wrap: anywhere; }

.comment-actions { margin-top: 0.55rem; }
.comment-like {
  display: inline-flex; align-items: center; gap: 0.4rem;
  background: none; border: 1px solid var(--panel-border); border-radius: 20px;
  padding: 0.22rem 0.65rem; color: var(--muted); cursor: pointer;
  font-family: var(--font-head); font-size: var(--t-label);
  transition: color var(--swift), border-color var(--swift), background var(--swift);
}
.comment-like svg { width: 13px; height: 13px; transition: fill var(--swift); }
.comment-like:hover { color: var(--gold-bright); border-color: rgba(212, 175, 55, 0.4); }
.comment-like.is-liked { color: var(--gold-bright); border-color: var(--gold); background: rgba(212, 175, 55, 0.12); }
.comment-like.is-liked svg { fill: currentColor; }
.comment-like:disabled { opacity: 0.6; cursor: default; }

@media (max-width: 600px) {
  .comment-form, .comment { gap: 0.6rem; }
  .comment-avatar { width: 34px; height: 34px; }
}

/* Single post */
.post-back {
  display: inline-block;
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--gold-bright);
  margin-bottom: 2rem;
}

.post-header h1 {
  font-family: var(--font-head);
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  letter-spacing: var(--track-tight);
  color: var(--gold);
  margin-bottom: 0.6rem;
  line-height: 1.3;
}

.post-body {
  margin-top: 2rem;
  color: var(--text-dim);
  font-size: var(--t-base);
  font-weight: 300;
  line-height: 1.8;
}

.post-body h1, .post-body h2, .post-body h3, .post-body h4 {
  font-family: var(--font-head);
  color: var(--gold);
  letter-spacing: var(--track-tight);
  margin: 1.8rem 0 0.8rem;
}

.post-body p { margin-bottom: 1.1rem; }

.post-body ul {
  margin: 0 0 1.1rem 1.2rem;
  list-style: disc;
}

.post-body li { margin-bottom: 0.4rem; }

.post-body blockquote {
  border-left: 2px solid var(--gold);
  padding: 0.4rem 0 0.4rem 1.2rem;
  margin: 1.2rem 0;
  color: var(--text);
  font-style: italic;
}

.post-body a { color: var(--gold-bright); }

.post-body img { max-width: 100%; border-radius: 2px; margin: 0.4rem 0; }

.post-figure { margin: 1.8rem 0; }
.post-figure img { width: 100%; border-radius: 8px; border: 1px solid var(--panel-border); }
.post-figure figcaption {
  margin-top: 0.6rem;
  text-align: center;
  font-size: var(--t-sm);
  color: var(--muted);
  font-style: italic;
}

/* ── Fashion / build posts ── */
.post-type-tag {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.4rem;
}

.build-gallery-view { margin: 1.6rem 0; }

.bg-main {
  width: 100%;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--panel-border);
  background: #060606;
}

.bg-main img { width: 100%; max-height: 70vh; object-fit: contain; display: block; cursor: zoom-in; }

.bg-thumbs { display: flex; gap: 0.5rem; margin-top: 0.7rem; flex-wrap: wrap; }

.bg-thumb {
  width: 76px; height: 60px; padding: 0; border-radius: 2px; overflow: hidden;
  border: 2px solid transparent; background: none; cursor: pointer; opacity: 0.7;
  transition: opacity var(--swift), border-color var(--swift);
}

.bg-thumb img { width: 100%; height: 100%; object-fit: cover; }
.bg-thumb:hover { opacity: 1; }
.bg-thumb.active { border-color: var(--gold); opacity: 1; }

.build-layout {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 2rem;
  margin-top: 2rem;
  align-items: start;
}

.build-main { margin-top: 0; }

.build-loadout-panel {
  position: sticky;
  top: 90px;
  background: var(--panel);
  border: 1px solid rgba(212, 175, 55, 0.25);
  border-radius: 10px;
  padding: 1.2rem 1.3rem;
}

/* Sidebar widget accordion */
.post-widgets { position: sticky; top: 90px; display: flex; flex-direction: column; gap: 0.8rem; }
.widget { background: var(--panel); border: 1px solid rgba(212, 175, 55, 0.22); border-radius: 10px; overflow: hidden; }
.widget-head {
  width: 100%; display: flex; align-items: center; gap: 0.6rem;
  background: none; border: none; cursor: pointer; text-align: left;
  padding: 0.95rem 1.1rem; color: var(--text);
  font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track-wide); text-transform: uppercase;
  transition: background var(--swift);
}
.widget-head:hover { background: rgba(212, 175, 55, 0.06); }
.widget-title { color: var(--gold-bright); }
.widget-count { font-family: var(--font-body); font-size: var(--t-label); color: var(--muted); letter-spacing: 0; }
.widget-chevron { margin-left: auto; color: var(--gold); transition: transform var(--glide); font-size: var(--t-sm); }
.widget.is-open .widget-chevron { transform: rotate(180deg); }
.widget-body { display: none; padding: 0 1rem 1.1rem; }
.widget.is-open .widget-body { display: block; }

/* In-game link rows */
.ingame-links { display: flex; flex-direction: column; gap: 0.5rem; }
.ingame-link { display: flex; align-items: center; gap: 0.6rem; }
.ingame-link-label { font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track-tight); color: var(--text); flex: 1; }
.ingame-copy {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--gold-bright); background: rgba(212, 175, 55, 0.1); border: 1px solid var(--gold);
  border-radius: 2px; padding: 0.32rem 0.75rem; cursor: pointer; transition: background var(--swift), color var(--swift);
}
.ingame-copy:hover { background: var(--gold); color: var(--bg); }
.ingame-copy.is-copied { background: #2ecc71; border-color: #2ecc71; color: var(--bg); }
.ingame-hint { font-size: var(--t-label); color: var(--muted); margin: 0.7rem 0 0; font-style: italic; line-height: 1.5; }

.loadout-title {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-bright);
  margin-bottom: 1rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--panel-border);
}

.loadout-items { display: flex; flex-direction: column; gap: 0.4rem; }

.loadout-item {
  display: flex; align-items: center; gap: 0.7rem;
  width: 100%; text-align: left; background: none; cursor: pointer;
  border: 1px solid transparent; border-radius: 8px; padding: 0.4rem;
  color: inherit; font: inherit;
  transition: background var(--swift), border-color var(--swift);
}

.loadout-item:hover { background: rgba(212, 175, 55, 0.08); border-color: rgba(212, 175, 55, 0.25); }

.loadout-expand { margin-left: auto; color: var(--muted); font-size: var(--t-base); flex-shrink: 0; opacity: 0.5; transition: opacity var(--swift), color var(--swift); }
.loadout-item:hover .loadout-expand { opacity: 1; color: var(--gold-bright); }

/* Item "maximize" modal */
.loadout-modal {
  position: fixed; inset: 0; z-index: 1100;
  background: rgba(4,4,4, 0.9);
  display: flex; align-items: center; justify-content: center; padding: 1.5rem;
}

.loadout-modal[hidden] { display: none; }

.loadout-modal-card {
  position: relative;
  background: var(--panel);
  border: 1px solid rgba(212, 175, 55, 0.3);
  border-radius: 14px;
  padding: 2rem 2rem 1.6rem;
  max-width: 420px; width: 100%;
  text-align: center;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}

.loadout-modal-close {
  position: absolute; top: 0.7rem; right: 1rem;
  background: none; border: none; color: var(--muted);
  font-size: var(--t-h3); cursor: pointer; line-height: 1;
}
.loadout-modal-close:hover { color: var(--gold-bright); }

.loadout-modal-iconwrap {
  width: 120px; height: 120px; margin: 0 auto 1rem;
  border-radius: 12px; background: #060606; border: 1px solid var(--panel-border);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.loadout-modal-icon { width: 100%; height: 100%; object-fit: contain; }

.loadout-modal-cat {
  font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--gold); margin-bottom: 0.3rem;
}

.loadout-modal-name { font-family: var(--font-head); color: var(--text); font-size: var(--t-lead); margin-bottom: 0.7rem; }

.loadout-modal-desc {
  color: var(--text-dim); font-size: var(--t-sm); line-height: 1.6;
  margin-bottom: 1.2rem; font-weight: 300; white-space: pre-line;
}

.loadout-modal-wiki {
  display: inline-block;
  font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--gold-bright) !important; border: 1px solid var(--gold);
  border-radius: 2px; padding: 0.5rem 1rem; transition: background var(--swift), color var(--swift);
}
.loadout-modal-wiki:hover { background: var(--gold); color: var(--bg) !important; }

.loadout-icon {
  width: 42px; height: 42px; border-radius: 2px; object-fit: cover; flex-shrink: 0;
  background: #060606; border: 1px solid var(--panel-border);
}

.loadout-icon--ph { display: inline-block; }
.loadout-meta { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }

.loadout-slot {
  font-family: var(--font-head);
  font-size: var(--t-micro); letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--muted);
}

.loadout-name { color: var(--text); font-size: var(--t-sm); font-weight: 500; }
a.loadout-name:hover { color: var(--gold-bright); }

@media (max-width: 820px) {
  .build-layout, .post-grid { grid-template-columns: 1fr; }
  .build-loadout-panel, .post-widgets, .post-col-side { position: static; }
  .post-col-side { flex-direction: column; }
  .post-col-main .comments { margin-top: 2.6rem; }
  /* On mobile the conversation card is redundant with the inline composer. */
  .conversation-card { display: none; }
}

/* Lightbox */
.bg-lightbox {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(4,4,4, 0.94);
  display: flex; align-items: center; justify-content: center;
}

.bg-lightbox[hidden] { display: none; }
.bg-lb-img { max-width: 90vw; max-height: 88vh; border-radius: 8px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6); }

.bg-lb-btn {
  position: absolute; background: rgba(21,21,21, 0.8); border: 1px solid rgba(212, 175, 55, 0.4);
  color: var(--gold-bright); width: 46px; height: 46px; border-radius: 50%;
  font-size: var(--t-lead); cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background var(--swift);
}

.bg-lb-btn:hover { background: rgba(212, 175, 55, 0.25); }
.bg-lb-close { top: 1.4rem; right: 1.4rem; }
.bg-lb-prev { left: 1.4rem; top: 50%; transform: translateY(-50%); }
.bg-lb-next { right: 1.4rem; top: 50%; transform: translateY(-50%); }

@media (max-width: 600px) {
  .bg-lb-prev { left: 0.5rem; }
  .bg-lb-next { right: 0.5rem; }
}

.post-body code {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  padding: 0.1rem 0.4rem;
  border-radius: 2px;
  color: var(--gold);
  font-size: 0.9em;
}

/* ============ Stream Info Grid ============ */
.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
  margin-top: 3rem;
}

.info-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.8rem 1.6rem;
}

.info-card h3 {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.8rem;
}

.info-card p {
  color: var(--text-dim);
  font-size: var(--t-sm);
  font-weight: 300;
  line-height: 1.7;
}

.info-card ul {
  margin-top: 0.6rem;
  list-style: none;
}

.info-card ul li {
  display: flex;
  justify-content: space-between;
  font-size: var(--t-sm);
  padding: 0.45rem 0;
  border-bottom: 1px dashed rgba(212,175,55,0.08);
  color: var(--text-dim);
}

.info-card ul li:last-child { border-bottom: none; }

.info-card ul li span:first-child {
  color: var(--text);
  font-family: var(--font-head);
  letter-spacing: var(--track-tight);
  font-size: var(--t-sm);
  text-transform: uppercase;
}

.info-card ul li span:last-child {
  color: var(--gold-bright);
}

/* ============ Join ============ */
.join {
  position: relative;
  padding: 8rem 1.5rem;
  overflow: hidden;
}

.join-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 25%;
  filter: saturate(0.7) brightness(0.4);
}

.join-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--bg) 0%, rgba(7,7,7,0.75) 40%, var(--bg) 100%);
}

.join-content {
  position: relative;
  z-index: 2;
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
}

.join-content .section-desc {
  margin-bottom: 2.5rem;
}

.join-requirements {
  display: grid;
  gap: 1rem;
  margin-bottom: 2.5rem;
  text-align: left;
}

.req {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1rem 1.25rem;
}

.req-icon {
  color: var(--gold);
  font-size: var(--t-base);
  line-height: 1.4;
}

.req p {
  color: var(--text-dim);
  font-size: var(--t-base);
  font-weight: 300;
}

.join-actions { margin-bottom: 1.2rem; }

.join-note {
  font-size: var(--t-label);
  color: var(--text-dim);
}

.join-note code {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  padding: 0.1rem 0.4rem;
  border-radius: 2px;
  color: var(--gold);
}

/* ============ Footer ============ */
.site-footer {
  position: relative;
  overflow: hidden;
  margin-top: auto;
  background: transparent;
}

/* Decorative dot-tech pattern fading in along the bottom edge */
.footer-pattern {
  position: absolute;
  inset: auto 0 0 0;
  height: 110px;
  background-image: radial-gradient(circle, rgba(212, 175, 55, 0.35) 1px, transparent 1.6px);
  background-size: 24px 24px;
  -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(to top, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
  opacity: 0.45;
  pointer-events: none;
}

.footer-content {
  position: relative;
  z-index: 1;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.5rem 1.5rem;
}

.footer-main {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
}

.footer-text,
.footer-credit {
  flex: 1 1 0;
}

.footer-logo {
  font-family: var(--font-head);
  font-weight: 900;
  font-size: var(--t-base);
  letter-spacing: var(--track-wide);
  white-space: nowrap;
  text-align: center;
}

.footer-logo span { color: var(--gold); }
.footer-logo .footer-network { color: var(--muted); font-size: 0.7em; letter-spacing: var(--track-max); }

.footer-text {
  color: var(--text-dim);
  font-size: var(--t-label);
  opacity: 0.7;
  text-align: left;
  line-height: 1.6;
}

.footer-credit {
  color: var(--text-dim);
  font-size: var(--t-micro);
  opacity: 0.55;
  text-align: right;
  line-height: 1.6;
}

@media (max-width: 760px) {
  .footer-main {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.6rem;
  }

  .footer-logo {
    order: -1;
  }

  .footer-text,
  .footer-credit {
    text-align: center;
  }
}

/* ============ Responsive ============ */
@media (max-width: 820px) {
  .stream-wrap {
    grid-template-columns: 1fr;
  }
  .stream-chat { min-height: 400px; }
}

@media (max-width: 860px) {
  .nav-links {
    position: fixed;
    inset: 0;
    background: rgba(7,7,7,0.97);
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2.5rem;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--glide), visibility var(--glide);
    pointer-events: none;
  }

  .nav-links a {
    font-size: 1.1rem;
  }

  .nav-links.open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  /* ⛔ NO SECOND DISCLOSURE ON A PHONE. The fullscreen menu already IS the
     disclosure — nesting "More" inside it would be two taps to reach a page
     that is one tap away on desktop, which is backwards. Both groups render,
     stacked, separated by a hairline so the split still reads. */
  .nav-primary,
  .nav-end,
  .nav-secondary { flex-direction: column; gap: 2.5rem; width: 100%; }
  .nav-more-btn { display: none; }
  .nav-more-panel {
    /* static, always open, and stripped of its plate: it is not a popover here */
    display: flex; position: static; background: none; border: 0;
    padding: var(--s6) 0 0; min-width: 0; gap: 2.5rem; align-items: center;
    border-top: 1px solid var(--etch); width: 100%;
  }
  .nav-end { padding-top: var(--s6); border-top: 1px solid var(--etch); }

  .nav-toggle {
    display: flex;
    position: relative;
    z-index: 101;
  }

  /* CRITICAL: the fullscreen menu (.nav-links) is position:fixed INSIDE .site-header.
     A backdrop-filter on the header turns it into the containing block for that fixed
     overlay, collapsing the menu into the thin bar the moment you scroll. So on mobile
     the scrolled header uses a solid background with NO filter — the overlay then stays
     sized to the viewport at every scroll position (and we avoid the mobile blur glitch). */
  .site-header.scrolled {
    background: rgba(7,7,7, 0.96);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@media (max-width: 480px) {
  .hero-actions { flex-direction: column; align-items: center; width: 100%; }
  .btn { width: 100%; max-width: 280px; text-align: center; }
  .focus, .stream, .worldstate { padding: 4.5rem 1.25rem; }
  .join { padding: 5rem 1.25rem; }
}

/* ============ Market responsive ============ */
@media (max-width: 640px) {
  .market { padding: 5.5rem 1.25rem; }

  .market-input {
    font-size: 1rem;
    padding: 1rem 1.1rem 1rem 2.8rem;
  }

  .market-modal { padding: 1.25rem; }

  .market-modal-head { flex-wrap: wrap; }

  .order-table { font-size: 0.82rem; }
}

/* ============ Account / Apply ============ */
/* ⛔ 880px WAS A READING MEASURE, AND IT EXPIRED.
   It was the right number when this page was an account settings FORM — labels
   and fields stay scannable in a single ~880px column. Nobody re-derived it
   when the page became a Command Hub, so a dashboard meant to sit open on a
   second monitor was living in a column narrower than the site's own 1440px
   content width. Stephan: "is there a specific reason why you're using that
   very narrow grid?" There was. It stopped being true.

   ⚠️ BUT NOT UNCAPPED. On a 3440px ultrawide, three panels stretched edge to
   edge means a chart a metre wide and an eye that has to travel it. The
   professional answer is MORE PANELS, never WIDER ones — so this is a generous
   dashboard cap, not the absence of one. */
/* ⛔ THE BOARD LINES UP WITH THE NAVIGATION, AND THAT IS THE WHOLE RULE.
   This was 1920 with its own clamped gutter while .nav-wrap is --max-width
   (1440) with a flat 1.5rem — so on a wide screen the cards ran about 220px
   PAST the logo on each side and the page read as two different documents
   stacked. Stephan: "it looks too stretched out."
   ⚠️ 1920 WAS A DELIBERATE CHOICE AND IT OVERSHOT. The note it replaced is
   right that 880px had expired — it was narrower than the site's own content
   width — but the fix for "narrower than the site" is to MATCH the site, not
   to jump past it. Every other page on EchoBorn sits in --max-width; this one
   now does too, and the gutter is the nav's exact 1.5rem so the card edge and
   the logo share a line.
   ⚠️ The ultrawide answer is unchanged and still stands: more panels, never
   wider ones. Held by the edge-alignment check in tools/render-relay.js. */
.account {
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 7rem 1.5rem 4rem;
}

/* ⚠️ THE SETTINGS HALF KEEPS A MEASURE. `.op-grid` is
   `auto-fit minmax(180px, 1fr)`, so at full width it would lay NINE 180px
   fields in a row — strictly worse than four, and exactly the "2010 grid"
   look. A form is read; a dashboard is scanned. Different jobs, different
   widths, on the same page. */
/* ⛔ THE OPERATIVE HALF GETS THE WIDTH TOO — it just gets it differently.
   Capping the PANEL left a 1200px column inside a 1920px board, which is the
   same dead space on the right that started this whole pass. So the panel goes
   full width like everything else and the FIELDS are capped instead: a form is
   read, so a field stops growing at a comfortable size and the row simply holds
   more of them. Nine 180px fields across was the thing to avoid; twelve fields
   in three tidy rows of four is not. */
#tab-settings { max-width: none; }
#tab-settings .op-grid {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s4) var(--s5);
}
/* the two cards under the form share the row discipline of the dashboard */
#tab-settings .account-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: stretch; }
#tab-settings .account-grid > * { height: 100%; }

@media (max-width: 1500px) {
  #tab-settings .op-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  #tab-settings .account-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  #tab-settings .op-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  #tab-settings .op-grid,
  #tab-settings .account-grid { grid-template-columns: 1fr; }
}

/* ── the toolbar the switch lives on ─────────────────────────────── */
/* ⚠️ A CONTROL READS AS A CONTROL WHEN SOMETHING ANCHORS IT. Alone on the left
   of a 1920px board the segmented pill was a small object adrift; on a rule
   that spans the width, with links holding the far end, it is a toolbar. */
.hub-bar {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s5); flex-wrap: wrap;
  margin-top: var(--s6);
  border-bottom: 1px solid var(--panel-border);
}
.hub-bar .hub-tabs { margin-top: 0; margin-bottom: -1px; border-bottom-color: transparent; }
.hub-bar-links { display: flex; gap: var(--s5); padding-bottom: var(--s3); }
.hub-bar-links a {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--muted); text-decoration: none;
  transition: color var(--swift);
}
.hub-bar-links a:hover { color: var(--gold); }
@media (max-width: 620px) {
  .hub-bar { align-items: stretch; }
  .hub-bar-links { display: none; }   /* the nav already reaches these on a phone */

  /* ⛔ THE TAB ROW DID NOT FIT A PHONE AND COULD NEITHER WRAP NOR SCROLL.
     Measured at 390px: .hub-tabs stood 407.7px inside a 350px bar, so the
     Command Hub scrolled sideways on every handset — three tabs at 1.5rem of
     padding each with white-space:nowrap, sized for a desktop and never
     re-derived. It has been live since the Hub shipped and nobody could
     report it, because a page that scrolls 38px sideways just feels loose.
     ⚠️ The tabs SHARE the row instead of shrinking the labels: flex-basis 0
     makes three equal thirds, and the padding comes off so the words still
     fit inside them. */
  .hub-tabs { display: flex; width: 100%; }
  .hub-tab {
    flex: 1 1 0; min-width: 0; justify-content: center;
    padding-inline: .5rem; gap: .35rem;
  }
}

/* the masthead is prose and stays a measure too — a centred title stretched
   across 1920px reads as a banner, not a heading */
.account > .section-head { max-width: 1200px; margin-inline: auto; }

.account-profile {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.2rem 1.6rem;
  margin-bottom: 2rem;
}

.account-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--gold);
}

.account-profile h3 {
  font-family: var(--font-head);
  letter-spacing: var(--track-tight);
  color: var(--gold);
  font-size: var(--t-base);
}

.account-sub {
  color: var(--text-dim);
  font-size: var(--t-sm);
}

.account-profile .btn {
  margin-left: auto;
}

.account-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.5rem;
}

.panel--full {
  grid-column: 1 / -1;
}

/* ── Operative Dossier ─────────────────────────────────── */
.dossier-wrap {
  position: relative;
  overflow: hidden;
  border-radius: 2px;
  margin-bottom: 2rem;
  min-height: 130px;
  display: flex;
  /* ⚠️ COLUMN. It was a row with one in-flow child, so the axis never mattered
     — until the counts moved onto this card, where a row would have stood them
     BESIDE the avatar instead of under it. `.dossier-inner` already carries
     `width:100%`, so stacking changes nothing about what was here before. */
  flex-direction: column;
  align-items: stretch;
}

.dossier-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  filter: brightness(0.72) saturate(0.95);
  transition: background-image var(--glide);
  background-color: var(--panel);
}

.dossier-bg.is-default {
  background-image: none !important;
  filter: none;
}

/* Legibility scrim — only when a real background image is set (keeps text readable
   on the left, lets the art show through on the right). */
.dossier-bg:not(.is-default) + .dossier-frame::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(95deg, rgba(8,8,8, 0.9) 0%, rgba(8,8,8, 0.55) 42%, rgba(8,8,8, 0.12) 100%);
}

/* Corner bracket decorations */
.dossier-frame {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) top left    / 28px 2px no-repeat,
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) top left    / 2px 28px no-repeat,
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) top right   / 28px 2px no-repeat,
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) top right   / 2px 28px no-repeat,
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) bottom left / 28px 2px no-repeat,
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) bottom left / 2px 28px no-repeat,
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) bottom right/ 28px 2px no-repeat,
    linear-gradient(var(--ta, #c8a84b), var(--ta, #c8a84b)) bottom right/ 2px 28px no-repeat;
}

/* Scan lines */
.dossier-frame::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    0deg,
    transparent,
    transparent 3px,
    rgba(0, 0, 0, 0.07) 3px,
    rgba(0, 0, 0, 0.07) 4px
  );
}

.dossier-inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 1.8rem 2rem;
  width: 100%;
}

.dossier-avatar {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 18px rgba(0,0,0,0.5);
}

.dossier-kicker {
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  color: var(--ta, #c8a84b);
  font-family: var(--font-head);
  margin-bottom: 0.25rem;
  opacity: 0.8;
}

.dossier-name {
  font-family: var(--font-head);
  font-size: var(--t-lead);
  font-weight: 700;
  letter-spacing: var(--track);
  color: #fff;
  margin-bottom: 0.3rem;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  flex-wrap: wrap;
}

.dossier-sub {
  font-size: var(--t-label);
  color: rgba(255, 255, 255, 0.45);
  letter-spacing: var(--track-tight);
}

.dossier-op {
  margin-top: 0.55rem;
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ta, var(--gold-bright));
}

/* Operative Profile dropdowns (Command Hub → Settings) */
.op-intro { color: var(--muted); font-size: var(--t-sm); margin: 0 0 1rem; }
.op-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; }
.op-field { display: flex; flex-direction: column; gap: 0.4rem; }
.op-field-label { font-family: var(--font-head); font-size: var(--t-micro); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted); }
/* ⚠️ ONE RULE FOR BOTH CONTROLS. The clan and handle fields arrived as
   <input> and inherited nothing, so they rendered as pale browser defaults next
   to eight dark dropdowns — the same field, two appearances. The only property
   that differs is the cursor: a dropdown is picked, a text box is typed in. */
.op-field select,
.op-field input[type="text"] {
  background: var(--bg); border: 1px solid var(--panel-border); border-radius: 8px; color-scheme: dark;
  padding: 0.6rem 0.7rem; color: var(--text); font-family: var(--font-body); font-size: var(--t-sm);
  transition: border-color var(--swift);
  width: 100%;
}
.op-field select { cursor: pointer; }
.op-field select:focus,
.op-field input[type="text"]:focus { outline: none; border-color: var(--gold); }
/* a handle is data, so it reads in the data face — and the placeholder must be
   quiet enough that an empty field does not look filled in */
.op-field input[type="text"] { font-family: var(--font-mono); }
.op-field input[type="text"]::placeholder { color: var(--muted); opacity: 1; }
.op-status { min-height: 1.1rem; margin: 0.9rem 0 0; font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track-tight); }
.op-status--ok { color: var(--gold-bright); }
.op-status--err { color: #f07840; }
/* ⚠️ AN EMPTY STATUS LINE RESERVES NOTHING. min-height plus a top margin is
   right while a control sits above it — it stops the panel jumping when a
   message appears. With the roadmap picker moved to the Relay there is nothing
   above it on that card any more, so the reserve became a visible hole between
   the heading and the first plan. It collapses until there is something to say
   and reserves its room again the moment there is. */
.op-status:empty { min-height: 0; margin: 0; }

.dossier-actions { margin-left: auto; flex-shrink: 0; }

/* ── Background picker ─────────────────────────────────── */
.bg-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 0.65rem;
}

.bg-option {
  position: relative;
  height: 68px;
  border-radius: 2px;
  overflow: hidden;
  cursor: pointer;
  border: 2px solid transparent;
  background-size: cover;
  background-position: center;
  transition: border-color var(--swift), transform var(--swift), box-shadow var(--swift);
}

.bg-option:hover { transform: scale(1.04); }

.bg-option.active {
  border-color: var(--gold);
  box-shadow: 0 0 12px rgba(200,168,75,0.4);
}

.bg-option.active::before {
  content: '✓';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(200,168,75,0.2);
  color: var(--gold);
  font-size: var(--t-lead);
  font-weight: 700;
  z-index: 1;
}

.bg-label {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  font-size: var(--t-micro);
  padding: 0.2rem 0.4rem;
  background: rgba(0,0,0,0.72);
  color: rgba(255,255,255,0.85);
  letter-spacing: var(--track-tight);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bg-option-none {
  height: 68px;
  border-radius: 2px;
  border: 2px dashed rgba(255,255,255,0.12);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--t-micro);
  color: var(--muted);
  letter-spacing: var(--track);
  font-family: var(--font-head);
  cursor: pointer;
  text-transform: uppercase;
  transition: border-color var(--swift), color var(--swift);
}

.bg-option-none:hover,
.bg-option-none.active {
  border-color: var(--gold);
  color: var(--gold);
}

.customization-locked {
  color: var(--muted);
  font-size: var(--t-sm);
}

/* ── Patron badge ──────────────────────────────────────── */
.patron-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* ⚠️ SMALLER, 2026-09-05 — they read as massive beside the name they
     annotate. A badge is an ANNOTATION on an identity, not a second headline.
     ⛔ AND `.staff-badge` MUST MOVE WITH IT: the two share one box model on
     purpose, so a role badge and a tier badge never shift the layout when one
     replaces the other in the single badge slot. Changing one alone reintroduces
     exactly the jump that box model exists to prevent. */
  /* ⚠️ QUIETER AGAIN, 2026-09-06. Stephan, looking at nine cards at once:
     "they should be there and seen, but not the first thing I look at".
     At 700 weight in the display face they out-shouted the member's NAME,
     which is the thing a roster card is actually about — an annotation had
     become the headline. Lighter, tighter, and the tracking does the work
     of making it legible at this size instead of the weight.
     ⛔ BOTH BADGES MOVE TOGETHER. They share one box model on purpose so a
     role and a tier never shift the layout when one replaces the other in
     the single badge slot; verify-banners fails the build on any drift. */
  padding: 0.12rem 0.42rem;
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  font-weight: 500;
  /* ⚠️ NO MARGIN. This badge renders in seven contexts — a dossier heading, a
     roster card, the support panel, two admin tables, the Tenno page and the
     comment popover — and not one of them wants space baked into the component.
     Whichever layout needs a gap owns it. `vertical-align` is the other half:
     inline-flex sits on the text BASELINE by default, so beside plain text the
     pill hangs low. */
  vertical-align: middle;
}

.patron-badge--guest {
  background: rgba(255,255,255,0.05);
  color: var(--muted);
  border: 1px solid rgba(255,255,255,0.1);
}

.patron-badge--tenno {
  background: rgba(200,168,75,0.12);
  color: var(--gold);
  border: 1px solid rgba(200,168,75,0.35);
}

.patron-badge--operator {
  background: rgba(192,189,176,0.1);
  color: #c0bdb0;
  border: 1px solid rgba(192,189,176,0.35);
}

/* ⛔ THIS WAS `--warlord` AND THE CODE HAS EMITTED `--founder` SINCE 08-31.
   The Patreon renumber retired Warlord and gave tier 2 the slug `founder`; the
   stylesheet never followed. So the badge a member gets for paying $29 — the
   FIRST perk the Founder's Pass advertises, "Founder badge on your profile and
   roster card" — has been rendering with no colour and no glow ever since,
   while every other tier kept its own. Nothing errored. It took a screenshot
   of two people side by side to see it.
   ⚠️ A migration must never move an entitlement, and this one moved the look
   of the thing being sold. */
.patron-badge--founder {
  background: rgba(232,148,10,0.15);
  color: #e8940a;
  border: 1px solid rgba(232,148,10,0.45);
  box-shadow: 0 0 12px rgba(232,148,10,0.2);
}

.patron-badge svg {
  width: 14px;
  height: 14px;
  fill: currentColor;
  flex-shrink: 0;
}

.patron-panel-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.patron-connected {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  /* ⛔ THE SPACING BELONGS TO THE ROW, NOT TO THE BADGE. It used to live on
     `.patron-badge` as `margin-bottom: 1rem` — and a bottom margin on a flex
     item with `align-items: center` centres the MARGIN BOX, which lifted the
     badge half a rem above the word beside it. That is the misalignment
     Stephan saw between FOUNDER and Active. The badge appears in seven places;
     a layout value baked into a component leaks into every one of them. */
  margin-bottom: 1rem;
}

.patron-perks {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.patron-perks li {
  font-size: var(--t-sm);
  color: var(--muted);
  padding-left: 1.1rem;
  position: relative;
}

.patron-perks li::before {
  content: '//';
  position: absolute;
  left: 0;
  color: var(--gold);
  font-size: var(--t-label);
}

.panel {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.6rem 1.8rem;
}

.panel h3 {
  font-family: var(--font-head);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--gold);
  font-size: var(--t-base);
  margin-bottom: 1rem;
}

/* ⛔ A MEASURE, BECAUSE A CARD IS NOT A COLUMN. This paragraph is the shared
   empty state across the whole board, and it had no width of its own — so on a
   2560px screen it set to 212 CHARACTERS PER LINE, which no reader can return
   from without losing the line. Stephan: "the cards keep stretching out, but
   the content is not stretching out, and it just looks weird."
   ⚠️ ch, not px: the limit typography cares about is characters, and a pixel
   cap says different things at different font sizes. Held by the prose measure
   check in tools/render-relay.js at 1440, 2560 and 390. */
.account-empty {
  color: var(--text-dim);
  font-size: var(--t-sm);
  max-width: 68ch;
  margin-bottom: 1rem;
}

.account-note {
  color: var(--text-dim);
  font-size: var(--t-sm);
  margin-top: 1rem;
}

/* ⛔ A PADDED PILL INSIDE A LINE OF TEXT SITS ON THE BASELINE, WHICH HANGS IT
   LOW. This was a plain inline `<span>` with vertical padding, so the browser
   aligned the TEXT INSIDE the pill to the sentence's baseline and let the
   padding and border drop below it — "Status: [ACCEPTED]" with the box sagging
   under the word. Vertical padding on a plain inline box also does not push the
   line apart, so it overlapped what was around it.
   `inline-flex` + `vertical-align: middle` centres the BOX against the line
   instead, which is the only version that reads as deliberate. */
/* ⛔ THE GAP BELONGS TO THE LINE. `.status` renders on the Command Hub and on
   /clan/apply.php; a `margin-left` on the component would be right in both
   today and wrong the first time it lands in a flex row or a table cell —
   which is the leak that had to be undone on `.patron-badge` an hour ago.
   ⚠️ And one space character is not a gap: PROXIMITY IS GROUPING, so a label
   and a boxed value need MORE air between them than two words of the same
   kind, or the pill reads as jammed onto the word. */
.status-line {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
}

.status {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  font-family: var(--font-head);
  /* ⚠️ SMALLER. At --t-label with 3px/10px it outweighed the word it annotates
     — a status is a footnote on a fact, not the headline. One step down the
     type scale and tighter padding; the colour still does the work. */
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  text-transform: uppercase;
  line-height: 1.2;
  padding: 2px 8px;
  border-radius: 2px;
  border: 1px solid var(--panel-border);
}

.status-pending { color: var(--gold); border-color: var(--gold); }
.status-accepted { color: #6fd17a; border-color: #6fd17a; }
.status-rejected { color: #ef5d5d; border-color: #ef5d5d; }

.app-summary {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4rem 1rem;
  margin: 1rem 0;
  font-size: var(--t-sm);
}

.app-summary dt {
  color: var(--text-dim);
}

.app-summary dd {
  color: var(--text);
}

.watch-list {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.watch-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

.watch-thumb {
  width: 32px;
  height: 32px;
  object-fit: contain;
}

.watch-name {
  color: var(--text);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.watch-name:hover { color: var(--gold); }

.watch-price {
  font-family: var(--font-head);
  color: var(--gold-bright);
  font-size: var(--t-sm);
  white-space: nowrap;
}

.watch-remove {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: var(--t-lead);
  cursor: pointer;
  line-height: 1;
  padding: 0 0.3rem;
}

.watch-remove:hover { color: #ef5d5d; }

.watch-btn.watching {
  border-color: var(--gold-bright);
  color: var(--gold-bright);
}

/* ── Command Hub — Sprint 1 ─────────────────────────────── */

.hub-stat-bar {
  display: flex;
  gap: 1px;
  margin: .9rem 0 0;
  background: var(--panel-border);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  overflow: hidden;
}

.hub-stat {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: .65rem .5rem;
  background: var(--panel);
  text-align: center;
}

.hub-stat-value {
  font-family: var(--font-head);
  font-size: var(--t-base);
  color: var(--gold-bright);
  letter-spacing: var(--track-tight);
  line-height: 1.1;
}

.hub-stat-dim { color: var(--text-dim) !important; }

.hub-stat-label {
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  color: var(--text-dim);
  margin-top: .18rem;
  text-transform: uppercase;
}

.hub-stat--patron .hub-stat-value { color: #e8940a; }

/* ⛔ THE SWITCH BETWEEN THE TWO HALVES OF THE HUB, AND IT WAS INVISIBLE.
   Two 15px labels on a bare hairline, under a stat bar that shouted louder —
   so the control deciding which half of the page you see read as a caption.
   Stephan: "you really have to look three times." It is a segmented control on
   its own surface now: the active half is LIT and RAISED out of a recessed
   track, which is a shape people already know means "one of these two". */
.hub-tabs {
  display: inline-flex;
  gap: 2px;
  margin: var(--s6) 0 0;
  padding: 3px;
  background: var(--bg-alt);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
}

.hub-tab {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .62rem 1.5rem;
  background: none;
  border: 1px solid transparent;
  border-radius: 2px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--swift), background var(--swift), border-color var(--swift);
}

.hub-tab svg { opacity: .7; transition: opacity var(--swift); }
.hub-tab:hover { color: var(--text); background: var(--panel); }
.hub-tab:hover svg { opacity: 1; }
.hub-tab:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }

/* ⚠️ LIT, RAISED **AND** LABELLED BY aria-selected — not colour alone. The
   nav spent today proving that one colour carrying two meanings is a bug. */
.hub-tab.active,
.hub-tab[aria-selected="true"] {
  color: var(--gold-bright);
  background: var(--panel);
  border-color: var(--gold-dim);
}
.hub-tab.active svg,
.hub-tab[aria-selected="true"] svg { opacity: 1; }

/* ── the counts, now on the dossier where the person is ────────────── */
.dossier-stats {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--panel-border);
}
.dstat {
  display: flex; flex-direction: column; gap: 2px; align-items: center;
  padding: var(--s3) var(--s2);
  text-decoration: none; color: inherit;
  border-left: 1px solid var(--panel-border);
  transition: background var(--swift);
}
.dstat:first-child { border-left: 0; }
a.dstat:hover { background: rgba(255,255,255,.03); }
a.dstat:hover .dstat-v { color: var(--gold-bright); }
.dstat-v {
  font-family: var(--font-head); font-size: var(--t-h3); font-weight: 500;
  color: var(--text); line-height: 1; font-variant-numeric: tabular-nums;
  transition: color var(--swift);
}
.dstat-l {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-max);
  text-transform: uppercase; color: var(--muted);
}
/* the tier is a STATE, so it is sized as words rather than as a numeral */
.dstat--tier .dstat-v { font-size: var(--t-base); color: var(--muted); }
.dstat--tier.is-on .dstat-v { color: #e8940a; }

@media (max-width: 560px) {
  .dossier-stats { grid-template-columns: repeat(2, 1fr); }
  .dstat:nth-child(3) { border-left: 0; }
  .dstat:nth-child(n+3) { border-top: 1px solid var(--panel-border); }
  .hub-tabs { display: flex; }
  .hub-tab { flex: 1; justify-content: center; padding: .62rem .7rem; }
}

.tab-panel { display: none; padding-top: 1.25rem; }
.tab-panel.active { display: block; }

.hub-panel {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 1.1rem 1.25rem;
}

.hub-panel + .hub-panel { margin-top: 1rem; }

.hub-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: .85rem;
  padding-bottom: .65rem;
  border-bottom: 1px solid var(--panel-border);
}

.hub-panel-head-left {
  display: flex;
  align-items: center;
  gap: .5rem;
}

.hub-panel-head-left svg { color: var(--gold); opacity: .8; flex-shrink: 0; }

.hub-panel-title {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track);
  color: var(--text);
}

.hub-panel-count {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  color: var(--gold-bright);
  background: rgba(200,168,75,.12);
  border: 1px solid rgba(200,168,75,.25);
  border-radius: 2px;
  padding: .1rem .35rem;
  line-height: 1.4;
}

.hub-panel-link {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  color: var(--gold);
  text-decoration: none;
  opacity: .7;
  transition: opacity var(--swift);
}
.hub-panel-link:hover { opacity: 1; }

.hub-alert-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .4rem;
}

.ws-alert-item {
  display: flex;
  align-items: center;
  gap: .65rem;
  padding: .45rem .55rem;
  background: rgba(255,255,255,.025);
  border: 1px solid rgba(255,255,255,.05);
  border-radius: 2px;
}

.ws-alert-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--gold);
  opacity: .5;
  flex-shrink: 0;
}

.ws-alert-name {
  color: var(--text);
  text-decoration: none;
  font-size: var(--t-sm);
  flex: 1;
}
.ws-alert-name:hover { color: var(--gold); }

.hub-empty {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 2rem 1.5rem;
}

.hub-empty-kicker {
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  color: var(--gold);
  margin: 0 0 .5rem;
}

.hub-empty-title {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track-tight);
  margin: 0 0 .4rem;
}

.hub-empty-desc {
  color: var(--text-dim);
  font-size: var(--t-sm);
  margin: 0 0 1.25rem;
}

.hub-empty-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

.hub-empty-step {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .5rem .75rem;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  font-size: var(--t-sm);
}

.hub-empty-check {
  font-family: var(--font-head);
  font-size: var(--t-sm);
  color: var(--text-dim);
  width: 1rem;
  text-align: center;
  flex-shrink: 0;
}

.hub-empty-step--done .hub-empty-check { color: var(--gold-bright); }
.hub-empty-step-label { flex: 1; color: var(--text); }
.hub-empty-step--done .hub-empty-step-label { color: var(--text-dim); }

.hub-empty-step-status {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  color: var(--gold);
}

.hub-empty-step-link {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  color: var(--gold);
  text-decoration: none;
}
.hub-empty-step-link:hover { color: var(--gold-bright); }

/* ── Theme picker ────────────────────────────────────────── */
.theme-section { margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--panel-border); }

.theme-section-label {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-wide);
  color: var(--text-dim);
  margin-bottom: .75rem;
}

.theme-picker {
  display: flex;
  gap: .7rem;
  flex-wrap: wrap;
}

.theme-swatch {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--swatch-color);
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform var(--swift), border-color var(--swift), box-shadow var(--swift);
  position: relative;
}

.theme-swatch:hover { transform: scale(1.18); }

.theme-swatch.active {
  border-color: #fff;
  box-shadow: 0 0 0 2px var(--swatch-color);
}

.theme-swatch-label {
  position: absolute;
  bottom: -1.4rem;
  left: 50%;
  transform: translateX(-50%);
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  color: var(--text-dim);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--swift);
}

.theme-swatch:hover .theme-swatch-label,
.theme-swatch.active .theme-swatch-label { opacity: 1; }

/* ============ Forms ============ */
.app-form {
  max-width: 640px;
  margin: 0 auto;
}

.form-errors {
  max-width: 640px;
  margin: 0 auto 1.5rem;
  border-color: #ef5d5d;
  color: #ef5d5d;
  font-size: var(--t-sm);
}

.form-errors ul {
  padding-left: 1.2rem;
}

.form-field {
  margin-bottom: 1.2rem;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.2rem;
}

.form-field label {
  display: block;
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 0.5rem;
}

.form-field input,
.form-field select,
.form-field textarea {
  width: 100%;
  background: var(--bg-alt);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 0.8rem 1rem;
  font-family: var(--font-body);
  font-size: var(--t-base);
  color: var(--text);
  transition: border-color var(--swift);
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: none;
  border-color: var(--gold);
}

.form-field textarea {
  resize: vertical;
}

@media (max-width: 700px) {
  .account-grid { grid-template-columns: 1fr; }
  .form-row { grid-template-columns: 1fr; }
  .account-profile { flex-wrap: wrap; }
  .account-profile .btn { margin-left: 0; }
  .hub-stat-value { font-size: .9rem; }
  .hub-tab { padding: .55rem .75rem; font-size: .7rem; }
}

/* ============ Home Hero ============ */
.home-hero {
  position: relative;
  height: 100vh;
  min-height: 700px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.home-hero-bg {
  position: absolute;
  inset: 0;
}

.home-hero-bg-layer {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-color: var(--bg-alt);
  opacity: 0;
  transition: opacity 2.5s ease;
}

.home-hero-bg-layer:first-child {
  background-image: url('/Images/hero-home.jpeg');
}

.home-hero-bg-layer.active {
  opacity: 1;
  /* Slow continuous zoom in / zoom out (Ken-Burns) */
  animation: hero-kenburns 24s ease-in-out infinite alternate;
}

@keyframes hero-kenburns {
  from { transform: scale(1); }
  to   { transform: scale(1.1); }
}

.home-hero-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(7,7,7,0.1) 0%, rgba(7,7,7,0.4) 60%, rgba(7,7,7,0.97) 100%),
    linear-gradient(90deg, rgba(7,7,7,0.5) 0%, rgba(7,7,7,0) 35%, rgba(7,7,7,0) 70%, rgba(7,7,7,0.5) 100%);
}

.home-hero-content {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  padding: 6rem 1.5rem 7.5rem;
}

.home-hero-text {
  max-width: min(620px, 100%);
  border-left: 2px solid var(--gold);
  padding-left: clamp(1.1rem, 2vw, 1.8rem);
}

.home-hero-kicker {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.9rem;
}

.home-hero-title {
  font-family: var(--font-body);
  font-weight: 700;
  font-style: italic;
  font-size: clamp(2.3rem, 1.2rem + 3.2vw, 4.2rem);
  line-height: 1.08;
  text-transform: uppercase;
  color: var(--text);
  text-shadow: 0 2px 30px rgba(0, 0, 0, 0.55);
  margin-top: 0.9rem;
}

.home-hero-sub {
  font-size: clamp(1rem, 0.96rem + 0.3vw, 1.15rem);
  font-weight: 300;
  color: var(--text-dim);
  margin: 1.15rem 0 0;
  max-width: 46ch;
}

.home-hero-ctas {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  flex-wrap: wrap;
  margin-top: 2.1rem;
}

/* Quiet secondary CTA (replaces the second big button) */
.hero-textlink {
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: border-color var(--swift), color var(--swift);
}
.hero-textlink:hover { border-color: var(--gold); color: var(--gold-bright); }

.hero-status-kicker {
  display: flex;
  align-items: center;
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  text-transform: uppercase;
  color: var(--gold);
  padding: 0 1.5rem;
  white-space: nowrap;
  border-right: 1px solid var(--panel-border);
  flex-shrink: 0;
  animation: flicker 4s infinite;
}

@media (max-width: 720px) {
  .home-hero-content {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
    padding: 6rem 1.25rem 7rem;
  }
  .home-hero-sub {
    font-size: 0.95rem;
  }
  .home-hero-ctas {
    flex-direction: column;
    width: 100%;
  }
  .home-hero-ctas .btn {
    text-align: center;
    width: 100%;
  }

  .hero-status {
    justify-content: flex-start;
  }
}

/* Hero status auto-scroll ticker */
.hero-status-inner.ticker {
  animation: hero-ticker 26s ease-in-out infinite;
}

.hero-status-inner.ticker.ticker-paused {
  animation-play-state: paused;
}

@keyframes hero-ticker {
  0%, 8%   { transform: translateX(0); }
  46%, 54% { transform: translateX(var(--ticker-shift, 0)); }
  92%, 100% { transform: translateX(0); }
}

/* ============ Baro Ki'Teer Page ============ */

/* ⛔ orbiter-bg.jpg (1,780 KB) IS GONE FROM HERE TOO — 2026-09-14.
   It was removed from `.page-bg` on 2026-09-02 and the note recording that is
   still fifty lines up this file, but THIS class kept loading it: the removal
   was done in one place and missed in the other, so the heaviest single asset
   on the site went on shipping to everyone who opened the Baro page. A fix
   applied to one call site instead of to the source is the same shape as the
   .patron-badge margin that kept reappearing.
   The ground is drawn now, like /market/, /ask/, /stream/ and the Hub. */
.page-bg--baro {
  background-image: none;
  background-color: var(--bg);
  overflow: hidden;
}

/* ⛔ THE VOID, DRIFTING — TWO LAYERS, ANIMATED BY TRANSFORM ONLY.
   Animating background-position or a gradient's colour stops repaints the whole
   surface every frame; animating a TRANSFORM on a composited layer does not
   touch the paint at all. On the one page somebody leaves open watching a
   countdown, that difference is a warm laptop versus a cool one.
   ⚠️ The colours are the Void Trader's own: Orokin gold meeting the cold
   teal that runs through both portraits. Nothing here reports a state — it is
   ground, and it looks the same whether he is here or not, because a drifting
   gradient cannot know anything and must not pretend to. */
.page-bg--baro::before,
.page-bg--baro::after {
  content: "";
  position: absolute;
  inset: -30%;
  pointer-events: none;
  will-change: transform;
}
.page-bg--baro::before {
  background:
    radial-gradient(ellipse 46% 38% at 32% 26%, rgba(212,175,55,.13) 0%, transparent 62%),
    radial-gradient(ellipse 40% 34% at 74% 62%, rgba(96,158,168,.11) 0%, transparent 64%);
  animation: baroDriftA 38s ease-in-out infinite alternate;
}
.page-bg--baro::after {
  background:
    radial-gradient(ellipse 36% 30% at 62% 18%, rgba(96,158,168,.09) 0%, transparent 60%),
    radial-gradient(ellipse 44% 36% at 22% 74%, rgba(212,175,55,.08) 0%, transparent 66%);
  animation: baroDriftB 53s ease-in-out infinite alternate;
}
@keyframes baroDriftA {
  from { transform: translate3d(-3%, -2%, 0) scale(1.02); }
  to   { transform: translate3d( 4%,  3%, 0) scale(1.10); }
}
@keyframes baroDriftB {
  from { transform: translate3d( 3%,  2%, 0) scale(1.08); }
  to   { transform: translate3d(-4%, -3%, 0) scale(1.01); }
}
/* ⛔ THE STATES SURVIVE, ONLY THE MOTION GOES — the same rule the orb and the
   mode switch follow. A reduced-motion reader still gets the colour. */
@media (prefers-reduced-motion: reduce) {
  .page-bg--baro::before,
  .page-bg--baro::after { animation: none; }
}

/* ═══ THE TRANSMISSION LAYER — an old tube, running slow ══════════════
   ⛔ CSS ONLY. Particles would need a canvas, a frame loop and a script tag on
   a page whose entire engine is arithmetic; this is three gradients and two
   transforms, and the compositor already owns the layer.
   ⛔ IT SITS BEHIND .page-content (z-index 0 against its 1), WHICH IS THE
   SAFETY ARGUMENT. Scanlines drawn over body text cost legibility on every word
   of the page for an effect nobody asked to read through. Over the GROUND they
   cost nothing, and they are still visible around the figure, which is where
   the effect was wanted.
   ⚠️ AND IT REPORTS NOTHING. A tube rolling and tearing on a schedule is
   weather — it does not brighten when he arrives or glitch when a feed fails,
   because it cannot know either of those things. Decoration that implies a
   state it cannot know is a false claim, same as an invented number. */
.baro-crt {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
  /* painted ONCE. the motion lives on the children, never on this. */
  background-image: repeating-linear-gradient(to bottom,
    rgba(0, 0, 0, .30) 0px, rgba(0, 0, 0, .30) 1px,
    transparent 1px, transparent 3px);
  opacity: .5;
}
.baro-crt::before,
.baro-crt::after {
  content: ""; position: absolute; left: -10%; width: 120%;
  pointer-events: none; will-change: transform;
}
/* the roll: a soft band walking down the tube, in the trader's own two colours */
.baro-crt::before {
  top: -42vh; height: 42vh;
  background: linear-gradient(to bottom,
    transparent 0%, rgba(212, 175, 55, .045) 40%,
    rgba(96, 158, 168, .06) 56%, transparent 100%);
  animation: baroRoll 15s linear infinite;
}
/* the tracking fault: it JUMPS, because a tear that glides is a sweep */
.baro-crt::after {
  top: 0; height: 2px; opacity: 0;
  background: linear-gradient(90deg, transparent 0%,
    rgba(96, 158, 168, .34) 28%, rgba(212, 175, 55, .30) 64%, transparent 100%);
  animation: baroTear 13s steps(1, end) infinite;
}
@keyframes baroRoll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, 142vh, 0); }
}
@keyframes baroTear {
  0%, 95.4%   { opacity: 0; transform: translate3d(0, 21vh, 0); }
  95.6%       { opacity: .8; transform: translate3d(0, 21vh, 0); }
  95.9%       { opacity: 0; transform: translate3d(0, 21vh, 0); }
  97.2%       { opacity: .55; transform: translate3d(0, 64vh, 0); }
  97.5%, 100% { opacity: 0; transform: translate3d(0, 64vh, 0); }
}
/* ⛔ THE TEXTURE SURVIVES, ONLY THE MOTION GOES — the same rule the orb, the
   mode switch and the void gradients follow. A reduced-motion reader still gets
   the tube; they just do not get it moving. */
@media (prefers-reduced-motion: reduce) {
  .baro-crt::before,
  .baro-crt::after,
  .baro-stage-art::before { animation: none; }
  .baro-crt::after { opacity: 0; }
}

/* ═══ THE STAGE — one centre line, cinematic ══════════════════════════
   ⛔ EVERY ELEMENT HANGS OFF THE SAME AXIS. The old hero put a portrait beside
   a left-aligned heading, then a full-width bar, then a rail off to one side:
   three compositions in a column, which is what read as messy rather than any
   one of them being wrong. */
.baro-hero { padding-bottom: 0; }

/* ⛔ THE TOKEN IS THE CONTENT MEASURE, NOT THE OUTER WIDTH — and that
   distinction is the whole bug. Setting both wrappers to the same max-width
   still produced two edges, because .baro-main carries a 1.5rem gutter and the
   console does not: 760 outer minus 48 of padding is a 712 content box sitting
   under a 760 card. Every block declares the same CONTENT width now and adds
   the same gutter, so the edges land on each other by construction rather than
   by both being "760". */
/* ⚠️ WIDER — 712 read as boxed in, and a three-column manifest beside a
   calculator genuinely needs the room. */
:root { --baro-col: 1120px; --baro-gutter: 1.5rem; }

/* ⚠️ THE CONTENT LAYERS ABOVE THE BACKDROP. .page-content is already
   position:relative z-index:1, so these only need lifting within it. */
.page-bg--baro ~ .page-content .baro-stage,
.page-bg--baro ~ .page-content .baro-main { position: relative; z-index: 1; }

.baro-stage {
  position: relative;
  /* ⚠️ no max-width: the art behind it is deliberately wider than the column,
     and clipping it here would crop the very thing that was too small */
  margin: 0 auto;
  /* the same gutter as .baro-main, so the console inside lands on its edge */
  padding: clamp(var(--s5), 5vh, var(--s7)) var(--baro-gutter) 0;
  text-align: center;
}

/* ⛔ THE ART DISSOLVES INSTEAD OF STOPPING. A hard bottom edge on a cut-out
   portrait reads as a sticker; the mask fades him into the drifting ground,
   which is also what makes the type below him legible without laying a grey
   rectangle over the art. Both states suit it — one of them is literally a
   figure coming apart. */
/* ⛔ HE IS BEHIND THE CONTENT NOW, NOT ABOVE IT. In the flow he cost 621px
   before the console even began, which pushed the manifest to 962px in a 980px
   viewport — a full screen of scrolling before you could see what he was
   selling. Taken out of the flow he can be three times the size AND the content
   starts near the top, which is the trade Stephan was asking for: "Baro looks
   almost like it is hovering over the content in the background."
   ⚠️ aria-hidden already; absolutely positioned art must never be the only
   copy of anything, and it is not — the state is in the badge and the words. */
.baro-stage-art {
  position: absolute;
  /* ⚠️ THE SIZE WAS NEVER THE PROBLEM — THE FRAMING WAS. At 1020px anchored to
     the top, the band you actually see is the top third of a square image,
     which on this portrait is the helmet crest: big, gold, and completely
     unreadable as a person. The figure's face sits about 35% down, which landed
     exactly where the console covers it.
     ⛔ So he is pulled UP until the face and shoulders own the visible band and
     the console lands on his chest — which is what "hovering over the content"
     actually looks like. The crest cropping off the top edge is framing, not a
     fault: that is what a backdrop does. */
  /* ⚠️ ANCHORED TO THE PAGE, IN PIXELS. A percentage `top` resolved against
     the old stage's height, so every change to the console's size silently
     re-framed the portrait — which is why three rounds of resizing kept moving
     the crop. */
  top: 16px; left: 50%; transform: translateX(-50%);
  /* ⚠️ SMALLER, BECAUSE 1280 WAS NOT A BACKDROP — IT WAS A WALL. At 104vw the
     art was wider than the reading column and touched both sides of the window,
     so there was no ground left beside it to dissolve INTO; a layer with no
     visible surround does not read as depth, it reads as the page's surface.
     The figure is a bust, and a bust wants air around it. */
  width: min(880px, 64vw);
  max-width: none;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--glide) ease;
  /* ⚠️ THE FADE STARTS WHERE THE TYPE STARTS, NOT WHERE THE ART ENDS. At 58%
     the kicker landed on the lit plates of his chest and simply could not be
     read — the mask was doing the right job at the wrong height. Bringing it up
     to 38% means the figure is already dissolving by the time the first line of
     text crosses it, so the contrast comes from the ART GIVING WAY rather than
     from a grey panel laid over it. */
  /* ⛔ THE MASK IS RADIAL NOW, AND THAT IS THE WHOLE FIX FOR "THE DIV HAS A
     BACKGROUND". A linear mask fades the FOOT of the layer and leaves its left
     and right edges perfectly straight — and the scrim below is inset:0, a
     rectangle, so those two straight verticals were painted in full contrast
     down the page. Nothing was wrong with the picture: what Stephan was seeing
     was the SCRIM'S BOX, and a dark box with hard sides on a dark page reads as
     a card that somebody forgot to style.
     ⚠️ ONE LAYER, NOT TWO COMPOSITED ONES. A vertical gradient intersected
     with a horizontal one would be tidier to read, but `mask-composite`
     defaults to ADD where it is not supported — the union of two gradients is
     opaque nearly everywhere, so the unsupported case does not lose the side
     fade, it loses the mask entirely and brings the hard foot back too. One
     ellipse degrades to nothing at all, which is the failure we can live with.
     The mask governs the scrim as well, because it composites the element's
     whole subtree — so the rectangle simply has no edges left to show. */
  -webkit-mask-image: radial-gradient(ellipse 56% 66% at 50% 42%, #000 0%, #000 42%, rgba(0,0,0,.62) 66%, rgba(0,0,0,.18) 86%, transparent 100%);
          mask-image: radial-gradient(ellipse 56% 66% at 50% 42%, #000 0%, #000 42%, rgba(0,0,0,.62) 66%, rgba(0,0,0,.18) 86%, transparent 100%);
}
/* ⛔ THE PORTRAIT IS A TRANSMISSION. Scanlines drifting down the figure, and
   the drift is a TRANSFORM of exactly one scanline period (4px) — so the loop
   has no visible reset and no frame of it repaints anything. Animating the
   background-position of a repeating gradient would repaint the whole surface
   sixty times a second on the one page people leave open watching a countdown.
   ⚠️ IT IS INSIDE THE ART, SO THE RADIAL MASK OWNS IT TOO — the lines fade out
   with him and never reach the reading column, and .baro-stage-fore sits above
   the whole stack, so not one word on the page is drawn through them. */
.baro-stage-art::before {
  content: ""; position: absolute; left: 0; right: 0; top: -6%; height: 112%;
  z-index: 2; pointer-events: none;
  background-image: repeating-linear-gradient(to bottom,
    rgba(0, 0, 0, .34) 0px, rgba(0, 0, 0, .34) 1px,
    transparent 1px, transparent 4px);
  will-change: transform;
  animation: baroScan 2.4s linear infinite;
}
@keyframes baroScan {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, 4px, 0); }
}
.baro-stage-art.is-shown { opacity: 1; }
.baro-stage-art img {
  display: block; width: 100%; height: auto;
  filter: drop-shadow(0 24px 60px rgba(0, 0, 0, .7));
  transition: filter var(--glide) ease;
}
.baro-stage-art.is-here img {
  filter: drop-shadow(0 24px 60px rgba(0, 0, 0, .7))
          drop-shadow(0 0 46px rgba(212, 175, 55, .24));
}
.baro-stage-art:not(.is-here) img {
  filter: drop-shadow(0 24px 60px rgba(0, 0, 0, .7))
          drop-shadow(0 0 40px rgba(96, 158, 168, .18));
  opacity: .9;
}

/* ⛔ THE FOREGROUND CLIMBS BACK OVER HIM. The overlap is what makes this one
   object instead of a picture with a caption — and it is a share of the art's
   own height, so it holds at every width the clamp above produces. */
/* ⛔ THE CONTENT SITS ON HIM, AND STARTS HIGH. The old negative margin existed
   to pull the type back over a portrait that was IN the flow; with the art
   behind, the foreground simply begins where it should and the whole page moves
   up by about six hundred pixels. */
/* ⛔ THE SCRIM IS WHERE THE LEGIBILITY LIVES. With the art behind the content
   the type lands on lit metal, and no amount of mask on the picture fixes the
   bright crest in the middle of it. A gradient wash from transparent down to
   the page ground does — and it degrades to a flat dark panel anywhere exotic,
   which a backdrop-filter would not. Same call as the mobile drawer. */
.baro-stage-art::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  /* ⚠️ IT NO LONGER ENDS ON `var(--bg)`. An opaque stop inside an inset:0 box
     is a painted rectangle, and it was the brightest thing on the layer at
     exactly the height where the mask used to hand over — which is why the
     foot of the backdrop looked like the bottom of a panel. It stops at .88 and
     the radial mask carries it the rest of the way to nothing. */
  background: linear-gradient(to bottom,
    rgba(6,6,6,.24) 0%, rgba(6,6,6,.50) 38%, rgba(6,6,6,.74) 66%, rgba(6,6,6,.88) 100%);
}

.baro-stage-fore {
  position: relative;
  z-index: 1;
  /* ⛔ FARTHER DOWN, AND THE REASON IS THE PORTRAIT'S ANATOMY. The art is a
     bust: crown in the top third, the mask and face at 30–48%, chest below
     that. The console was landing at 36% — straight across the face — so the
     only part of him anyone ever saw was the crest, which is what "it looks
     very cut off on the top" was describing. The type now begins past the jaw
     and the card sits on his chest, which is both legible and the pose the
     picture was drawn for. */
  /* ⚠️ MEASURED OFF THE PICTURE, NOT GUESSED. In the source art the crown
     runs to 34%, the face sits between 35% and 46%, and the chest begins at
     47%. At 21vw the heading landed at 42% — across his eyes — so the one part
     of the portrait worth showing was the one part covered. The type now starts
     past 50%, which puts the whole head in clear air and the console on his
     chest. Every number here is a share of the ART, so they hold at any width. */
  padding-top: clamp(var(--s8), 27vw, 420px);
  max-width: var(--baro-col);
  margin: 0 auto;
}
.baro-stage-head { margin: 0 auto var(--s5); max-width: 46ch; }
.baro-stage-head .section-desc { margin-top: .5rem; }

/* ── the one card: state, schedule and the Almanac ──────────────────── */
.baro-console {
  max-width: var(--baro-col); margin: 0 auto; text-align: left;
  border: 1px solid var(--etch-soft); border-radius: 2px;
  /* ⚠️ THE CONTRAST IS IN THIS ALPHA, NOT IN THE BLUR. backdrop-filter is an
     enhancement: where it is unsupported this stays a flat dark wash and the
     type stays readable, which is the same reason the mobile scrim carries its
     own opacity rather than relying on a filter. */
  background: rgba(10, 10, 10, .72);
  -webkit-backdrop-filter: blur(14px) saturate(120%);
          backdrop-filter: blur(14px) saturate(120%);
  box-shadow: 0 26px 70px rgba(0, 0, 0, .55);
  overflow: hidden;
}
.baro-console-top {
  display: flex; align-items: center;
  /* ⚠️ THREE CONTROLS THAT CANNOT FIT ON A HANDSET SHOULD MOVE TO A SECOND
     LINE, NOT COMPETE FOR ONE. Without this the badge, the bell and the ⊙ mark
     all shrink together and every one of them is a little bit wrong. */
  flex-wrap: wrap;
  gap: var(--s3); padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--hairline);
}
.baro-console-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s4) var(--s5); padding: var(--s5);
  align-items: start;
}
/* ⚠️ the Almanac line is part of the same card, divided by a hairline rather
   than by being a second object — that separation is what Stephan was reading
   as clutter */
.baro-console-foot {
  display: flex; align-items: baseline; gap: var(--s3);
  flex-wrap: wrap;
  padding: var(--s3) var(--s5) var(--s4);
  border-top: 1px solid var(--hairline);
  background: rgba(255, 255, 255, .015);
  font-size: var(--t-sm);
}
.baro-console-foot-k {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--gold-dim);
}
.baro-console-foot-v { color: var(--text-dim); }
.baro-console-foot-v strong { color: var(--text); }
/* ⚠️ ONE SHAPE FOR BOTH CONTROLS. The pair that used to sit between the cards
   were a bordered button and a ghost button of different heights — Stephan:
   "those two buttons have completely different sizes." Here they are the same
   quiet mono control, and the state badge keeps the only filled shape. */
/* ⚠️ SCOPED TO THE CONSOLE SO IT WINS OUTRIGHT. `.ws-watch-btn` carries
   `position:absolute; top:.9rem; right:.9rem` from the World State cards, and a
   same-specificity override only wins on source order — which is a coin toss
   the next time this file is reorganised. `.baro-console .ws-watch-btn` is
   (0,2,0) and cannot lose. The button was landing in the card's top-right
   corner and sitting on top of the ⊙ COMPUTED mark. */
.baro-console .ws-watch-btn,
.baro-console-bell {
  display: inline-flex; align-items: center; gap: var(--s2);
  margin-left: auto; padding: .4rem .7rem;
  border: 1px solid var(--panel-border); border-radius: 2px;
  background: transparent; color: var(--text-dim); text-decoration: none;
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  cursor: pointer; white-space: nowrap;
  /* ⛔ SIZE AS WELL AS POSITION, AND THAT IS THE HALF I MISSED. The base rule
     is a 28x28 corner glyph — overriding only `position` left the box at 28px
     with the label spilling out across the ⊙ COMPUTED mark beside it. A
     component borrowed from another context needs every dimension it assumed
     unsaid, not just the one that moved it. */
  position: static; top: auto; right: auto;
  width: auto; height: auto; min-width: 0; z-index: auto;
  /* ⛔ AND IT MAY NOT BE SHRUNK EITHER — THE THIRD DIMENSION OF THE SAME BUG.
     `width: auto` sizes a flex item to its content and then lets flex take the
     width back: at 390px the row squeezed an 87px label into an 80px box and
     the text clipped. Found by opening the page at phone width, which nothing
     had ever done — the desktop fix above was written, verified and complete
     at 1440 and wrong at 390. */
  flex: 0 0 auto;
  transition: color var(--swift), border-color var(--swift), background var(--swift);
}
.baro-console .ws-watch-btn svg,
.baro-console-bell svg { width: 13px; height: 13px; flex: 0 0 auto; }
.baro-console .ws-watch-btn:hover,
.baro-console-bell:hover {
  color: var(--gold); border-color: var(--gold-dim); background: var(--bg-alt);
}
/* the mark keeps its own place at the end of the row */
.baro-console-top .data-mark { flex: 0 0 auto; }

.baro-console-foot-ws { margin-left: var(--s4); }
.baro-console-foot-go {
  margin-left: auto; color: var(--gold); text-decoration: none;
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
}
.baro-console-foot-go:hover { color: var(--gold-bright); }

@media (max-width: 720px) {
  /* ⚠️ A BACKDROP HAS TO BE BACKDROP-SIZED. At 74vw the art was a small
     vignette floating in a wide margin — the desktop composition shrunk rather
     than a phone one. */
  .baro-stage-art { width: min(86vw, 400px); }
  /* ⛔ THE NEGATIVE MARGIN IS GONE. It was pulling the type back over a
     portrait that USED to sit in the flow; with the art behind the content it
     only dragged the heading onto his face — measured at 32% of the backdrop
     on a 390px screen, which is precisely the eye line. */
  .baro-stage-fore { margin-top: 0; }
  .baro-console-grid { grid-template-columns: 1fr; gap: var(--s4); }
  .baro-console-foot-go { margin-left: 0; }
}

/* ⛔ ONE COLUMN FOR THE WHOLE PAGE. The console was 640 and this was 900, so
   every block below the hero sat on a wider measure than the card above it —
   two edges, two axes, and the eye reads that as things being "all over the
   place" even when each block is individually centred. Both use the same
   number now, declared once. */
/* ── the body: manifest and record on the left, the calculator always beside it ─
   ⛔ STICKY, BECAUSE THE QUESTION IS ASKED WHILE SCROLLING. "Can I afford this"
   is answered against a row forty items down; a calculator that scrolls away is
   a calculator you have to remember the numbers from. */
.baro-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 288px;
  gap: var(--s6);
  align-items: start;
}
/* ⛔ THE COLUMN OWNS THE SPACING BETWEEN ITS CARDS, exactly as the rail does.
   .baro-last carried `margin-top: 32px` — correct while it always followed the
   manifest, and wrong the twelve days in fourteen when it is the FIRST thing in
   the column: it dropped the main column 32px below the rail and the row read
   as broken. Third time one rule has done this on this page. A margin on a
   possibly-first child is a statement about a neighbour that may not exist;
   `gap` is a statement about the gaps that do. */
.baro-body-main {
  min-width: 0;
  display: flex; flex-direction: column; gap: var(--s5);
}
.baro-rail { position: sticky; top: 92px; }
@media (max-width: 940px) {
  /* ⚠️ ON A NARROW SCREEN IT GOES BACK INTO THE FLOW, ABOVE the manifest —
     sticky in one column pins it over the thing it is meant to be read against,
     and a thumb cannot scroll past a control that follows it down the page. */
  .baro-body { grid-template-columns: 1fr; }
  .baro-rail { position: static; order: -1; }
}

.baro-main {
  max-width: calc(var(--baro-col) + var(--baro-gutter) * 2);
  margin: 0 auto;
  padding: 2rem var(--baro-gutter) 6rem;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

/* Status card */
.baro-status-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 2rem 2rem 1.6rem;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.baro-status-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(212,175,55,0.06) 0%, transparent 70%);
  pointer-events: none;
}

.baro-state-badge {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-head);
  font-size: var(--t-label);
  letter-spacing: var(--track-max);
  padding: .3rem .8rem;
  border-radius: 2px;
  margin-bottom: 1.25rem;
  border: 1px solid currentColor;
}

.baro-state-badge--here  { color: var(--gold-bright); background: rgba(212,175,55,0.1); }
.baro-state-badge--away  { color: var(--text-dim);    background: rgba(255,255,255,0.04); border-color: var(--panel-border); }

.baro-state-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
}

.baro-state-badge--here  .baro-state-dot { box-shadow: 0 0 6px currentColor; }

.baro-countdown-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  margin-bottom: 1rem;
}

.baro-countdown-label {
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  color: var(--text-dim);
}

.baro-countdown-time {
  font-family: var(--font-head);
  font-size: clamp(1.8rem, 5vw, 3rem);
  font-weight: 700;
  color: var(--gold-bright);
  letter-spacing: var(--track-tight);
  line-height: 1.1;
}

.baro-location {
  font-size: var(--t-sm);
  color: var(--text-dim);
  letter-spacing: var(--track-tight);
}

/* Active: 2-column grid */
.baro-active-grid {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 1.25rem;
  align-items: start;
}

/* Calculator */
.baro-calc-inputs {
  display: flex;
  gap: .75rem;
}

.baro-calc-field { flex: 1; }

.baro-calc-field label {
  display: flex;
  align-items: center;
  gap: .35rem;
  font-family: var(--font-head);
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  color: var(--text-dim);
  margin-bottom: .45rem;
}

.baro-currency-icon {
  font-size: var(--t-sm);
  color: var(--gold);
}

.baro-currency-cr { color: #8ab4d8; }

.baro-calc-input {
  width: 100%;
  background: var(--bg-alt);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: .6rem .75rem;
  font-family: var(--font-head);
  font-size: var(--t-base);
  color: var(--text);
  transition: border-color var(--swift);
}

.baro-calc-input:focus { outline: none; border-color: var(--gold); }

.baro-calc-total {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--panel-border);
}

.baro-total-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--t-sm);
  color: var(--text-dim);
  margin-bottom: .35rem;
}

.baro-total-vals { display: flex; gap: .75rem; }

.baro-total-duc { font-family: var(--font-head); color: var(--gold-bright); font-size: var(--t-sm); }
.baro-total-cr  { font-family: var(--font-head); color: #8ab4d8; font-size: var(--t-sm); }

.baro-afford-yes { color: #6fcf8e; font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track); }
.baro-afford-no  { color: #ef5d5d; font-family: var(--font-head); font-size: var(--t-label); letter-spacing: var(--track); }

/* Inventory list */
.baro-inv-count {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--t-sm);
  color: var(--text-dim);
}

.baro-inv-list {
  list-style: none;
  margin: .75rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .35rem;
}

.baro-inv-loading {
  color: var(--text-dim);
  font-size: var(--t-sm);
  padding: .5rem 0;
}

.baro-inv-item {
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  transition: border-color var(--swift), background var(--swift);
}

.baro-inv-item.affordable   { border-color: rgba(111,207,142,0.35); background: rgba(111,207,142,0.05); }
.baro-inv-item.unaffordable { opacity: .55; }

.baro-inv-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .65rem;
  cursor: pointer;
}

.baro-inv-check {
  accent-color: var(--gold);
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  cursor: pointer;
}

.baro-inv-name {
  flex: 1;
  font-size: var(--t-sm);
  color: var(--text);
}

.baro-inv-ducats {
  font-family: var(--font-head);
  font-size: var(--t-label);
  color: var(--gold);
  white-space: nowrap;
}

.baro-inv-credits {
  font-family: var(--font-head);
  font-size: var(--t-label);
  color: #8ab4d8;
  white-space: nowrap;
}

.baro-inv-status {
  width: 1.1rem;
  text-align: center;
  font-size: var(--t-label);
  flex-shrink: 0;
}

.baro-can  { color: #6fcf8e; }
.baro-cant { color: #ef5d5d; }

/* Notification row */
/* ⚠️ THE .baro-notify-row / .baro-away-actions OVERRIDES WERE REMOVED with
   the containers they targeted — both rows are gone, their one useful control
   now lives on the console. A rule matching nothing is not harmless: the next
   reader styles around a ghost. */
.baro-notify-row {
  display: flex;
  justify-content: center;
  padding-top: .5rem;
}

/* Away panel */
.baro-away-panel {
  text-align: center;
  padding: 2.5rem 2rem;
}

.baro-away-label {
  font-family: var(--font-head);
  font-size: var(--t-base);
  letter-spacing: var(--track-tight);
  color: var(--text);
}

.baro-away-actions {
  display: flex;
  gap: .75rem;
  justify-content: center;
  margin-top: 1.25rem;
  flex-wrap: wrap;
}

/* Last visit */
.baro-last-meta {
  display: flex;
  gap: 1rem;
  color: var(--text-dim);
  font-size: var(--t-sm);
  margin: .4rem 0 .85rem;
}

.baro-last-date { color: var(--text-dim); }

.baro-last-inv-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .3rem;
}

.baro-last-inv-item {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .35rem .5rem;
  border-bottom: 1px solid rgba(255,255,255,.04);
  font-size: var(--t-sm);
}

.baro-last-name    { flex: 1; color: var(--text-dim); }
.baro-last-ducats  { font-family: var(--font-head); font-size: var(--t-label); color: var(--gold); }
.baro-last-credits { font-family: var(--font-head); font-size: var(--t-label); color: #8ab4d8; }

@media (max-width: 700px) {
  .baro-active-grid { grid-template-columns: 1fr; }
  .baro-countdown-time { font-size: 1.8rem; }
  .baro-calc-inputs { flex-direction: column; }
}

/* ============================================================
   TYPE KIT — labels / data / UI in monospace (the "relay terminal"
   texture from the stage concept). Appended last so it wins the
   cascade over the earlier single-class font-family rules.
   ============================================================ */
.section-kicker,
.home-hero-kicker,
.hero-status-kicker,
.hs-label,
.hs-value,
.btn,
.btn-sm,
.focus-link,
.hero-textlink,
.nav-links a,
.nav-login,
.nav-baro-time,
.nav-stream-status,
.support-tier-name,
.support-tier-price,
.support-tier-badge-flag,
.patron-badge,
.staff-badge,
.home-install-hint {
  font-family: var(--font-mono);
}

/* ============ Founder's Pass page (/founders/) ============ */
/* Reuses .support-tier-card for the card itself; this is only the page around
   it. Phase 3's premium pass will rework all of this — deliberately restrained
   for now rather than inventing a look that is about to be replaced. */

.founder-grid {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(1.5rem, 3vw, 3rem);
  align-items: start;
  max-width: 1040px;
  margin: 0 auto;
}

.founder-pitch { padding-top: 0.6rem; }

.founder-lede {
  font-size: clamp(1rem, 1.1vw, 1.12rem);
  line-height: 1.75;
  color: var(--text);
  margin: 0 0 1.4rem;
}
.founder-lede strong { color: var(--gold-bright); font-weight: 600; }

.founder-perks { text-align: left; margin: 0 0 1.5rem; }

.founder-note {
  font-size: var(--t-sm);
  line-height: 1.7;
  color: var(--text-dim);
  margin: 0 0 1.5rem;
  padding-left: 1rem;
  border-left: 2px solid var(--panel-border);
}

.founder-deadline {
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0;
}
.founder-deadline-label { color: var(--gold); margin-right: 0.55rem; }
.founder-deadline time  { color: var(--text); }

/* The card is sticky on desktop so the price stays with you while the pitch
   scrolls. Disabled below the breakpoint, where it would pin over the content. */
.founder-card { position: sticky; top: 6rem; }

.founder-state {
  font-size: var(--t-sm);
  line-height: 1.65;
  color: var(--text-dim);
  margin: 1.2rem 0;
}
.founder-state strong { color: var(--gold-bright); }
.founder-state--done  { color: var(--text); }
.founder-state--err   { color: #e5736b; }

/* PayPal renders its own iframe here; give it room and never restyle its
   internals — a broken PayPal button is a broken checkout. */
.founder-paypal { margin-top: 1.3rem; min-height: 52px; }

/* "Opening at launch" on a card that has no button. Reads as a status rather
   than a disabled control, because a greyed-out button invites clicking. */
.support-tier-soon {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--text-dim);
  border: 1px dashed var(--panel-border);
  border-radius: 2px;
  padding: 0.6rem 1rem;
}

@media (max-width: 860px) {
  .founder-grid { grid-template-columns: 1fr; }
  .founder-card { position: static; transform: none; }
  .founder-card:hover { transform: translateY(-6px); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   RELAY PLATING — foundation
   Appended last so it wins the cascade, same reason as the TYPE KIT block.
   Deliberately ADDITIVE: refinements to what exists, plus one new opt-in
   utility. Nothing here changes a layout, so the foundation cannot break a
   page it has not been applied to yet.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── headings ──────────────────────────────────────────────────────────────
   Chakra Petch is a display face; it wants tighter tracking as it gets bigger
   and it must never set body copy. `text-wrap: balance` is the single cheapest
   premium detail there is — it stops a two-line heading leaving one orphan
   word on the second line, on every heading, for free. */
h1, h2, h3, h4,
.section-title, .net-title, .dossier-name, .hero h1 {
  text-wrap: balance;
}
.section-title, .net-title { letter-spacing: -0.015em; }
h1, .hero h1 { letter-spacing: -0.02em; }

/* Body copy stays on the body face even inside a heading-styled block —
   Chakra Petch below ~14px loses its counters and reads as noise. */
p, li, td, input, textarea, select { font-family: var(--font-body); }

/* ── the kicker ───────────────────────────────────────────────────────────
   The site's own voice ("// THE COMPANION APP"), given a hairline that runs
   off to the right. The rule is what turns a label into a masthead. */
.section-kicker {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-size: var(--t-label);
  letter-spacing: var(--track-max);
}
.section-kicker::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--etch), transparent);
}
/* ⛔ REMOVED 2026-09-02: a centring override gated on `.is-centered` or an
   inline style. No page in this codebase ever used either, so it never once
   fired, and twelve section heads rendered their kickers hard left inside
   centred blocks from launch day. Centring is the DEFAULT now, further down. */

/* ── THE PLATE — the one structural device ────────────────────────────────
   A notched rectangle with a 1px etched gold edge. clip-path alone would clip
   a border away and leave the diagonal bare, so the gold is the OUTER
   element's background and the inner sits 1px inside it — the edge then
   follows the notch exactly. Opt-in: add .plate + an inner .plate-in.

   ⚠️ Both layers need the clip. Clipping only the outer leaves the inner's
   square corner poking through the diagonal. */
.plate {
  --edge: var(--etch);
  position: relative;
  background: var(--edge);
  padding: 1px;
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch),
                     100% 100%, var(--notch) 100%, 0 calc(100% - var(--notch)));
  transition: background var(--swift);
}
.plate > .plate-in {
  background: var(--panel);
  height: 100%;
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch),
                     100% 100%, var(--notch) 100%, 0 calc(100% - var(--notch)));
}
.plate:hover { --edge: var(--etch-lit); }
.plate--lit  { --edge: var(--etch-mid); }
.plate--lit > .plate-in { background: linear-gradient(165deg, var(--panel-2), var(--panel)); }
.plate--flush > .plate-in { background: var(--bg-alt); }

/* ── controls ─────────────────────────────────────────────────────────────
   Sharpened rather than notched. A clip-path on a bordered button eats the
   border along the diagonal and reads as a rendering fault; the notch stays
   on plates, where the two-layer trick keeps the edge intact. */
.btn, .btn-sm {
  border-radius: 2px;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  font-size: var(--t-label);
  transition: background var(--swift), border-color var(--swift),
              color var(--swift), transform var(--swift);
}
.btn:focus-visible, .btn-sm:focus-visible,
.nav-links a:focus-visible, .focus-link:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}

/* ── data ─────────────────────────────────────────────────────────────────
   Anything that is a number in a column lines up. Costs one declaration and
   is the difference between a table and a readout. */
.hs-value, .support-tier-price, .tier-price,
.hub-stat-value, .op-v, .diag td { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* The upstream World State feed has stopped advancing. Shown INSTEAD of letting
   every countdown render 00:00:00 and every fissure count as expired, which
   read as a broken site rather than an out-of-date feed. A fault must fail
   loudly and name itself. */
/* ⛔ the old static notice rule — replaced by the animated one at the end of
   this file. Removed rather than shadowed. */

/* The feed has stopped advancing: the countdown is not a countdown any more.
   Rendered as a dim label rather than a confident 00:00:00, which read as a
   dead cycle rather than an old feed. */
.cycle-time--stale {
  color: var(--muted) !important;
  font-style: italic;
  letter-spacing: var(--track-tight);
}

/* ============ Data provenance marks ============
   ⊙ COMPUTED = EPHEMERIS worked it out, no server involved.
   ◇ LIVE FEED = genuinely live, and only as available as somebody else's box.
   Same visual language as Mars Deck's ⊙/◇, deliberately — two products in one
   House should not invent two vocabularies for the same idea.
   Deliberately quiet: this is a footnote on each card, not a badge competing
   with the data it describes. */
.data-mark {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  line-height: 1;
  margin: 0 0 var(--s3);
  cursor: help;                 /* there is a title attribute worth finding */
  user-select: none;
}
.data-mark-glyph { font-size: 1.05em; line-height: 1; }
.data-mark--computed { color: var(--gold); }
.data-mark--live     { color: var(--muted); }

/* The key. Wraps to two rows on a phone rather than squashing. */
/* ⛔ A SUPERSEDED .data-legend BLOCK LIVED HERE AND WAS REMOVED 2026-09-07.
   A second, complete .data-legend rule further down redefined every property
   this one set — margin, padding, border, gap — so this block styled nothing
   while looking authoritative. Two definitions of one component is the same rot
   as a rule outliving its markup: the next person edits the one that loses. */

/* ═══════════════════════════════════════════════════════════════════════════
   BARO — the arrival state (2026-09-02)

   ⛔ [hidden] IS ONE USER-AGENT RULE AND ANY AUTHOR `display` BEATS IT BY
   ORIGIN. Every element below that ships `hidden` gets an explicit override.
   This exact trap shipped a live "Sign up" button to a paying member on Mars
   Deck; the markup was right and the element was painted anyway.
   ═══════════════════════════════════════════════════════════════════════════ */

#baroActiveSection[hidden],
#baroAwaySection[hidden],
.baro-degraded[hidden],
.baro-calc-total[hidden],
.baro-total-row[hidden],
.baro-visits-empty[hidden] { display: none !important; }

/* ── the arrival banner ──────────────────────────────────────────────────── */
.baro-arrival {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--etch-mid);
  background:
    radial-gradient(120% 140% at 12% 0%, rgba(212,175,55,.14), transparent 58%),
    linear-gradient(180deg, var(--panel-2), var(--bg-alt));
  padding: var(--s6);
  margin-bottom: var(--s6);
}
/* A faint scanline wash. Decorative only, so it never intercepts a click. */
.baro-arrival::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg, transparent 0 3px, rgba(212,175,55,.026) 3px 4px);
}
.baro-arrival-top {
  display: flex; align-items: center; gap: var(--s3);
  flex-wrap: wrap; margin-bottom: var(--s5);
}
.baro-arrival-top .data-mark { margin: 0; }

.baro-state-badge {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-family: var(--font-mono); font-size: var(--t-label);
  letter-spacing: var(--track-max); text-transform: uppercase; font-weight: 600;
  padding: 5px 12px;
}
.baro-state-badge--away { color: var(--text-dim); border: 1px solid var(--hairline); }
.baro-state-badge--here { color: var(--bg); background: var(--gold); }
.baro-state-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.baro-state-badge--here .baro-state-dot { animation: baroBeat 2.4s ease-in-out infinite; }
@keyframes baroBeat { 0%,100% { opacity: 1 } 50% { opacity: .35 } }

.baro-arrival-body {
  display: flex; align-items: flex-end; gap: var(--s7); flex-wrap: wrap;
  padding-top: var(--s5); border-top: 1px solid var(--etch-soft);
}
.baro-clock-label {
  display: block; font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted);
}
.baro-clock {
  font-family: var(--font-mono); font-size: 2.75rem; font-weight: 500;
  line-height: 1.05; color: var(--text);
  font-variant-numeric: tabular-nums; margin-top: 6px;
}
.baro-clock span { color: var(--muted); font-size: 1.35rem; margin: 0 2px 0 1px; }
.baro-relay {
  font-family: var(--font-mono); font-size: var(--t-lead);
  color: var(--gold-bright); letter-spacing: var(--track); margin-top: 6px;
}
.baro-when { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted); margin-top: var(--s1); }

/* ── layout ──────────────────────────────────────────────────────────────── */
.baro-split { display: grid; grid-template-columns: 1fr 320px; gap: var(--s6); align-items: start; }
@media (max-width: 900px) { .baro-split { grid-template-columns: 1fr; } }

.baro-ledger-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap; margin: 0 0 var(--s4);
}
.baro-ledger-head .data-mark { margin: 0; }
.baro-inv-count { font-family: var(--font-mono); font-size: var(--t-sm); color: var(--muted); margin-left: var(--s2); }
.baro-last-when { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted); }

/* ── the manifest ledger ─────────────────────────────────────────────────── */
.baro-scroller { overflow-x: auto; }
.baro-table { width: 100%; border-collapse: collapse; min-width: 560px; }
.baro-table thead th {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted);
  text-align: left; font-weight: 500; padding: 0 var(--s4) var(--s3);
  border-bottom: 1px solid var(--etch);
}
.baro-table thead th.baro-c-num { text-align: right; }
.baro-table tbody td {
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--hairline);
  font-size: var(--t-sm);
}
.baro-table tbody tr:hover { background: rgba(212,175,55,.045); }
.baro-c-num {
  text-align: right; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text);
}
.baro-c-num .u { color: var(--muted); margin-left: 3px; }
.baro-pick { display: flex; align-items: center; gap: var(--s3); cursor: pointer; }
.baro-name { color: var(--text); }
.baro-inv-empty { color: var(--muted); font-style: italic; }

.baro-row.affordable   .baro-name { color: var(--gold-bright); }
.baro-row.unaffordable { opacity: .45; }

/* ── the three ALMANAC tags ──────────────────────────────────────────────── */
.tag {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track); text-transform: uppercase;
  padding: 2px 6px; white-space: nowrap;
}
.tag--new   { color: var(--c-lith); border: 1px solid rgba(94,194,232,.4); }
.tag--seen  { color: var(--muted);  border: 1px solid var(--hairline); }
.tag--first { color: var(--gold-bright); border: 1px solid var(--etch-mid); background: var(--etch-soft); }

/* ── the degraded banner ─────────────────────────────────────────────────── */
.baro-degraded {
  display: flex; gap: var(--s4); align-items: flex-start;
  border: 1px solid rgba(226,84,107,.35); background: rgba(226,84,107,.05);
  padding: var(--s4) var(--s5); margin-bottom: var(--s5);
}
.baro-degraded-glyph { color: var(--c-neo); font-family: var(--font-mono); font-size: var(--t-lead); line-height: 1.2; }
.baro-degraded p { margin: 0; font-size: var(--t-sm); color: var(--text-dim); }
.baro-degraded strong {
  display: block; color: var(--text); font-family: var(--font-head);
  letter-spacing: var(--track-tight); margin-bottom: 3px;
}
.data-mark--ours { color: var(--gold-bright); }

/* ── the ALMANAC rail ────────────────────────────────────────────────────── */
/* ⛔ THE PADDING IS GONE, AND IT WAS A LIVE MISALIGNMENT. This block is a
   leftover from when .baro-rail was itself a panel; once it became the sticky
   COLUMN, 24px of padding inset every card in it — so the calculator's right
   edge stopped 24px short of the grid's, while every card in the main column
   landed on the grid exactly. One column obeyed the edge and the other did not,
   which is the "all over the place" complaint in its smallest form.
   ⚠️ AND MY OWN EDGE GUARD COULD NOT SEE IT: it walks .baro-body-main and
   nothing else, so a rail that drifted was outside its denominator entirely. */
.baro-rail {
  padding: 0;
  display: flex; flex-direction: column; gap: var(--s5);
}
.baro-rail h4 {
  font-family: var(--font-mono); font-size: var(--t-base); font-weight: 500;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--gold); margin: 0;
}
.baro-rail p { font-size: var(--t-sm); color: var(--text-dim); margin: var(--s3) 0 0; }
.baro-visits { list-style: none; margin: var(--s5) 0 0; padding: 0; }
.baro-visits li {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  padding: var(--s3) 0; border-bottom: 1px solid var(--hairline); font-size: var(--t-sm);
}
.baro-visits li:last-child { border-bottom: 0; }
.baro-visits .d { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--text-dim); font-variant-numeric: tabular-nums; }
.baro-visits .c { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted); }
.baro-visits-empty { font-size: var(--t-sm); color: var(--muted); font-style: italic; margin-top: var(--s4); }

/* ── calculator + away ───────────────────────────────────────────────────── */
/* ⚠️ NO TOP MARGIN. It is the FIRST card in the rail, and a margin on the
   first child of a column is a rule about a neighbour that is not there — it
   pushed the calculator below the main column's first card, so the two columns
   started at different heights. The rail's own `gap` handles the spacing
   between cards, which is where that decision belongs. */
.baro-calc { margin-top: 0; }

/* ── the ALMANAC note: reference beside the record ───────────────────────── */
.baro-note-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); flex-wrap: wrap;
}
/* ⚠️ THE MARK KEEPS ITS OWN PLACE WHEN THE COLUMN IS TOO NARROW FOR BOTH.
   A provenance mark squeezed against a heading is the same fault as the bell
   clipping its label — it is a CLAIM, and a claim has to stay readable. */
.baro-note-head .data-mark { flex: 0 0 auto; }
.baro-almanac-note p { margin: var(--s3) 0 0; }
.baro-calc-hint { color: var(--text-dim); font-size: var(--t-sm); margin: var(--s2) 0 var(--s4); }
.baro-away-sub { color: var(--text-dim); font-size: var(--t-sm); margin-top: var(--s2); }
.baro-last { margin-top: 0; }

/* ═══ THE BROWSABLE RECORD ══════════════════════════════════════════
   ⛔ THIS COMPONENT SHIPPED WITH ZERO CSS RULES, AND WAS ABOUT A FORTNIGHT
   FROM BEING SEEN. It only renders once ALMANAC holds three captured visits,
   the record holds one, and Baro returns every fourteen days — so an <ol> of
   <details> with browser-default numbering and grey disclosure triangles was
   queued to appear on a live page with nobody having laid eyes on it. Second
   time on this project: .ask-kept had the same fault. A class that is never
   styled raises nothing; it is just a div.
   ⚠️ ROWS, NOT CARDS — the same call the Command Hub made. A visit is one
   line of a ledger: when, where, how many. Wrapping each in its own panel would
   make twelve visits read as twelve announcements. */
.baro-archive { display: flex; flex-direction: column; gap: var(--s5); }
.baro-archive-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap; margin: 0;
}
.baro-archive-head h3 { margin: 0; }
.baro-archive-head .data-mark { margin: 0; flex: 0 0 auto; }

.baro-archive-list {
  list-style: none; margin: 0; padding: 0;
  border-top: 1px solid var(--etch);
}
.baro-visit { border-bottom: 1px solid var(--hairline); }
/* ⛔ THE NATIVE MARKER GOES, AND IT TAKES TWO DECLARATIONS. Chrome reads
   `list-style` on the summary, older WebKit only honours the pseudo-element —
   leave either out and a grey triangle sits beside our own mark. */
.baro-visit summary { list-style: none; }
.baro-visit summary::-webkit-details-marker { display: none; }
.baro-visit summary {
  display: flex; align-items: baseline; gap: var(--s4); flex-wrap: wrap;
  padding: var(--s4) var(--s2); cursor: pointer;
  transition: background var(--swift), color var(--swift);
}
.baro-visit summary:hover { background: rgba(212, 175, 55, .045); }
.baro-visit summary:focus-visible { outline: 1px solid var(--gold); outline-offset: -1px; }
/* our own disclosure mark: a rotating caret that says which way it will go.
   ⚠️ A LITERAL CHARACTER, NEVER A CODE FOR ONE. This caret was first
   written as a four-digit hex escape and arrived in the file as the bytes
   0x83 0x41 — a stray control character followed by a capital A, because the
   backslash was eaten one layer up and what survived parsed as an OCTAL
   escape. It rendered as tofu on every row of the record. Writing the comment
   about it reproduced the same fault in the same minute, which is the whole
   lesson: put the character in, never a code for it. The glyph is already
   proven on .road-archive, so it is copied rather than reinvented. */
.baro-visit summary::marker { content: ""; }
.baro-visit summary::before {
  content: '▸'; flex: 0 0 auto; width: 1ch;
  color: var(--gold-dim); font-family: var(--font-mono);
  transition: transform var(--swift), color var(--swift);
}
.baro-visit details[open] > summary::before { transform: rotate(90deg); color: var(--gold); }
.baro-visit details[open] > summary { color: var(--gold); }

.baro-visit-when {
  font-family: var(--font-mono); font-size: var(--t-sm);
  color: var(--text); font-variant-numeric: tabular-nums; flex: 0 0 auto;
}
.baro-visit-where { font-size: var(--t-sm); color: var(--text-dim); flex: 1 1 auto; min-width: 0; }
/* ⚠️ THE COUNT SITS AT THE END OF THE ROW AND KEEPS ITS OWN WIDTH. It is the
   one number here, and a number that moves as its neighbour's text length
   changes cannot be scanned down a column. */
.baro-visit-n {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--muted); flex: 0 0 auto; margin-left: auto;
}
.baro-visit-table { margin: 0 0 var(--s4); }
.baro-visit-table thead th { padding-top: var(--s2); }
.baro-visit-none {
  margin: 0 0 var(--s4); padding: 0 var(--s2);
  font-size: var(--t-sm); color: var(--muted); font-style: italic;
}
.baro-archive-more {
  margin: 0; font-size: var(--t-sm); color: var(--text-dim);
}
.baro-archive-more a { color: var(--gold); }
.baro-notify-row { margin-top: var(--s6); }

@media (max-width: 640px) {
  .baro-arrival { padding: var(--s5) var(--s4); }
  .baro-clock { font-size: 2rem; }
  .baro-arrival-body { gap: var(--s5); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   RELAY PLATING — THE PLATE, APPLIED (2026-09-02)

   The approved concept (30 Aug) is built on ONE structural device: a notched
   rectangle with a 1px etched gold edge. The tokens and the type kit shipped on
   launch day; the device itself did not, and `.plate` sat in this stylesheet
   used by exactly two elements. Stephan: *"I did not really see it anywhere."*
   He was right — this file's own record has carried "the .plate device is built
   but applied nowhere" since 31 August.

   ⛔ WHY THIS IS NOT JUST `border` + `clip-path`.
   A clip-path clips the BORDER away along the diagonal, so the notch corner
   renders bare and reads as a rendering fault. The concept solves it with two
   elements: gold on the outer, panel 1px inside. Most cards on this site are a
   SINGLE element, so here the two layers are ::before and ::after instead — the
   same trick, no markup change on 90+ card instances.

   ⛔ WHY `z-index: 0` ON THE HOST IS LOAD-BEARING.
   It creates a stacking context, so the two `z-index: -1` layers paint behind
   ALL of the card's content — including bare text nodes, which a
   `.card > * { z-index: 1 }` approach would leave stranded underneath. Remove
   it and the pseudo-layers escape behind the PAGE instead, and every plated
   card goes transparent.

   ⚠️ BOX-SHADOWS ARE DROPPED, NOT FORGOTTEN. A clip-path clips the element's
   own shadow, so a shadow on a plated card renders as a hard-edged smear along
   the notch. The concept replaces depth-by-shadow with edge-brightening, which
   is also the reason "28 shadows" was on the diagnosis list.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Written once. The notch is top-right and bottom-left — the Orokin cut. */
  --plate-notch: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch),
                         100% 100%, var(--notch) 100%, 0 calc(100% - var(--notch)));
}

/* ── Group A: full plate — edge + fill ──────────────────────────────────────
   ⚠️ `.ws-card--baro` is excluded: it already owns ::after as an image scrim,
   and a second ::after would silently replace it. It gets the edge below. */
.panel,
.info-card,
.cycle-card,
.focus-card,
.support-tier-card,
.ws-card:not(.ws-card--baro) {
  --edge: var(--etch);
  --plate-fill: var(--panel);
  position: relative;
  z-index: 0;                      /* ⛔ see note above — do not remove */
  background: transparent;
  /* ⛔ A TRANSPARENT 1px BORDER, NOT `border: 0`. With global border-box it
     insets BOTH in-flow content and absolutely-positioned inset:0 children by
     one pixel, so nothing paints over the etched edge. Without it the cycle
     cards' `.cycle-card-bg` (inset:0, opacity .6) and `.focus-img` sit ON the
     gold line and mud it out — the device would render and the edge would
     quietly not. */
  border: 1px solid transparent;
  border-radius: 0;
  box-shadow: none;
  clip-path: var(--plate-notch);   /* also clips image children into the notch */
}

.panel::before,             .panel::after,
.info-card::before,         .info-card::after,
.cycle-card::before,        .cycle-card::after,
.focus-card::before,        .focus-card::after,
.support-tier-card::before, .support-tier-card::after,
.ws-card:not(.ws-card--baro)::before,
.ws-card:not(.ws-card--baro)::after {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  clip-path: var(--plate-notch);
}

/* the etched edge */
.panel::before,
.info-card::before,
.cycle-card::before,
.focus-card::before,
.support-tier-card::before,
.ws-card:not(.ws-card--baro)::before {
  /* ⚠️ -1px, not 0. An absolutely-positioned child is placed against the
     PADDING box, which the transparent border has already moved inward. */
  inset: -1px;
  background: var(--edge);
  transition: background var(--swift);
}

/* the plate face, one pixel in — this is what makes the diagonal read gold */
.panel::after,
.info-card::after,
.cycle-card::after,
.focus-card::after,
.support-tier-card::after,
.ws-card:not(.ws-card--baro)::after {
  inset: 0;                        /* = the padding box = 1px in from the edge */
  background: var(--plate-fill);
}

/* ── Group B: the Baro card keeps its own scrim, takes the edge ───────────── */
.ws-card--baro {
  --edge: var(--etch);
  position: relative;
  border: 1px solid transparent;
  border-radius: 0;
  box-shadow: none;
  clip-path: var(--plate-notch);
}
.ws-card--baro::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 0;                      /* above the panel fill, below the image */
  pointer-events: none;
  clip-path: var(--plate-notch);
  background: var(--edge);
  transition: background var(--swift);
}

/* ── hover: the edge lifts. Only on cards that are actually a target ──────── */
.cycle-card:hover,
.focus-card:hover,
.support-tier-card:hover,
.ws-card:hover {
  --edge: var(--etch-lit);
}
/* ⚠️ `.panel` is a content block, not a control — it does not respond to hover.
   Lighting up a page's worth of static panels reads as everything being
   clickable. */

/* ── variants, translated from border-color onto the edge ─────────────────── */
.focus-card:hover         { box-shadow: none; }
.support-tier-card:hover  { box-shadow: none; }
.support-tier-card--featured {
  --edge: var(--tier-accent);
  --plate-fill: linear-gradient(160deg, var(--panel) 0%, rgba(255, 255, 255, 0.05) 100%);
  background: transparent;         /* the gradient moved onto the plate face */
  box-shadow: none;
}
.support-tier-card--featured:hover { --edge: var(--tier-accent); }

/* ── the plate device on its own, for new markup (unchanged contract) ─────── */
.plate--flat > .plate-in { background: var(--bg-alt); }

@media (prefers-reduced-motion: reduce) {
  .panel::before, .info-card::before, .cycle-card::before, .focus-card::before,
  .support-tier-card::before, .ws-card::before { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   HOMEPAGE FINE-TUNE (2026-09-02) — Stephan's page-by-page pass.
   Four items, three of them fixes to things this stylesheet itself broke.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. ⛔ THE KICKER WAS LEFT-ALIGNED ON ALL TWELVE SECTION HEADS ──────────
   `.section-head` sets `text-align: center`. The Relay Plating pass then made
   `.section-kicker` a FLEX ROW to carry a trailing hairline — and a flex
   container ignores `text-align`, aligning by `justify-content`, which defaults
   to flex-start. The centring override was gated on `.is-centered` or an inline
   style, and **no section head in the codebase uses either**. A guard that had
   never once fired, shipped on launch day.

   Fixed by inverting the default: heads are centred, so the kicker centres. And
   rather than deleting the hairline, it becomes SYMMETRIC — a rule on one side
   of centred text reads as broken, a rule on both reads as a chapter mark. */
/* ⛔ CENTRED IS THE DEFAULT, AND THAT CHOICE IS THE FIX.
   The first version of this scoped centring to `.section-head`, which missed
   `.join-content` — Stephan found "// RECRUITMENT OPEN" still hanging left days
   later. Scoping to known containers means every NEW centred container repeats
   the bug in silence.

   ⚠️ So the default fails in the LOUD direction. A kicker dropped into a new
   context now arrives centred with a symmetric rule: correct in a centred
   block, and instantly, visibly wrong in a left-aligned one (a hairline
   appears before the text). The opposite default is the one that hides.

   The container list below was DERIVED FROM THE MARKUP, not remembered:
   2 centred contexts (.section-head ×12, .join-content) and 4 deliberately
   left ones. tools/verify-kickers.php asserts it stays that way. */
.section-kicker {
  justify-content: center;
}
.section-kicker::before,
.section-kicker::after {
  content: "";
  flex: 0 1 78px;
  height: 1px;
  display: block;
}
.section-kicker::before { background: linear-gradient(90deg, transparent, var(--etch)); }
.section-kicker::after  { background: linear-gradient(90deg, var(--etch), transparent); }

/* The four contexts that are deliberately left-aligned: their own CSS sets
   text-align:left, or they sit in a space-between masthead. One rule, trailing
   only, running out to fill the line. */
.blog-masthead-left .section-kicker,
.home-install-text  .section-kicker,
.home-lab-content   .section-kicker,
.section-head--left .section-kicker { justify-content: flex-start; }

.blog-masthead-left .section-kicker::before,
.home-install-text  .section-kicker::before,
.home-lab-content   .section-kicker::before,
.section-head--left .section-kicker::before { display: none; }

.blog-masthead-left .section-kicker::after,
.home-install-text  .section-kicker::after,
.home-lab-content   .section-kicker::after,
.section-head--left .section-kicker::after { flex: 1 1 auto; }

.section-head--left { text-align: left; }

/* ── 2. THE PITCH ICONS — finer line, framed, alive ────────────────────────
   "the icon is a little bit too bold for me." They were stroke-width 2 at 52px,
   which is a heavy 4% of the glyph. CSS overrides the presentation attribute,
   so this thins them without touching a single SVG.

   The frame is the plate device at icon scale — the same notch, so an icon
   reads as part of the same system as the cards around it. */
.home-pitch-icon {
  --edge: var(--etch);
  width: 76px;
  height: 76px;
  margin: 0 auto 1.35rem;
  position: relative;
  z-index: 0;
  display: grid;
  place-items: center;
  clip-path: var(--plate-notch);
  --notch: 10px;                  /* a smaller cut at a smaller size */
  transition: background var(--glide);
}
.home-pitch-icon::before,
.home-pitch-icon::after {
  content: "";
  position: absolute;
  z-index: -1;
  clip-path: var(--plate-notch);
  pointer-events: none;
}
.home-pitch-icon::before { inset: 0;   background: var(--edge); transition: background var(--swift); }
.home-pitch-icon::after  { inset: 1px; background: linear-gradient(160deg, var(--panel-2), var(--panel)); }

.home-pitch-icon svg {
  width: 34px;
  height: 34px;
  stroke: var(--gold);            /* overrides the inline stroke attribute */
  stroke-width: 1.2;              /* was 2 — the "too bold" */
  overflow: visible;
}
/* The line draws itself in as the item reveals. `pathLength` is not set on
   these paths, so the dash length is generous enough to cover any of them. */
.home-pitch-item.in-view .home-pitch-icon svg > * {
  stroke-dasharray: 120;
  stroke-dashoffset: 120;
  animation: pitchDraw 1.1s var(--glide) forwards;
}
.home-pitch-item.in-view .home-pitch-icon svg > *:nth-child(2) { animation-delay: .18s; }
@keyframes pitchDraw { to { stroke-dashoffset: 0; } }

.home-pitch-item:hover .home-pitch-icon { --edge: var(--etch-lit); }
.home-pitch-item:hover .home-pitch-icon svg { stroke: var(--gold-bright); }

/* ── 3. THE FEATURE MARKS — drawn, not photographed ────────────────────────
   Four 2–3 MB photographs became four inline SVGs. ~7 MB off the homepage, and
   they finally speak the same language as the rest of the site: this is a DRAWN
   instrument. Orbits are lines, the graticule is lines, the plate is a line. */
.focus-mark {
  position: relative;
  height: clamp(150px, 12vw, 190px);
  display: grid;
  place-items: center;
  overflow: hidden;
  background:
    radial-gradient(ellipse 70% 90% at 50% 30%, rgba(212,175,55,.07), transparent 70%),
    linear-gradient(180deg, var(--panel-2), var(--panel));
}
/* the faint plating grid behind every mark — atmosphere, not decoration */
.focus-mark::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(var(--etch-soft) 1px, transparent 1px) 0 0 / 100% 22px,
    linear-gradient(90deg, var(--etch-soft) 1px, transparent 1px) 0 0 / 22px 100%;
  opacity: .5;
}
.focus-mark svg {
  position: relative;
  width: clamp(96px, 9vw, 118px);
  height: clamp(96px, 9vw, 118px);
  overflow: visible;
}
.focus-mark svg * { fill: none; stroke-linecap: round; }

.fm-arc   { stroke: var(--gold);        stroke-width: 1.1; stroke-dasharray: 62 38; opacity: .85; }
.fm-arc--2{ stroke: var(--gold-bright); stroke-dasharray: 34 66; opacity: .7; }
.fm-arc--3{ stroke: var(--gold);        stroke-dasharray: 18 82; opacity: .55; }
.fm-core  { fill: var(--gold-bright); stroke: none; }
.fm-dot   { fill: var(--gold-bright); stroke: none; }
.fm-gem   { stroke: var(--gold);        stroke-width: 1.2; }
.fm-gem--in { stroke: var(--gold-bright); opacity: .75; }
.fm-chip  { stroke: var(--gold-bright); stroke-width: 1; opacity: .8; }
.fm-rule  { stroke: var(--etch-mid);    stroke-width: 1; }
.fm-bar   { stroke: var(--gold); stroke-width: 1; fill: rgba(212,175,55,.08); }
.fm-trend { stroke: var(--gold-bright); stroke-width: 1.4; }
.fm-scan  { stroke: var(--gold-bright); stroke-width: 1; opacity: .5; }
.fm-hex   { stroke: var(--gold); stroke-width: 1.2; }
.fm-hex--in { stroke: var(--gold-bright); opacity: .6; }
.fm-spoke { stroke: var(--etch-mid); stroke-width: 1; }
.fm-node  { fill: var(--gold); stroke: none; }

/* motion — slow, ambient, never a distraction */
.fm-spin        { transform-origin: 60px 60px; animation: fmSpin 26s linear infinite; }
.fm-spin--b     { animation-duration: 18s; animation-direction: reverse; }
.fm-spin--c     { animation-duration: 34s; }
.fm-orbit       { transform-origin: 60px 60px; animation: fmSpin 14s linear infinite; }
.fm-bar         { animation: fmBar 3.6s var(--glide) infinite; animation-delay: calc(var(--i) * .22s); transform-origin: 50% 100%; }
.fm-trend       { stroke-dasharray: 100; stroke-dashoffset: 100; animation: fmDraw 3.2s var(--glide) infinite; }
.fm-scan        { animation: fmScan 4.2s ease-in-out infinite; }
.fm-node        { animation: fmNode 4.8s ease-in-out infinite; animation-delay: calc(var(--i) * .32s); }

@keyframes fmSpin { to { transform: rotate(360deg); } }
@keyframes fmBar  { 0%,100% { transform: scaleY(.86); } 50% { transform: scaleY(1); } }
@keyframes fmDraw { 0% { stroke-dashoffset: 100; } 55%,100% { stroke-dashoffset: 0; } }
@keyframes fmScan { 0%,100% { transform: translateY(0); opacity: .18; } 50% { transform: translateY(74px); opacity: .55; } }
@keyframes fmNode { 0%,100% { opacity: .32; } 50% { opacity: 1; } }

.focus-card:hover .fm-spin,
.focus-card:hover .fm-orbit { animation-duration: 9s; }

/* ── 4. ⛔ THE "LIMITED · LIFETIME" FLAG WAS CUT IN HALF ────────────────────
   MY REGRESSION, shipped this afternoon. It sat at `top: -0.65rem` to overhang
   the card's top edge — and `clip-path` clips descendants, so the plate pass
   sliced it. Brought inside, and restyled: a 10px-radius pill was a leftover
   from before Relay Plating and fought the one device the system is built on. */
.support-tier-flag {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  align-self: center;
  margin: 0 0 1rem;
  padding: .32rem .8rem;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-bright);
  border: 1px solid var(--etch-mid);
  background: var(--etch-soft);
  border-radius: 0;
  white-space: nowrap;
}
.support-tier-flag span {
  color: var(--bg);
  background: var(--tier-accent, var(--gold));
  padding: .1rem .42rem;
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .fm-spin, .fm-spin--b, .fm-spin--c, .fm-orbit,
  .fm-bar, .fm-trend, .fm-scan, .fm-node { animation: none; }
  .fm-trend { stroke-dashoffset: 0; }
  .fm-node  { opacity: .8; }
  .home-pitch-item.in-view .home-pitch-icon svg > * {
    animation: none; stroke-dashoffset: 0;
  }
}

@media (max-width: 860px) {
  .home-pitch-grid { grid-template-columns: 1fr; }
  .section-head .section-kicker::before,
  .section-head .section-kicker::after { flex-basis: 34px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE ORB (2026-09-02) — see php/orb.php for the concept.

   ⚜️ One component, five faces: a logo mark and the four clan cards. Every
   stroke is a LINE, because this site is a drawn instrument.

   ⚠️ It takes its colour from `currentColor` wherever it can, so the same mark
   works on a plate, in a heading, or as a favicon without a second definition.
   ═══════════════════════════════════════════════════════════════════════════ */

.orb {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  color: var(--gold);
  /* Contain the paint so a card full of orbs cannot force layout work on the
     rest of the page while they animate. */
  contain: paint;
}
.orb svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* ⛔ `fill: none` goes on the ROOT and INHERITS -- it must not be a `*` rule.
   `.orb svg *` scores (0,1,1) and `.orb-core` only (0,1,0), so the reset would
   have BEATEN the core's own fill and rendered the ◈ and the craft hollow.
   Inheritance carries no specificity, so a plain class wins against it. */
.orb svg { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.orb svg * { vector-effect: non-scaling-stroke; }

/* ── the shell ───────────────────────────────────────────────────────────── */
.orb-limb        { stroke: currentColor; stroke-width: 1.4; opacity: .9; }
.orb-lat         { stroke: currentColor; stroke-width: .9;  opacity: .32; }
.orb-lat--faint  { opacity: .18; }
.orb-ticks path  { stroke: var(--gold-bright); stroke-width: 1.4; opacity: .55; }
.orb-core        { stroke: var(--gold-bright); stroke-width: 1.6; fill: rgba(212,175,55,.10); }

/* the sphere turns by narrowing and widening its longitudes — a real rotation
   read, at the cost of two animated attributes rather than a transform */
.orb-long { transform-origin: 100px 100px; animation: orbTurn 18s ease-in-out infinite; }
@keyframes orbTurn { 0%,100% { transform: scaleX(1); } 50% { transform: scaleX(.16); } }

/* ── the echo — the thing the name is about ──────────────────────────────── */
.orb-ring {
  stroke: var(--gold-bright);
  stroke-width: 1.1;
  transform-origin: 100px 100px;
  opacity: 0;
  animation: orbEcho 5.4s cubic-bezier(.22,.61,.36,1) infinite;
  animation-delay: calc(var(--i) * 1.8s);
}
@keyframes orbEcho {
  0%   { transform: scale(.34); opacity: 0; }
  14%  { opacity: .75; }
  100% { transform: scale(2.28); opacity: 0; }
}
/* ⛔ NO transform-origin HERE, AND THIS LINE IS WHY THE ORB HAD A TRIANGLE.
   The core used to be a <rect> carrying transform="rotate(45 100 100)" as an
   ATTRIBUTE. In SVG 2 a CSS transform-origin applies to the attribute transform
   too, so this line applied the origin a SECOND time —

       T(100,100) · rotate(45 100 100) · T(-100,-100) == rotate 45° about (200,200)

   — throwing the ◈ out to (200, 58.6), the top-right corner of the viewBox,
   with its right half clipped off by the edge. Half a diamond is a triangle
   pointing left. It has been like that in EVERY orb since the orb shipped, and
   the reason nobody could name it is that at clan-card size a missing core and
   a small gold wedge just look like part of the drawing.
   ⚠️ The core is a plain polygon with no transform now, so an origin here would
   be inert — it is removed anyway, because inert today is not inert after the
   next change. */
.orb-core { animation: orbBreathe 5.4s ease-in-out infinite; }
@keyframes orbBreathe { 0%,100% { opacity: .85; } 50% { opacity: 1; } }

/* ── railjack ────────────────────────────────────────────────────────────── */
.orb-path  { stroke: var(--gold-bright); stroke-width: 1.3; stroke-dasharray: 100; stroke-dashoffset: 100; animation: orbDraw 6s ease-in-out infinite; }
.orb-arc   { stroke: currentColor; stroke-width: 1; opacity: .45; }
.orb-arc--2 { opacity: .3; }
.orb-craft { fill: var(--gold-bright); stroke: none; }
.orb-travel { offset-path: path("M40 132 C74 108, 126 92, 160 68"); animation: orbTravel 6s ease-in-out infinite; }
@keyframes orbDraw   { 0% { stroke-dashoffset: 100; } 60%,100% { stroke-dashoffset: 0; } }
@keyframes orbTravel { 0% { offset-distance: 0%; opacity: 0; } 8% { opacity: 1; }
                       60% { offset-distance: 100%; opacity: 1; } 70%,100% { offset-distance: 100%; opacity: 0; } }

/* ── genetics ────────────────────────────────────────────────────────────── */
.orb-helix     { stroke: currentColor; stroke-width: 1.3; opacity: .85; }
.orb-helix--2  { stroke: var(--gold-bright); opacity: .6; }
.orb-rungs line { stroke: var(--gold-bright); stroke-width: 1; opacity: .25;
                  animation: orbRung 4.4s ease-in-out infinite; animation-delay: calc(var(--i) * .28s); }
@keyframes orbRung { 0%,100% { opacity: .18; } 50% { opacity: .85; } }

/* ── dojo ────────────────────────────────────────────────────────────────── */
.orb-halls path { stroke: currentColor; stroke-width: 1.2; fill: rgba(212,175,55,.06);
                  animation: orbRaise 6.4s ease-in-out infinite; animation-delay: calc(var(--i) * .5s);
                  transform-origin: 100px 105px; }
@keyframes orbRaise { 0%,100% { opacity: .28; } 45% { opacity: 1; } }
.orb-spine        { stroke: var(--gold-bright); stroke-width: 1; opacity: .4; }
.orb-spine--faint { opacity: .2; }

/* ── fashion ─────────────────────────────────────────────────────────────── */
.orb-triad { transform-origin: 100px 100px; animation: orbTurnFull 26s linear infinite; }
.orb-triad rect { stroke: var(--gold-bright); stroke-width: 1.2; fill: rgba(212,175,55,.07);
                  animation: orbFacet 4.8s ease-in-out infinite; animation-delay: calc(var(--i) * .5s); }
@keyframes orbTurnFull { to { transform: rotate(360deg); } }
@keyframes orbFacet { 0%,100% { opacity: .4; } 50% { opacity: 1; } }
.orb-sweep { stroke: currentColor; stroke-width: 1.2; opacity: .5;
             stroke-dasharray: 100; stroke-dashoffset: 100; animation: orbDraw 7s ease-in-out infinite; }

/* ── the logo face: no wireframe, just core + echo, legible at 24px ───────── */
.orb--mark .orb-sphere,
.orb--mark .orb-ticks { display: none; }
.orb--mark .orb-limb  { display: none; }

/* ── the clan cards ──────────────────────────────────────────────────────── */
.clan-run-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s3);
}
@media (max-width: 1040px) { .clan-run-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .clan-run-grid { grid-template-columns: 1fr; } }

.clan-run-card { display: flex; flex-direction: column; }
.clan-run-orb {
  position: relative;
  padding: var(--s6) var(--s6) var(--s4);
  display: grid;
  place-items: center;
}
/* the plating grid behind the mark — the same atmosphere as the home marks */
.clan-run-orb::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 70% 70% at 50% 45%, rgba(212,175,55,.08), transparent 72%),
    linear-gradient(var(--etch-soft) 1px, transparent 1px) 0 0 / 100% 22px,
    linear-gradient(90deg, var(--etch-soft) 1px, transparent 1px) 0 0 / 22px 100%;
  opacity: .55;
}
.clan-run-orb .orb { position: relative; max-width: 148px; }
.clan-run-body { padding: 0 var(--s5) var(--s6); }
.clan-run-body h3 {
  font-family: var(--font-head);
  font-size: var(--t-lead);
  letter-spacing: var(--track-tight);
  margin: 0 0 var(--s3);
  color: var(--text);
}
.clan-run-body p { font-size: var(--t-sm); color: var(--text-dim); margin: 0; }

.clan-run-card:hover .orb { color: var(--gold-bright); }
.clan-run-card:hover .orb-long,
.clan-run-card:hover .orb-triad { animation-duration: 6s; }

/* ── the join band, with the photograph gone ─────────────────────────────── */
/* ⚠️ It was a 2.7 MB portrait. What replaces it is the orb at watermark scale
   plus the plating grid — the section now carries the brand mark instead of a
   stock frame, and costs nothing. */
.join-plate {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background:
    radial-gradient(ellipse 80% 70% at 78% 40%, rgba(212,175,55,.10), transparent 68%),
    linear-gradient(180deg, var(--bg-alt), var(--bg));
}
.join-plate::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(var(--etch-soft) 1px, transparent 1px) 0 0 / 100% 30px,
    linear-gradient(90deg, var(--etch-soft) 1px, transparent 1px) 0 0 / 30px 100%;
  opacity: .5;
}
.join-watermark {
  position: absolute;
  right: -6%;
  top: 50%;
  transform: translateY(-50%);
  width: min(46vw, 560px);
  opacity: .16;
  pointer-events: none;
}
@media (max-width: 900px) {
  .join-watermark { right: -22%; opacity: .1; }
}

/* ── the hero photograph, brought into the palette ───────────────────────── */
/* ⚠️ THE ONE PHOTOGRAPH LEFT ON THIS PAGE IS BRIGHT DAYLIGHT — green field,
   blue sky — and it now carries a void-black-and-gold page on its own. Dropped
   into the hero raw it fights everything around it. Desaturated and darkened it
   reads as the same world as the rest of the site, and the Kubrow is still
   perfectly legible. The overlay does the rest. */
.clan-hero-bg {
  position: absolute;
  inset: 0;
  background-image: url('/Images/clan-hero.webp');
  background-size: cover;
  /* Computed: 60vh band is ~2.96:1 against a 1.5:1 image, so `cover` crops
     vertically and shows only 50.6% of the height. The Kubrow's head sits
     ~18–58%; centring the window on 38% puts the top edge at 26%. */
  background-position: center 26%;
  filter: saturate(.62) brightness(.68) contrast(1.06);
  transform: scale(1.02);      /* hides the filter's edge softening */
}

@media (prefers-reduced-motion: reduce) {
  .orb-long, .orb-ring, .orb-core, .orb-path, .orb-travel,
  .orb-rungs line, .orb-halls path, .orb-triad, .orb-triad rect, .orb-sweep {
    animation: none;
  }
  .orb-path, .orb-sweep { stroke-dashoffset: 0; }
  .orb-ring { opacity: .22; }
  .orb-halls path, .orb-triad rect, .orb-rungs line { opacity: .7; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   DRAWN CYCLE ART — the Cambion proof (2026-09-02)
   See php/cycle-art.php for why. Two states, two drawings, two motions.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⛔ The photograph comes off. It was the SAME image for both states, separated
   only by a hue-rotate — which is why Fass and Vome read as one place tinted
   twice rather than as two atmospheres. */
.cycle-card-bg--cambion {
  background-image: none;
  opacity: 1;                       /* the art carries its own restraint */
  transition: background var(--glide);
}
.cycle-card-bg--cambion.fass {
  background:
    radial-gradient(ellipse 90% 120% at 12% 108%, rgba(224,122,52,.30), transparent 62%),
    radial-gradient(ellipse 70% 90%  at 88% 100%, rgba(196,88,40,.18),  transparent 66%),
    linear-gradient(180deg, var(--panel), #1b1410);
  filter: none;                     /* replaces the old hue-rotate entirely */
}
.cycle-card-bg--cambion.vome {
  background:
    radial-gradient(ellipse 90% 120% at 84% 106%, rgba(92,170,214,.26), transparent 62%),
    radial-gradient(ellipse 60% 80%  at 16% 96%,  rgba(120,196,224,.14), transparent 68%),
    linear-gradient(180deg, var(--panel), #0d151b);
  filter: none;
}

.cycle-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}
.cycle-art .art { opacity: 0; transition: opacity var(--glide); }
.cycle-card-bg--cambion.fass .art--fass { opacity: 1; }
.cycle-card-bg--cambion.vome .art--vome { opacity: 1; }
/* ⚠️ Only ONE state animates at a time. The hidden group is opacity:0 but still
   composited, and four breathing paths behind an invisible layer is work nobody
   sees — on a page that also runs four other cards. */
/* ⚠️ `animation-play-state` does NOT inherit, so pausing the GROUP does nothing
   — it has to be set on the animated elements themselves. The first draft
   paused `.art` (which has no animation) and read as if it worked. */
.cycle-art .art * { animation-play-state: paused; }
.cycle-card-bg--cambion.fass .art--fass *,
.cycle-card-bg--cambion.vome .art--vome * { animation-play-state: running; }

/* ── FASS: organic. Curves, swelling, warm. ──────────────────────────────── */
.af-mem {
  fill: none;
  stroke: rgba(240,150,80,.42);
  stroke-width: 1.2;
  animation: afMem 9s ease-in-out infinite;
}
.af-mem--2 { stroke: rgba(240,150,80,.22); animation-duration: 12s; animation-direction: reverse; }
@keyframes afMem { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }

.af-sacs path {
  fill: rgba(224,122,52,.14);
  stroke: rgba(245,168,104,.55);
  stroke-width: 1.1;
  transform-origin: center;
  transform-box: fill-box;          /* ⚠️ or the origin is the whole SVG, not the shape */
  animation: afBreathe 5.2s ease-in-out infinite;
  animation-delay: calc(var(--i) * .7s);
}
@keyframes afBreathe {
  0%,100% { transform: scale(.94); opacity: .78; }
  50%     { transform: scale(1.06); opacity: 1; }
}

.af-spores circle {
  fill: rgba(255,186,120,.75);
  stroke: none;
  animation: afRise 7.5s ease-in-out infinite;
  animation-delay: calc(var(--i) * 1.3s);
}
@keyframes afRise {
  0%   { transform: translateY(14px); opacity: 0; }
  25%  { opacity: .9; }
  100% { transform: translateY(-26px); opacity: 0; }
}

/* ── VOME: crystalline. Angles, stillness, cold. ─────────────────────────── */
.av-lattice path {
  fill: none;
  stroke: rgba(120,196,224,.16);
  stroke-width: 1;
}
.av-shards path {
  fill: rgba(92,170,214,.10);
  stroke: rgba(150,214,240,.6);
  stroke-width: 1.1;
  stroke-linejoin: miter;           /* hard corners — the whole point of Vome */
  transform-origin: center;
  transform-box: fill-box;
  animation: avSettle 11s ease-in-out infinite;
  animation-delay: calc(var(--i) * .9s);
}
/* ⚠️ Vome does NOT breathe. It shifts a hair and holds — the difference between
   the two states has to be in the MOTION, not only the palette. */
@keyframes avSettle {
  0%,44%,100% { transform: translateY(0);     opacity: .82; }
  50%         { transform: translateY(-1.5px); opacity: 1; }
}
.av-glints path {
  fill: none;
  stroke: rgba(190,232,250,.85);
  stroke-width: 1;
  opacity: 0;
  animation: avGlint 6.5s ease-in-out infinite;
  animation-delay: calc(var(--i) * 2.1s);
}
@keyframes avGlint {
  0%,86%,100% { opacity: 0; transform: scale(.7); }
  92%         { opacity: .9; transform: scale(1); }
}

/* Embers belong to Fass alone — a spore drifting through a frozen card was the
   old photo's problem in miniature. */
.cycle-card-bg--cambion.vome ~ .cycle-fx--embers { opacity: 0; }
.cycle-fx--embers { transition: opacity var(--glide); }

/* ═══════════════════════════════════════════════════════════════════════════
   THE PAGE GROUND — the orbiter photograph comes off (1,780 KB)
   The heaviest single asset on this page, sitting behind a scrim at low
   opacity: a 1.7 MB photograph nobody could actually see. Replaced with the
   thing the page is actually about — an orbital graticule, drawn.
   ═══════════════════════════════════════════════════════════════════════════ */
.page-bg--orbiter {
  background-image: none;
  background-color: var(--bg);
  overflow: hidden;
}
.page-bg--orbiter::before {
  content: "";
  position: absolute;
  /* Oversized and centred so the rotation never sweeps an edge into view. */
  top: 50%; left: 50%;
  width: 160vmax; height: 160vmax;
  margin: -80vmax 0 0 -80vmax;
  background:
    repeating-radial-gradient(circle at 50% 50%,
      transparent 0 78px, rgba(212,175,55,.055) 78px 79px),
    repeating-conic-gradient(from 0deg at 50% 50%,
      rgba(212,175,55,.045) 0deg 0.12deg, transparent 0.12deg 15deg);
  animation: orbiterTurn 240s linear infinite;
  opacity: .85;
}
@keyframes orbiterTurn { to { transform: rotate(360deg); } }
/* the warm core, so the ground is not a flat void */
.page-bg--orbiter::after {
  background:
    radial-gradient(ellipse 70% 55% at 50% 8%, rgba(212,175,55,.10), transparent 62%),
    linear-gradient(180deg, rgba(6,6,6,.30), rgba(6,6,6,.86));
}

@media (prefers-reduced-motion: reduce) {
  .af-mem, .af-sacs path, .af-spores circle,
  .av-shards path, .av-glints path,
  .page-bg--orbiter::before { animation: none; }
  .af-spores circle { opacity: .55; transform: none; }
  .av-glints path   { opacity: .5;  transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   DRAWN CYCLE ART — the remaining four (2026-09-02)
   Approved after the Cambion proof. Every photograph on this grid comes off.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⛔ The photographs, gone. cetus-day/night and vallis-day/night alone were
   10.6 MB on disk. The FILES stay — the operative-card backgrounds and the
   homepage perk preview still use two of them — but this grid stops loading
   them.
   ⚠️ Cetus and Vallis are addressed BY ID, because the renderer sets only
   `day`/`night` with no distinguishing modifier: `.cycle-card-bg.day` would
   match both cards at once. */
#cycle-cetus-bg,
#cycle-vallis-bg,
.cycle-card-bg--duviri,
.cycle-card-bg--zariman {
  background-image: none;
  opacity: 1;
  filter: none;
  transition: background var(--glide);
}

/* every drawn card hides the state that is not current */
#cycle-cetus-bg .art,
#cycle-vallis-bg .art,
.cycle-card-bg--zariman .art { opacity: 0; transition: opacity var(--glide); }
#cycle-cetus-bg.day   .art--day,
#cycle-cetus-bg.night .art--night,
#cycle-vallis-bg.day  .art--day,
#cycle-vallis-bg.night .art--night,
.cycle-card-bg--zariman.corpus  .art--corpus,
.cycle-card-bg--zariman.grineer .art--grineer { opacity: 1; }

/* ⚠️ Only the visible state animates. animation-play-state does not inherit, so
   it goes on the elements, never the group. */
#cycle-cetus-bg .art *,
#cycle-vallis-bg .art *,
.cycle-card-bg--zariman .art * { animation-play-state: paused; }
#cycle-cetus-bg.day   .art--day *,
#cycle-cetus-bg.night .art--night *,
#cycle-vallis-bg.day  .art--day *,
#cycle-vallis-bg.night .art--night *,
.cycle-card-bg--zariman.corpus  .art--corpus *,
.cycle-card-bg--zariman.grineer .art--grineer * { animation-play-state: running; }

/* ── PLAINS OF EIDOLON ───────────────────────────────────────────────────── */
#cycle-cetus-bg.day {
  background:
    radial-gradient(ellipse 60% 90% at 80% 74%, rgba(232,168,72,.30), transparent 62%),
    linear-gradient(180deg, #221a10 0%, var(--panel) 72%);
}
#cycle-cetus-bg.night {
  background:
    radial-gradient(ellipse 70% 90% at 36% 76%, rgba(86,132,196,.22), transparent 64%),
    linear-gradient(180deg, #0b1020 0%, var(--panel) 76%);
}
.ce-ridge { fill: rgba(10,10,10,.55); stroke: rgba(212,175,55,.30); stroke-width: 1.1; }
.ce-ridge--far { fill: none; stroke: rgba(212,175,55,.16); }
.ce-sun {
  fill: rgba(255,196,110,.22); stroke: rgba(255,206,132,.85); stroke-width: 1.3;
  animation: ceSun 12s ease-in-out infinite;
}
@keyframes ceSun { 0%,100% { opacity: .85; r: 17px; } 50% { opacity: 1; r: 18.5px; } }
.ce-shimmer path {
  fill: none; stroke: rgba(255,196,110,.35); stroke-width: 1;
  animation: ceShim 4.4s ease-in-out infinite; animation-delay: calc(var(--i) * .5s);
}
@keyframes ceShim { 0%,100% { transform: translateX(0);   opacity: .3; }
                    50%     { transform: translateX(6px); opacity: .7; } }
.ce-moon { fill: rgba(180,206,255,.16); stroke: rgba(196,220,255,.8); stroke-width: 1.2; }
.ce-stars circle {
  fill: rgba(214,232,255,.85); stroke: none;
  animation: ceStar 5.6s ease-in-out infinite; animation-delay: calc(var(--i) * .8s);
}
@keyframes ceStar { 0%,100% { opacity: .25; } 50% { opacity: 1; } }
/* the Eidolon: one slow swell, because that is the whole reason for this timer */
.ce-eidolon path   { fill: none; stroke: rgba(140,196,255,.6); stroke-width: 1.3; }
.ce-eidolon circle { fill: rgba(180,224,255,.5); stroke: rgba(200,232,255,.9); stroke-width: 1; }
.ce-eidolon { animation: ceEidolon 7s ease-in-out infinite; transform-origin: 140px 90px; transform-box: fill-box; }
@keyframes ceEidolon { 0%,100% { opacity: .55; transform: scaleY(.97); }
                       50%     { opacity: 1;   transform: scaleY(1.03); } }

/* ── ORB VALLIS ──────────────────────────────────────────────────────────── */
#cycle-vallis-bg.day {
  background:
    radial-gradient(ellipse 60% 90% at 50% 100%, rgba(226,140,70,.24), transparent 62%),
    linear-gradient(180deg, #1c1712 0%, var(--panel) 74%);
}
#cycle-vallis-bg.night {
  background:
    radial-gradient(ellipse 80% 90% at 50% 96%, rgba(120,196,224,.20), transparent 64%),
    linear-gradient(180deg, #0c161c 0%, var(--panel) 76%);
}
.va-spires { fill: rgba(10,10,10,.5); stroke: rgba(170,214,236,.42); stroke-width: 1.1; stroke-linejoin: miter; }
#cycle-vallis-bg.day .va-spires { stroke: rgba(232,176,120,.42); }
.va-vents path {
  fill: none; stroke: rgba(255,190,130,.5); stroke-width: 1.2;
  animation: vaVent 6s ease-in-out infinite; animation-delay: calc(var(--i) * .9s);
}
@keyframes vaVent { 0%   { transform: translateY(10px); opacity: 0; }
                    30%  { opacity: .75; }
                    100% { transform: translateY(-24px); opacity: 0; } }
/* frost GROWS rather than pulses — the opposite physics to the vents */
.va-frost path {
  fill: none; stroke: rgba(190,232,250,.6); stroke-width: 1.1;
  stroke-dasharray: 60; stroke-dashoffset: 60;
  animation: vaFrost 9s ease-in-out infinite; animation-delay: calc(var(--i) * .7s);
}
@keyframes vaFrost { 0%   { stroke-dashoffset: 60; opacity: 0; }
                     20%  { opacity: .9; }
                     55%,100% { stroke-dashoffset: 0; opacity: .55; } }

/* ── DUVIRI — one spiral, five moods ─────────────────────────────────────── */
/* ⚠️ The mood is carried by TWO custom properties and nothing else, so adding a
   sixth mood is two lines. The old version needed a new hue-rotate guess. */
.cycle-card-bg--duviri {
  --dv: 212,175,55;
  --dv-speed: 1;
  background:
    radial-gradient(ellipse 70% 100% at 50% 60%, rgba(var(--dv),.20), transparent 66%),
    linear-gradient(180deg, var(--panel-2), var(--panel));
}
.cycle-card-bg--duviri.joy    { --dv: 245,196,90;  --dv-speed: .8; }
.cycle-card-bg--duviri.anger  { --dv: 226,84,72;   --dv-speed: .35; }
.cycle-card-bg--duviri.envy   { --dv: 120,196,128; --dv-speed: 1.6; }
.cycle-card-bg--duviri.sorrow { --dv: 108,150,214; --dv-speed: 2.2; }
.cycle-card-bg--duviri.fear   { --dv: 168,124,214; --dv-speed: .5; }

.dv-spiral {
  fill: none; stroke: rgba(var(--dv),.75); stroke-width: 1.2;
  stroke-dasharray: 100; stroke-dashoffset: 100;
  animation: dvDraw calc(9s * var(--dv-speed)) ease-in-out infinite;
}
@keyframes dvDraw { 0% { stroke-dashoffset: 100; } 55%,100% { stroke-dashoffset: 0; } }
.dv-petals path {
  fill: rgba(var(--dv),.12); stroke: rgba(var(--dv),.7); stroke-width: 1;
  transform-origin: 200px 70px;
  animation: dvBloom calc(5s * var(--dv-speed)) ease-in-out infinite;
  animation-delay: calc(var(--i) * .3s * var(--dv-speed));
}
@keyframes dvBloom { 0%,100% { opacity: .45; transform: scale(.9)  rotate(0deg); }
                     50%     { opacity: 1;   transform: scale(1.06) rotate(4deg); } }
.dv-motes circle {
  fill: rgba(var(--dv),.8); stroke: none;
  animation: dvDrift calc(7s * var(--dv-speed)) ease-in-out infinite;
  animation-delay: calc(var(--i) * .6s);
}
@keyframes dvDrift { 0%,100% { opacity: .2;  transform: translateY(6px); }
                     50%     { opacity: .95; transform: translateY(-8px); } }
/* ⚠️ Anger is the exception: it does not glide, it snaps. A mood that differs
   only in speed is still one mood at five tempos. */
.cycle-card-bg--duviri.anger .dv-petals path,
.cycle-card-bg--duviri.anger .dv-motes circle { animation-timing-function: steps(3, end); }
.cycle-card-bg--duviri.fear  .dv-motes circle { animation-timing-function: steps(6, end); }

/* ── ZARIMAN ─────────────────────────────────────────────────────────────── */
.cycle-card-bg--zariman.corpus {
  background:
    radial-gradient(ellipse 70% 90% at 70% 20%, rgba(92,170,214,.20), transparent 64%),
    linear-gradient(180deg, #0c141c, var(--panel));
}
.cycle-card-bg--zariman.grineer {
  background:
    radial-gradient(ellipse 70% 90% at 30% 84%, rgba(206,104,58,.22), transparent 64%),
    linear-gradient(180deg, #1b1310, var(--panel));
}
/* Corpus: drawn with a ruler. */
.zc-frames rect {
  fill: none; stroke: rgba(140,206,240,.5); stroke-width: 1;
  animation: zcPulse 6s ease-in-out infinite; animation-delay: calc(var(--i) * .45s);
}
@keyframes zcPulse { 0%,100% { opacity: .35; } 50% { opacity: .9; } }
.zc-trace path {
  fill: none; stroke: rgba(170,224,255,.75); stroke-width: 1.1;
  stroke-dasharray: 100; stroke-dashoffset: 100;
  animation: zcTrace 5.5s ease-in-out infinite;
}
.zc-trace path:nth-child(2) { animation-delay: 1.2s; }
@keyframes zcTrace { 0% { stroke-dashoffset: 100; } 45%,100% { stroke-dashoffset: 0; } }
.zc-node { fill: rgba(190,232,255,.9); stroke: none; animation: zcPulse 3s ease-in-out infinite; }
/* Grineer: welded, and the seams show. */
.zg-plates path {
  fill: rgba(206,104,58,.08); stroke: rgba(232,140,86,.55); stroke-width: 1.3;
  stroke-linejoin: miter;
  animation: zgHeat 7s ease-in-out infinite; animation-delay: calc(var(--i) * .8s);
}
@keyframes zgHeat { 0%,100% { opacity: .6; } 50% { opacity: 1; } }
.zg-rivets circle {
  fill: rgba(248,182,120,.8); stroke: none;
  animation: zgHeat 5s ease-in-out infinite; animation-delay: calc(var(--i) * .35s);
}
.zg-hatch path { fill: none; stroke: rgba(232,140,86,.28); stroke-width: 1; }

/* the fx layers follow their state */
#cycle-cetus-bg.day ~ .cycle-fx--rain { opacity: .35; }
#cycle-vallis-bg.day ~ .cycle-fx--snow { opacity: 0; }
.cycle-fx--rain, .cycle-fx--snow, .cycle-fx--stardust { transition: opacity var(--glide); }

@media (prefers-reduced-motion: reduce) {
  .ce-sun, .ce-shimmer path, .ce-stars circle, .ce-eidolon,
  .va-vents path, .va-frost path,
  .dv-spiral, .dv-petals path, .dv-motes circle,
  .zc-frames rect, .zc-trace path, .zc-node,
  .zg-plates path, .zg-rivets circle { animation: none; }
  .va-frost path, .dv-spiral, .zc-trace path { stroke-dashoffset: 0; }
  .va-vents path, .dv-motes circle { opacity: .5; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WORLD STATE — the header area (2026-09-02)
   The cards earned the attention; this is the chrome above them getting out of
   the way. Nothing here should compete with a card.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the key ─────────────────────────────────────────────────────────────────
   ⚠️ It sits directly under a CENTRED section head, and was a left-aligned row
   of prose — which is half of why the area read as unformatted. It is centred
   now, and it wears the same symmetric-hairline device the kickers do, so it
   reads as a rule rather than as a sentence someone left there. */
.data-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s2) var(--s5);
  /* ⚠️ MORE AIR UNDER THE KEY (2026-09-07). It sat 32px above the cards while
     the title sat ~88px above IT, so the header, the key and the grid read as
     one compressed block. The key explains the marks ON the cards, so it stays
     nearer them than the title — but "nearer" is not "touching". */
  margin: 0 0 clamp(var(--s6), 4vw, var(--s8));
  padding: 0;
  border-bottom: 0;                 /* the hairlines replace the full rule */
  font-size: var(--t-micro);
  color: var(--muted);
}
.data-legend::before,
.data-legend::after {
  content: "";
  flex: 0 1 96px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--etch));
}
.data-legend::after { background: linear-gradient(90deg, var(--etch), transparent); }

.data-legend-key {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  cursor: help;                     /* the full explanation is one hover away */
  white-space: nowrap;
}
.data-legend .data-mark {
  margin: 0;
  text-transform: none;             /* "Computed", not COMPUTED — a key is quiet */
  letter-spacing: var(--track);
}
.data-legend-src {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track);
  color: var(--text-dim);
  padding-left: var(--s2);
  border-left: 1px solid var(--hairline);
}
.data-legend-key:hover .data-legend-src { color: var(--text); }

@media (max-width: 560px) {
  .data-legend { gap: var(--s2) var(--s4); }
  .data-legend::before,
  .data-legend::after { flex-basis: 28px; }
}

/* ── the feed notice ─────────────────────────────────────────────────────────
   ⚠️ It appeared and vanished instantly. On a page of slow ambient motion an
   element popping in mid-layout reads as a glitch, and popping OUT mid-sentence
   reads as a bug rather than as the good news it actually is. It arrives and
   leaves the same way now.

   ⛔ The collapse is animated with grid-template-rows, not max-height. A guessed
   max-height either clips long copy or leaves the timing wrong on short copy;
   `1fr` collapses to exactly the content's height whatever it is. */
.ws-feed-notice {
  display: grid;
  /* two columns: the ◇ glyph and the copy. Using the LIVE FEED mark here is
     deliberate — the notice is about exactly that half of the page. */
  grid-template-columns: auto 1fr;
  column-gap: var(--s3);
  grid-template-rows: 0fr;
  align-items: start;
  margin: 0;
  padding: 0 var(--s5);
  border-left: 2px solid transparent;
  background: rgba(224, 160, 58, 0);
  color: var(--text);
  font-size: var(--t-sm);
  line-height: 1.6;
  opacity: 0;
  transform: translateY(-4px);
  transition:
    grid-template-rows var(--glide),
    margin var(--glide),
    padding var(--glide),
    opacity var(--glide),
    transform var(--glide),
    background var(--glide),
    border-color var(--glide);
}
.ws-feed-notice > * { min-height: 0; overflow: hidden; }

.ws-feed-notice.is-in {
  grid-template-rows: 1fr;
  margin: 0 0 var(--s5);
  padding: var(--s4) var(--s5);
  border-left-color: #e0a03a;
  background: rgba(224, 160, 58, .07);
  opacity: 1;
  transform: translateY(0);
}

.ws-feed-notice-glyph {
  color: #e0a03a;
  font-family: var(--font-mono);
  font-size: var(--t-lead);
  line-height: 1.2;
}
.ws-feed-notice strong {
  color: var(--text);
  font-family: var(--font-head);
  letter-spacing: var(--track-tight);
}

@media (prefers-reduced-motion: reduce) {
  .ws-feed-notice { transition: opacity 1ms linear; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE BARO CARD — drawn, and it quotes /baro/ (2026-09-02)
   The last photograph on the World State page. Away is a JOURNEY, here is an
   ARRIVAL, and the scanline wash is lifted straight off the Baro page's own
   arrival banner so clicking through feels like the same room.
   ═══════════════════════════════════════════════════════════════════════════ */

.ws-baro-bg {
  background-image: none;
  opacity: 1;                        /* was 0 until a photo loaded */
  transform: none;
  transition: background var(--glide);
}
/* ⛔ The Ken Burns hover is gone with the photograph it panned. Panning a
   drawing that is already animating is two motions arguing. */
.ws-card--baro:hover .ws-baro-bg { animation: none; transform: none; }

.ws-baro-bg.away {
  background:
    radial-gradient(ellipse 60% 90% at 12% 74%, rgba(212,175,55,.13), transparent 64%),
    linear-gradient(115deg, #0d0f16 0%, var(--panel) 70%);
}
.ws-baro-bg.here {
  background:
    radial-gradient(ellipse 70% 100% at 22% 60%, rgba(232,148,10,.24), transparent 62%),
    linear-gradient(115deg, #1a1409 0%, var(--panel) 72%);
}
/* the quote: /baro/'s arrival banner wears exactly this */
.ws-baro-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(90deg, transparent 0 3px, rgba(212,175,55,.026) 3px 4px);
}

/* ⛔ THE ART MUST NOT SCALE WITH THE CARD, AND THIS IS WHY.
   `.worldstate-grid` stretches its rows, and the Varzia card beside this one
   renders EIGHTEEN items — so Baro gets dragged from its 210px floor to ~600px.
   The viewBox is 400x140 (2.86:1); `slice` is cover, so in a 750x600 box it
   scales by max(1.875, 4.28) = 4.28x. The 34px gem became 146px and 88% of the
   drawing was cropped away. On first paint, before Varzia has its rows, the row
   is short and the art looks right — which is exactly the "it changes after it
   loads" Stephan saw.

   The art is a BAND pinned to the bottom at its own aspect now. However tall
   the card is dragged, the drawing keeps its proportions and the gradient fills
   the rest. */
.ws-baro-bg .cycle-art {
  position: absolute;
  left: 0;
  right: 0;
  /* ⚠️ CENTRED, not floor-pinned. The grid can drag this card to 600px, and a
     band welded to the bottom then reads as art that has slid off the design
     rather than art placed in it. */
  top: 50%;
  bottom: auto;
  transform: translateY(-50%);
  width: 100%;
  height: auto;
  aspect-ratio: 400 / 140;
  max-height: 100%;
}
.ws-baro-bg .art { opacity: 0; transition: opacity var(--glide); }
.ws-baro-bg.away .art--away,
.ws-baro-bg.here .art--here { opacity: 1; }
.ws-baro-bg .art * { animation-play-state: paused; }
.ws-baro-bg.away .art--away *,
.ws-baro-bg.here .art--here * { animation-play-state: running; }

/* ── AWAY: still out on the route ────────────────────────────────────────── */
.bo-route {
  fill: none; stroke: rgba(212,175,55,.34); stroke-width: 1.1;
  stroke-dasharray: 3 5;
}
.bo-relay circle { fill: none; stroke: rgba(212,175,55,.5); stroke-width: 1.1; }
.bo-relay--faint { stroke: rgba(212,175,55,.2) !important; }
.bo-relay path   { fill: none; stroke: rgba(212,175,55,.35); stroke-width: 1; }
.bo-ship rect {
  fill: rgba(244,217,122,.85); stroke: none;
}
/* he travels the same path the route is drawn on — one definition, so the ship
   can never drift off its own line */
.bo-ship {
  offset-path: path("M14 112 C 96 104, 158 78, 232 62 C 260 56, 282 52, 302 50");
  animation: boTravel 14s ease-in-out infinite;
}
@keyframes boTravel {
  0%        { offset-distance: 0%;   opacity: 0; }
  6%        { opacity: 1; }
  86%       { offset-distance: 100%; opacity: 1; }
  94%, 100% { offset-distance: 100%; opacity: 0; }
}
.bo-ticks path {
  fill: none; stroke: rgba(212,175,55,.45); stroke-width: 1;
  animation: boTick 4.6s ease-in-out infinite; animation-delay: calc(var(--i) * .9s);
}
@keyframes boTick { 0%,100% { opacity: .2; } 50% { opacity: .8; } }

/* ── HERE: docked, and the relay knows it ────────────────────────────────── */
/* the echo is the EchoBorn mark's own device — the same rings the orb runs */
.bo-echo circle {
  fill: none; stroke: rgba(244,217,122,.6); stroke-width: 1.1;
  transform-origin: 86px 76px;
  opacity: 0;
  animation: boEcho 4.8s cubic-bezier(.22,.61,.36,1) infinite;
  animation-delay: calc(var(--i) * 1.6s);
}
@keyframes boEcho {
  0%   { transform: scale(.5); opacity: 0; }
  16%  { opacity: .7; }
  100% { transform: scale(2.1); opacity: 0; }
}
.bo-gem     { fill: rgba(232,148,10,.14); stroke: rgba(248,196,96,.9); stroke-width: 1.4; }
.bo-gem--in { fill: none; stroke: rgba(255,222,150,.7); stroke-width: 1; }
.bo-dock path { fill: none; stroke: rgba(212,175,55,.4); stroke-width: 1; }
.bo-dock--rows { stroke: rgba(212,175,55,.16) !important; }
.bo-wares rect {
  fill: rgba(248,196,96,.5); stroke: rgba(255,222,150,.8); stroke-width: 1;
  animation: boWare 3.8s ease-in-out infinite; animation-delay: calc(var(--i) * .5s);
}
@keyframes boWare { 0%,100% { opacity: .35; } 50% { opacity: 1; } }

/* ── the way through to /baro/ ───────────────────────────────────────────────
   ⚠️ It was already a link, and that was the problem: a 11px text line at .75
   opacity sitting under a photograph is not a call to action, it is a footnote.
   The whole card is the target now, and the control looks like one. */
.ws-card--baro { position: relative; }
.ws-baro-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  align-self: flex-start;
  margin-top: var(--s3);
  padding: var(--s2) var(--s4);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-bright);
  text-decoration: none;
  border: 1px solid var(--etch-mid);
  background: rgba(212,175,55,.06);
  transition: background var(--swift), border-color var(--swift), color var(--swift);
}
.ws-baro-cta-arrow { transition: transform var(--swift); }
.ws-card--baro:hover .ws-baro-cta,
.ws-baro-cta:hover {
  background: rgba(212,175,55,.14);
  border-color: var(--etch-lit);
  color: #fff3d4;
}
.ws-card--baro:hover .ws-baro-cta-arrow,
.ws-baro-cta:hover .ws-baro-cta-arrow { transform: translateX(3px); }
.ws-baro-cta:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }

/* ⚠️ The whole card is clickable via a stretched link, but the watch button and
   the CTA itself must stay reachable — a stretched link swallows every sibling
   that is not lifted above it. */
.ws-baro-cta::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
.ws-card--baro .ws-watch-btn,
.ws-card--baro .ws-baro-cta > * { position: relative; z-index: 2; }

@media (prefers-reduced-motion: reduce) {
  .bo-ship, .bo-ticks path, .bo-echo circle, .bo-wares rect { animation: none; }
  .bo-ship   { offset-distance: 100%; }
  .bo-echo circle { opacity: .2; }
  .bo-wares rect  { opacity: .7; }
}

/* ⚠️ `justify-content: space-between` in a card the grid has stretched to 600px
   leaves the title at the very top and the countdown at the very bottom with a
   canyon between them. The blocks group at the bottom now, over the art band,
   which is where the composition actually is. */
.ws-baro-inner { justify-content: flex-end; }
.ws-baro-top { margin-bottom: auto; }

/* ── THE CARD RUNS FULL HEIGHT, AND THE CONTENT FILLS IT ─────────────────────
   Stephan's call: level with Varzia beside it, not shorter. I had capped it
   with `align-self: start` because half the card was empty — but the emptiness
   was never the height, it was that nothing was distributed into it.

   ⚠️ Three declarations, not one. Letting the grid stretch the CARD does
   nothing on its own: `.ws-baro-inner` is an ordinary block child, so it would
   sit at its own ~300px at the top and leave the bottom 300px blank — the same
   hole, moved. The card becomes a flex column, the inner takes `flex: 1`, and
   `space-between` puts the title at the top and the countdown at the floor with
   the centred art band breathing in the gap between them. */
.ws-card--baro {
  display: flex;
  flex-direction: column;
}
.ws-baro-inner {
  position: relative;
  inset: auto;
  flex: 1;
  min-height: 210px;
  justify-content: space-between;
}
/* ⚠️ `margin-bottom: auto` on the top block would fight space-between — it
   pushes everything else to the floor and collapses the middle gap the art
   needs. It was there for the capped card and is wrong for this one. */
.ws-baro-top { margin-bottom: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   /stream/ — THE STANDBY CARD (2026-09-02)

   ⛔ THE PROBLEM WAS NEVER STYLING, IT WAS THE IFRAME. Twitch's player renders
   its own pale offline panel of recommendations when the channel is dark. It
   is cross-origin, so not one property below could ever have reached it. The
   fix lives in php/stream.php: while he is off air the embed is not loaded at
   all, and this is what stands in its place.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the page ground: the brick photograph comes off (488 KB) ───────────── */
/* A broadcast carrier, drawn — scanlines and a transmitter glow. Same move as
   the orbiter graticule: replace the photograph with the thing the page is
   actually about. */
.page-bg--stream {
  background-image: none;
  background-color: var(--bg);
  overflow: hidden;
}
.page-bg--stream::before {
  content: "";
  position: absolute;
  inset: -10%;
  background:
    repeating-linear-gradient(180deg,
      rgba(212,175,55,.030) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(90deg,
      rgba(212,175,55,.018) 0 1px, transparent 1px 120px);
  animation: streamDrift 26s linear infinite;
}
/* ⚠️ Travelling less than one band period would strobe against the 4px rhythm;
   drifting by exactly one reads as a slow sync roll and loops seamlessly. */
@keyframes streamDrift { to { transform: translateY(4px); } }

.page-bg--stream::after {
  background:
    radial-gradient(ellipse 60% 45% at 50% 0%, rgba(212,175,55,.11), transparent 60%),
    linear-gradient(180deg, rgba(6,6,6,.55) 0%, rgba(6,6,6,.90) 55%, var(--bg) 100%);
}

/* ── the cover ──────────────────────────────────────────────────────────── */
.stream-cover {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  isolation: isolate;
}

/* ⚠️ The subject sits left of centre in this frame, so the copy goes RIGHT and
   the scrim is directional. A bottom-only scrim would have laid the text
   across his face. */
.stream-cover-art {
  position: absolute;
  inset: 0;
  background: url("/Images/stream-offline.webp") center/cover no-repeat;
  /* A held-frame feel: cooled and pushed back, so the copy is what is in focus. */
  filter: saturate(.85) brightness(.72);
  transform: scale(1.03);
}
.stream-cover::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(90deg, transparent 0%, rgba(6,6,6,.30) 38%, rgba(6,6,6,.88) 72%, rgba(6,6,6,.95) 100%),
    linear-gradient(180deg, rgba(6,6,6,.55) 0%, transparent 30%, rgba(6,6,6,.75) 100%);
}

/* the sync sweep — one slow pass, the signature of a channel holding a frame */
.stream-cover-sweep {
  position: absolute;
  left: 0; right: 0;
  top: -30%;
  height: 30%;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(180deg,
    transparent 0%, rgba(212,175,55,.045) 45%, rgba(244,217,122,.10) 50%,
    rgba(212,175,55,.045) 55%, transparent 100%);
  animation: streamSweep 7.5s cubic-bezier(.4,0,.6,1) infinite;
}
@keyframes streamSweep {
  0%       { transform: translateY(0);    opacity: 0; }
  8%       { opacity: 1; }
  70%,100% { transform: translateY(440%); opacity: 0; }
}

/* ── DEAD AIR: the level meter, and why it is nearly flat ────────────────── */
/* ⛔ THE BARS MUST NOT DANCE. A lively equaliser on an offline card would say
   "there is audio" while there is none — a small animated lie on the one panel
   whose entire job is to say that nothing is transmitting. They idle at the
   floor with a breath of noise, which is what a meter actually shows on dead
   air. */
.stream-cover-bars {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 26px;
  padding: 0 var(--s4);
  pointer-events: none;
  opacity: .5;
}
.stream-cover-bars span {
  flex: 1;
  height: 2px;
  background: linear-gradient(180deg, var(--gold), var(--gold-dim));
  transform-origin: bottom;
  animation: streamIdle 4.2s ease-in-out infinite;
}
@keyframes streamIdle {
  0%, 100% { transform: scaleY(1);   opacity: .55; }
  50%      { transform: scaleY(2.6); opacity: .9;  }
}
/* Uneven offsets, so ten bars never line up into one pulsing rule. */
.stream-cover-bars span:nth-child(1)  { animation-delay: -0.0s; }
.stream-cover-bars span:nth-child(2)  { animation-delay: -1.7s; }
.stream-cover-bars span:nth-child(3)  { animation-delay: -0.9s; }
.stream-cover-bars span:nth-child(4)  { animation-delay: -2.6s; }
.stream-cover-bars span:nth-child(5)  { animation-delay: -1.3s; }
.stream-cover-bars span:nth-child(6)  { animation-delay: -3.4s; }
.stream-cover-bars span:nth-child(7)  { animation-delay: -0.5s; }
.stream-cover-bars span:nth-child(8)  { animation-delay: -2.1s; }
.stream-cover-bars span:nth-child(9)  { animation-delay: -3.8s; }
.stream-cover-bars span:nth-child(10) { animation-delay: -1.1s; }

/* ── the copy ───────────────────────────────────────────────────────────── */
.stream-cover-body {
  position: relative;
  z-index: 3;
  margin-left: auto;
  width: min(46%, 30rem);
  padding: var(--s6) var(--s6) calc(var(--s6) + 12px);
  text-align: left;
}

.stream-lamp {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0 0 var(--s3);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track-max);
  color: var(--text-dim);
}
.stream-lamp-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--muted);
  box-shadow: 0 0 0 3px rgba(99,99,99,.14);
  animation: streamLamp 3.6s ease-in-out infinite;
}
/* ⚠️ A DIM lamp, never a red one. Red reads as a fault, and this is not a
   fault — it is a scheduled quiet. The gold arrives only when he is on. */
@keyframes streamLamp {
  0%, 100% { opacity: .35; }
  50%      { opacity: .85; }
}

.stream-cover-title {
  margin: 0 0 var(--s2);
  font-family: var(--font-head);
  font-size: clamp(1.15rem, 2.2vw, 1.75rem);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  color: var(--text);
  text-wrap: balance;
}
.stream-cover-next {
  margin: 0 0 var(--s5);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track);
  color: var(--gold);
}
.stream-cover-cta { display: flex; gap: var(--s3); }

/* ── the week strip ─────────────────────────────────────────────────────── */
.stream-week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--s2);
  list-style: none;
  margin: var(--s4) 0;
  padding: 0;
}
.stream-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3) 0;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  background: rgba(255,255,255,.012);
  transition: border-color var(--swift), background var(--swift);
}
.stream-day-name {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  color: var(--muted);
}
.stream-day-mark {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--muted);
  opacity: .35;
}
/* a transmission night */
.stream-day.is-on {
  border-color: var(--etch-mid);
  background: rgba(212,175,55,.05);
}
.stream-day.is-on .stream-day-name { color: var(--text); }
.stream-day.is-on .stream-day-mark {
  background: var(--gold);
  opacity: 1;
  box-shadow: 0 0 8px rgba(212,175,55,.55);
}
/* today — an outline, never a second fill, or the strip carries two accents
   competing and neither one reads */
.stream-day.is-today { border-color: var(--gold); }
.stream-day.is-today .stream-day-name { color: var(--gold-bright); }

.stream-week-note {
  margin: 0;
  font-size: var(--t-sm);
  color: var(--text-dim);
}
.stream-week-key {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-right: var(--s3);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  color: var(--muted);
}
.stream-week-key .k-on {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 8px rgba(212,175,55,.55);
}

@media (max-width: 700px) {
  /* The copy cannot sit beside the subject at this width, so it goes under a
     bottom scrim — and the directional one is dropped, or the frame is
     darkened twice and the photograph disappears entirely. */
  .stream-cover::before {
    background: linear-gradient(180deg, rgba(6,6,6,.35) 0%, rgba(6,6,6,.55) 40%, rgba(6,6,6,.94) 100%);
  }
  .stream-cover-body {
    width: 100%;
    margin-left: 0;
    padding: var(--s4) var(--s4) calc(var(--s4) + 10px);
  }
  .stream-cover-bars { height: 18px; padding: 0 var(--s3); }
  .stream-week { gap: var(--s1); }
  .stream-day  { padding: var(--s2) 0; }
}

@media (prefers-reduced-motion: reduce) {
  .page-bg--stream::before,
  .stream-cover-sweep,
  .stream-cover-bars span,
  .stream-lamp-dot { animation: none; }
  .stream-cover-sweep { opacity: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   OPERATIVE BANNER CARDS (2026-09-03)

   ⛔ THE PROBLEM THIS REPLACES. A full-bleed image behind text forces a scrim
   heavy enough to kill the art AND still muddy the name — both jobs lose. A
   banner separates them: the art gets a place to be art, the data gets a clean
   plate, and every card shares one silhouette so the roster reads as a system.

   ⚜️ EVERY BANNER IS DRAWN. No file is fetched, so none can 404 and none costs
   bandwidth. Seasonal and event banners are an afternoon of CSS each.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the banner frame ───────────────────────────────────────────────────── */
.op-banner {
  position: relative;
  aspect-ratio: 3.1 / 1;
  overflow: hidden;
  background: var(--bg-alt);
  border-bottom: 1px solid var(--panel-border);
}
.op-banner > .bn { position: absolute; inset: 0; }
/* the floor shadow, so the identity row reads over any banner */
.op-banner::after {
  content: "";
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(180deg, transparent 45%, rgba(6,6,6, .78) 100%);
}
.bn-art { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--gold); }

/* .op-banner-tag was REMOVED 2026-09-06 with the markup that used it.
   A rule that matches nothing is not harmless: the next reader assumes the
   element still exists and styles around a ghost. */

/* ⛔ LIVE is only ever rendered from a real Twitch answer. */
.op-live {
  position: absolute; left: var(--s3); top: var(--s2); z-index: 3;
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 7px;
  background: rgba(6,6,6, .72); border: 1px solid var(--gold);
  font-family: var(--font-mono); font-size: 9px;
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--gold-bright);
}
.op-live i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--gold-bright);
  animation: opLamp 2.4s ease-in-out infinite;
}
@keyframes opLamp { 0%, 100% { opacity: .55 } 50% { opacity: 1 } }

/* ═══ THE DRAWN BANNERS ═══
   Each takes the member's accent through --gold, which the six colour themes
   already override — so a Corpus-blue account gets a Corpus-blue banner. */

/* VOID — the starfield */
.bn-void {
  background:
    radial-gradient(1.4px 1.4px at 12% 30%, #fff, transparent),
    radial-gradient(1.2px 1.2px at 27% 68%, #cfd6e4, transparent),
    radial-gradient(1.8px 1.8px at 41% 20%, #fff, transparent),
    radial-gradient(1.1px 1.1px at 58% 55%, #b9c2d4, transparent),
    radial-gradient(1.6px 1.6px at 73% 34%, #fff, transparent),
    radial-gradient(1.2px 1.2px at 86% 71%, #cfd6e4, transparent),
    radial-gradient(1.3px 1.3px at 94% 22%, #fff, transparent),
    radial-gradient(ellipse 60% 90% at 30% 110%, rgba(212, 175, 55, .13), transparent 70%),
    linear-gradient(180deg, #05050b, #0b0b14);
}
.bn-void::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(1px 1px at 20% 50%, rgba(255, 255, 255, .7), transparent),
    radial-gradient(1px 1px at 50% 26%, rgba(255, 255, 255, .6), transparent),
    radial-gradient(1px 1px at 80% 60%, rgba(255, 255, 255, .7), transparent);
  animation: bnTwinkle 5.5s ease-in-out infinite;
}
@keyframes bnTwinkle { 0%, 100% { opacity: .25 } 50% { opacity: .9 } }

/* GRATICULE — the orbital grid, same language as the /world-state/ ground */
.bn-grat { background: linear-gradient(180deg, #080810, #0d0d16); }
.bn-grat::after {
  content: ""; position: absolute; inset: -40%;
  background:
    repeating-radial-gradient(circle at 50% 130%, transparent 0 26px, rgba(212, 175, 55, .10) 26px 27px),
    repeating-conic-gradient(from 200deg at 50% 130%, rgba(212, 175, 55, .09) 0 .1deg, transparent .1deg 12deg);
  animation: bnGrat 90s linear infinite;
}
@keyframes bnGrat { to { transform: rotate(360deg) } }

/* PLAINS — the ridgeline is drawn in banner_art(), this is its sky */
.bn-plains { background: linear-gradient(180deg, #0a0d16 0%, #121826 55%, #0c1018 100%); }

/* FISSURE — a void tear */
.bn-fiss { background: radial-gradient(ellipse 40% 120% at 50% 50%, #1a1030, #08060f 70%); }
.bn-fiss::after {
  content: ""; position: absolute; inset: 0;
  background: repeating-radial-gradient(ellipse 22% 70% at 50% 50%,
    transparent 0 9px, rgba(170, 136, 245, .22) 9px 10px);
  animation: bnFiss 4.5s ease-in-out infinite;
}
@keyframes bnFiss { 0%, 100% { transform: scale(1); opacity: .65 } 50% { transform: scale(1.09); opacity: 1 } }

/* RAILJACK — velocity */
.bn-rail { background: linear-gradient(100deg, #060a12, #0d1522 60%, #070b12); }
.bn-rail::after {
  content: ""; position: absolute; inset: -20%;
  background: repeating-linear-gradient(102deg,
    transparent 0 22px, rgba(212, 175, 55, .16) 22px 23px, transparent 23px 46px);
  animation: bnRail 3.2s linear infinite;
}
/* one full band period, so the loop is seamless rather than a visible jump */
@keyframes bnRail { to { transform: translateX(46px) } }

/* CAMBION — infested bloom */
.bn-camb { background: radial-gradient(ellipse 80% 120% at 30% 100%, #241318, #0a0709 72%); }
.bn-camb::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(5px 5px at 18% 70%, rgba(228, 138, 96, .5), transparent),
    radial-gradient(3px 3px at 37% 40%, rgba(228, 138, 96, .42), transparent),
    radial-gradient(6px 6px at 62% 76%, rgba(200, 102, 80, .4), transparent),
    radial-gradient(3px 3px at 80% 44%, rgba(228, 138, 96, .45), transparent),
    radial-gradient(4px 4px at 91% 68%, rgba(200, 102, 80, .35), transparent);
  animation: bnSpore 9s ease-in-out infinite;
}
@keyframes bnSpore { 0%, 100% { transform: translateY(3px) } 50% { transform: translateY(-4px) } }

/* ORO — Founder exclusive */
.bn-oro { background: linear-gradient(180deg, #0d0a04, #1a1207 60%, #0b0904); }
.bn-oro::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(2px 2px at 14% 80%, var(--gold-bright), transparent),
    radial-gradient(1.6px 1.6px at 33% 55%, var(--gold), transparent),
    radial-gradient(2.4px 2.4px at 52% 88%, var(--gold-bright), transparent),
    radial-gradient(1.8px 1.8px at 71% 48%, var(--gold), transparent),
    radial-gradient(2px 2px at 88% 74%, var(--gold-bright), transparent);
  animation: bnOro 7s linear infinite;
}
@keyframes bnOro {
  0%   { transform: translateY(10px); opacity: 0 }
  15%  { opacity: 1 }
  85%  { opacity: 1 }
  100% { transform: translateY(-14px); opacity: 0 }
}

/* PLATE — the quiet default, for a member who picks nothing */
.bn-plate {
  background:
    repeating-linear-gradient(135deg, rgba(212, 175, 55, .05) 0 1px, transparent 1px 11px),
    linear-gradient(180deg, #141420, #0d0d15);
}

/* ── the card ───────────────────────────────────────────────────────────── */
.op-card {
  position: relative;
  background: var(--panel);
  clip-path: var(--plate-notch);
  display: flex; flex-direction: column;
  isolation: isolate;
  z-index: 0;
}
/* ⛔ THE ETCHED EDGE IS A LAYER, NOT A BORDER. clip-path removes a border along
   the notch diagonal and leaves it bare, so the gold sits underneath and the
   plate is inset 1px over it. */
.op-card::before {
  content: ""; position: absolute; inset: -1px; z-index: -1;
  background: linear-gradient(160deg, var(--tier-accent, var(--gold)),
                              rgba(212, 175, 55, .12) 55%, transparent);
  clip-path: var(--plate-notch);
  opacity: .55;
  transition: opacity var(--glide);
}
.op-card:hover::before { opacity: .85; }
.op-card.is-live::before { opacity: 1; }

.op-id {
  display: flex; align-items: flex-end; gap: var(--s3);
  padding: 0 var(--s4); margin-top: -26px;
  position: relative; z-index: 3;
}
.op-sigil {
  width: 56px; height: 56px; flex: 0 0 56px;
  /* ⛔ NO RING, NO GROUND. Ten cards each framed an avatar in the tier accent,
     which is a second copy of what the tier BADGE already says — and stacked ten
     down a grid it read as weight rather than as structure. The notch stays: it
     shapes a real photograph, which is the one thing it was ever needed for.
     ⚠️ The background had to go WITH the border. --bg is darker than the card,
     so a borderless slot is not a clean mark, it is a dark square. */
  background: none;
  display: grid; place-items: center; overflow: hidden;
  clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 8px 100%, 0 calc(100% - 8px));
}
.op-sigil img { width: 100%; height: 100%; object-fit: cover; display: block; }

.op-who { padding-bottom: var(--s1); min-width: 0; }
.op-name {
  font-family: var(--font-head); font-size: var(--t-base); font-weight: 600;
  line-height: 1.25; margin: 0; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.op-handle {
  font-family: var(--font-mono); font-size: var(--t-micro);
  color: var(--muted); margin: 0;
}

/* ── the mono data strip ────────────────────────────────────────────────── */
.op-strip {
  display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3);
  padding: var(--s3) var(--s4) 0;
  font-family: var(--font-mono); font-size: var(--t-micro);
  color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.op-strip b { color: var(--text); font-weight: 500; }
.op-strip .sep { color: var(--muted); }
.op-strip .op-clan-tier { color: var(--muted); }

/* ── the showcase: FIXED SLOTS ──────────────────────────────────────────── */
/* ⚜️ THE LOADOUT STRIP — a ruled row, not four little boxes (2026-09-06).
   ⛔ WHAT WAS WRONG WAS THE ALIGNMENT, and the icons were only the loudest part
   of it. Each slot was a bordered box that CENTRED its own contents, so with
   four different word lengths no two labels and no two values shared an x
   position — inside a card, or across nine cards in the grid. Four boxes also
   drew four borders per card, thirty-six on a screen, around content that is
   simply four label/value pairs.
   ⚜️ It is one ruled row now: left-aligned, hairline-separated, and every
   label and value lands on the same baseline in every card. The hairline is the
   device the whole design system is named for, so the structure costs one pixel
   instead of a rectangle. */
.op-showcase {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 0;
  margin-top: auto; margin-inline: var(--s4);
  padding: var(--s3) 0 var(--s4);
  border-top: 1px solid var(--panel-border);
}
.op-slot {
  display: flex; flex-direction: column; gap: 3px;
  min-width: 0; padding-inline: var(--s3);
  border-left: 1px solid var(--panel-border);
}
/* ⚠️ the first column has nothing to its left to separate it FROM */
.op-slot:first-child { border-left: 0; padding-left: 0; }
.op-slot .op-slot-lbl {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  color: var(--muted); text-transform: uppercase;
}
.op-slot .op-slot-val {
  /* ⚠️ QUIETER, 2026-09-06. With the boxes gone, 14px in full --text made four
     weapon names the loudest text on the card — louder than the member's own
     name, which is the thing a roster card is about. The strip is a REFERENCE
     you read when you want it, not a headline. Smaller, dimmer, and the label
     above it keeps the tracking so the pair still reads as a unit. */
  font-size: var(--t-micro); color: var(--text-dim); line-height: 1.3;
  letter-spacing: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* ⚠️ An unfilled slot is an invitation, not a fault — quiet, never dashed-and-
   shouting. The label stays legible so you can still read WHICH slot is open. */
.op-slot.is-empty .op-slot-val { color: var(--muted); }
/* ⛔ LOCKED IS A STATE, NOT A VALUE, so it is set in the MONO face — the same
   signal the labels use. In the body face "Operator" reads as a weapon name. */
.op-slot.is-locked .op-slot-val {
  font-family: var(--font-mono); font-size: 9px; text-transform: uppercase;
  letter-spacing: var(--track-wide);
  color: var(--tier-accent, var(--gold)); opacity: .5;
}

@media (max-width: 520px) {
  .op-showcase { grid-template-columns: repeat(2, 1fr); row-gap: var(--s3); }
  /* ⚠️ at two columns the THIRD cell starts a new row, so it is the one with
     nothing to its left — the rule has to follow the layout, not the markup */
  .op-slot:nth-child(3) { border-left: 0; padding-left: 0; }
  .op-slot:first-child  { border-left: 0; padding-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bn-void::after, .bn-grat::after, .bn-fiss::after,
  .bn-rail::after, .bn-camb::after, .bn-oro::after,
  .op-live i { animation: none; }
}

/* ── the banner picker (Command Hub) ─────────────────────────────────────── */
.banner-picker {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--s3); margin-top: var(--s4);
}
.banner-cell {
  background: var(--panel); border: 1px solid var(--panel-border);
  padding: 0; font: inherit; color: inherit; text-align: left;
  cursor: pointer; display: block; width: 100%;
  transition: border-color var(--swift), transform var(--swift);
}
.banner-cell:hover:not(:disabled) { border-color: var(--gold); transform: translateY(-2px); }
.banner-cell:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }
.banner-cell[aria-pressed="true"] { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
.banner-cell-prev { display: block; position: relative; }
.banner-cell-prev .op-banner { border-bottom: 0; }
.banner-cell-cap {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s2); padding: var(--s2) var(--s3);
}
.banner-cell-nm {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track); text-transform: uppercase; color: var(--text);
}
.banner-cell-tr {
  font-family: var(--font-mono); font-size: 9px;
  letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--gold);
}
/* ⚠️ A locked tile is SHOWN, dimmed — an absent control tells a free member
   nothing, while a locked one names what upgrading buys. */
.banner-cell:disabled { cursor: not-allowed; opacity: .45; }
.banner-cell:disabled .banner-cell-nm { color: var(--muted); }

/* a locked field in the operative grid, same reasoning */
.op-field.is-locked select,
.op-field.is-locked input { opacity: .45; cursor: not-allowed; }
.op-field.is-locked .op-field-label em {
  font-style: normal; font-family: var(--font-mono); font-size: 9px;
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--gold); margin-left: var(--s2);
  border: 1px solid rgba(212, 175, 55, .35); padding: 1px 5px;
}

/* ── the Tenno page: /network/profile.php?u=<name> ───────────────────────── */
/* ⚠️ PAGE-SCOPED, NOT `.page-content--top`. The shared wrapper's 6.5rem sets the
   top gap on every page on the site; the card was tight against the nav only
   HERE, because this page grew a 46vh hero behind it and the card was eating
   most of it. Nudging the shared rule would move eleven other pages to fix one.
   ⚠️ Declared HERE and not at the second `.tp-page` block further down — two
   rules for one selector already exist in this sheet, and splitting a third
   property across them is how a value ends up being edited in the wrong copy. */
.tp-page { max-width: 900px; padding-top: clamp(2rem, 5vh, 4rem); }
/* \u26d4 ONE ROW, NO FLOAT. The Follow button was `float: right` immediately
   above the card, and `.op-banner`'s `overflow: hidden` makes it a block
   formatting context root \u2014 which refuses to overlap a float and shrinks away
   from it. The banner lost ~80px from its right edge while every other block
   in the card stayed full width. Flex has no such interaction. */
.tp-topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); margin: 0 0 var(--s4);
}
.tp-back { margin: 0; }
.tp-back a {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--muted); text-decoration: none;
}
.tp-back a:hover { color: var(--gold); }
.tp-acts { display: flex; align-items: center; gap: var(--s2); }

/* ⛔ THE ACCENT IS AN EDGE, NOT A WASH — AND IT HAD BEEN A WASH SINCE THIS CARD
   SHIPPED. `.tp-card` sets `position:relative` AND `z-index:0`, which makes it a
   STACKING CONTEXT; inside one, a negative-z child paints AFTER the element's
   own background, not behind it. So `::before` — written to be a 1px plate edge
   peeking out at inset:-1px — was clipped back to the card's own shape by the
   card's own clip-path and then painted the full-strength tier accent straight
   over `--panel`, fading out around the signals row. Stephan: "it makes
   everything look very unstable."

   ⚠️ `z-index:-1` was never the bug and removing it is not the fix: the card
   MUST make a stacking context, or the edge escapes and paints over the page.
   The fix is the device the design system already ships — `.plate` + `.plate-in`,
   two clipped layers, the gold on the outer one and the panel 1px inside it.
   Here that is ::before and ::after, because the card is one element in the
   markup and a wrapper would be a DOM change for a paint problem.
   ⚠️ BOTH LAYERS NEED THE CLIP, or the inner square corner pokes through the
   diagonal — the note already written above `.plate` and worth repeating. */
.tp-card {
  position: relative; background: var(--panel);
  clip-path: var(--plate-notch); z-index: 0;
}
/* ⛔ NO OUTLINE ON THE CARD AT ALL. Two attempts at a plate edge here both
   failed for the same structural reason: `.tp-banner` is opaque and full-bleed
   at the top of the card, so it covers the top 1px and the stroke NEVER CLOSES
   — Stephan: "it breaks with the header, that stroke looks weird." A rectangle
   missing one side is not an edge, it is a defect.
   ⛔ And the wider point he made is the right one: the page had too many
   strokes. The card is a --panel surface on a --bg page, cut with the Orokin
   notch. Shape and surface define it; it needs no line drawn round it. */
/* the page banner is wider than the card's — same drawing, more room.
   ⚠️ 4.2, not 5.4: at 850px of card that is 202px instead of 157px. The banner
   is the member's one piece of chosen art and the thing the Founder's Pass
   actually sells, and it was reading as a letterbox strip above the portrait. */
.tp-banner .op-banner { aspect-ratio: 4.2 / 1; }
.tp-body { padding: 0 var(--s6) var(--s6); }

.tp-id { display: flex; align-items: flex-end; gap: var(--s4); margin-top: -34px; position: relative; z-index: 3; }
.tp-sigil { width: 76px; height: 76px; flex-basis: 76px; }
.tp-who { padding-bottom: var(--s2); min-width: 0; }
/* ⛔ A PADDED PILL INLINE WITH TEXT IS NEVER BASELINE-ALIGNED. The rank badge is
   an inline-flex box, so it sat on the heading's BASELINE — and a box with
   vertical padding hung off that baseline reads low against a 1.75rem name,
   never centred on it. Stephan: "the developer patch… needs to be centered to
   the name", and the same slot carries OPERATOR and FOUNDER on other profiles.

   ⛔ THE FIX IS THE LINE, NOT THE PILL. `.patron-badge` and `.staff-badge`
   render in SEVEN places and deliberately share one box model, so a
   `vertical-align` or a margin on the badge would shift the roster card, the
   dossier, the comment popover and the Hub to fix one heading — which is
   exactly how `.patron-badge`'s old `margin-bottom` leaked into seven contexts
   and got patched at one call site instead of the source. The heading becomes a
   flex row and centres its own children; the badge is untouched.
   ⚠️ `wrap` because a long display name must be able to drop the badge to the
   next line rather than squeezing it — the name is user data, not a fixture. */
.tp-name {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3);
  font-family: var(--font-head); font-size: var(--t-h3); font-weight: 600;
  margin: 0; color: var(--text); letter-spacing: var(--track-tight);
}
.tp-handle { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted); margin: 0; }

/* ⚜️ SIGNALS — two slots that always exist (2026-09-07) --------------------
   ⛔ THE ROW MUST NOT REFLOW BETWEEN STATES. Fixed 1fr 1fr, and every branch
   emits a value AND a sub-line (an `&nbsp;` where there is nothing to say), so
   a page with both slots connected and a page with neither are the same height.
   That constancy is the whole reason the empty state can advertise anything:
   a slot that collapses when unused teaches a visitor nothing. */
.tp-signals {
  display: grid; grid-template-columns: 1fr 1fr; margin-top: var(--s5);
  border-top: 1px solid var(--etch); border-bottom: 1px solid var(--etch);
}
.tp-sig {
  display: flex; flex-direction: column; gap: 3px;
  padding: var(--s4); min-width: 0;
}
.tp-sig + .tp-sig { border-left: 1px solid var(--etch-soft); }
.tp-sig-k {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--muted);
}
.tp-sig-v {
  font-family: var(--font-head); font-weight: 600; font-size: var(--t-lead);
  line-height: 1.15; color: var(--text);
}
.tp-sig-sub {
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* an invitation is an affordance, so it is quieter than a fact and it is gold */
.tp-sig--invite, .tp-sig--locked { background: var(--etch-soft); }
.tp-sig-cta {
  font-family: var(--font-mono); font-weight: 400; font-size: var(--t-sm);
  color: var(--gold); text-decoration: none;
}
.tp-sig-cta:hover { color: var(--gold-bright); }
/* ⛔ "Not shown" states a fact about the PAGE, never a verdict on the person,
   so it recedes to muted rather than wearing an alert colour. */
.tp-sig--off .tp-sig-v {
  font-family: var(--font-mono); font-weight: 400;
  font-size: var(--t-sm); color: var(--muted);
}
.tp-sig-quiet { color: var(--text-dim); font-weight: 500; }
.tp-sig-live { display: inline-flex; align-items: center; gap: .4em; color: var(--gold); }
.tp-sig-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--gold);
  box-shadow: 0 0 0 3px var(--etch-soft);
  animation: tpPlayingBreath 2.8s var(--glide) infinite;
}
@media (prefers-reduced-motion: reduce) { .tp-sig-dot { animation: none; } }

/* ⚜️ ELSEWHERE — full-width rows, promoted above the dossier ---------------
   ⛔ ONE HEIGHT, ONE SHAPE, REPEATED. These were inline chips sized by their
   own text, so eleven services produced eleven different rectangles in a
   ragged wrap — the opposite of the thing a link page is for. Identical rows
   are what read as premium, and 44px of height is what makes them tappable. */
.tp-links { display: grid; gap: var(--s2); margin-top: var(--s5); }
/* ⛔ A ROW ON A PLATE IS ONE STEP UP FROM THE PLATE, NEVER THE SAME STEP.
   This was `background: var(--panel)` sitting on a `--panel` card — literally
   the same colour, so the only thing separating a row from the surface under it
   was a 20%-gold hairline. `--panel-2` is the ladder's "raised plate" and had
   gone unused on this page while the rows it exists for were painted flat.
   ⚠️ AND THE HOVER WENT THE WRONG WAY: `--bg-alt` is DARKER than `--panel`, so
   pointing at a row pushed it further into the card. Hover now brightens the
   EDGE and leaves the surface alone — the same depth-by-edge move the plate
   comment argues for, and it costs no second surface colour. */
.tp-link {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); min-height: 44px;
  background: var(--panel-2); border: 1px solid var(--hairline);
  border-radius: 2px;
  color: var(--text); text-decoration: none;
  transition: border-color var(--swift), background var(--swift);
}
/* ⛔ GOLD IS RESERVED, AND A ROW IS REPETITION. Seven rows each outlined in
   --etch put seven gold rectangles on one card and buried the two lines that
   are actually structural — the signals band and the section rules. The
   approved concept drew these in --hairline for exactly that reason, and gold
   arrives only on HOVER, where it means "this one, now". */
.tp-link:hover { border-color: var(--etch-lit); background: var(--panel-2); }
.tp-link:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }
/* ⚠️ A FIXED LABEL COLUMN IS DOING THE JOB AN ICON WOULD. Eleven near-identical
   abstract marks are noise and third-party logos are somebody else's trademark
   on our page — an aligned column gives the same scan rhythm and says something
   true. Same call as the loadout slots. */
.tp-link-lbl {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--gold); flex: 0 0 5.5rem;
}
.tp-link-hnd {
  font-family: var(--font-body); font-size: var(--t-sm); color: var(--text);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tp-link-go { margin-left: auto; color: var(--muted); font-size: var(--t-lead); line-height: 1; }
.tp-link:hover .tp-link-go { color: var(--gold); }
.tp-link--add { background: none; border-style: dashed; }
.tp-link--add .tp-link-lbl { color: var(--text-dim); }
.tp-link--add .tp-link-hnd { color: var(--muted); font-family: var(--font-mono); font-size: var(--t-micro); }

/* ⚜️ THE DOSSIER — ruled sections, not boxes -------------------------------
   ⛔ THREE BORDERED PANELS BECAME ONE COLUMN OF RULED SECTIONS. The panels were
   an auto-fit grid, so the column count changed with how much a member had
   filled in and no two pages had the same layout; and a border around every
   group flattens the hierarchy — everything shouts "separate object" equally.
   A mono label with a hairline running off it is the same device the roster
   masthead uses, and it costs no boxes. */
.tp-dossier { margin-top: var(--s6); display: grid; gap: var(--s6); }
.tp-block { min-width: 0; }
.tp-h {
  display: flex; align-items: center; gap: var(--s3);
  font-family: var(--font-mono); font-size: 9px; font-weight: 500;
  letter-spacing: var(--track-max); text-transform: uppercase;
  color: var(--gold); margin: 0 0 var(--s3);
}
.tp-h::after { content: ""; flex: 1; height: 1px; background: var(--etch); }
.tp-row {
  display: flex; justify-content: space-between; gap: var(--s3);
  padding: 7px 0; border-bottom: 1px solid var(--hairline);
  font-size: var(--t-sm);
}
.tp-row .k { color: var(--muted); font-family: var(--font-mono); font-size: var(--t-micro); }
.tp-row .v { color: var(--text-dim); font-variant-numeric: tabular-nums; text-align: right; }

.tp-clan { display: inline-flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; justify-content: flex-end; }
.tp-clan-nm { color: var(--text); }
.tp-clan-tr {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--gold);
  border: 1px solid var(--etch); padding: 1px 6px;
}

/* ⛔ FOUR COLUMNS THAT ALWAYS EXIST, divided by a hairline rather than boxed.
   The chips were sized by their own text, so the four slots landed at four
   different widths on every card and nothing aligned between members. */
.tp-loadout {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--hairline); padding-top: var(--s3);
}
.tp-slot {
  display: flex; flex-direction: column; gap: 4px; min-width: 0;
  padding-inline: var(--s3); border-left: 1px solid var(--hairline);
}
.tp-slot:first-child { border-left: 0; padding-left: 0; }
.tp-slot-k {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--muted);
}
.tp-slot-v { font-size: var(--t-micro); color: var(--text-dim); min-width: 0; }
.tp-slot-empty { color: var(--muted); }
/* the same word the roster card uses for a slot a tier has not opened yet */
.tp-slot-lock {
  font-family: var(--font-mono); font-size: 9px; text-transform: uppercase;
  letter-spacing: var(--track-wide); color: var(--gold); opacity: .5;
}
.tp-item { display: flex; align-items: center; gap: 6px; min-width: 0; }
.tp-item svg { width: 15px; height: 15px; color: var(--tier-accent, var(--gold)); flex: 0 0 15px; }
.tp-item-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tp-note {
  font-family: var(--font-mono); font-size: var(--t-micro);
  color: var(--muted); margin: var(--s3) 0 0;
}

/* ⚜️ THE VISITOR'S LINE — addressed to the reader, not to the member */
.tp-foot {
  margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--etch);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap;
}
.tp-foot p {
  margin: 0; font-family: var(--font-mono); font-size: var(--t-micro);
  color: var(--muted); max-width: 46ch;
}
.tp-foot a {
  color: var(--gold); text-decoration: none; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--track);
}
.tp-foot a:hover { color: var(--gold-bright); }

.tp-upsell {
  margin: var(--s5) 0 0; padding: var(--s3) var(--s4);
  border-left: 2px solid var(--gold);
  background: linear-gradient(90deg, var(--etch-soft), transparent 70%);
  font-size: var(--t-sm); color: var(--text-dim);
}
.tp-upsell a { color: var(--gold); }

/* the roster card becomes a link to the page */
/* the flex chain has to run unbroken from .op-card down to .op-showcase, or
   `margin-top:auto` on the showcase has nothing to push against and the slot
   rows stop lining up between cards of different content height */
.op-card-link,
.op-card-inner { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.op-card-link { color: inherit; text-decoration: none; }
.op-card-link:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 3px; }

@media (max-width: 620px) {
  .tp-body { padding: 0 var(--s4) var(--s5); }
  .tp-id { gap: var(--s3); }
  .tp-sigil { width: 60px; height: 60px; flex-basis: 60px; }
  /* ⛔ THE TWO SIGNAL SLOTS STACK — they never squeeze. Half of 390px minus the
     card padding is about 160px, and "85 h ⊙ verified" does not fit in it; a
     two-column row at that width buys a tidy grid and spends the number. */
  .tp-signals { grid-template-columns: 1fr; }
  .tp-sig + .tp-sig { border-left: 0; border-top: 1px solid var(--panel-border); }
  /* four loadout columns become two, and the third cell starts a new left edge */
  .tp-loadout { grid-template-columns: repeat(2, 1fr); row-gap: var(--s3); }
  .tp-slot:nth-child(3) { border-left: 0; padding-left: 0; }
  /* the label column costs too much of a narrow row; the handle needs it more */
  .tp-link-lbl { flex-basis: 4.5rem; }
  .tp-topbar { flex-wrap: wrap; }
}

/* ── the three that let the photographs retire (2026-09-03) ──────────────── */
/* ⚜️ Vallis, Zariman and Duviri, drawn — so `cetus-day.webp` and friends stop
   being served without anyone losing the background they chose. */

/* VALLIS — Orb Vallis: frozen, an aurora over the ice */
.bn-vallis { background: linear-gradient(180deg, #07131c 0%, #0d2233 45%, #0a1a24 100%); }
.bn-vallis::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(102deg, transparent 0 40%, rgba(120, 220, 240, .10) 47%, transparent 54%),
    radial-gradient(ellipse 70% 40% at 50% 0%, rgba(120, 220, 240, .14), transparent 62%);
  animation: bnAurora 11s ease-in-out infinite;
}
@keyframes bnAurora {
  0%, 100% { opacity: .5; transform: translateX(-6%) }
  50%      { opacity: 1;  transform: translateX(6%) }
}

/* ZARIMAN — the void ship: angular, drifting geometry */
.bn-zariman { background: linear-gradient(160deg, #120a24, #22133f 55%, #0b0714); }
.bn-zariman::after {
  content: ""; position: absolute; inset: 0;
  background:
    repeating-linear-gradient(58deg, transparent 0 15px, rgba(170, 136, 245, .13) 15px 16px),
    radial-gradient(circle at 78% 28%, rgba(200, 170, 255, .22), transparent 42%);
  animation: bnZar 8s ease-in-out infinite;
}
@keyframes bnZar { 0%, 100% { opacity: .6 } 50% { opacity: 1 } }

/* DUVIRI — the spiral */
.bn-duviri { background: linear-gradient(180deg, #2a1a2e, #4a2e3a 48%, #1b1119); }
.bn-duviri::after {
  content: ""; position: absolute; inset: -25%;
  background: repeating-conic-gradient(from 0deg at 50% 60%,
    rgba(244, 217, 122, .09) 0 3deg, transparent 3deg 16deg);
  animation: bnSpiral 40s linear infinite;
}
@keyframes bnSpiral { to { transform: rotate(360deg) } }

@media (prefers-reduced-motion: reduce) {
  .bn-vallis::after, .bn-zariman::after, .bn-duviri::after { animation: none; }
}

/* the dossier and the comment popover wear the member's BANNER now */
.dossier-bg--banner, .uc-bg--banner { overflow: hidden; }
.dossier-bg--banner .op-banner,
.uc-bg--banner .op-banner { position: absolute; inset: 0; aspect-ratio: auto; border-bottom: 0; }
.dossier-bg--banner .op-banner::after, .uc-bg--banner .op-banner::after { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE COMMAND HUB DASHBOARD (2026-09-03)
   Was two plain lists — a bell list and a price list. The data was always
   here; what was missing was rendering it as instruments.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ⛔ ROWS ARE THE UNIT, NOT COLUMNS — AND THAT IS THE WHOLE FIX.
   This was two columns of four cards with `align-items: start`, so Market Watch
   (rows plus a chart) sat beside World State alerts (two lines when you follow
   two things) and the right column simply ran out of content. The hole under it
   is not a spacing bug to tune: two variable-height children in one grid with
   `start` ALWAYS do that. It is what that layout IS — the identical diagnosis
   as the Relay's two columns, which is on record in TOMORROW.md.

   So the tall one takes a row of its own, and the three that are comparable
   share the next row. `stretch` (the default, no longer overridden) makes every
   card in a row end on the same line by construction, which is what reads as
   harmony — whitespace INSIDE a card is ordinary, ragged card EDGES are not. */
/* ⚠️ THE COLUMN COUNT FOLLOWS THE CARD COUNT. Four secondary cards in a
   three-column grid leaves one orphan on a row of its own — the same
   raggedness this rule exists to remove, reintroduced by adding a card. */
/* ⛔ TWO COLUMNS, AND THE COUNT IS DERIVED FROM THE CARDS — NOT A PREFERENCE.
   This was four, because four cards sat under the full-width market card and
   filled exactly one row. Then Your status moved to the Operative tab (it is
   something you SET UP, not something you WATCH) and World state alerts took a
   row of its own, which left TWO cards in a grid of four — one short row and
   two empty cells, the orphan row this board has produced twice before.
   ⚠️ ROWS ARE THE UNIT: re-derive this the moment a card is added or removed,
   or the layout is balanced by luck rather than by construction. */
.hub-dash {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5);
}
/* the tall card, full width — and its chart stops being squeezed into 1.5fr of
   a two-column grid, which a price history genuinely needed */
.hub-card--market { grid-column: 1 / -1; }
/* ⚠️ ALERTS NO LONGER TAKE A WHOLE ROW. Given one last round they read as a
   panel instead of a thumbnail strip — true, and it over-corrected: Stephan,
   "it just takes too much space away… it is never going to be full there." A
   card's footprint should match how much it can ever hold, and this one holds
   a handful of subscriptions. It shares a row with the crew now. */

/* ⚠️ cards must FILL the row they were stretched into, or the border ends
   where the content does and the stretch buys nothing */
.hub-dash > .hub-card { display: flex; flex-direction: column; height: 100%; }

/* ── inside Market Watch: the list is a rail, the chart takes the room ──── */
.hub-card--market .mkt-split {
  display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: var(--s5); align-items: start;
}
.hub-card--market .mkt-detail { margin-top: 0; padding-top: 0; border-top: 0; }

@media (max-width: 1500px) {
  /* ⚠️ already two at every width now; kept so the market card's own split
     still collapses here, which is what this breakpoint was really for */
  .hub-card--market .mkt-split { grid-template-columns: 1fr; }
  .hub-card--market .mkt-detail {
    margin-top: var(--s4); padding-top: var(--s4);
    border-top: 1px solid var(--panel-border);
  }
}
@media (max-width: 720px) {
  .hub-dash { grid-template-columns: 1fr; }
}
.hub-card { background: var(--panel); border: 1px solid var(--panel-border); padding: var(--s5); }
.hub-card-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3);
  margin-bottom: var(--s4); padding-bottom: var(--s3); border-bottom: 1px solid var(--panel-border);
}
.hub-card-head h3 {
  font-family: var(--font-mono); font-size: var(--t-micro); font-weight: 500;
  letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--gold); margin: 0;
}
.hub-card-meta { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted); }
.hub-card-meta a { color: var(--muted); }
.hub-card-meta a:hover { color: var(--gold); }

/* ── market rows ─────────────────────────────────────────────────────────── */
.mkt-rows { display: flex; flex-direction: column; }
.mkt-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 92px 74px 66px;
  gap: var(--s3); align-items: center; width: 100%;
  padding: var(--s3) var(--s2); border: 0; border-top: 1px solid rgba(255, 255, 255, .05);
  background: none; font: inherit; color: inherit; text-align: left; cursor: pointer;
  transition: background var(--swift);
}
.mkt-row:first-child { border-top: 0; }
.mkt-row:hover { background: var(--bg-alt); }
.mkt-row[aria-pressed="true"] { background: var(--bg-alt); box-shadow: inset 2px 0 0 var(--gold); }
.mkt-row:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: -2px; }
.mkt-id { min-width: 0; display: flex; flex-direction: column; }
.mkt-nm { font-size: var(--t-sm); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mkt-slug { font-family: var(--font-mono); font-size: 9px; color: var(--muted); }
.mkt-spark { display: block; width: 92px; height: 30px; }
.mkt-px {
  font-family: var(--font-mono); font-size: var(--t-sm); color: var(--text);
  text-align: right; font-variant-numeric: tabular-nums;
}
.mkt-px small { color: var(--muted); font-size: 9px; }
/* ⛔ status = colour AND glyph AND number, never colour alone. The obvious
   green/red pair failed a colour-blindness check at deutan 5.8; this is
   teal/coral, measured at 12.2. */
.mkt-delta, .mkt-delta-inline {
  font-family: var(--font-mono); font-size: var(--t-micro); text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mkt-delta.is-up,   .mkt-delta-inline.is-up   { color: #3fb5b0; }
.mkt-delta.is-down, .mkt-delta-inline.is-down { color: #e26a55; }
.mkt-delta.is-flat, .mkt-delta-inline.is-flat { color: var(--muted); }

/* ── the detail chart ────────────────────────────────────────────────────── */
.mkt-detail { position: relative; margin-top: var(--s4); padding-top: var(--s4);
              border-top: 1px solid var(--panel-border); }
.mkt-detail-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: var(--s4); flex-wrap: wrap; margin-bottom: var(--s3);
}
.mkt-detail-title { font-family: var(--font-head); font-size: var(--t-lead); color: var(--text); margin: 0; }
.mkt-detail-now { font-family: var(--font-mono); font-size: var(--t-sm); color: var(--text-dim); margin: 0; }
.mkt-detail-now b { color: var(--gold-bright); font-size: var(--t-lead); font-weight: 500; }
.mkt-ranges { display: flex; gap: var(--s1); }
.mkt-range {
  background: none; border: 1px solid var(--panel-border); color: var(--muted); cursor: pointer;
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; padding: 3px 9px; transition: border-color var(--swift), color var(--swift);
}
.mkt-range[aria-pressed="true"] { border-color: var(--gold); color: var(--gold); }
.mkt-range:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }
/* ⛔ SIZED BY CLASS, NOT BY ID. These used to be `#mktPrice` / `#mktVol`, which
   worked while the Command Hub was the only place a chart existed. /market/ now
   draws the same picture in its item modal, and a second pair of ID rules would
   be the same chart at two different heights the first time either was nudged.
   One decision, one place — the same reason the renderer itself was extracted
   into js/market-chart.js. */
.mkt-canvas { display: block; width: 100%; }
.mkt-canvas--price { height: 186px; cursor: crosshair; }
.mkt-canvas--vol   { height: 48px; margin-top: 2px; }
.mkt-legend {
  display: flex; gap: var(--s4); flex-wrap: wrap; margin-top: var(--s2);
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.mkt-legend i { display: inline-block; width: 14px; margin-right: 5px; vertical-align: middle; }
.mkt-legend .k-line { height: 2px; background: var(--gold); }
.mkt-legend .k-band { height: 8px; background: rgba(212, 175, 55, .16);
  border-top: 1px solid rgba(212, 175, 55, .3); border-bottom: 1px solid rgba(212, 175, 55, .3); }
.mkt-legend .k-avg  { height: 0; border-top: 2px dashed var(--muted); }
.mkt-src { margin-left: auto; }
/* ⚠️ A stale figure says so rather than passing for current. */
.mkt-src.is-stale { color: #e26a55; }
.mkt-tip {
  position: absolute; pointer-events: none; opacity: 0; transition: opacity 120ms;
  background: var(--bg); border: 1px solid var(--gold); padding: var(--s2) var(--s3);
  font-family: var(--font-mono); font-size: 10px; color: var(--text);
  white-space: nowrap; line-height: 1.5; z-index: 5;
}
.mkt-tip.is-on { opacity: 1; }
.mkt-tip b { color: var(--gold-bright); }
.mkt-tip .d { color: var(--muted); display: block; margin-bottom: 3px; }

/* ── world-state alert minis ─────────────────────────────────────────────── */
/* ⛔ These reuse cycle_art() from /world-state/ UNCHANGED. Drawing a second,
   smaller version of the same five cycles would be two copies of one truth,
   and the smaller one would rot first. */
.ws-mini-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(142px, 1fr)); gap: var(--s3); }
.ws-mini {
  position: relative; overflow: hidden; isolation: isolate;
  border: 1px solid var(--panel-border); padding: var(--s3);
  min-height: 96px; display: flex; flex-direction: column; justify-content: flex-end;
}
.ws-mini-art { position: absolute; inset: 0; z-index: -1; }
.ws-mini-art .cycle-art { position: absolute; inset: 0; width: 100%; height: 100%; }
.ws-mini-art::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6,6,6, .30), rgba(6,6,6, .88));
}
.ws-mini-art--plate {
  background: repeating-linear-gradient(135deg, rgba(212, 175, 55, .05) 0 1px, transparent 1px 11px),
              linear-gradient(180deg, #141420, #0d0d15);
}
.ws-mini-nm {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--text-dim); text-decoration: none;
}
.ws-mini-nm:hover { color: var(--gold); }
.ws-mini-st {
  font-family: var(--font-mono); font-size: var(--t-sm); color: var(--gold-bright);
  font-variant-numeric: tabular-nums; margin-top: 2px;
}
.ws-mini-x {
  position: absolute; top: 4px; right: 4px; z-index: 2;
  background: rgba(6,6,6, .6); border: 1px solid var(--panel-border);
  color: var(--muted); width: 20px; height: 20px; line-height: 1; cursor: pointer;
  font-size: 13px; opacity: 0; transition: opacity var(--swift), color var(--swift);
}
.ws-mini:hover .ws-mini-x, .ws-mini-x:focus-visible { opacity: 1; }
.ws-mini-x:hover { color: #e26a55; border-color: #e26a55; }

@media (max-width: 940px) { .hub-dash { grid-template-columns: 1fr; } }
@media (max-width: 560px) {
  .mkt-row { grid-template-columns: minmax(0, 1fr) 62px 58px; }
  .mkt-spark { width: 62px; }
  .mkt-delta { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ACTIVITY STATUS + FOLLOW (2026-09-03)
   ⛔ A status, not a feed. A board with seven members shows zero posts for
   ever, and an empty room makes a site look dead. A status lives on your own
   card, decays by itself, and is the thing that actually makes someone squad up.
   ═══════════════════════════════════════════════════════════════════════════ */

/* the status as it appears on a card / page */
.op-act {
  display: inline-flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  margin: var(--s3) var(--s4) 0;
}
.op-act-kind {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--tier-accent, var(--gold));
  border: 1px solid var(--etch); padding: 1px 6px;
}
.op-act-target { font-size: var(--t-micro); color: var(--text-dim); }
.op-act--compact { margin: var(--s2) var(--s4) 0; }

/* the setter on the Hub */
.act-set { display: flex; gap: var(--s2); flex-wrap: wrap; }
.act-set select, .act-set input[type="text"] {
  background: var(--bg); border: 1px solid var(--panel-border); border-radius: 8px;
  color: var(--text); padding: .55rem .65rem; font-size: var(--t-sm); color-scheme: dark;
  font-family: var(--font-mono);
}
.act-set select { cursor: pointer; flex: 0 0 auto; }
.act-set input[type="text"] { flex: 1 1 140px; min-width: 0; }
.act-set select:focus, .act-set input:focus { outline: none; border-color: var(--gold); }
.act-set input::placeholder { color: var(--muted); opacity: 1; }

.act-now {
  margin-top: var(--s3); padding: var(--s3);
  background: var(--bg-alt); border: 1px solid var(--etch-soft);
}
.act-now .op-act { margin: 0; }
.act-now.is-empty { border-style: dashed; border-color: var(--panel-border); }
.act-none { font-size: var(--t-sm); color: var(--muted); }

/* ── the crew strip ──────────────────────────────────────────────────────── */
.crew-list { display: flex; flex-direction: column; }
.crew-row {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) 0; border-top: 1px solid rgba(255, 255, 255, .05);
  text-decoration: none; color: inherit; transition: background var(--swift);
}
.crew-row:first-child { border-top: 0; }
.crew-row:hover { background: var(--bg-alt); }
/* ⛔ LIVE READS AT A GLANCE, NOT ONLY IN THE CORNER. The list was already
   ordered correctly — live, then whoever is doing something, then the rest —
   but every row looked identical apart from a small lamp on the far right, so
   the ordering was information nobody could see. A gold left rule is the
   site's existing mark for STRUCTURE, and "this one is on air" is meaning
   rather than decoration, which is what earns the accent here.
   ⚠️ The rule is drawn with a border, never a background tint: a tinted row
   would read as selected, and nothing here is selected. */
.crew-row { border-left: 2px solid transparent; padding-left: var(--s2); }
.crew-row:has(.crew-lamp) { border-left-color: var(--gold); }
/* ⚠️ and the others recede, so the lit ones are the figure. Dimmed, never
   hidden — a crew member who is offline is still somebody you follow. */
.crew-row:not(:has(.crew-lamp)) .crew-nm { color: var(--text-dim); }
.crew-av {
  width: 34px; height: 34px; flex: 0 0 34px; overflow: hidden;
  background: none;
  clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 6px 100%, 0 calc(100% - 6px));
}
.crew-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.crew-who { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.crew-nm {
  font-size: var(--t-sm); color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.crew-act {
  font-family: var(--font-mono); font-size: 9px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ⛔ Lit by Twitch or not at all — the same rule as the card lamp. */
.crew-lamp {
  display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
  font-family: var(--font-mono); font-size: 9px; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--gold-bright);
}
.crew-lamp i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--gold-bright);
  box-shadow: 0 0 7px var(--gold); animation: opLamp 2.4s ease-in-out infinite;
}

/* ── the follow control ──────────────────────────────────────────────────── */
.tp-follow {
  /* \u26a0\ufe0f NO FLOAT. See .tp-topbar \u2014 a float here silently narrows the banner,
     because the banner is a BFC root and steps aside for floats. */
  flex: 0 0 auto;
  background: none; border: 1px solid var(--panel-border); color: var(--text-dim);
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 6px 14px; cursor: pointer;
  transition: border-color var(--swift), color var(--swift), background var(--swift); border-radius: 2px;
}
.tp-follow:hover { border-color: var(--gold); color: var(--gold); }

/* ⚜️ COPY LINK — the one control a link page must have.
   ⛔ THE SAME BOX MODEL AS .tp-follow, declared rather than inherited. The two
   sit side by side in one flex row, and a pair of buttons whose padding differs
   by two pixels is exactly the kind of unevenness that made the roster read as
   messy. ⚠️ Both labels are always in the DOM and swapped by a class, so the
   button cannot change width when it says "Copied" and shove Follow sideways. */
.tp-copy {
  flex: 0 0 auto;
  background: none; border: 1px solid var(--panel-border); color: var(--text-dim);
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 6px 14px; cursor: pointer;
  transition: border-color var(--swift), color var(--swift); border-radius: 2px;
}
.tp-copy:hover { border-color: var(--gold); color: var(--gold); }
.tp-copy:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }
.tp-copy .tp-copy-done { display: none; }
.tp-copy.is-done { border-color: var(--gold); color: var(--gold); }
.tp-copy.is-done .tp-copy-idle { display: none; }
.tp-copy.is-done .tp-copy-done { display: inline; }
.tp-follow:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }
.tp-follow.is-on { border-color: var(--gold); color: var(--bg); background: var(--gold); }
/* ⚠️ A failed request puts the button back AND says so — an optimistic flip
   that silently reverts looks like the click missed. */
.tp-follow.is-error { border-color: #e26a55; color: #e26a55; background: none; }
.tp-follow .tp-follow-on  { display: none; }
.tp-follow .tp-follow-off { display: inline; }
.tp-follow.is-on .tp-follow-on  { display: inline; }
.tp-follow.is-on .tp-follow-off { display: none; }

@media (prefers-reduced-motion: reduce) { .crew-lamp i { animation: none; } }

/* ═══════════════════════════════════════════════════════════════════════════
   /market/ — THE DRAWN GROUND AND THE PRICE-HISTORY PANEL          2026-09-03
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⛔ THE GROUND IS AN ORDER-BOOK LADDER, DRAWN. It replaces a 2,067 KB
   photograph that sat behind a 78% scrim — a megabyte nobody could actually
   see. The figure is the page's own subject: a dense price ladder, one
   brighter rung where the spread sits, and depth falling away either side.

   ⛔ AND IT DOES NOT MOVE. Every other drawn ground here carries a slow drift,
   and a scrolling order book was the obvious thing to reach for — but motion
   on THIS figure would say "orders are updating", which is a claim about live
   trading that a background cannot possibly know. Same rule that keeps the
   dead-air meter on /stream/ pinned to the floor: decoration may not imply a
   state it cannot know. */
.page-bg--market {
  background-image: none;
  background-color: var(--bg);
  overflow: hidden;
}
.page-bg--market::before {
  content: "";
  position: absolute;
  inset: 0;
  /* ⚠️ THE FIRST VERSION OF THIS WAS INVISIBLE, and I only found that by being
     shown a screenshot. Rungs at .055 alpha, under a mask, under a scrim
     reaching .88 — the sums were all defensible and the result was a black
     page. Trading 2 MB for nothing is not a win. Roughly doubled here, the
     mask window widened, and the scrim pulled back at the top. */
  background:
    /* the spread — one brighter rung, off-centre so it reads as a price
       rather than as a divider */
    linear-gradient(180deg, transparent 0 41.5%, rgba(212,175,55,.30) 41.5% calc(41.5% + 1px),
                    transparent calc(41.5% + 1px) 100%),
    /* the ladder: dense rungs, and a coarser set behind them so it has depth
       instead of reading as a single hatch */
    repeating-linear-gradient(180deg,
      rgba(212,175,55,.105) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(180deg,
      rgba(212,175,55,.055) 0 1px, transparent 1px 45px),
    /* the depth columns — quantity falling away from the book */
    repeating-linear-gradient(90deg,
      rgba(212,175,55,.038) 0 1px, transparent 1px 96px);
  /* ⚠️ The rungs thin out top and bottom rather than being cropped by the
     viewport edge: a hatch that stops dead reads as a rendering fault. */
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 13%, #000 84%, transparent);
          mask-image: linear-gradient(180deg, transparent, #000 13%, #000 84%, transparent);
}
.page-bg--market::after {
  background:
    radial-gradient(ellipse 70% 40% at 50% 30%, rgba(212,175,55,.09), transparent 64%),
    linear-gradient(180deg, rgba(6,6,6,.34) 0%, rgba(6,6,6,.72) 58%, var(--bg) 100%);
}

/* ── the price-history panel inside the item modal ───────────────────────── */
/* ⚜️ The orders below it say what people are asking RIGHT NOW; this says
   whether that is a normal price. Same engine and same canvas sizing as the
   Command Hub — .mkt-range, .mkt-delta-inline, .mkt-src and .mkt-tip are all
   shared rules, not restyled copies. */
/* ⚠️ NO border-top HERE. `.market-modal-head` already ends in a
   `border-bottom`, so adding one produced TWO hairlines with a gap between
   them above the panel — which reads as a rendering fault, not as structure.
   One rule per boundary; the head owns this one. */
.mkt-history { margin: var(--s2) 0 var(--s5); }
.mkt-history-head {
  display: flex;
  align-items: baseline;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s3);
}
.mkt-history-head .market-section-label { margin: 0; }
/* the figure is the point of the panel, so it gets the weight — and the ranges
   are pushed to the far end rather than crowding it */
.mkt-history-figure { display: flex; align-items: baseline; gap: var(--s2); }
.mkt-history .mkt-ranges { margin-left: auto; }
.mkt-now {
  font-family: var(--font-mono);
  font-size: var(--t-h3);
  font-weight: 500;
  color: var(--gold-bright);
  font-variant-numeric: tabular-nums;
}
/* the canvas needs a positioned parent or the tooltip's left/top escape it */
.mkt-canvas-wrap { position: relative; }
.mkt-history .mkt-src {
  margin: var(--s2) 0 0;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--muted);
  letter-spacing: var(--track-wide);
}

/* ⚠️ On a phone the modal is narrow and the chart is the first thing to become
   unreadable. Shorter, and the head stacks rather than wrapping mid-figure. */
@media (max-width: 700px) {
  .mkt-canvas--price { height: 150px; }
  .mkt-canvas--vol   { height: 38px; }
  .mkt-history-head  { gap: var(--s2); }
  .mkt-history .mkt-ranges { margin-left: 0; }
}

/* ⛔ THE VARIANT CHIPS — a mod has one market per RANK.
   warframe.market returns a row per day PER RANK, so an unranked and a maxed
   Primed Continuity arrive interleaved: 178 rows for 89 days, which drew a
   solid sawtooth between 65p and 135p. They are split server-side now and
   this row chooses which market you are reading.
   ⚠️ Hidden entirely when an item has ONE market — a lone "Intact" button
   beside a relic chart is a control that cannot do anything, and a dead
   control invites a click that changes nothing. */
.mkt-vars {
  display: flex;
  gap: var(--s1);
  margin: 0 0 var(--s3);
}
.mkt-vars[hidden] { display: none !important; }
.mkt-var {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
  background: none;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 3px 9px;
  cursor: pointer;
  transition: color var(--swift), border-color var(--swift);
}
.mkt-var:hover { color: var(--gold-bright); border-color: var(--gold-dim); }
.mkt-var[aria-pressed="true"] { border-color: var(--gold); color: var(--gold); }
.mkt-var:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }

/* ── /market/ : the head and the search field are ONE unit ────────────────── */
/* ⛔ ON DESKTOP ONLY. Stephan's words were "the search bar looks good on
   mobile, but on desktop it is too much going on" — so every number below is
   behind a min-width. Shrinking it globally would have fixed the desktop and
   broken the one place he said was right, which is the commonest way a
   responsive tweak makes things worse.

   Two things made it heavy, and they are different problems:
     · the head said the same thing three times (the description is gone from
       the markup — it restated the search field's own placeholder);
     · and the field then floated ALONE in a very wide viewport, 104px below
       the title with 64px of head margin under it. Isolation reads as weight.
   So the field is pulled up under the title, and trimmed. */
@media (min-width: 861px) {
  .market { padding-top: 4.25rem; }
  /* the head stops being a separate block and becomes the field's label */
  .market .section-head { margin-bottom: 1.6rem; }
  /* ⚠️ Narrower AND shorter. At 640 x 56 it read as a panel; a search field
     should read as a field. The icon is positioned from the centre, so it
     follows the height without a second number to keep in step. */
  .market .market-search { max-width: 520px; }
  .market .market-input  { padding: 0.85rem 1.2rem 0.85rem 2.85rem; }
  .market .market-search-icon { left: 1rem; width: 18px; height: 18px; }
}

/* ── /privacy/ : a legal page is READ, so it is set for reading ───────────── */
/* ⚠️ Measure ~68ch and let it be left-aligned. The site's section-head is
   centred, which is right for a masthead over a grid and wrong over eight
   paragraphs of prose — centred body copy makes every line start in a
   different place and the eye loses the column. */
.legal{max-width:760px;margin:0 auto;padding:var(--s8) var(--s5) var(--s9)}
/* ⚠️ NO SECOND LEFT RULE HERE. `.section-head--left` already exists and already
   carries all three declarations the kicker needs (align, suppress the leading
   hairline, let the trailing one fill) — verify-kickers.php asserts exactly
   that for every left context. Inventing `.legal .section-kicker` would have
   been a sixth context the guard did not know about, i.e. the same decision
   made twice and drifting from then on. */
.legal .section-head{margin:0 0 var(--s6);padding:0;max-width:none}
.legal-lede{font-size:var(--t-lead);color:var(--text-dim);line-height:1.7}
.legal-stamp{
  font-family:var(--font-mono);font-size:var(--t-micro);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--muted);
  border-top:1px solid var(--panel-border);padding-top:var(--s3);margin:var(--s5) 0 var(--s7);
}
.legal h2{
  font-family:var(--font-head);font-size:var(--t-h3);text-transform:uppercase;
  letter-spacing:var(--track);color:var(--gold);
  margin:var(--s7) 0 var(--s3);
}
.legal p{color:var(--text-dim);line-height:1.75;margin:0 0 var(--s4)}
.legal strong{color:var(--text);font-weight:500}
.legal a{color:var(--gold);text-decoration:underline;text-underline-offset:3px}
.legal a:hover{color:var(--gold-bright)}
.legal-note{font-size:var(--t-sm);color:var(--muted)}
.legal-list{color:var(--text-dim);line-height:1.75;padding-left:var(--s5);margin:0 0 var(--s4)}
.legal-list li{margin-bottom:var(--s2)}
/* ⚠️ The table scrolls in its own container. A three-column table of third
   parties is the widest thing on this page, and the BODY must never scroll
   sideways to accommodate one element. */
.legal-table{
  width:100%;border-collapse:collapse;margin:0 0 var(--s4);
  font-size:var(--t-sm);display:block;overflow-x:auto;
}
.legal-table th{
  font-family:var(--font-mono);font-size:var(--t-micro);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--gold);text-align:left;
  padding:var(--s2) var(--s4) var(--s2) 0;border-bottom:1px solid var(--panel-border);
  white-space:nowrap;
}
.legal-table td{
  color:var(--text-dim);padding:var(--s3) var(--s4) var(--s3) 0;
  border-bottom:1px solid var(--edge,rgba(255,255,255,.05));vertical-align:top;
}
.legal-table td:first-child{color:var(--text);white-space:nowrap}
/* ⚠️ The footer credit block is where /privacy lives — it is the one link on
   every page. It must be legible, not decorative: a privacy link nobody can
   read is a privacy link nobody can use. */
.footer-credit a{color:var(--text-dim);text-decoration:underline;text-underline-offset:3px;
  transition:color var(--swift)}
.footer-credit a:hover{color:var(--gold)}

/* ═══ THE RELAY ORB — the sixth face of the one orb ════════════════════════
   Every transform here declares `transform-origin:100px 100px`, which is the
   idiom .orb-long already uses: the variants draw in absolute 0–200 viewBox
   coordinates, so a rotation has to be told where the centre is. SVG's default
   transform-box makes that ambiguous across engines, and a shard cluster
   rotating about the top-left corner leaves the canvas entirely. */
.orb-cluster,.orb-shard,.orb-chip,.orb-scan{transform-origin:100px 100px}

/* radius is ONE number per state, not one keyframe set per state */
.orb-shard{
  --r:58px;
  transform:rotate(var(--a)) translateY(calc(-1 * var(--r)));
  transition:transform 620ms cubic-bezier(.34,1.3,.5,1);
}
.orb-chip{
  stroke:var(--gold);stroke-width:1.3;fill:rgba(212,175,55,.07);
  animation:orbChipDrift 7s ease-in-out infinite;animation-delay:var(--d);
}
@keyframes orbChipDrift{
  0%,100%{transform:rotate(0deg) translateX(0)}
  50%    {transform:rotate(9deg) translateX(2.5px)}
}
.orb-scan{stroke:var(--gold-bright);stroke-width:1.2;opacity:0}

/* ── LISTENING: the cluster draws IN ─────────────────────────────────────── */
.orb--relay.is-listening .orb-shard{--r:44px}

/* ── THINKING: the only state that turns, plus a sweep ───────────────────── */
.orb--relay.is-thinking .orb-shard{--r:50px}
.orb--relay.is-thinking .orb-cluster{animation:orbClusterTurn 2.4s linear infinite}
@keyframes orbClusterTurn{to{transform:rotate(360deg)}}
.orb--relay.is-thinking .orb-scan{animation:orbScanSweep 1.5s cubic-bezier(.5,0,.5,1) infinite}
@keyframes orbScanSweep{
  0%{opacity:0;transform:rotate(-100deg)}
  20%{opacity:.9}
  100%{opacity:0;transform:rotate(180deg)}
}
.orb--relay.is-thinking .orb-core{animation:orbCoreFlicker .9s steps(3,end) infinite}
@keyframes orbCoreFlicker{0%,100%{opacity:1}40%{opacity:.45}70%{opacity:.85}}

/* ── ANSWERING: the shards settle OUT, the fracture reassembles ──────────── */
.orb--relay.is-answering .orb-shard{--r:66px}
.orb--relay.is-answering .orb-core{fill:rgba(244,217,122,.22)}

/* ⚠️ REDUCED MOTION STOPS THE LOOPS AND KEEPS THE STATES. Someone who asked
   for less movement still needs to tell listening from thinking — that
   difference is information, not decoration. The radius transition survives at
   1ms so the shape still changes; only the perpetual motion goes. */
@media (prefers-reduced-motion:reduce){
  .orb-chip,.orb-scan,
  .orb--relay.is-thinking .orb-cluster,
  .orb--relay.is-thinking .orb-core{animation:none!important}
  .orb-shard{transition-duration:1ms}
  .orb--relay.is-thinking .orb-scan{opacity:.7}
}

/* ═══ /ask/ — ASK THE RELAY ════════════════════════════════════════════════════
   Rewritten from scratch 2026-09-04.

   ⛔ THE DIAGNOSIS THAT DECIDED EVERY RULE BELOW: this is not a chat. The
   exchanges are FIVE WORDS EACH, and a scrolling transcript around five words
   is ninety percent chrome — which is what read as fragmented. It is an
   instrument that answers: ONE VERTICAL SPINE, one answer at a time, set LARGE.

   Three consequences, and they are the whole design:
     · the orb is the anchor and NEVER moves;
     · the stage is a FIXED FRAME — older exchanges shrink and fade inside it
       rather than pushing the composer down;
     · the answer is the hero, at display size, with a left-rule in the colour
       of its own provenance mark, so you can SEE ⊙ from ◇ while scanning. */

/* ⛔ The ground is drawn, like every page since 09-02, and it does NOT move —
   a pulsing ground would say something is transmitting when nothing is. */
.page-bg--ask{background-image:none;background-color:var(--bg);overflow:hidden}
/* ⛔ THE RINGS ARE DRAWN FROM THE ORB, NOT FROM THE VIEWPORT — see
   .ask-orb-wrap::before. They used to live here, centred at `50% 30%` of a
   FIXED layer, so they could only ever line up with the orb by coincidence and
   drifted the moment anything above changed height or the page scrolled.
   Concentric-by-construction beats concentric-by-arithmetic. */
.page-bg--ask::before{
  content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(90deg,
    rgba(212,175,55,.024) 0 1px, transparent 1px 128px);
  -webkit-mask-image:linear-gradient(180deg,#000,#000 55%,transparent);
          mask-image:linear-gradient(180deg,#000,#000 55%,transparent);
}
.page-bg--ask::after{
  background:
    radial-gradient(ellipse 56% 40% at 50% 24%,rgba(212,175,55,.11),transparent 64%),
    linear-gradient(180deg,rgba(6,6,6,.30) 0%,rgba(6,6,6,.72) 62%,var(--bg) 100%);
}

/* ── the spine ───────────────────────────────────────────────────────────── */
.ask{max-width:820px;margin:0 auto;padding:var(--s4) var(--s5) var(--s9);width:100%}

.ask-masthead{text-align:center;margin:0 0 var(--s6)}
/* ⚠️ `.section-kicker` is a centred flex row with two hairlines; here it is
   centred, so the shared default is exactly right and nothing is overridden. */
.ask-masthead .section-title{margin-bottom:var(--s3)}
.ask-legend{
  display:flex;justify-content:center;align-items:center;gap:8px;flex-wrap:wrap;
  margin:0;font-family:var(--font-mono);font-size:var(--t-micro);
  letter-spacing:var(--track-wide);color:var(--muted);
}
.ask-legend-sep{opacity:.5}

/* ── the marks. ⛔ A MARK IS A CLAIM: gold ONLY means our engine produced it.
   ⚠️ 9px, borderless-light. They shipped bigger than the answers they labelled;
   a provenance tag annotates, it does not compete. */
.mark{
  display:inline-flex;align-items:center;gap:4px;font-family:var(--font-mono);
  font-size:9px;line-height:1;letter-spacing:.1em;text-transform:uppercase;
  padding:2px 5px;border:1px solid;border-radius:2px;white-space:nowrap;
  vertical-align:middle;
}
.mark--computed{color:var(--gold);border-color:rgba(138,111,40,.5);background:rgba(212,175,55,.05)}
/* ⚠️ Deliberately NOT gold and deliberately not red — reference is not a fault,
   it is a different kind of claim. */
.mark--reference{color:#7f8ea0;border-color:rgba(127,142,160,.28);background:rgba(127,142,160,.04)}
.mark--unknown{color:var(--muted);border-color:rgba(99,99,99,.32)}

/* ── standby ─────────────────────────────────────────────────────────────── */
.ask-standby{text-align:center;padding:var(--s7) 0}
.ask-standby-title{font-family:var(--font-head);font-size:var(--t-lead);color:var(--gold);
  text-transform:uppercase;letter-spacing:var(--track);margin:0 0 var(--s2)}
.ask-standby-body{color:var(--text-dim);margin:0 auto;line-height:1.7;max-width:52ch}

/* ── the console spine ───────────────────────────────────────────────────── */
.ask-console{display:flex;flex-direction:column;align-items:center}

/* ── the orb: the anchor, and it never moves ─────────────────────────────── */
/* ⚠️ margin-inline:auto BECAUSE THE ORB IS NO LONGER A FLEX CHILD. Hoisting it
   out of .ask-console so the standby state keeps it made it a child of `.ask`,
   which is a plain block — `align-items:center` stopped reaching it and it sat
   flush left in the column. My own fix caused it; Stephan saw it immediately. */
/* ⛔ THE BLOOM IS DECORATION AND IT WAS WIDENING THE DOCUMENT.
   .ask-orb-wrap::before is inset -165%, so on a 150px orb it reaches about
   250px past each side — harmless at 1440px, and at 390px it pushed the page
   to 512px wide. A phone then either scrolls sideways into empty black or
   shrinks the whole page to 77% to fit it, which is what makes every control
   on this page read slightly too small on a handset.
   ⚠️ overflow-x:clip, never hidden: clip does not create a scroll container,
   so nothing here becomes scrollable or traps a sticky descendant. Where it
   is unsupported the rule is simply ignored and the old behaviour returns —
   a cosmetic absence failing quietly, which is the correct direction. */
.ask { overflow-x: clip; }

.ask-orb-wrap{
  position:relative;width:min(280px,58vw);flex:0 0 auto;
  margin:0 auto var(--s5);
}
.ask-orb{display:block;width:100%;aspect-ratio:1;height:auto;color:var(--gold)}
/* the bloom: depth behind the orb without the orb gaining one filled pixel */
/* the bloom AND the echo rings, on one layer centred on the orb itself, so the
   figure is concentric with the mark by construction and cannot drift */
.ask-orb-wrap::before{
  content:"";position:absolute;inset:-165%;border-radius:50%;pointer-events:none;z-index:-1;
  background:
    radial-gradient(circle,rgba(212,175,55,.11) 0 18%,transparent 42%),
    repeating-radial-gradient(circle,
      rgba(212,175,55,.06) 0 1px, transparent 1px 46px);
  -webkit-mask-image:radial-gradient(circle,#000 12%,transparent 62%);
          mask-image:radial-gradient(circle,#000 12%,transparent 62%);
  animation:askBloom 7s ease-in-out infinite;
}
@keyframes askBloom{0%,100%{opacity:.55;transform:scale(.97)}50%{opacity:1;transform:scale(1.03)}}

/* ⛔ THE ANSWER-ECHO IS DRAWN HERE, OUTSIDE THE ORB. php/orb.php is not touched
   — "the orb looks absolutely perfect, we should not change anything on it."
   One ring leaves the core when an answer lands: the answer arriving as an
   echo, which is what the mark on this whole site is named for. */
.ask-orb-wrap::after{
  content:"";position:absolute;inset:0;border-radius:50%;pointer-events:none;
  border:1px solid var(--gold-bright);opacity:0;
}
.ask-console.is-answering .ask-orb-wrap::after{animation:askEcho 1.5s cubic-bezier(.2,.7,.3,1) 1}
@keyframes askEcho{
  0%  {opacity:.85;transform:scale(.34)}
  100%{opacity:0;  transform:scale(1.5)}
}

/* ── the stage: a FIXED FRAME, so the composer never moves ───────────────── */
.ask-stage{
  width:100%;min-height:190px;display:flex;flex-direction:column;
  justify-content:flex-end;align-items:stretch;gap:var(--s4);
  margin-bottom:var(--s6);
}
.ask-invite{
  margin:auto 0;text-align:center;color:var(--muted);font-family:var(--font-mono);
  font-size:var(--t-sm);letter-spacing:var(--track-wide);text-transform:uppercase;
}

/* ── a turn ──────────────────────────────────────────────────────────────── */
.ask-turn{
  padding-left:var(--s4);border-left:2px solid var(--panel-border);
  transition:opacity var(--glide),transform var(--glide),font-size var(--glide);
}
/* ⛔ PROVENANCE AS STRUCTURE, NOT ONLY A LABEL. The rule takes the mark's
   colour, so which KIND of answer this is reads at a glance, before the chip. */
.ask-turn.is-computed {border-left-color:var(--gold)}
.ask-turn.is-reference{border-left-color:rgba(127,142,160,.55)}
.ask-turn.is-unknown  {border-left-color:var(--muted)}
.ask-turn.is-error    {border-left-color:#e26a55}

.ask-q{
  font-family:var(--font-mono);font-size:var(--t-sm);color:var(--muted);
  letter-spacing:var(--track);margin:0 0 var(--s2);
}
/* ⛔ THE ANSWER IS THE HERO. Five words at display size is a statement; the
   same five words in a bubble are a fragment. This one rule is most of the
   redesign. */
.ask-a{
  font-family:var(--font-head);font-weight:500;color:var(--text);
  font-size:clamp(1.3rem,1.05rem + 1.1vw,1.95rem);line-height:1.35;
  letter-spacing:var(--track-tight);margin:0 0 var(--s3);
}
.ask-turn.is-error .ask-a{color:#e26a55;font-size:var(--t-base);font-family:var(--font-body)}

/* ⚠️ OLDER TURNS RECEDE INSIDE THE FRAME rather than pushing it. That is what
   keeps the composer still without pinning it to the viewport, which would cut
   it off from the answer it belongs to. */
.ask-turn.is-past{opacity:.42}
.ask-turn.is-past .ask-a{font-size:var(--t-base);font-family:var(--font-body);font-weight:400;margin-bottom:0}
.ask-turn.is-past .ask-q{font-size:var(--t-micro);margin-bottom:2px}
.ask-turn.is-past .mark{display:none}
.ask-turn.is-past-2{opacity:.22}
.ask-turn.is-past-2 .ask-q{display:none}

/* ── THE CONSOLE: one plate, the writing surface and every tool on it ────── */
/* ⛔ SIX LOOSE PARTS BECAME ONE OBJECT. A footer, a control at each end, the
   send button, the allowance and the disclosure were all separate things
   competing at the bottom of the page, and three passes at REARRANGING them
   only moved the problem. ⚜️ This is the `.plate` device from Relay Plating —
   built in August, applied nowhere until now, the last unused piece of the
   approved concept.
   ⚠️ It is not the grey card he rejected: that was a slab around a FRAGMENT,
   two buttons boxed off under an unboxed field. Here the frame IS the
   instrument, so nothing is left outside it to arrange. */
.ask-plate{width:100%;margin-top:var(--s5);--notch:16px}
.ask-plate > .plate-in{
  display:flex;flex-direction:column;
  padding:var(--s4) var(--s4) var(--s3);
  background:var(--bg-alt);      /* a shade under --panel: a surface, not a card */
}
/* the plate's own edge answers focus, so the field never needs a border */
.ask-plate:focus-within{--edge:var(--etch-lit)}

.ask-plate textarea{
  /* ⛔ display:block, BECAUSE A TEXTAREA IS INLINE-LEVEL BY DEFAULT and its
     line box reserves room for descenders under it — 7.6px of it here. That
     phantom strip made the Ask body 71.6px tall where the field itself is 64,
     so the plate still shrank by exactly that much on every switch to Plan
     after the real 20px difference was fixed. A constant residual at two
     different widths is the tell: it is not layout, it is a line box. */
  display:block;
  width:100%;background:none;border:0;outline:none;resize:none;
  color:var(--text);font-family:var(--font-body);font-size:var(--t-lead);
  line-height:1.55;padding:0;overflow:hidden;   /* height is set by JS */
  /* ⚠️ OPEN AND LARGE. "I always do not like it when I'm boxed in when I'm
     typing and I do not see the entire text I'm typing." The plate is the box,
     which is exactly why the field is allowed not to be one. */
  min-height:3.2em;max-height:40vh;
}
.ask-plate textarea::placeholder{color:var(--muted)}
/* ⚠️ overflow flips to auto only past the cap, so a long question scrolls
   instead of pushing the page apart. */
.ask-plate textarea.is-capped{overflow-y:auto}

/* ══ THE TWO MODES ══════════════════════════════════════════════════════════
   ⛔ THE TABS ARE PART OF THE PLATE, NOT A BAR ABOVE IT. Stephan counted the
   parts once — footer, a control at each end, send, the allowance, the
   disclosure — and said "too many parts going on"; four passes rearranged six
   loose things before the answer turned out to be that they stop being
   separate. A tab row floating above the plate would be a seventh part and
   would undo exactly that. So it is a HEAD matching the tool row's FOOT: same
   hairline, same mono label, inside the same frame.

   ⛔ AND `[hidden]` GETS ITS OWN RULE WHEREVER WE SET A DISPLAY. That attribute
   is a single user-agent declaration and any author `display` beats it by
   ORIGIN, before specificity is consulted — the bug that served "Sign up" to a
   member who had already paid. */
.ask-body[hidden], .ask-tools-ask[hidden], .ask-chips[hidden],
.ask-disc[hidden] { display: none !important; }

/* ⛔ THE MODE SWITCH IS A CROSS-FADE, AND IT CANNOT BE A TRANSITION.
   `display` does not animate, and these two bodies are toggled with `hidden`
   — which is also what keeps them out of the tab order and the accessibility
   tree, so replacing it with opacity alone would leave an invisible textarea
   focusable behind the picker. So the INCOMING body is animated on arrival
   instead: the outgoing one is simply gone, and what the eye reads as a
   cross-fade is the new panel rising into a row whose height never moved.
   ⚠️ Gated on .has-switched so the page does not animate its own first
   paint — an entrance nobody asked for reads as a flicker on load. */
.ask-console.has-switched .ask-body:not([hidden]),
.ask-console.has-switched .ask-tools-ask:not([hidden]) {
  animation: askModeIn var(--glide) cubic-bezier(.2,.7,.3,1) both;
}
@keyframes askModeIn {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}
/* the tab label settles at the same pace as the panel it names */
.ask-mode { transition: color var(--swift), border-bottom-color var(--swift); }

@media (prefers-reduced-motion: reduce) {
  /* ⛔ THE STATES SURVIVE, ONLY THE MOTION GOES — the same rule the orb
     follows. A reduced-motion reader still needs the panel to appear. */
  .ask-console.has-switched .ask-body:not([hidden]),
  .ask-console.has-switched .ask-tools-ask:not([hidden]) { animation: none; }
  .ask-chips { transition: none; }
}

.ask-modes {
  display: flex; gap: var(--s4);
  margin-bottom: var(--s3); padding-bottom: var(--s3);
  border-bottom: 1px solid var(--panel-border);
}
.ask-mode {
  background: none; border: 0; padding: 0 0 2px; cursor: pointer;
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--text-dim); border-bottom: 1px solid transparent;
  transition: color var(--swift), border-color var(--swift);
}
.ask-mode:hover { color: var(--text); }
/* ⚠️ THE SELECTED TAB IS THE ONE THING HERE THAT EARNS GOLD. Gold is reserved
   for structure and meaning; which mode you are in is the state the whole
   console depends on, so it qualifies — and nothing else in this row does. */
.ask-mode.is-on { color: var(--gold); border-bottom-color: var(--gold); }

.ask-tools-ask { display: flex; align-items: center; gap: var(--s2); flex: 1 1 auto; }

/* the picker shares the writing surface's room, so the plate does not resize
   between modes — a frame that jumps when you switch tab reads as a fault */
/* ⛔ 3.2em MEANT TWO DIFFERENT NUMBERS IN TWO PLACES, AND THAT WAS THE JUMP.
   .ask-plate textarea reserves `min-height:3.2em` against --t-lead (22.4px),
   so the Ask body stands 71.6px tall. This rule said 3.2em too — but em here
   resolves against the INHERITED 16px, giving 51.2px. Identical intent,
   identical number, a 20px difference on screen, and the plate shrank every
   time Stephan pressed Plan.
   ⚠️ So it is stated in the writing surface's own basis instead of being
   retyped as a pixel value: change --t-lead and both move together. */
#bodyPlan { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
            min-height: calc(3.2 * var(--t-lead)); }
/* ⛔ THE SELECTS ARE STYLED ONCE, IN .road-fields, AND NOT AGAIN HERE.
   This block used to re-declare them — and an ID selector beats a class by
   specificity, so `#bodyPlan select` silently overrode `.road-fields select`
   INCLUDING the @media rule that gives each field its own line on a phone.
   The Command Hub's picker went full-width at 720px and the Relay's did not,
   from one copy of a declaration nobody knew was fighting the original.
   ⚠️ Only the note field is declared here, because it is the one control
   that belongs to this page rather than to the picker. */
#bodyPlan input[type="text"] {
  background: var(--bg); border: 1px solid var(--panel-border); border-radius: 2px;
  color: var(--text); padding: .55rem .65rem; font-size: var(--t-sm);
  font-family: var(--font-mono); color-scheme: dark;
  flex: 1 1 160px; min-width: 0;
}
#bodyPlan input:focus { outline: none; border-color: var(--gold); }
#bodyPlan input::placeholder { color: var(--muted); opacity: 1; }
.ask-plan-invite { margin: 0; font-size: var(--t-sm); color: var(--muted); }
.ask-plan-invite a { color: var(--gold); }

/* ── A BUILT PLAN, SHOWN WHERE IT WAS ASKED FOR ──────────────────────────────
   ⛔ THE STAGE WAS SIZED FOR FIVE WORDS. It holds three turns with the older two
   receding, which is right for an answer and wrong for a document — a roadmap is
   6 to 20 steps. So the CURRENT turn is allowed to grow and scroll inside the
   frame while the receded ones stay exactly as they were: the console does not
   move, which is the whole reason the stage is a fixed frame in the first place.
   ⚠️ Only the current turn scrolls. A receded card is a thumbnail of something
   you have moved past, and giving it its own scrollbar would invite fiddling
   with a thing that is on its way out. */
.ask-turn--plan .ask-plan-card {
  max-height: 46vh; overflow-y: auto; margin-top: var(--s3);
  /* the card draws its own ground, so the turn does not double it */
  background: none; border: 0; padding: 0;
}
.ask-turn--plan.is-past .ask-plan-card,
.ask-turn--plan.is-past-2 .ask-plan-card { max-height: 6rem; overflow: hidden; }

/* ⚠️ THE CARD IS THE HUB'S CARD, UNCHANGED — one renderer, so a preview cannot
   drift from the roadmap it becomes. It only needs its outer margin removed
   here, because a shared component declares no outer margin and the turn owns
   the spacing. */
.ask-plan-card .road { margin: 0; }

/* ⛔ SAVE READS AS THE ACTION, NOT AS A DESTRUCTIVE ONE. Archive on a live card
   is quiet and neutral; this is the thing you came to do, so it earns the
   accent — gold marks structure and meaning, and "keep this" is both. */
.road-save {
  color: var(--gold); border-color: var(--gold);
}
.road-save:hover { background: var(--bg-alt); }
/* ⚠️ AND ONCE SAVED IT IS A STATEMENT OF FACT, not a second offer — saving
   twice would make two identical roadmaps out of one decision. */
.road-save.is-saved {
  color: var(--muted); border-color: var(--panel-border);
  cursor: default; pointer-events: none;
}

/* the link out of a finished plan, in the stage beside the ⊙ mark */
.ask-plan-go {
  display: inline-block; margin-top: var(--s2);
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); color: var(--gold);
}

@media (max-width: 720px) {
  /* ⚠️ the selects are handled by .road-fields' own @media rule, which can
     finally reach them now that no ID selector outranks it */
  #bodyPlan input[type="text"] { flex: 1 1 100%; }

  /* ⛔ AT THIS WIDTH THE PICKER IS THE TALLER MODE, so the reserve runs the
     other way: the kinds switch, the goal select and the note each take their
     own line, and the writing surface matches that height so the plate still
     never resizes. The Ask field simply gets roomier on a phone, which is the
     right direction for the control somebody types into with a thumb.
     ⚠️ THIS ONE IS MEASURED, NOT DERIVED — three stacked rows plus their
     gaps have no token to express them. tools/render-relay.js holds the two
     modes to the same height at 390px, so if the picker ever grows a field
     this value fails loudly instead of quietly reintroducing the jump. */
  #bodyAsk, #bodyPlan, .ask-plate textarea { min-height: 125px; }
}

/* the tool row, inside the plate, under its own hairline */
.ask-tools{
  display:flex;align-items:center;gap:var(--s2);
  margin-top:var(--s3);padding-top:var(--s3);
  border-top:1px solid var(--panel-border);
}
.ask-tool{
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--font-mono);font-size:var(--t-micro);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--text-dim);
  background:none;border:0;border-radius:2px;padding:5px 7px;cursor:pointer;
  line-height:1;white-space:nowrap;transition:color var(--swift),background var(--swift);
}
.ask-tool svg{width:13px;height:13px;flex:0 0 auto;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.ask-tool:hover{color:var(--gold-bright);background:var(--panel)}
.ask-tool:focus-visible{outline:2px solid var(--gold-bright);outline-offset:1px}
.ask-tool b{color:var(--gold);font-weight:500}
/* the door to the drawer is the lit one; starting another is the quiet move */
.ask-hist-open{color:var(--gold)}

/* ⚠️ `auto` here, so the allowance and send sit right whether or not the two
   conversation tools rendered — a signed-out visitor has neither. */
.ask-quota{
  margin-left:auto;font-family:var(--font-mono);font-size:9px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
  font-variant-numeric:tabular-nums;
}
/* ⛔ SEND HUGS THE RIGHT EDGE BY ITS OWN MARGIN, NOT BY A NEIGHBOUR'S FLEX.
   It used to be pushed there by .ask-tools-ask growing to fill the row — so
   the moment that wrapper was hidden in Plan mode, or emptied of everything but
   the allowance, Send slid back and sat against the other controls. A control's
   position may not depend on whether a SIBLING happens to be visible; that is
   the same class of fault as a layout that deletes a legal notice. */
.ask-send{
  margin-left:auto;
  flex:0 0 auto;width:34px;height:34px;display:grid;place-items:center;
  background:none;border:1px solid var(--panel-border);border-radius:50%;
  color:var(--muted);cursor:pointer;opacity:.45;
  transition:color var(--swift),border-color var(--swift),background var(--swift),opacity var(--swift);
}
.ask-send svg{width:16px;height:16px;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
/* the control wakes up when there is something to send — an affordance that
   answers "will this do anything" before the click */
.ask-plate.has-text .ask-send{opacity:1;color:var(--gold);border-color:var(--gold-dim)}
.ask-plate.has-text .ask-send:hover{background:var(--gold);color:var(--bg);border-color:var(--gold)}
.ask-send:disabled{opacity:.25;cursor:not-allowed;background:none;color:var(--muted)}
.ask-send:focus-visible{outline:2px solid var(--gold-bright);outline-offset:2px}

/* ── chips: short questions fit chips, which is why they are chips ───────── */
.ask-chips{display:flex;gap:var(--s2);flex-wrap:wrap;justify-content:center;margin-top:var(--s4)}
/* ⛔ QUIET, NOT GONE — SEE setMode() IN js/ask.js. The row holds its own
   height, so switching Ask↔Plan moves nothing below it. Fading rather than
   snapping means the chips leave with the mode rather than blinking out. */
.ask-chips{transition:opacity var(--swift) linear,visibility 0s linear var(--swift)}
.ask-chips.is-quiet{visibility:hidden;opacity:0}
.ask-chips:not(.is-quiet){transition-delay:0s,0s}
.ask-chips button{
  font-size:var(--t-sm);color:var(--text-dim);background:rgba(23,23,23,.55);
  border:1px solid var(--panel-border);border-radius:999px;padding:5px 13px;
  cursor:pointer;font-family:var(--font-body);
  transition:color var(--swift),border-color var(--swift),background var(--swift);
}
.ask-chips button:hover{color:var(--gold-bright);border-color:var(--gold-dim);background:var(--panel)}

/* ── the control bar ─────────────────────────────────────────────────────── */
/* ⛔ THESE ARE THE PAGE'S TWO MAIN CONTROLS AND THEY READ AS SMALL PRINT.
   They were 9px text sharing a line with the vendor notice — Stephan: "it looks
   more like it's in the credits down there." A control is not made important by
   being told it is; it needs a surface, a size and a boundary. */
/* ⛔ AND THE FIRST FIX WENT TOO FAR THE OTHER WAY: a filled, bordered card is
   the ONE heavy object on a page made entirely of hairlines, so it stopped
   reading as small print and started reading as a slab. Stephan: "too dominant
   … this needs to be lighter." The page's whole language is unboxed — the
   composer is a rule, the chips are ghosts, the answer is bare type — so these
   get their weight from SIZE, COLOUR and SPACE instead of from a box. No fill,
   no border: that is the same call as killing the card round the thread. */
/* ⛔ STILL BESIDE THE FIELD, JUST NOT COMPETING WITH THE BUTTONS. The rule is
   that the disclosure sits on the control and not only in the policy — one line
   under the bar is still on the control. */
/* ⚠️ ONE quiet line under the console, CENTRED — the page has no site footer,
   so this is its last edge and a ragged right-aligned stack would be the only
   thing under a symmetrical instrument. */
.ask-disclosure{
  width:100%;margin:var(--s4) 0 0;text-align:center;
  font-family:var(--font-mono);font-size:9px;line-height:1.9;letter-spacing:.1em;
  color:var(--muted);
}
/* the notice the missing footer used to carry — the page's last edge. Rendered
   in BOTH states, because closing the Relay must not delete a legal line. */
.ask-de{
  width:100%;margin:var(--s7) 0 0;text-align:center;
  font-family:var(--font-mono);font-size:9px;line-height:1.9;letter-spacing:.1em;
  color:var(--muted);opacity:.72;
}
.ask-de a{color:var(--muted);text-decoration:underline;text-underline-offset:3px}
.ask-de a:hover{color:var(--gold)}
.ask-disclosure a{color:var(--muted);text-decoration:underline;text-underline-offset:3px}
.ask-disclosure a:hover{color:var(--gold)}

/* ⛔ IT SHIPS `hidden`, SO IT CARRIES ITS OWN DISPLAY OVERRIDE. `[hidden]` is
   one user-agent rule and any author `display` beats it by ORIGIN — the trap
   that sold "Sign up" to a member who had already paid, on Mars Deck. */
.ask-new[hidden]{display:none!important}

.ask-signin{text-align:center;color:var(--text-dim);font-size:var(--t-sm);margin:var(--s6) 0 0}
.ask-signin a{color:var(--gold);text-decoration:underline;text-underline-offset:3px}

/* ── the drawer ──────────────────────────────────────────────────────────── */
/* ⛔ `[hidden]` IS ONE USER-AGENT RULE AND ANY AUTHOR `display` BEATS IT BY
   ORIGIN. This exact trap shipped a "Sign up" button to a paying member on Mars
   Deck. Both of these set `display`, so both need the override. */
.ask-scrim[hidden],.ask-drawer[hidden]{display:none!important}
/* ⛔ ABOVE `.site-header`, WHICH IS z-index 100 — AND THAT ONLY WORKS BECAUSE
   THE MARKUP LEFT `.page-content`. Inside it these numbers were resolved within
   a stacking context worth 1, so the drawer slid in UNDER the navigation and no
   value here could have lifted it. The fix is structural; these numbers only
   state the intent. */
.ask-scrim{
  position:fixed;inset:0;z-index:990;background:rgba(0,0,0,.6);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  opacity:0;transition:opacity var(--glide);
}
.ask-scrim.is-open{opacity:1}
.ask-drawer{
  /* ⚠️ 520, not 400: this panel now carries plans, conversations AND answers,
     and an answer is a paragraph. Stephan: "we have a lot of space here."
     Still capped — on a wide monitor the professional answer is a readable
     column, never a panel that grows with the window. */
  position:fixed;top:0;right:0;bottom:0;z-index:1000;width:min(520px,94vw);
  background:var(--bg-alt);border-left:1px solid var(--panel-border);
  display:flex;flex-direction:column;
  transform:translateX(100%);transition:transform var(--glide);
  box-shadow:-24px 0 60px rgba(0,0,0,.5);
}
.ask-drawer.is-open{transform:translateX(0)}
.ask-drawer-head{
  display:flex;align-items:center;gap:var(--s3);
  padding:var(--s5) var(--s5) var(--s3);
}
.ask-drawer-head .market-section-label{margin:0;text-align:left;flex:1}
.ask-drawer-close{
  background:none;border:0;color:var(--muted);font-size:1rem;cursor:pointer;
  width:30px;height:30px;border-radius:2px;transition:color var(--swift)
}
.ask-drawer-close:hover{color:var(--gold-bright)}
.ask-drawer-close:focus-visible{outline:2px solid var(--gold-bright);outline-offset:2px}

/* ── the conversations ───────────────────────────────────────────────────── */
/* ⚠️ CAPPED AND SCROLLED IN ITS OWN BOX. Two lists in one column, both free to
   grow, is the layout fight that broke the first build — the answers list below
   owns `flex:1` and this one may never take it. */
.ask-threads{
  padding:0 var(--s5) var(--s4);max-height:min(34vh,220px);
  overflow-y:auto;flex:0 0 auto;
}
.ask-threads-label{
  margin:0 0 var(--s2);font-family:var(--font-mono);font-size:9px;
  letter-spacing:var(--track-max);text-transform:uppercase;color:var(--muted);
}
.ask-threads-none[hidden]{display:none!important}
.ask-threads-none{margin:0 0 var(--s2);color:var(--muted);font-size:var(--t-sm)}
.ask-answers-label{padding:0 var(--s5);margin:var(--s2) 0}
.ask-threads ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.ask-thread{
  display:flex;align-items:baseline;gap:var(--s3);width:100%;text-align:left;
  background:none;border:0;border-left:2px solid transparent;
  padding:6px var(--s2) 6px var(--s3);cursor:pointer;color:var(--text-dim);
  transition:color var(--swift),border-color var(--swift),background var(--swift);
}
.ask-thread:hover{color:var(--text);background:var(--panel)}
.ask-thread.is-current{border-left-color:var(--gold);color:var(--gold)}
.ask-thread:focus-visible{outline:2px solid var(--gold-bright);outline-offset:-2px}
.ask-thread-title{
  flex:1;min-width:0;font-size:var(--t-sm);line-height:1.35;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.ask-thread-n{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;color:var(--muted);
  font-variant-numeric:tabular-nums;
}

/* ⚠️ ONE SURFACE, TWO VIEWS. The library is the same rows under a filter —
   a second section was inventing a second place for one thing. */
.ask-filter{display:flex;gap:var(--s2);padding:0 var(--s5) var(--s4)}
.ask-filter button{
  font-family:var(--font-mono);font-size:var(--t-micro);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--text-dim);background:none;
  border:1px solid var(--panel-border);border-radius:2px;padding:4px 10px;cursor:pointer;
  transition:color var(--swift),border-color var(--swift);
}
.ask-filter button[aria-pressed="true"]{border-color:var(--gold);color:var(--gold)}
.ask-filter button b{color:var(--muted);font-weight:400}
.ask-filter button[aria-pressed="true"] b{color:var(--gold-bright)}
.ask-filter button:focus-visible{outline:2px solid var(--gold-bright);outline-offset:2px}

/* ── the Records panel: Command Hub link, then Plans ───────────────────
   ⚠️ The link is a ROW, not a button. It leaves the page, and the page's own
   language for leaving is a full-width rule with an arrow — the same shape the
   Tenno page uses for Elsewhere. A filled button here would outrank Save, which
   is the control that actually does something. */
.ask-hub-link{
  display:flex;align-items:center;gap:var(--s2);
  margin:0 var(--s5) var(--s4);padding:var(--s3) var(--s3);
  border:1px solid var(--panel-border);border-radius:2px;
  color:var(--text-dim);text-decoration:none;
  font-family:var(--font-mono);font-size:var(--t-micro);
  letter-spacing:var(--track-wide);text-transform:uppercase;
  transition:color var(--swift),border-color var(--swift),background var(--swift);
}
.ask-hub-link svg{width:14px;height:14px;flex:0 0 auto;fill:none;
  stroke:currentColor;stroke-width:1.6;stroke-linecap:round}
.ask-hub-link span{flex:1}
.ask-hub-link b{font-weight:400}
.ask-hub-link:hover{color:var(--gold);border-color:var(--gold-dim);background:var(--panel)}
.ask-hub-link:focus-visible{outline:2px solid var(--gold-bright);outline-offset:2px}

.ask-plans{padding:0 var(--s5)}
.ask-plan-list{list-style:none;margin:0 0 var(--s2);padding:0}
/* ⛔ ONE ROW SHAPE FOR BOTH KINDS. A saved plan is an <a> and a session
   preview is a <button>, because one navigates and one re-opens a card — but
   they are the same object to the reader, so they get the same box. */
.ask-plan-row{
  display:flex;align-items:center;gap:var(--s2);width:100%;
  padding:var(--s2) 0;background:none;border:0;border-top:1px solid var(--hairline);
  color:var(--text);text-align:left;text-decoration:none;cursor:pointer;
  font-family:var(--font-body);font-size:var(--t-sm);
  transition:color var(--swift);
}
.ask-plan-list li:first-child .ask-plan-row{border-top:0}
.ask-plan-row:hover{color:var(--gold)}
.ask-plan-row:focus-visible{outline:2px solid var(--gold-bright);outline-offset:2px}
.ask-plan-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ask-plan-meta{
  flex:0 0 auto;font-family:var(--font-mono);font-size:var(--t-micro);
  letter-spacing:var(--track-wide);color:var(--muted);text-transform:uppercase;
}
/* ⚠️ AN UNSAVED ROW SAYS SO IN THE ONE PLACE A READER LOOKS. It lives only
   as long as the tab does; styling it identically to a saved plan would be
   decoration implying a state it cannot keep. */
.ask-plan-row.is-unsaved .ask-plan-name{color:var(--text-dim)}

.ask-hist{list-style:none;margin:0;padding:0 var(--s5) var(--s6);overflow-y:auto;flex:1}
.ask-hist-item{padding:var(--s4) 0;border-top:1px solid var(--edge,rgba(255,255,255,.05))}
.ask-hist-item:first-child{border-top:0}
.ask-hist-item[hidden]{display:none!important}
.ask-hist-when{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted);display:block;margin-bottom:3px}
.ask-hist-q{color:var(--text);margin:0 0 4px;font-size:var(--t-sm);line-height:1.45}
/* the same provenance rule as the stage, at rail scale */
.ask-hist-a{color:var(--text-dim);font-size:var(--t-sm);margin:0 0 var(--s2);line-height:1.55;
  padding-left:var(--s3);border-left:2px solid var(--panel-border)}
.ask-a--computed {border-left-color:var(--gold)}
.ask-a--reference{border-left-color:rgba(127,142,160,.5)}
.ask-a--unknown  {border-left-color:var(--muted)}
.ask-hist-foot{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}
.ask-hist-none{color:var(--muted);font-size:var(--t-sm);padding:0 var(--s5);margin:0}

.ask-save{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-dim);background:none;border:1px solid var(--panel-border);
  border-radius:2px;padding:2px 6px;cursor:pointer;
  transition:color var(--swift),border-color var(--swift);
}
.ask-save:hover{color:var(--gold-bright);border-color:var(--gold-dim)}
.ask-save[aria-pressed="true"]{border-color:var(--gold);color:var(--gold)}
.ask-save:focus-visible{outline:2px solid var(--gold-bright);outline-offset:2px}
.ask-save.is-error{border-color:#e26a55;color:#e26a55}
.ask-save .on{display:none}
.ask-save.is-on .on{display:inline}
.ask-save.is-on .off{display:none}

/* ⚠️ On a phone the orb stops being the hero and becomes a mark, at a size that
   reads without eating the fold — and the stage gives back its reserved height,
   because a fixed frame on a short screen is the frame winning over the
   content. */
@media (max-width:700px){
  .ask{padding-top:0}
  .ask-orb-wrap{width:150px;margin-bottom:var(--s4)}
  .ask-stage{min-height:120px}
  .ask-a{font-size:var(--t-lead)}
  .ask-vendor{margin-left:0}
}
@media (prefers-reduced-motion:reduce){
  .ask-orb-wrap::before,.ask-console.is-answering .ask-orb-wrap::after{animation:none}
  .ask-turn{transition:none}
}


/* ═══ THE COMMAND HUB — a station, not a settings page ══════════════════════
   Stephan: "make it a real command tab … something you really want to have
   online on a screen while playing Warframe. It looks interactive. It feels
   alive." A dashboard earns a second monitor by CHANGING, and everything here
   was a static number about an account. */

/* ⛔ THE DRAWN GROUND, replacing a 2.3 MB photograph. A command grid: a fine
   lattice that reads as an instrument surface, fading out before it reaches the
   content so nothing has to be scrimmed into mud to stay legible. */
.page-bg--hub::before{
  content:"";position:absolute;inset:0;
  background:
    repeating-linear-gradient(90deg, rgba(212,175,55,.030) 0 1px, transparent 1px 96px),
    repeating-linear-gradient(0deg,  rgba(212,175,55,.022) 0 1px, transparent 1px 96px);
  -webkit-mask-image:radial-gradient(ellipse 120% 80% at 50% 0%, #000 15%, transparent 72%);
          mask-image:radial-gradient(ellipse 120% 80% at 50% 0%, #000 15%, transparent 72%);
}

/* ── the live band ─────────────────────────────────────────────────────────
   ⚜️ Six cells, ticking, computed by EPHEMERIS with no network at all. */
.ops{
  display:grid;grid-template-columns:repeat(6,1fr);gap:1px;
  width:100%;margin:var(--s5) 0 var(--s2);
  background:var(--panel-border);          /* the 1px gaps ARE the hairlines */
  border:1px solid var(--panel-border);border-radius:2px;overflow:hidden;
}
.ops-cell{
  position:relative;display:flex;flex-direction:column;gap:3px;
  padding:var(--s3) var(--s3) calc(var(--s3) + 3px);
  background:var(--bg-alt);min-width:0;
}
.ops-where{
  font-family:var(--font-mono);font-size:9px;letter-spacing:var(--track-max);
  text-transform:uppercase;color:var(--muted);
}
.ops-state{
  font-family:var(--font-head);font-size:var(--t-sm);font-weight:500;
  text-transform:uppercase;letter-spacing:var(--track);
  color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  transition:color var(--glide);
}
/* ⛔ a WORD carries the state and the colour only reinforces it — the nav spent
   today proving that a colour alone is not a state */
.ops-cell[data-tone="warm"] .ops-state{color:var(--gold)}
.ops-cell[data-tone="live"] .ops-state{color:var(--gold-bright)}
.ops-time{
  font-family:var(--font-mono);font-size:var(--t-base);color:var(--text);
  font-variant-numeric:tabular-nums;letter-spacing:var(--track-tight);
}
/* the share of THIS state already spent — derived from the engine's own two
   timestamps, never from a period constant (Cetus day and night differ) */
.ops-bar{
  position:absolute;left:0;right:0;bottom:0;height:2px;
  background:rgba(255,255,255,.045);overflow:hidden;
}
.ops-bar i{
  display:block;height:100%;width:100%;
  transform:scaleX(0);transform-origin:left center;
  background:var(--gold-dim);transition:transform 1s linear;
}
.ops-cell[data-tone="warm"] .ops-bar i{background:var(--gold)}
.ops-cell[data-tone="live"] .ops-bar i{background:var(--gold-bright)}
/* ⚠️ the engine is loaded site-wide, but if it ever is not, SAY SO rather than
   sit there showing dashes for ever — a fault must be loud */
.ops[data-engine="missing"] .ops-state{color:var(--muted);font-style:italic}
.ops[data-engine="missing"] .ops-bar{display:none}

.ops-foot{
  margin:0 0 var(--s5);text-align:right;
  font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;color:var(--muted);
}
.ops-foot a{color:var(--muted);text-decoration:underline;text-underline-offset:3px}
.ops-foot a:hover{color:var(--gold)}
.ops-foot-sep{opacity:.5;margin:0 4px}

/* Six across is right on a monitor and unreadable on a phone. Three, then two —
   never one: the point of the band is seeing the system at a glance. */
@media (max-width:900px){ .ops{grid-template-columns:repeat(3,1fr)} }
@media (max-width:480px){ .ops{grid-template-columns:repeat(2,1fr)} }

@media (prefers-reduced-motion:reduce){
  .ops-bar i{transition:none}
}


/* ═══ THE ROSTER ASSEMBLES ═════════════════════════════════════════════════
   ⛔ FROM A VISIBLE RESTING STATE. The cards used to carry
   `.scroll-reveal-item`, which parks an element at opacity 0 until an observer
   fires — so the first painted frame of /network/ was an EMPTY GRID, and that
   is exactly the frame a thumbnail and a shared link capture. Here the resting
   state is the finished state: no script, no animation, every card present.
   The entrance is added by js/network.js on load and plays once. */
.network-grid.is-entering .network-card {
  animation: netCardIn 460ms cubic-bezier(.22,.7,.3,1) backwards;
}
@keyframes netCardIn {
  from { opacity: 0; transform: translateY(16px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
/* ⚠️ A STAGGER THAT CANNOT RUN AWAY. Ten cards at 45ms is under half a second;
   a per-index delay with no ceiling would make a roster of sixty take three
   seconds to finish arriving, which stops reading as an entrance and starts
   reading as a slow page. Capped at the tenth card. */
.network-grid.is-entering .network-card:nth-child(1)  { animation-delay: 0ms; }
.network-grid.is-entering .network-card:nth-child(2)  { animation-delay: 45ms; }
.network-grid.is-entering .network-card:nth-child(3)  { animation-delay: 90ms; }
.network-grid.is-entering .network-card:nth-child(4)  { animation-delay: 135ms; }
.network-grid.is-entering .network-card:nth-child(5)  { animation-delay: 180ms; }
.network-grid.is-entering .network-card:nth-child(6)  { animation-delay: 225ms; }
.network-grid.is-entering .network-card:nth-child(7)  { animation-delay: 270ms; }
.network-grid.is-entering .network-card:nth-child(8)  { animation-delay: 315ms; }
.network-grid.is-entering .network-card:nth-child(9)  { animation-delay: 360ms; }
.network-grid.is-entering .network-card:nth-child(n+10) { animation-delay: 400ms; }


/* ⛔ SOMEBODY WHO ASKED THE OS FOR LESS MOTION GETS LESS MOTION — and the
   site-wide reveal never honoured it at all: the parked opacity and the
   translate applied regardless. Content visible, nothing moving. */
@media (prefers-reduced-motion: reduce) {
  .network-grid.is-entering .network-card { animation: none; }
  .scroll-reveal-item { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ═══ THE OPERATIVE SIGIL ═══════════════════════════════════════════════════
   ⛔ A DRAWN MARK WHERE A BLANK SQUARE USED TO BE. Seven of ten roster cards
   rendered Discord's `embed/avatars/0.png` — hardcoded index 0, so the same
   face ten times. php/sigil.php draws one per account instead.

   ⚠️ It is inline SVG on purpose, NOT a data-URI `<img>`: `currentColor` is the
   whole point, so a Corpus-blue account gets a blue mark for free and no copy
   of the six theme colours has to exist in PHP. Everything below is therefore
   about `color`, and the SIZE is inherited from whatever avatar class the slot
   already used — those are all class selectors, so a <span> wears them fine. */
.op-sigil-gen {
  display: grid; place-items: center; overflow: hidden;
  color: var(--tier-accent, var(--gold));
  background: none;
}
.op-sigil-gen > svg { width: 100%; height: 100%; display: block; }

/* the two slots that size their child rather than themselves */
.op-sigil > .op-sigil-gen,
.crew-av > .op-sigil-gen { width: 100%; height: 100%; }

/* ⚠️ round slots must clip the mark, or the faint 30r disc squares off the
   corners of a circular avatar — visible mainly at the small comment size.
   ⚠️ THE DOSSIER LEFT THIS GROUP 2026-09-13: it is notched now, not round. The
   three that remain are small inline slots sitting in rows of text, where a
   disc is the right shape and a notch would read as a chip of debris. */
.comment-avatar.op-sigil-gen,
.uc-avatar.op-sigil-gen,
.nav-avatar.op-sigil-gen { border-radius: 50%; }

/* ⛔ THE SIGIL IS NOTCHED, NOT ROUND — Stephan's call, and it is what the rest
   of this site already does. The crew list has clipped its avatars to this
   exact polygon since it shipped; the plate, the patch and every card carry the
   same cut corner. A circle in the middle of it was the one round object on a
   board built from angles.
   ⚠️ border-radius is set to 0 EXPLICITLY, because .dossier-avatar declares
   50% of its own further up — and clip-path does not override a radius, it
   composes with it: leaving both would clip the notch out of a circle. */
.dossier-avatar,
.dossier-avatar.op-sigil-gen {
  border-radius: 0;
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px,
                     100% 100%, 10px 100%, 0 calc(100% - 10px));
}
/* ⛔ AND THE PICKER PREVIEWS MATCH WHAT YOU WILL GET. A chooser showing round
   marks for a square result is a promise the page then breaks — the same fault
   as an empty state that implies a capability, only pointed at a shape. */
.sigil-cell-prev .op-sigil-gen {
  border-radius: 0;
  clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px,
                     100% 100%, 6px 100%, 0 calc(100% - 6px));
}

/* the nav sits on the header's own ground, not a panel */
.nav-avatar.op-sigil-gen { background: none; color: var(--gold); }

/* ⛔ an unfilled slot RECEDES; a locked one recedes further and says why on
   hover. Neither may compete with a real value for attention — 23 of 63 slots
   on the roster were shouting the word "Empty" in the same weight as a frame
   name, which is what made ten dossiers read as one grey field. */
.op-slot.is-empty .op-slot-val { opacity: .45; letter-spacing: var(--track-wide); }
.op-slot.is-locked { opacity: .55; }
.op-slot.is-locked svg { opacity: .6; }

/* ═══ THE SIGIL PICKER ══════════════════════════════════════════════════════
   Same shape as .banner-picker — one visual language for "choose your card
   artwork", not two. The tiles are square because the marks are. */
.sigil-picker {
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  margin-top: var(--s4);
}
.sigil-cell {
  display: flex; flex-direction: column; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s2);
  background: var(--panel); border: 1px solid var(--panel-border);
  color: var(--gold); cursor: pointer;
  transition: border-color var(--swift), background var(--swift), color var(--swift);
}
.sigil-cell:hover:not(:disabled) { border-color: var(--gold); background: var(--bg-alt); }
.sigil-cell:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.sigil-cell[aria-pressed="true"] {
  border-color: var(--gold); background: var(--bg-alt);
  box-shadow: inset 0 0 0 1px var(--gold);
}
.sigil-cell-prev { width: 48px; height: 48px; display: grid; place-items: center; }
.sigil-cell-prev > svg { width: 100%; height: 100%; display: block; }
/* the Automatic tile previews a real avatar, which arrives as an <img> */
.sigil-cell-prev > img,
.sigil-cell-prev .op-sigil-gen { width: 100%; height: 100%; object-fit: cover; }
.sigil-cell-cap { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.sigil-cell-nm {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--text);
}
/* ⚠️ A LOCKED TILE IS SHOWN, NOT HIDDEN — an absent tile tells a free member
   nothing, while a locked one names what it would take. It must still read as
   unavailable rather than as broken, so it recedes and keeps its label. */
.sigil-cell.is-locked { opacity: .4; cursor: not-allowed; }
.sigil-cell.is-locked:hover { border-color: var(--panel-border); background: var(--panel); }
.sigil-cell-tr {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); color: var(--muted);
}
.op-intro-note { display: block; margin-top: var(--s2); color: var(--muted); }

/* the chosen mark, wherever it renders */
.op-sigil-pick > svg { width: 100%; height: 100%; display: block; }

/* ═══ SIGIL INK AND GROUND ══════════════════════════════════════════════════
   ⛔ THE COLOURS LIVE HERE AND NOWHERE ELSE. php/sigil.php holds the KEYS and
   the labels; these rules hold the values. A `['corpus' => '#4ab0cc']` map in
   PHP would be a second copy of a palette, and the copy is the one that rots —
   `.patron-badge--founder` was exactly that: a PHP slug whose stylesheet rule
   still said `--warlord`, so the $29 badge had no colour for five days and
   nothing errored. tools/verify-sigil.php parses THIS BLOCK, proves every key
   has a rule, and computes the contrast from what it finds.

   ⚠️ NO INK KEY MAY BE OMITTED HERE. A missing rule is not a fallback — the
   mark would silently keep the accent while the picker showed it as chosen. */
.op-sigil-gen[data-ink="gold"]    { color: #d4af37; }
.op-sigil-gen[data-ink="silver"]  { color: #c8c5bc; }
.op-sigil-gen[data-ink="corpus"]  { color: #5cc0dc; }
.op-sigil-gen[data-ink="grineer"] { color: #e07038; }
.op-sigil-gen[data-ink="void"]    { color: #9c7ce8; }
.op-sigil-gen[data-ink="steel"]   { color: #8fb0c8; }
.op-sigil-gen[data-ink="infest"]  { color: #7fc08a; }
.op-sigil-gen[data-ink="oro"]     { color: #ece4d0; }

/* ⚠️ THE GROUNDS ARE DELIBERATELY FAINT. He asked for the ring to come off
   because ten framed avatars read as weight; a saturated plate behind every
   mark would put that weight straight back in a brighter colour. These are
   tints — enough to separate a mark from the card, never enough to become the
   thing you notice first. `plate` is the one neutral, for a mark that wants a
   surface rather than a hue. */
/* ⛔ THESE WERE TOO CLOSE TO THE CARD TO EXIST. `plate` was #1e1e28 sitting on
   a #171717 card — eight units of separation, which is not a ground, it is a
   rumour of one. The tints at .13 alpha were the same problem in colour. A
   ground either reads as a surface the mark sits ON or it should not be drawn.
   ⚠️ The inset hairline is what actually makes it an object: at 56px behind a
   notched clip-path, one etched pixel does more than any amount of fill. */
.op-sigil-gen[data-ground] {
  background: rgba(255,255,255,.06);
  box-shadow: inset 0 0 0 1px var(--etch);
}
.op-sigil-gen[data-ground="plate"]         { background: #242424; }
.op-sigil-gen[data-ground="gold"]          { background: rgba(212,175,55,.20); }
.op-sigil-gen[data-ground="corpus"]        { background: rgba(92,192,220,.20); }
.op-sigil-gen[data-ground="grineer"]       { background: rgba(224,112,56,.20); }
.op-sigil-gen[data-ground="void"]          { background: rgba(156,124,232,.21); }
.op-sigil-gen[data-ground="infest"]        { background: rgba(127,192,138,.20); }

/* the swatch rows in the picker */
.sigil-colours { display: grid; gap: var(--s4); margin-top: var(--s4); }
.sigil-row-lbl {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted);
  margin-bottom: var(--s2); display: block;
}
.sigil-swatches { display: flex; flex-wrap: wrap; gap: var(--s2); }
.sigil-sw {
  width: 34px; height: 34px; padding: 0; cursor: pointer;
  border: 1px solid var(--panel-border); background: var(--panel);
  display: grid; place-items: center;
  transition: border-color var(--swift), transform var(--swift);
}
.sigil-sw:hover { border-color: var(--gold); }
.sigil-sw:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.sigil-sw[aria-pressed="true"] { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
.sigil-sw i { display: block; width: 18px; height: 18px; border-radius: 50%; background: currentColor; }
/* ⚠️ "Accent" and "None" are CHOICES, not empty states, so each gets a mark of
   its own rather than an empty box that reads as a rendering fault. */
.sigil-sw--auto i { background: var(--gold); box-shadow: inset 0 0 0 2px var(--panel); }
.sigil-sw--none i { background: none; border: 1px solid var(--muted); position: relative; overflow: hidden; }
.sigil-sw--none i::after {
  content: ""; position: absolute; left: -2px; top: 7px; width: 22px; height: 1px;
  background: var(--muted); transform: rotate(-45deg);
}

/* ═══ THE TENNO PAGE HERO ═══════════════════════════════════════════════════
   ⛔ IT FADES OUT INSTEAD OF SITTING BEHIND THE TEXT. A full-page photograph
   under body copy is precisely what came off every other page in the 45 MB →
   5.7 MB pass: it buys atmosphere and spends legibility. Here the photograph
   owns the top of the page and hands over to the page's own ground. */
/* ⛔ MEASURED IN THE VIEWPORT'S HEIGHT, NOT ITS WIDTH. This was
   `clamp(220px, 34vw, 420px)` — a width-driven band, which is why it read as a
   letterbox strip pasted across the top rather than as the room the card is
   standing in. Stephan asked for "at least 40% of the site", and 40% of the
   site is a vertical measurement.
   ⚠️ The 320px floor is what keeps the promise on a short window: below a
   ~700px-tall viewport 46vh would fall under 320px, and the floor is then MORE
   than 40%, never less. Above it 46vh carries. The 720px ceiling stops a 4K
   display handing over two thirds of the screen to a backdrop. */
.tp-hero {
  position: absolute; inset: 0 0 auto 0; height: clamp(320px, 46vh, 720px);
  overflow: hidden; pointer-events: none; z-index: 0;
}
.tp-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ⚠️ TWO masks, deliberately. The gradient alone leaves a hard edge on browsers
   that ignore mask-image, so the overlay does the fade and the mask sharpens it
   where supported — degrading to a scrim rather than to a photograph with a
   guillotine across it. */
/* ⛔ ONE HERO FADE, TWO HOMES. The Tenno page and the Appearance preview were
   fading their hero by different numbers, so the preview showed the whole
   photograph edge to edge while the real page masks it out from 40% down — the
   preview was flattering the choice rather than showing it.
   ⚠️ Shared as a selector list rather than copied: two declarations that agree
   today drift the first time either is nudged, and the preview's entire value
   is that it cannot disagree with the page.
   ⚠️ `--hero-fade-to` is the only difference, because the two fade into
   different grounds — the page into --bg, the boxed stage into --bg-alt. */
/* ⚜️ DARKER, 2026-09-07 — .35/.55 became .58/.74. The hero got half again as
   tall, and area and brightness trade against each other: the same exposure
   over a much bigger surface stops being atmosphere and starts being a
   photograph the card is sitting on top of.
   ⛔ THE DARKENING IS IN THE SCRIM ALPHA, NEVER A `filter: brightness()`. A
   filter on a 1920px image is a full-surface repaint on every composite, and it
   degrades to nothing where it is unsupported — a scrim degrades to a flat
   wash, which is the safe direction. Already on record from the mobile pass.
   ⚠️ AND IT STAYS SHARED WITH THE APPEARANCE PREVIEW ON PURPOSE. The preview's
   entire value is that it cannot disagree with the page; darkening only the
   page would put the preview back to flattering the member's choice, which is
   the exact fault this selector list was written to fix. */
/* ⚠️ THE SCRIM IS THE PAGE GROUND AT FOUR OPACITIES, AND IT HAS TO STAY THAT
   WAY. Its last stop is already `var(--bg)`; the first three were the hand-typed
   `rgba(6,6,6,…)` that happens to equal it today. Change `--bg` and the fade
   would arrive at a different black than the page it fades into — a visible
   seam straight across the top of every Tenno page, from one token edit nobody
   would connect to it. Derived now, so the seam cannot open.
   ⚠️ Declared TWICE: plain rgba first, color-mix second. That is the house
   pattern for every --etch variant, and it means a browser without color-mix
   gets exactly today's rendering rather than no scrim at all. */
.tp-hero::after,
.look-stage-hero::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    rgba(6,6,6,.58) 0%, rgba(6,6,6,.74) 45%, rgba(6,6,6,.95) 82%,
    var(--hero-fade-to, var(--bg)) 100%);
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--bg) 58%, transparent) 0%,
    color-mix(in srgb, var(--bg) 74%, transparent) 45%,
    color-mix(in srgb, var(--bg) 95%, transparent) 82%,
    var(--hero-fade-to, var(--bg)) 100%);
}
@supports (mask-image: linear-gradient(#000, transparent)) {
  /* ⚠️ THE MASK IS WHAT STOPS YOU SEEING THE WHOLE PHOTOGRAPH. Without it the
     preview showed the image full-bleed to its own edge while the real page
     dissolved it from 40% — the two pictures disagreed about the one thing
     the hero actually does. */
  .tp-hero img,
  .look-stage-hero img { mask-image: linear-gradient(to bottom, #000 40%, transparent 100%); }
}
/* the card must sit above the photograph it is introduced by */
.tp-page { position: relative; z-index: 1; }

/* ═══ THE FRAME PORTRAIT, WITH THE SIGIL ON TOP ═════════════════════════════
   ⛔ ONE MARK, NOT TWO. The sigil is the identity everywhere on the site; the
   frame is the portrait, and it appears on this page alone. Overlapping them
   reads as one object — set side by side they would compete, and a member would
   look like they had two avatars. */
.tp-portrait {
  position: relative; width: 128px; height: 128px; flex: 0 0 128px;
  overflow: hidden;
  clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 14px 100%, 0 calc(100% - 14px));
}
.tp-portrait > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tp-portrait-sigil {
  position: absolute; right: -2px; bottom: -2px; width: 46px; height: 46px;
  display: grid; place-items: center;
  background: var(--bg); color: var(--tier-accent, var(--gold));
  clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 8px 100%, 0 calc(100% - 8px));
}
.tp-portrait-sigil .op-sigil-gen,
.tp-portrait-sigil > img { width: 38px; height: 38px; }
.tp-portrait-sigil > img { object-fit: cover; }

@media (max-width: 560px) {
  .tp-portrait { width: 96px; height: 96px; flex: 0 0 96px; }
  .tp-portrait-sigil { width: 36px; height: 36px; }
  .tp-portrait-sigil .op-sigil-gen, .tp-portrait-sigil > img { width: 30px; height: 30px; }
}

/* ═══ THE FRAME PICKER ══════════════════════════════════════════════════════
   ⚠️ Sized for SEVENTY-ONE cells, not eleven. It scrolls in its own box rather
   than pushing the rest of the Operative tab off the screen, and the thumbs are
   lazy so an unopened tab costs nothing. */
.frame-picker {
  display: grid; gap: var(--s2);
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  margin-top: var(--s4);
  max-height: 420px; overflow-y: auto; padding-right: var(--s2);
}
.frame-cell {
  display: flex; flex-direction: column; align-items: center; gap: var(--s1);
  padding: var(--s2); cursor: pointer;
  background: var(--panel); border: 1px solid var(--panel-border);
  transition: border-color var(--swift), background var(--swift);
}
.frame-cell:hover { border-color: var(--gold); background: var(--bg-alt); }
.frame-cell:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.frame-cell[aria-pressed="true"] { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
.frame-cell-prev { width: 62px; height: 62px; display: grid; place-items: center; overflow: hidden; }
.frame-cell-prev > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.frame-cell-prev .op-sigil-gen { width: 100%; height: 100%; }
.frame-cell-nm {
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--text-dim);
  letter-spacing: var(--track-tight); text-align: center; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; max-width: 100%; white-space: nowrap;
}


/* ═══ THE AVATAR EDITOR ═════════════════════════════════════════════════════
   ⛔ PREVIEW LEFT, CHOICES RIGHT. Two separate panels could not show that the
   sigil rides on top of the page avatar — you had to pick something, leave, and
   go and look. Everything on the right changes something visible on the left. */
.av-editor {
  display: grid; gap: var(--s5); margin-top: var(--s4);
  grid-template-columns: minmax(200px, 260px) minmax(0, 1fr);
  align-items: start;
}

.av-stage { display: grid; gap: var(--s2); justify-items: center; }
.av-stage-big {
  position: relative; width: 100%; max-width: 200px; aspect-ratio: 1;
  background: var(--bg-alt); overflow: hidden;
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 18px 100%, 0 calc(100% - 18px));
}
.av-stage-big > img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ⚠️ with no portrait the sigil is the whole thing, centred — not a small mark
   marooned in the corner of an empty box */
.av-stage-big.is-empty .av-stage-sigil,
.av-stage-big:not(:has(img)) .av-stage-sigil {
  position: static; width: 100%; height: 100%; background: none;
  display: grid; place-items: center;
}
.av-stage-big:not(:has(img)) .av-stage-sigil .op-sigil-gen { width: 62%; height: 62%; }

.av-stage-sigil {
  position: absolute; right: -2px; bottom: -2px; width: 62px; height: 62px;
  display: grid; place-items: center; background: var(--bg);
  color: var(--tier-accent, var(--gold));
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
}
.av-stage-sigil .op-sigil-gen, .av-stage-sigil > img { width: 50px; height: 50px; }
.av-stage-sigil > img { object-fit: cover; }

.av-stage--sm .av-stage-mark {
  width: 56px; height: 56px; color: var(--tier-accent, var(--gold));
  display: grid; place-items: center;
}
.av-stage--sm .op-sigil-gen { width: 100%; height: 100%; }
.av-stage-cap {
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted);
  letter-spacing: var(--track-wide); text-transform: uppercase; text-align: center;
}

/* ── the tabs ── */
.av-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--panel-border); }
.av-tab {
  padding: var(--s2) var(--s4); background: none; border: 0; cursor: pointer;
  font-family: var(--font-mono); font-size: var(--t-label); color: var(--muted);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color var(--swift), border-color var(--swift);
}
.av-tab:hover { color: var(--text); }
.av-tab.is-on { color: var(--gold); border-bottom-color: var(--gold); }
.av-tab:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
/* ⛔ `hidden` is ONE user-agent rule and any author display beats it by origin */
.av-pane[hidden] { display: none !important; }
.av-pane { padding-top: var(--s4); }
.av-pane .sigil-picker { margin-top: 0; }

@media (max-width: 860px) {
  .av-editor { grid-template-columns: 1fr; }
}

/* ═══ AVATAR EDITOR — corrections from Stephan's screenshots (2026-09-05) ════

   ⛔ 1. THE GROUND SWATCHES WERE ALL GOLD, AND THE BUG IS INSTRUCTIVE. Each one
   renders `<span class="op-sigil-gen" data-ground="x"><i></i></span>`. The
   wrapper carried the tint on its BACKGROUND — correct — while the `<i>` dot on
   top of it is `background: currentColor`, and `.op-sigil-gen` sets colour to
   the accent. So every swatch drew a gold dot over the very colour it was
   meant to be showing. It advertised a state it was covering up.
   The fix is to stop drawing a dot at all here: the tint IS the swatch, filling
   the cell, which is also how a real colour picker behaves. */
.sigil-sw[data-ground] .op-sigil-gen {
  width: 100%; height: 100%; border-radius: 0;
  border: 1px solid rgba(255,255,255,.07);
}
.sigil-sw[data-ground] i { display: none; }
/* ⚠️ these tints are 13% alpha ON PURPOSE — faint behind a mark is right, and
   invisible in a picker is not. The cell sits on the page ground rather than
   the panel so the difference actually reads. */
.sigil-sw[data-ground] { background: var(--bg); padding: 0; }
.sigil-sw--none i { display: block; }

/* ⛔ 2. ONE CARD SIZE. Switching tabs changed the panel height, so the card grew
   and shrank and read as a rendering fault. Both panes now reserve the same
   room and the long grid scrolls INSIDE it. */
@media (min-width: 861px) {
  .av-pane { min-height: 486px; }
  .av-choices { min-height: 528px; }
}
.av-pane .frame-picker { max-height: 472px; }

/* ⛔ 3. THE PREVIEW IS THE POINT, SO IT GETS THE ROOM. It was 200px beside a
   118-cell grid;
.av-stage-big { max-width: 300px; }
.av-stage-sigil { width: 76px; height: 76px; }
.av-stage-sigil .op-sigil-gen, .av-stage-sigil > img { width: 62px; height: 62px; }

/* ⚠️ and the sigil tiles come DOWN — eleven marks were taking more space than
   the preview they feed */
.av-pane .sigil-picker { grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); }
.av-pane .sigil-cell-prev { width: 36px; height: 36px; }
.av-pane .sigil-cell { padding: var(--s2); }

/* the None tile in the avatar grid — a mark of absence, never somebody's sigil */
.frame-cell--none .frame-cell-prev { color: var(--muted); }
.frame-cell--none .frame-cell-prev svg { width: 34px; height: 34px; }

/* ═══ THE SCROLLBAR ═════════════════════════════════════════════════════════
   ⛔ ONE RULE, REUSED — not a bespoke scrollbar per panel. The frame picker is
   the first in-panel scroller this site has had; the next one gets this by
   adding a selector here rather than by inventing its own.

   ⚠️ TWO ENGINES, BOTH DECLARED. `scrollbar-width`/`scrollbar-color` is the
   standard (Firefox, and newer Chromium); `::-webkit-scrollbar` is what Safari
   and older Chromium actually obey. Shipping only one leaves half the audience
   with the operating system's default bar cutting through the panel — which is
   exactly the "somebody else's chrome inside our layout" problem the Twitch
   offline panel taught us. */
/* ⚠️ THE RELAY'S FOUR SCROLLERS JOINED BY ADDING SELECTORS HERE, which is
   what the note above asks for. The plan card, the Records list, the drawer
   itself and the stage were all showing the operating system's default bar
   cutting through a panel made of hairlines. */
.frame-picker,
.av-pane,
.ask-plan-card,
.ask-hist,
.ask-drawer,
.ask-stage {
  scrollbar-width: thin;
  /* ⛔ NEUTRAL AT REST, GOLD ONLY WHILE IN USE. This was --gold-dim always,
     which put a lit bar down the side of every scroller — Stephan: "it takes
     too much attention." It is also the site's own rule applied wrongly: GOLD
     IS FOR STRUCTURE, NEUTRAL MARKS REPETITION, and a scrollbar is chrome. It
     earns the accent at the moment you grab it and not before.
     ⚠️ The standard property takes ONE colour and cannot express states, so
     Firefox gets the resting grey and no hover step. That is the right way
     round: the quiet state is the one that must be correct everywhere. */
  scrollbar-color: var(--muted) transparent;
}
.frame-picker::-webkit-scrollbar,
.av-pane::-webkit-scrollbar,
.ask-plan-card::-webkit-scrollbar,
.ask-hist::-webkit-scrollbar,
.ask-drawer::-webkit-scrollbar,
.ask-stage::-webkit-scrollbar { width: 8px; height: 8px; }

/* ⚠️ the track is a HAIRLINE, not a trough. A filled channel reads as a second
   border down the side of the grid; the design is built from 1px edges.
   ⚠️ AND THE NEUTRAL HAIRLINE, NOT --panel-border: that token is a gold alpha,
   so the track was quietly contributing to the yellow cast as much as the
   thumb was. --hairline is the system's own neutral edge and already exists. */
.frame-picker::-webkit-scrollbar-track,
.av-pane::-webkit-scrollbar-track,
.ask-plan-card::-webkit-scrollbar-track,
.ask-hist::-webkit-scrollbar-track,
.ask-drawer::-webkit-scrollbar-track,
.ask-stage::-webkit-scrollbar-track {
  background: transparent;
  border-left: 1px solid var(--hairline);
}
.frame-picker::-webkit-scrollbar-thumb,
.av-pane::-webkit-scrollbar-thumb,
.ask-plan-card::-webkit-scrollbar-thumb,
.ask-hist::-webkit-scrollbar-thumb,
.ask-drawer::-webkit-scrollbar-thumb,
.ask-stage::-webkit-scrollbar-thumb {
  background: var(--muted);
  border: 2px solid transparent;      /* insets the thumb without a radius */
  background-clip: content-box;
}
.frame-picker:hover::-webkit-scrollbar-thumb,
.av-pane:hover::-webkit-scrollbar-thumb,
.ask-plan-card:hover::-webkit-scrollbar-thumb,
.ask-hist:hover::-webkit-scrollbar-thumb,
.ask-drawer:hover::-webkit-scrollbar-thumb,
.ask-stage:hover::-webkit-scrollbar-thumb { background: var(--gold-dim); background-clip: content-box; }
/* ⛔ FULL GOLD ONLY WHILE IT IS BEING DRAGGED — "greyed out if it is not
   pressed", which is also the honest reading: the accent marks the thing you
   are acting on. */
.frame-picker::-webkit-scrollbar-thumb:active,
.av-pane::-webkit-scrollbar-thumb:active,
.ask-plan-card::-webkit-scrollbar-thumb:active,
.ask-hist::-webkit-scrollbar-thumb:active,
.ask-drawer::-webkit-scrollbar-thumb:active,
.ask-stage::-webkit-scrollbar-thumb:active,
.road-col-body::-webkit-scrollbar-thumb:active { background: var(--gold); background-clip: content-box; }
/* ⛔ NO border-radius. Every radius on this site is 2px or 0 — a pill-shaped
   scrollbar would be the one rounded object on the page. */
.frame-picker::-webkit-scrollbar-corner,
.av-pane::-webkit-scrollbar-corner,
.ask-plan-card::-webkit-scrollbar-corner,
.ask-hist::-webkit-scrollbar-corner,
.ask-drawer::-webkit-scrollbar-corner,
.ask-stage::-webkit-scrollbar-corner { background: transparent; }

/* the HERO mark on a banner tile — five of eleven carry a photograph, and which
   ones was invisible until after you had chosen */
.banner-cell { position: relative; }
.banner-cell-hero {
  position: absolute; top: 4px; left: 4px; z-index: 1;
  padding: 1px 5px; background: rgba(6,6,6,.78);
  border: 1px solid var(--gold-dim); color: var(--gold-bright);
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; pointer-events: none;
}

/* ═══ THE PAGE HERO PICKER ══════════════════════════════════════════════════
   Wide tiles, because the thing being chosen is wide. A square thumbnail of a
   4:1 panorama is a crop, and choosing from crops is choosing blind. */
.hero-picker {
  display: grid; gap: var(--s3); margin-top: var(--s4);
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.hero-cell {
  display: flex; flex-direction: column; gap: var(--s2); padding: 0 0 var(--s2);
  background: var(--panel); border: 1px solid var(--panel-border);
  cursor: pointer; overflow: hidden; color: var(--muted);
  transition: border-color var(--swift), background var(--swift);
}
.hero-cell:hover { border-color: var(--gold); background: var(--bg-alt); }
.hero-cell:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.hero-cell[aria-pressed="true"] { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
.hero-cell-prev { display: block; width: 100%; aspect-ratio: 4 / 1; overflow: hidden; }
.hero-cell-prev > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hero-cell-prev > svg { width: 100%; height: 100%; display: block; padding: var(--s2); }
.hero-cell-nm {
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--text);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 0 var(--s3); text-align: left;
}

/* ⛔ A PREVIEW IS A PICTURE, NOT A CONTROL. The card preview inherits the real
   card's affordances, and `.op-slot:hover` lights each loadout slot gold under
   the cursor — a promise of interactivity on markup carrying zero anchors and
   zero buttons. Stephan watched people go for it and wonder why nothing
   happened. `pointer-events: none` removes the invitation at its source rather
   than by deleting the slots: the preview stays the REAL renderer's output, so
   it still matches the roster exactly, and it simply stops pretending. */
.look-prev-body { pointer-events: none; user-select: none; }
.look-prev-note {
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted);
  letter-spacing: var(--track-tight); margin-top: var(--s1);
}
/* ⚠️ the ONE thing here that IS a control, so it must look like one */
.look-jump {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--gold);
  text-decoration: underline; text-underline-offset: 3px;
}
.look-jump:hover { color: var(--gold-bright); }
.look-jump:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* ═══ THE APPEARANCE STAGE ══════════════════════════════════════════════════
   ⛔ ONE COMPOSITION, NOT TWO PREVIEWS. Side by side, the sigil was drawn twice
   and the banner twice, and neither picture showed how the pieces sit TOGETHER.
   The hero runs full width with the card centred on it — which is how a Tenno
   page is actually built: hero band, then card. */
.look-stage-wrap { margin-bottom: var(--s6); }
/* ⚠️ sticky, so the composition stays in view while the 118-cell grid scrolls
   past it. A preview you must scroll back up to is a preview nobody looks at. */
@media (min-width: 981px) {
  .look-stage-wrap {
    position: sticky; top: 0; z-index: 2;
    background: var(--bg); padding: var(--s4) 0;
    border-bottom: 1px solid var(--panel-border);
  }
}
.look-stage-cap {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s2);
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted);
  letter-spacing: var(--track-wide); text-transform: uppercase;
}
.look-stage-links { display: flex; gap: var(--s4); }
.look-stage-cap a { color: var(--gold); text-decoration: none; }
.look-stage-cap a:hover { text-decoration: underline; text-underline-offset: 3px; }

.look-stage {
  position: relative; overflow: hidden;
  border: 1px solid var(--panel-border); background: var(--bg-alt);
  padding: clamp(var(--s5), 5vw, var(--s8)) var(--s4) var(--s5);
  display: grid; place-items: center;
}
/* the photograph, full width behind everything */
/* the boxed stage fades into its own ground, not the page's */
.look-stage-hero { position: absolute; inset: 0; --hero-fade-to: var(--bg-alt); }
.look-stage-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ⚠️ the fade itself is declared ONCE, up beside .tp-hero::after — the preview
   and the real page share that rule so they cannot drift. */
/* with no hero chosen the stage is the drawn ground, exactly as the page is */
.look-stage-hero:empty { background:
  repeating-linear-gradient(135deg, transparent 0 22px, rgba(255,255,255,.02) 22px 44px), var(--bg); }

.look-stage-card { position: relative; z-index: 1; width: 100%; max-width: 380px; }
/* ⛔ INERT. `.op-slot:hover` lights each loadout slot gold, promising an
   interaction this picture cannot give — see the note in the markup. */
.look-stage-card { pointer-events: none; user-select: none; }

/* the portrait with the sigil riding on it — the Tenno page arrangement, inside
   the card's own avatar slot */
.look-av { position: relative; display: block; width: 100%; height: 100%; }
.look-av > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.look-av-sigil {
  position: absolute; right: -2px; bottom: -2px; width: 26px; height: 26px;
  display: grid; place-items: center; background: var(--bg);
  color: var(--tier-accent, var(--gold));
  clip-path: polygon(0 0, calc(100% - 5px) 0, 100% 5px, 100% 100%, 5px 100%, 0 calc(100% - 5px));
}
.look-av-sigil .op-sigil-gen, .look-av-sigil > img { width: 21px; height: 21px; }
.look-av-sigil > img { object-fit: cover; }
/* ⚠️ no portrait chosen: the sigil fills the slot rather than hiding in a corner
   of an empty box, which is what the Tenno page does too */
.look-av:not(:has(img)) .look-av-sigil {
  position: static; width: 100%; height: 100%; background: none;
}
.look-av:not(:has(img)) .look-av-sigil .op-sigil-gen { width: 100%; height: 100%; }

/* ═══ REAL GAME ICONS ON A TENNO PAGE ═══════════════════════════════════════
   ⛔ THE DRAWN GLYPH IS ALWAYS IN THE MARKUP AND ONLY HIDDEN WHILE THE IMAGE IS
   THERE. cdn.warframestat.us was down for nine hours on 2026-08-31 and emptied
   every blog loadout icon; `onerror` removes the broken image and this rule
   stops matching, so the glyph we draw ourselves comes straight back. A
   cosmetic absence must fail quietly, never as a torn-page mark.
   ⚠️ Where :has() is unsupported both show — cluttered, not broken, which is
   the safe direction for a fallback to degrade in. */
/* ⚠️ 44px, NOT 26. The measurement corrected me: Revenant.png is 126.7 KB and a
   Tenno page pulls 141-251 KB of these — three to five times what I estimated
   from a 13 KB nikana. We cannot resize somebody else's hot-linked image, so
   the only honest lever left is to make the pixels we are paying for actually
   visible. A 126 KB file rendered into a 26px box is the waste; at 44px it is
   at least the picture somebody came to see. */
.tp-item-ico { width: 44px; height: 44px; object-fit: contain; flex: 0 0 44px; }
.tp-item:has(.tp-item-ico) .tp-item-glyph { display: none; }

/* ⚜️ STEAM — the verified mark (2026-09-06) ------------------------------- */
/* ⛔ It reads as a CLAIM WE STAND BEHIND, not as decoration. Same gold and the
   same ⊙ the World State cards use for a computed figure, because it means the
   same thing: this number did not come from somebody telling us. */
.tp-verified{font:500 var(--t-micro)/1 var(--font-mono);letter-spacing:var(--track-wide);
  color:var(--gold);text-decoration:none;margin-left:.5rem;white-space:nowrap;vertical-align:baseline}
.tp-verified:hover{color:var(--gold-bright)}
/* ═══ THE STEAM LINK ═══════════════════════════════════════════════
   ⛔ A CARD, BECAUSE THIS IS THE ONLY VERIFIED FACT ON THE SITE. It was two
   lines of dim body copy under a form — the typographic weight of a footnote
   for the one claim we can actually stand behind. It gets a ground, an edge and
   the mark of the service it names. */
.op-verify{
  margin-top:var(--s5);
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--s4) var(--s5);
  /* ⚠️ --s5, not --s4: at 16px the Unlink control sat almost against the card
     edge and the patch had no air around it. 24px is the same inset the other
     panels on this tab use, so the card stops looking tighter than its
     neighbours. */
  padding:var(--s5);background:var(--bg-alt);border:1px solid var(--etch-soft);
}
.op-verify p{margin:0;flex:1 1 18rem;font-size:var(--t-sm);color:var(--text-dim)}
.op-verify-on strong,.op-verify-off strong{color:var(--gold)}
.op-verify-note{color:var(--muted);font-size:var(--t-micro)}
/* ⛔ THE UNDO IS THE QUIETEST THING ON THE CARD, NOT THE LOUDEST. `.btn` ships
   a gold border and inherits the body's off-white, so Unlink rendered as the
   brightest object here — the control that TAKES AWAY the verification shouting
   over the patch that proves it. It reads as a way out now, not as an
   invitation: neutral edge, muted label, and it warms only under the pointer.
   ⚠️ WARMS, NEVER ALARMS. This is reversible and the copy beside it says so
   ("Unlink any time"), so an error-red would overstate what pressing it costs —
   decoration implying a state it cannot know, pointed at a button. */
.op-verify-form{margin:0 0 0 auto}
.op-verify-form .btn{
  border-color:var(--panel-border);color:var(--muted);background:transparent;
}
.op-verify-form .btn:hover{
  border-color:var(--gold-dim);color:var(--text-dim);background:var(--bg);
  box-shadow:none;
}
.op-verify-form .btn:focus-visible{outline:2px solid var(--gold-bright);outline-offset:2px}
.op-verify-body{flex:1 1 20rem;min-width:0;display:flex;flex-wrap:wrap;
  align-items:center;gap:var(--s3) var(--s4)}
/* the invitation's mark is quiet — it names the service, it is not the hero */
.op-verify-mark{width:34px;height:34px;flex:0 0 auto;color:var(--text-dim)}
.op-verify--off:hover .op-verify-mark{color:var(--gold-dim)}

/* ── the patch, once it is linked ───────────────────────────────────
   ⚠️ IT CARRIES A NUMBER, AND THE NUMBER IS THE POINT. "Linked" tells a member
   what they already know; the hours are what the link bought them. Built from
   the same parts as everything else here — a plate, a hairline, one gold seal —
   so it reads as issued by this site rather than pasted on from Valve. */
.steam-patch{
  flex:0 0 auto;display:grid;justify-items:center;gap:2px;
  padding:var(--s3) var(--s4);min-width:9.5rem;
  background:var(--bg);border:1px solid var(--gold-dim);
  /* the notch, the same device the plate uses — an issued mark, not a sticker */
  clip-path:polygon(0 0,calc(100% - 9px) 0,100% 9px,100% 100%,9px 100%,0 calc(100% - 9px));
}
.steam-patch-mark{width:26px;height:26px;color:var(--gold)}
.steam-patch-n{
  font-family:var(--font-head);font-size:var(--t-h3);line-height:1;
  color:var(--text);letter-spacing:var(--track-tight);
}
.steam-patch-lab{
  font-family:var(--font-mono);font-size:9px;letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--muted);
}
/* ⛔ ⊙ IS A CLAIM AND IT IS EARNED HERE. Steam told us this, not the member —
   the one place on this page the mark is allowed. */
.steam-patch-seal{
  margin-top:4px;padding-top:5px;width:100%;text-align:center;
  border-top:1px solid var(--hairline);
  font-family:var(--font-mono);font-size:9px;letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--gold);
}
/* ⛔ IN-GAME IS NOT STREAMING. The Twitch lamp rides the banner and means "live
   on stream"; this means "in Warframe right now". Two facts, two marks — one
   badge doing both would imply a state it cannot know. Deliberately NOT the
   Twitch red: this is the accent, breathing, not an alert. */
.tp-playing{display:inline-flex;align-items:center;gap:.35em;margin-left:.5rem;
  padding:.15em .5em;border:1px solid var(--etch);border-radius:2px;
  font:500 var(--t-micro)/1 var(--font-mono);letter-spacing:var(--track-wide);
  color:var(--gold);white-space:nowrap;animation:tpPlayingBreath 3.2s var(--glide) infinite}
@keyframes tpPlayingBreath{0%,100%{opacity:1}50%{opacity:.62}}
@media (prefers-reduced-motion: reduce){.tp-playing{animation:none}}

/* ⚜️ THE RANK PLATE (2026-09-06) --------------------------------------------
   ⛔ ONE FIXED PLACE ON EVERY CARD. The tier/staff badge used to sit inline
   after the member's name, so its position moved with the length of the name —
   ten cards, ten different x-positions, which is precisely what made the roster
   read as messy — and because `.op-name` is nowrap+ellipsis, a long name could
   CLIP THE BADGE AWAY. Anchored to the banner it cannot be pushed, cannot be
   clipped, and lands in the same spot on every card in the grid.
   ⚠️ Top-LEFT. It shared the banner with a top-right label naming the
   banner itself; that label is gone now (nine cards meant nine of PLAINS /
   VALLIS / VOID down one edge), and the rank stays left so the corner it
   has always occupied does not move under anyone. */
.op-rank-slot { position: relative; }
.op-rank {
  position: absolute; left: var(--s3); top: var(--s2); z-index: 4;
  display: inline-flex; align-items: center;
  /* ⛔ NO BORDER HERE — THE BADGE ALREADY HAS ONE. My first version wrapped a
     bordered pill in a bordered plate: two frames around one word, which is a
     large part of why the ranks read as the loudest thing on the card. The
     wrapper's only remaining job is legibility, because the banner takes the
     member's accent and a badge alone would sit on seven different colours.
     A scrim does that; a frame was decoration pretending to be structure. */
  background: rgba(6,6,6, .55);
  border-radius: 2px;
  padding: 2px;
}
/* ⚠️ The badge COMPONENT is shared by seven surfaces and declares no outer
   margin by design — the card neutralises spacing on its own instance only,
   never on the component, which is the leak that kept coming back. */
.op-rank .patron-badge,
.op-rank .staff-badge { margin: 0; }

/* ⚜️ THE MASTHEAD PLATE (2026-09-07) --------------------------------------
   Concept approved as an Artifact before porting — the method that has been
   right six times now.
   ⛔ IT REUSES `.plate`, IT DOES NOT REDRAW IT. The notch lives in one place
   (`--notch`) and the device in another (`.plate` / `.plate-in`); a second
   clip-path here would be two definitions of one object, and the copy is the
   one that rots when the notch changes. */
/* ⚜️ THE MASTHEAD IS AN OPEN BAND, NOT A PLATE (2026-09-07, second pass).
   ⛔ THE PLATE WAS THE WRONG DEVICE HERE AND THE CONCEPT HID IT. The Artifact
   showed the masthead alone on a stage, where a notched plate reads as
   structure. On the real page it sits directly above a GRID OF PLATES — the
   operative cards are the same device — so the header simply became the
   biggest card on the page. Stephan: "it should not be a card... it should
   still be open."
   ⚠️ THE LESSON IS ABOUT THE CONCEPT, NOT THE CSS: a component shown in
   isolation cannot reveal a collision with its neighbours. The next concept
   gets rendered in context.
   ⛔ AND NO HOVER. `.plate:hover` lifts the edge — an affordance for plates you
   can CLICK. A masthead reacting to the cursor promises an interaction that
   does not exist. */
.net-masthead {
  margin-bottom: var(--s6);
  padding-bottom: var(--s4);
  /* the etch, used as a RULE rather than as a container — open on all sides */
  border-bottom: 1px solid var(--etch);
}
/* .net-mast-top / .net-desc REMOVED 2026-09-07 — the two-column head and its
   standfirst are gone; the head is the site's own centred `.section-head` now.
   These were written EARLIER TODAY and were dead within hours, which is exactly
   why they are deleted rather than left "in case". */
.net-mast-head { margin-bottom: 0; padding: 0; }
/* ⚠️ the shelf the live strip sits on: a soft hairline, not a border box */
.net-mast-bar {
  margin-top: var(--s5); padding-top: var(--s4);
  border-top: 1px solid var(--etch-soft);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4) var(--s5); flex-wrap: wrap;
}
.net-pulse-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s5); }
.net-pulse {
  display: inline-flex; align-items: baseline; gap: .45em;
  font-family: var(--font-mono); font-size: var(--t-sm); color: var(--text);
}
.net-pulse b {
  font-family: var(--font-head); font-weight: 600; font-size: var(--t-lead);
  line-height: 1; color: var(--gold-bright);
}
.net-pulse .u {
  font-size: 10px; letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted);
}
.net-pulse .mk { color: var(--gold); align-self: center; font-size: 12px; }
/* ⛔ TEAL IS SEMANTIC "LIVE", NEVER THE ACCENT. Gold is identity; a state colour
   borrowing it would make "streaming" and "Founder" the same signal. */
.net-pulse.is-live b { color: #4fd1c5; }
.net-pulse.is-live .dot {
  width: 7px; height: 7px; border-radius: 50%; background: #4fd1c5; align-self: center;
  box-shadow: 0 0 0 3px rgba(79,209,197,.16); animation: netBreathe 2.8s ease-in-out infinite;
}
@keyframes netBreathe { 0%,100% { opacity: 1 } 50% { opacity: .45 } }
/* ⚠️ NOT a row of zeros. "0 LIVE · 0 IN WARFRAME" reads as a dead clan; one
   quiet sentence is the same fact without the funeral. */
.net-quiet { margin: 0; font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted); }
.net-facts { margin: 0; font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: .04em; color: var(--muted); }
.net-facts b { color: var(--text-dim); font-weight: 500; }
.net-facts .sep { color: var(--gold-dim); margin: 0 .5em; }

@media (prefers-reduced-motion: reduce) { .net-pulse.is-live .dot { animation: none } }

/* ⚜️ THE WORLD-STATE RHYTHM (2026-09-07) ------------------------------------
   ⛔ The section used to carry `style="padding: 0 1.5rem 6rem"` inline, which
   zeroed the 6rem top padding `.cycles` declares — so the header, the key and
   the card grid stacked with almost nothing between them. Stephan: "it looks
   all very cramped together."
   ⚠️ Not simply the 6rem restored either: `.ws-hero` already ends with its own
   padding and `.section-head` carries a fluid bottom margin, so a flat 96px on
   top of those would have opened a hole instead of a breath. Fluid, and tuned
   against what is actually above it. */
.ws-cycles { padding-top: clamp(2.5rem, 4vw, 4.5rem); padding-bottom: 6rem; }

/* ═══════════════════════════════════════════════════════════════════════════
   ABOUT (2026-09-08)
   ⛔ EVERY VALUE IS A TOKEN. This page was written after the day the whole
   stylesheet was resynced onto one colour system — 12 ad-hoc literals on the
   Tenno page, all of them hue-drift. A new page is exactly where that creeps
   back in, so: --panel / --panel-2 for surfaces, --hairline for a divider
   INSIDE a block, --etch for a structural rule, and gold reserved for
   structure and accent. Nothing invented.
   ═══════════════════════════════════════════════════════════════════════════ */

.about-hero { padding-bottom: 0; }

/* ── a copy column beside a piece of art ─────────────────────────────────── */
.about-split {
  display: grid; grid-template-columns: 1.15fr 1fr;
  gap: clamp(2rem, 4vw, 4.5rem); align-items: center;
}
/* ⚠️ THE FLIP IS `direction`, NOT `order`. Reordering flex/grid items detaches
   the visual sequence from the DOM sequence, so a screen reader and a keyboard
   walk the page in one order while eyes read another. Placing the figure first
   in the markup and letting the grid do nothing is simpler and honest. */
.about-split--flip { grid-template-columns: 1fr 1.15fr; }

.about-copy p { color: var(--text-dim); margin: 0 0 var(--s4); max-width: 62ch; }
.about-copy p strong { color: var(--text); font-weight: 600; }
.about-kicker {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-max); text-transform: uppercase;
  color: var(--gold); margin: 0 0 var(--s3) !important;
}
.about-h {
  font-family: var(--font-head); font-weight: 600; font-size: var(--t-h2);
  line-height: 1.1; margin: 0 0 var(--s5); color: var(--text);
  letter-spacing: var(--track-tight); text-wrap: balance;
}
.about-sign {
  font-family: var(--font-mono); font-size: var(--t-sm);
  color: var(--text) !important; margin-top: var(--s5) !important;
}
.about-sign span { color: var(--muted); }

/* ── the drawn placeholder ───────────────────────────────────────────────────
   ⛔ NOT AN <img> AT A PATH THAT DOES NOT EXIST YET. A missing image renders
   the browser's torn-page glyph: a purely cosmetic absence failing LOUDLY, in
   the one place it should simply be quiet. This is a plate that draws itself,
   names the art that belongs in it, and cannot 404. Swap the whole <figure>
   for an <img> when the real art lands. */
.about-art {
  margin: 0; min-height: 320px; position: relative;
  display: grid; place-items: center;
  background:
    repeating-linear-gradient(135deg, transparent 0 14px, var(--etch-soft) 14px 15px),
    linear-gradient(160deg, var(--panel-2), var(--panel));
  border: 1px dashed var(--etch);
  clip-path: var(--plate-notch);
}
.about-art--tall { min-height: 420px; }
.about-art-label {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--muted); padding: var(--s2) var(--s4);
  border: 1px solid var(--hairline); background: var(--bg-alt);
}

/* ── card grids ──────────────────────────────────────────────────────────── */
.about-grid {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
/* ⛔ THE COLUMN COUNT IS DECLARED, NOT LEFT TO auto-fit, WHERE THE CARD COUNT
   IS KNOWN. auto-fit on four cards gives 3 + 1 at some widths — the orphan row
   that made the Command Hub read as unfinished. Rows are the unit. */
.about-grid--2 { grid-template-columns: repeat(2, 1fr); }
.about-grid--3 { grid-template-columns: repeat(3, 1fr); }

.about-card {
  background: var(--panel); border: 1px solid var(--hairline);
  border-radius: 2px; padding: var(--s5);
}
.about-card h3 {
  font-family: var(--font-head); font-size: var(--t-lead); font-weight: 600;
  margin: 0 0 var(--s3); color: var(--text);
}
.about-card p { margin: 0 0 var(--s3); color: var(--text-dim); font-size: var(--t-sm); }
.about-card p:last-child { margin-bottom: 0; }
.about-card code {
  font-family: var(--font-mono); font-size: .9em; color: var(--gold);
}

/* an engine card wears the accent, because the engine IS the differentiator */
.about-card--engine { border-color: var(--etch); background: var(--bg-alt); }
.about-engine-name {
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-max); text-transform: uppercase;
  color: var(--gold); margin: 0 0 var(--s2) !important;
}
.about-proof {
  border-top: 1px solid var(--hairline); padding-top: var(--s3);
  font-family: var(--font-mono); font-size: var(--t-micro);
  color: var(--muted) !important; line-height: 1.6;
}
.about-proof code { color: var(--text-dim); }

/* ── the provenance block ────────────────────────────────────────────────── */
.about-marks {
  margin-top: var(--s6); padding-top: var(--s6);
  border-top: 1px solid var(--etch); text-align: center;
}
.about-marks h3 {
  font-family: var(--font-head); font-size: var(--t-h3); font-weight: 600;
  margin: 0 0 var(--s3); color: var(--text);
}
.about-marks > p { color: var(--text-dim); max-width: 60ch; margin: 0 auto var(--s4); }
.about-marks .data-legend { justify-content: center; }

.about-note {
  font-family: var(--font-mono); font-size: var(--t-micro);
  color: var(--muted) !important; line-height: 1.7;
  border-left: 2px solid var(--etch); padding-left: var(--s4);
  max-width: 66ch;
}
.about-note--wide { margin: var(--s6) auto 0; text-align: left; }
.about-note strong { color: var(--gold); }
.about-note a { color: var(--gold); }

/* ── who builds it ───────────────────────────────────────────────────────── */
.about-card--who { text-align: center; }
.about-who-mark {
  display: block; font-size: var(--t-h3); color: var(--gold);
  margin-bottom: var(--s3); line-height: 1;
}
.about-role {
  font-family: var(--font-mono); font-size: var(--t-micro) !important;
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--gold) !important; margin-bottom: var(--s3) !important;
}

.about-close .section-title { font-size: var(--t-h3); }
.about-cta {
  display: flex; align-items: center; justify-content: center;
  gap: var(--s5); flex-wrap: wrap; margin-top: var(--s5);
}

@media (max-width: 860px) {
  /* ⚠️ ONE COLUMN, AND THE ART GOES LAST. On a phone a 320px placeholder
     between the heading and the copy is a screenful of nothing before the
     first sentence. The grid stacks in DOM order, so the flipped section puts
     its figure first — pushed down explicitly here rather than reordered. */
  .about-split, .about-grid--2, .about-grid--3 { grid-template-columns: 1fr; }
  .about-split--flip .about-art { grid-row: 2; }
  .about-art, .about-art--tall { min-height: 200px; }
  .about-h { font-size: var(--t-h3); }
  .about-cta { flex-direction: column; gap: var(--s4); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   FAQ (2026-09-08) — built on <details>, so it works with JS off and a
   browser's in-page find expands a closed answer to reveal a match.
   ═══════════════════════════════════════════════════════════════════════════ */

.faq-page { max-width: 900px; }
.faq-group { margin-bottom: var(--s7); }
.faq-group-title {
  font-family: var(--font-mono); font-size: var(--t-micro); font-weight: 500;
  letter-spacing: var(--track-max); text-transform: uppercase;
  color: var(--gold); margin: 0 0 var(--s4);
  display: flex; align-items: center; gap: var(--s3);
}
/* the section rule: a label with a hairline running off it — the same open
   device the Tenno dossier and the roster masthead use */
.faq-group-title::after { content: ""; flex: 1; height: 1px; background: var(--etch); }

.faq-item { border-bottom: 1px solid var(--hairline); }
.faq-item summary {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); padding: var(--s4) 0; cursor: pointer;
  list-style: none; transition: color var(--swift);
}
/* the default triangle, removed in both dialects */
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::marker { content: ""; }
.faq-item summary:hover { color: var(--gold); }
.faq-item summary:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 3px; }

.faq-q {
  font-family: var(--font-head); font-size: var(--t-lead); font-weight: 600;
  color: inherit; line-height: 1.35;
}
/* ⚠️ A PLUS DRAWN FROM TWO BARS, not a glyph — a "+" in the display face sits
   off-centre against a two-line question and there is no font-independent way
   to fix that. Two 1px rules are always exactly centred on themselves. */
.faq-mark {
  position: relative; flex: 0 0 14px; width: 14px; height: 14px;
  transition: transform var(--glide);
}
.faq-mark::before, .faq-mark::after {
  content: ""; position: absolute; background: var(--gold);
  left: 50%; top: 50%;
}
.faq-mark::before { width: 14px; height: 1px; transform: translate(-50%, -50%); }
.faq-mark::after  { width: 1px; height: 14px; transform: translate(-50%, -50%); transition: opacity var(--swift); }
.faq-item[open] .faq-mark { transform: rotate(180deg); }
.faq-item[open] .faq-mark::after { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .faq-mark { transition: none; } }

.faq-a { padding: 0 0 var(--s5); max-width: 68ch; }
.faq-a p { color: var(--text-dim); font-size: var(--t-sm); margin: 0 0 var(--s3); }
.faq-a p:last-child { margin-bottom: 0; }
.faq-a strong { color: var(--text); font-weight: 600; }
.faq-a a { color: var(--gold); }
.faq-a a:hover { color: var(--gold-bright); }
.faq-a code { font-family: var(--font-mono); font-size: .9em; color: var(--gold); }
.faq-a .data-legend { margin: var(--s4) 0; }

.faq-foot {
  margin-top: var(--s7); padding-top: var(--s5);
  border-top: 1px solid var(--etch); text-align: center;
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted);
}
.faq-foot a { color: var(--gold); }

@media (max-width: 620px) {
  .faq-q { font-size: var(--t-base); }
}

/* ═══ ROADMAPS — the Command Hub's checklist ═════════════════════════════════
   ⛔ ITS OWN FULL-WIDTH ROW, like the market card. .hub-dash is four columns
   and the four cards below it fill exactly one row; a fifth would sit alone on
   a row of four — the orphan the Relay card already caused once. A step reads
   "Best sources: Pluto/Fenton's Field (Skirmish), Rotation A — 13.33% · …",
   which is a line, not a column, so the width is earned rather than borrowed.
   ⛔ TOKENS ONLY. No neighbouring value invented: --hairline for repetition
   (the step dividers), --etch for structure. That distinction is the system. */
.hub-card--road { grid-column: 1 / -1; }

/* ⛔ THE SAME PANEL, OUTSIDE THE DASHBOARD GRID. The empty state is what a
   member with no watchlist and no alerts sees, and the picker has to be
   reachable there or a roadmap can only be made from inside the dashboard that
   a roadmap unlocks. Not in a grid here, so it needs its own top margin — the
   card itself declares no outer margin, because a shared component that bakes
   one in leaks into every context that uses it. */
.hub-solo { margin-top: var(--s5); }

/* ── the archive drawer ─────────────────────────────────────────────────────
   ⛔ `[hidden]` GETS ITS OWN RULE. That attribute is a single user-agent
   declaration and ANY author `display` beats it by ORIGIN, before specificity
   is even consulted — which is how a "Sign up" button was served live to a
   member who had already paid. <details> carries a UA display of its own, so
   the moment this element gains any author display the attribute stops working
   silently. Declared here, once, so el.hidden means what it says. */
.road-archive[hidden] { display: none !important; }
.road-archive { margin-top: var(--s4); border-top: 1px solid var(--hairline); padding-top: var(--s3); }
.road-archive > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: var(--s2);
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  transition: color var(--swift);
}
.road-archive > summary::-webkit-details-marker { display: none; }
.road-archive > summary::before { content: '▸'; transition: transform var(--swift); }
.road-archive[open] > summary::before { transform: rotate(90deg); }
.road-archive > summary:hover { color: var(--gold); }
.road-arch-n {
  border: 1px solid var(--panel-border); border-radius: 2px; padding: 0 .35rem;
}
.road-list--archived { margin-top: var(--s3); }
/* ⚠️ DIMMED, NEVER HIDDEN-BY-COLOUR. An archived plan is still readable — it is
   a record you came back for, and greying it into the ground would make the
   drawer look broken rather than quiet. */
.road--archived { opacity: .72; }
.road--archived:hover, .road--archived:focus-within { opacity: 1; }

.hub-card--road-solo { display: flex; flex-direction: column; }

/* the composer, mirroring .act-set so the two control rows on this board are
   one idea rather than two spellings of it */
.road-new { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }

/* ── the goal kinds ─────────────────────────────────────────────────────────
   ⛔ `[hidden]` GETS ITS OWN RULE because .road-fields is given a display.
   That attribute is a single user-agent declaration and any author `display`
   beats it by ORIGIN, before specificity is consulted — the bug that served a
   "Sign up" button to a member who had already paid. */
.road-fields[hidden] { display: none !important; }

/* ⛔ 2px, LIKE EVERYTHING ELSE. These controls were written after the Relay
   Plating pass and never got its radius: the site collapsed 64 radii to 2px and
   uses it 78 times, while this picker sat at 8px — soft, bubbly, and visibly
   from a different kit than the plate it sits inside. That mismatch is most of
   why the row read as unstyled; it was not missing polish, it was wearing
   somebody else's shape. */
/* ── RELAY RECORDS: two blocks under one heading ───────────────────────
   ⚠️ A RULE, NOT A SECOND CARD HEAD. These sit INSIDE a card that already has
   an <h3>, so they must read as one level down — a second bold heading inside a
   panel makes the panel look like two panels that failed to separate, which is
   the scattering this whole pass exists to undo. Mono small-caps and a hairline
   is the site's existing language for a sub-level. */
.road-sub {
  display: flex; align-items: baseline; gap: var(--s2);
  margin: var(--s5) 0 var(--s3); padding-bottom: var(--s2);
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--text-dim);
}
.road-sub:first-of-type { margin-top: var(--s3); }
.road-sub-n { color: var(--muted); }
/* the door to the builder sits on the block it fills, not only in the card head */
.road-sub-go { margin-left: auto; color: var(--gold); text-decoration: none; }
.road-sub-go:hover { color: var(--gold-bright); }

/* ── the two columns inside Relay Records ──────────────────────────── */
.road-cols {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s5); align-items: start;
}
.road-col { min-width: 0; }
/* ⛔ EACH SIDE SCROLLS ITSELF, so neither can push the card down the page.
   min() of a viewport share and a ceiling: vh alone would give a 4K monitor a
   1200px-tall well, and a px cap alone expires the way the old 880px reading
   measure did. */
.road-col-body {
  max-height: min(52vh, 560px); overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--muted) transparent;
}
.road-col-body::-webkit-scrollbar { width: 8px; }
.road-col-body::-webkit-scrollbar-track {
  background: transparent; border-left: 1px solid var(--hairline);
}
.road-col-body::-webkit-scrollbar-thumb {
  background: var(--muted); border: 2px solid transparent; background-clip: content-box;
}
.road-col-body:hover::-webkit-scrollbar-thumb { background: var(--gold-dim); background-clip: content-box; }

/* ⚠️ ONE COLUMN AND NO INNER SCROLLER ON A PHONE. Stephan likes the mobile
   layout as it is, and a capped scroll area inside a page that already scrolls
   is the one place nested scrolling genuinely fights the reader's thumb. */
@media (max-width: 1100px) {
  .road-cols { grid-template-columns: 1fr; }
  .road-col-body { max-height: none; overflow: visible; }
}

/* ═══ THE SAVED ANSWERS ═════════════════════════════════════════
   ⛔ THIS COMPONENT HAD NO CSS AT ALL, and had shipped that way. Not one rule
   for .ask-kept or any of its children — so the global `* { padding: 0 }` reset
   stripped the <ol>'s indent and the list markers hung OUTSIDE the box, which
   is the "text overlapping the card" Stephan reported. In a narrow column it
   merely looked plain; given the full width it looked broken.
   ⚠️ No guard could have caught it: every check here reads markup, and the
   markup was always correct. It is the stylesheet that was missing. */
/* ⛔ A SAVED ANSWER IS FRAMED LIKE A SAVED PLAN, because they are the same
   KIND of object sitting in the same card. Side by side the left column was
   framed cards and the right column was bare paragraphs, so the answers read
   as leftovers rather than as records — Stephan: "it looks a little bit lost
   up there." Same ground, same edge, same padding as .road: one system. */
.ask-kept { list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--s4); }
.ask-kept-item {
  background: var(--bg-alt); border: 1px solid var(--etch-soft);
  padding: var(--s4);
}
/* ⚠️ THE QUESTION IS THE TITLE, so it takes the display face the plan titles
   take — one step down, because an answer is a smaller object than a plan. It
   was body copy at the same size as the answer under it, which is why the pair
   had no hierarchy and the block floated. */
.ask-kept-q {
  margin: 0 0 var(--s2); color: var(--text);
  font-family: var(--font-head); font-size: var(--t-base);
  letter-spacing: var(--track-tight); line-height: 1.35;
}
/* ⛔ A MEASURE, BECAUSE A CARD IS NOT A COLUMN. Stephan: "if I go full screen
   the cards keep stretching out but the content is not." Prose set across
   1400px is unreadable at any font size — the answer is a measure on the TEXT,
   never a narrower card, which is the same call as the Hub's own 1920 cap. */
.ask-kept-a {
  margin: 0 0 6px; max-width: 64ch;
  padding-left: var(--s3); border-left: 1px solid var(--panel-border);
  color: var(--text-dim); font-size: var(--t-sm); line-height: 1.55;
}
/* the provenance mark colours the rule, exactly as it does in the /ask/ drawer */
.ask-kept-item--computed  .ask-kept-a { border-left-color: var(--gold); }
.ask-kept-item--reference .ask-kept-a { border-left-color: rgba(127, 142, 160, .5); }
.ask-kept-item--unknown   .ask-kept-a { border-left-color: var(--muted); }
.ask-kept-foot {
  display: flex; align-items: center; gap: var(--s2); margin: var(--s3) 0 0;
  padding-top: var(--s2); border-top: 1px solid var(--hairline);
}
.ask-kept-when {
  font-family: var(--font-mono); font-size: 9px; color: var(--muted);
  letter-spacing: var(--track-wide);
}

.road-kinds { display: flex; gap: 0; flex: 0 0 auto; }
.road-kind {
  background: none; border: 1px solid var(--panel-border); border-radius: 0;
  color: var(--text-dim); cursor: pointer; padding: .5rem .75rem;
  font-family: var(--font-mono); font-size: var(--t-micro);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  transition: color var(--swift), border-color var(--swift), background var(--swift);
}
.road-kind:first-child { border-radius: 2px 0 0 2px; }
.road-kind:last-child  { border-radius: 0 2px 2px 0; border-left-width: 0; }
.road-kind:hover { color: var(--text); }
/* ⚠️ THE SELECTED KIND EARNS GOLD — it is the state every field below depends
   on, which is structure rather than decoration. Nothing else in this row does. */
.road-kind.is-on { color: var(--gold); border-color: var(--gold); background: var(--bg-alt); }
.road-kind.is-on + .road-kind { border-left-color: var(--gold); }

/* ⛔ ANNOUNCED, NOT AVAILABLE — AND IT HAS TO LOOK IT. Quest is held for V2,
   so its tab reserves the space (that is how somebody learns the capability is
   coming) while reading unmistakably as not-yet. A control that looks live and
   refuses is a broken promise wearing an affordance; that is the line between
   `locked` and `invite` on the Tenno page, and it is the same line here.
   ⚠️ NO HOVER AND NO POINTER. The two things a mouse uses to decide whether
   something is pressable both say no, before the disabled attribute is reached. */
.road-kind--soon {
  color: var(--muted); cursor: default; opacity: .75;
  border-style: dashed;
}
.road-kind--soon:hover { color: var(--muted); }
.road-soon {
  margin-left: .45rem; padding: 1px 5px;
  border: 1px solid var(--panel-border); border-radius: 2px;
  font-size: 9px; letter-spacing: var(--track-wide);
  color: var(--muted); text-transform: uppercase;
}

.road-fields { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.road-mr { display: flex; align-items: center; gap: var(--s2); }
.road-mr > span {
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted);
  letter-spacing: var(--track-wide); text-transform: uppercase;
}
/* ⛔ THE RIGHT PADDING IS THE ARROW'S ROOM, NOT DECORATION. Chrome paints a
   select's dropdown indicator INSIDE the content box, so .65rem of right
   padding leaves the glyph sitting on top of the last two characters —
   "— What are you going fo⌄". Stephan reported it as text overlapping inside
   the dropdown and he was reading it exactly right.
   ⚠️ No max-width: the widest option is the placeholder (31 characters),
   longer than any of the 101 goal names, so the control sized to its own
   content is both the smallest it can honestly be and wide enough that
   nothing clips. A cap here is what produced the fault. */
.road-fields select {
  background: var(--bg); border: 1px solid var(--panel-border); border-radius: 2px;
  color: var(--text); padding: .55rem 2rem .55rem .65rem; font-size: var(--t-sm);
  font-family: var(--font-mono); color-scheme: dark; cursor: pointer;
  /* ⚠️ min-width:0 defeats the flex item's AUTOMATIC MINIMUM, which is its
     min-content width — the widest option plus the arrow's room. Without it
     the control refuses to shrink and pushes straight out of the plate on a
     narrow screen; with it the row fits and the text is held honest by the
     clipped-text check in tools/render-relay.js instead. */
  max-width: 100%; min-width: 0;
}
.road-fields select:focus { outline: none; border-color: var(--gold); }

/* ═══ THE DROPDOWN ITSELF ═══════════════════════════════════════════
   ⛔ THE OPEN OPTION LIST IS THE BROWSER'S, NOT OURS — AND THAT USED TO BE THE
   END OF IT. A native <select> popup is painted by the operating system: our
   scrollbar rule cannot reach it, the highlight is the OS accent, and there is
   no animation to hook. Stephan: "really Windows 98 vibes." Same shape as the
   Twitch offline panel — you cannot style inside something the browser owns.

   ⚖️ THE USUAL ANSWER IS TO REPLACE THE CONTROL WITH A DIV, AND IT IS A BAD
   TRADE. A hand-rolled listbox has to re-implement arrow keys, Home/End,
   type-ahead, Escape, aria-activedescendant and focus return — and on a phone
   it replaces the OS wheel, which is genuinely better than anything we would
   build. A prettier control nobody can drive with a keyboard is a worse control.

   ✅ SO WE STYLE THE REAL ONE. `appearance: base-select` hands the picker over
   to CSS while the element stays a <select>: native semantics, native keyboard,
   native mobile sheet, our paint. Measured in Chrome 151 before it was written
   — CSS.supports('appearance','base-select') is true and ::picker(select)
   parses.
   ⚠️ BEHIND @supports, so a browser without it keeps exactly today's control
   rather than a half-styled one. A cosmetic absence fails quietly. */
@supports (appearance: base-select) {

  .road-fields select,
  .road-new select {
    appearance: base-select;
    /* the closed control keeps the look it already had; only the parts the UA
       used to paint for us now need saying out loud */
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s2); text-align: left;
    /* ⛔ THE CONTROL MUST BE THE SAME HEIGHT IN BOTH APPEARANCE MODES, or the
       Plan row is 2.6px taller in Chrome than everywhere else and the plate
       resizes on a browser we did not test in. Opting into base-select turns
       the arrow into a flex ITEM, and an item taller than the text grows the
       button; pinning both line boxes to the text makes the height come from
       the font, which is the one thing both modes share. Caught by the
       mode-height guard on the first run after this block was written. */
    line-height: 1.35;
  }
  .road-fields select::picker-icon,
  .road-new select::picker-icon { line-height: 1; font-size: var(--t-sm); }

  /* ⚠️ the arrow is a real element now, so the text can no longer be painted
     under it — the overlap Stephan reported is structurally impossible here
     rather than merely padded around. The padding stays for the fallback. */
  .road-fields select::picker-icon,
  .road-new select::picker-icon {
    color: var(--muted); transition: transform var(--swift), color var(--swift);
  }
  .road-fields select:open::picker-icon,
  .road-new select:open::picker-icon { transform: rotate(180deg); color: var(--gold); }

  /* the panel: a plate, like everything else on this site */
  .road-fields select::picker(select),
  .road-new select::picker(select) {
    appearance: base-select;
    background: var(--panel); border: 1px solid var(--gold-dim); border-radius: 2px;
    padding: 4px; margin-block-start: 4px;
    box-shadow: 0 18px 46px rgba(0, 0, 0, .55);
    max-height: 19rem;
    /* ⛔ OUR SCROLLBAR, AT LAST — the same one .frame-picker and the Records
       list use. This is the whole reason for opting in. */
    scrollbar-width: thin; scrollbar-color: var(--muted) transparent;
  }
  .road-fields select::picker(select)::-webkit-scrollbar,
  .road-new select::picker(select)::-webkit-scrollbar { width: 8px; }
  .road-fields select::picker(select)::-webkit-scrollbar-track,
  .road-new select::picker(select)::-webkit-scrollbar-track {
    background: transparent; border-left: 1px solid var(--hairline);
  }
  .road-fields select::picker(select)::-webkit-scrollbar-thumb,
  .road-new select::picker(select)::-webkit-scrollbar-thumb {
    background: var(--muted); border: 2px solid transparent; background-clip: content-box;
  }

  .road-fields select option,
  .road-new select option {
    padding: .42rem .6rem; border-radius: 2px;
    background: transparent; color: var(--text-dim);
    font-family: var(--font-mono); font-size: var(--t-sm);
    transition: color var(--swift), background var(--swift);
  }
  /* ⚠️ :hover AND :focus both, because the keyboard moves a focus ring through
     this list and a highlight that only follows the mouse leaves a keyboard user
     with no idea where they are. */
  .road-fields select option:hover,
  .road-new select option:hover,
  .road-fields select option:focus,
  .road-new select option:focus { background: var(--bg-alt); color: var(--text); }
  /* ⚠️ AND THE RING IS GOLD, NOT THE UA'S WHITE. The browser draws its own
     focus outline on the focused option, which lands as a bright rectangle in
     the middle of a panel built from gold hairlines — one white object in a
     room with no other white objects. Replaced, never removed: a keyboard user
     has to be able to see where they are. */
  .road-fields select option:focus,
  .road-new select option:focus {
    outline: 1px solid var(--gold-dim); outline-offset: -1px;
  }
  .road-fields select option:checked,
  .road-new select option:checked { color: var(--gold); }
  /* the UA tick is a generic glyph from another design; the gold IS the state,
     exactly as it is on .road-kind.is-on */
  .road-fields select option::checkmark,
  .road-new select option::checkmark { display: none; }

  /* ⛔ AND IT FADES IN. The picker lives in the top layer, so `display` and
     `overlay` have to be transitioned with allow-discrete or it snaps; the
     opening frame comes from @starting-style, which is the only way to give an
     element an entry state it has never had before. */
  .road-fields select::picker(select),
  .road-new select::picker(select) {
    opacity: 0; transform: translateY(-4px);
    transition: opacity var(--swift) ease, transform var(--swift) ease,
                display var(--swift) allow-discrete,
                overlay var(--swift) allow-discrete;
  }
  .road-fields select:open::picker(select),
  .road-new select:open::picker(select) { opacity: 1; transform: none; }
  @starting-style {
    .road-fields select:open::picker(select),
    .road-new select:open::picker(select) { opacity: 0; transform: translateY(-4px); }
  }

  @media (prefers-reduced-motion: reduce) {
    .road-fields select::picker(select),
    .road-new select::picker(select) { transition: none; }
    .road-fields select::picker-icon,
    .road-new select::picker-icon { transition: none; }
  }
}

/* the Relay's Plan tab renders the same picker, so it inherits all of the above
   and only needs the row to breathe inside the plate */
#bodyPlan .road-kinds { margin-right: var(--s2); }

@media (max-width: 720px) {
  .road-kinds, .road-fields { flex: 1 1 100%; }
  .road-kind { flex: 1 1 0; }
  .road-fields select { flex: 1 1 100%; }
  /* ⛔ "DONE UP TO" IS A LABEL WRAPPING A SELECT, AND THAT MADE THE ROW
     IMPOSSIBLE TO SATISFY. The rule above gives every picker select
     flex-basis:100% — including the one inside this label, whose own width is
     being derived from its contents at the same time. A percentage basis
     inside an auto-width parent resolves against max-content, so .road-mr
     inflated to the widest quest name and dragged .road-fields out past the
     plate with it: both selects overflowed by an identical 17.4px, which is
     the tell that the PARENT was the thing that was wrong.
     So the label takes the full row and the select inside it merely fills
     what is left beside the caption. */
  /* ⚠️ AND EVERY WRAPPER GETS min-width:0. A flex item's automatic minimum
     is its min-content width, so a nested row can refuse to shrink and push
     its whole column out of the plate — which is what put both quest selects
     17.4px past the body, in lockstep, from a parent neither of them owned. */
  .road-kinds, .road-fields { min-width: 0; }
  /* ⛔ THE CAPTION SITS ABOVE ITS FIELD AT THIS WIDTH, NOT BESIDE IT. Sharing
     a 285px row, "Done up to" wrapped to two lines AND still left the select
     too narrow for the longest quest name by under a pixel — a control that
     fits only until somebody picks Angels Of The Zariman. Stacked, the select
     gets the whole row and the caption reads on one line. */
  .road-mr { flex: 1 1 100%; min-width: 0; flex-direction: column; align-items: stretch; gap: var(--s1); }
  .road-mr select { flex: 1 1 auto; min-width: 0; }
}

.road-new select, .road-new input[type="text"] {
  background: var(--bg); border: 1px solid var(--panel-border); border-radius: 2px;
  color: var(--text); padding: .55rem .65rem; font-size: var(--t-sm); color-scheme: dark;
  font-family: var(--font-mono);
}
/* the same arrow allowance as .road-fields select — the Hub's picker draws the
   identical control and had the identical overlap */
.road-new select { padding-right: 2rem; }
.road-new select { cursor: pointer; flex: 0 0 auto; max-width: 16rem; }
.road-new input[type="text"] { flex: 1 1 180px; min-width: 0; }
.road-new select:focus, .road-new input:focus { outline: none; border-color: var(--gold); }
.road-new input::placeholder { color: var(--muted); opacity: 1; }
.road-new .btn { flex: 0 0 auto; }

/* ⚠️ auto-fit, but with a floor wide enough to hold a drop line. A roadmap
   squeezed into a narrow column wraps every step to four lines and stops being
   scannable, which is the whole job. */
.road-list {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 30rem), 1fr));
  gap: var(--s4); margin-top: var(--s4);
}
.road { background: var(--bg-alt); border: 1px solid var(--etch-soft); padding: var(--s4); }

.road-head { display: flex; align-items: baseline; gap: var(--s3); }
.road-title {
  margin: 0; flex: 1 1 auto; min-width: 0;
  font-family: var(--font-head); font-size: var(--t-lead); letter-spacing: var(--track-tight);
}
.road-prog-n { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted); }
.road-arch {
  background: none; border: 1px solid var(--panel-border); border-radius: 2px;
  color: var(--muted); cursor: pointer; padding: .2rem .5rem;
  font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--track-wide);
  transition: color var(--swift), border-color var(--swift);
}
.road-arch:hover { color: var(--gold); border-color: var(--gold); }

.road-bar { height: 2px; background: var(--hairline); margin: var(--s3) 0; }
.road-bar-fill { display: block; height: 100%; background: var(--gold); transition: width var(--glide); }

.road-note {
  margin: 0 0 var(--s3); font-size: var(--t-sm); color: var(--text-dim);
  border-left: 1px solid var(--etch); padding-left: var(--s3);
}

.road-steps { list-style: none; margin: 0; padding: 0; }
/* ⚠️ NEUTRAL, NOT GOLD. These dividers are repetition, and seven gold rules on
   one card bury the two lines that are actually structural. */
.road-step + .road-step { border-top: 1px solid var(--hairline); }

.road-tick {
  display: flex; align-items: flex-start; gap: var(--s3); width: 100%;
  background: none; border: 0; padding: var(--s3) 0; cursor: pointer;
  color: inherit; text-align: left; font: inherit;
}
.road-box {
  flex: 0 0 auto; width: 1.15rem; height: 1.15rem; margin-top: .1rem;
  border: 1px solid var(--panel-border); border-radius: 2px;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--t-micro); color: var(--gold);
  transition: border-color var(--swift);
}
.road-tick:hover .road-box { border-color: var(--gold); }
.road-step-body { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.road-step-label { font-size: var(--t-sm); color: var(--text); }
.road-step-detail {
  font-family: var(--font-mono); font-size: var(--t-micro); color: var(--muted);
  line-height: 1.5;
}
/* ⚠️ The done state dims the label and rules it through — but the DETAIL stays
   readable, because a drop location you already used is still the answer to
   "where did I get that one". */
.road-step.is-done .road-step-label { color: var(--muted); text-decoration: line-through; }

/* ⛔ THE VAULTED NOTICE. No checkbox, because there is nothing to do — and no
   farming location inside it, which is the rule the whole feature is arranged
   around. It is a fault-shaped thing, so it is allowed to be loud. */
.road-step--notice {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: var(--s3); margin-bottom: var(--s2);
  background: var(--panel); border: 1px solid var(--etch);
}
.road-block-mark { flex: 0 0 auto; font-size: var(--t-sm); }
.road-step--notice .road-step-label { color: var(--gold-bright); }

/* ⛔ A TRADE STEP IS STILL A TASK. `blocked` means the relics do not drop, not
   that there is nothing to do — you trade for the part — so it keeps its tick
   and its place in the count, and only says so with a left edge. Styling it as
   a notice left a member trading through a vaulted set unable to move their own
   progress bar, which is worse than the problem that change was solving. */
.road-step--trade .road-box { border-style: dashed; }
.road-step--trade .road-step-detail { color: var(--text-dim); }

@media (max-width: 720px) {
  .road-new select { max-width: none; flex: 1 1 100%; }
  .road-new input[type="text"] { flex: 1 1 100%; }
  .road-new .btn { width: 100%; }
}
