/* ==========================================================================
   stackoverflo.lol — retro arcade theme
   Dual-mode design tokens. Dark = CRT arcade cabinet. Light = 8-bit game manual.
   ========================================================================== */

:root {
  /* type — Silkscreen for display/labels, VT323 for numerals, IBM Plex Mono
     for copy. `--fs` is the global size unit every font-size multiplies, so
     the whole scale can be nudged from one place. */
  --fs: 1.15px;
  --font-display: "Silkscreen", ui-monospace, monospace;
  --font-num: "VT323", "Courier New", monospace;
  --font-body: "IBM Plex Mono", ui-monospace, "Courier New", monospace;

  /* geometry */
  --px: 4px;              /* pixel unit — all borders/offsets are multiples */
  --radius: 0px;          /* retro = no rounding, ever */
  --header-h: 64px;

  /* motion */
  --snap: 90ms steps(3, end);
  --ease: 160ms cubic-bezier(.2, .8, .2, 1);
}

/* ------------------------------- DARK (default) ------------------------- */
:root,
[data-theme="dark"] {
  color-scheme: dark;

  --bg-void: #08060f;
  --bg-base: #0d0a1a;
  --bg-panel: #141028;
  --bg-panel-2: #1c1638;
  --bg-inset: #0a0816;

  --grid-line: #241c4a;
  --grid-line-strong: #33276b;

  --fg: #e8e4ff;
  --fg-dim: #9c93c9;
  --fg-faint: #635a8f;

  --accent: #b26cff;         /* primary purple */
  --accent-2: #7c3aed;
  --accent-ink: #10061f;
  --accent-glow: rgba(178, 108, 255, .45);

  --cyan: #35e6ff;
  --lime: #7dff5a;
  --amber: #ffc44d;
  --rose: #ff4d7d;

  --ok: var(--lime);
  --bad: var(--rose);

  --bevel-light: rgba(255, 255, 255, .16);
  --bevel-dark: rgba(0, 0, 0, .62);
  --shadow-hard: 0 var(--px) 0 0 #05030c, 0 0 0 var(--px) #05030c;

  --scanline: rgba(0, 0, 0, .22);
  --vignette: rgba(0, 0, 0, .55);
  --glow-strength: 1;
}

/* ------------------------------- LIGHT ---------------------------------- */
[data-theme="light"] {
  color-scheme: light;

  --bg-void: #ded7c8;
  --bg-base: #f4efe2;
  --bg-panel: #fffdf6;
  --bg-panel-2: #f0e9d8;
  --bg-inset: #e7e0cd;

  --grid-line: #d3c9b2;
  --grid-line-strong: #b9ab8c;

  --fg: #1d1630;
  --fg-dim: #5b5175;
  --fg-faint: #928aa8;

  --accent: #6d28d9;
  --accent-2: #4c1d95;
  --accent-ink: #fffdf6;
  --accent-glow: rgba(109, 40, 217, .22);

  --cyan: #0f7d94;
  --lime: #2f8f2a;
  --amber: #9a6a00;
  --rose: #c81e4f;

  --ok: var(--lime);
  --bad: var(--rose);

  --bevel-light: rgba(255, 255, 255, .95);
  --bevel-dark: rgba(84, 70, 44, .35);
  --shadow-hard: 0 var(--px) 0 0 #1d1630, 0 0 0 var(--px) #1d1630;

  --scanline: rgba(120, 105, 70, .07);
  --vignette: rgba(120, 105, 70, .16);
  --glow-strength: 0;
}

/* ============================== base =================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg-base);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: calc(14 * var(--fs));
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* CRT overlay — scanlines + vignette. Purely decorative, pointer-transparent. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  background:
    repeating-linear-gradient(
      to bottom,
      transparent 0 2px,
      var(--scanline) 2px 3px
    ),
    radial-gradient(120% 120% at 50% 50%, transparent 55%, var(--vignette) 100%);
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0s !important; transition-duration: 0s !important; }
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: .02em;
  margin: 0;
}

h1 { font-size: calc(20 * var(--fs)); }
h2 { font-size: calc(15 * var(--fs)); }
h3 { font-size: calc(12 * var(--fs)); }
h4 { font-size: calc(11 * var(--fs)); }

p { margin: 0 0 1em; }

a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: border-color var(--ease), color var(--ease);
}
a:hover { border-bottom-color: var(--accent); }

code, kbd {
  font-family: var(--font-body);
  background: var(--bg-inset);
  border: 2px solid var(--grid-line);
  padding: 1px 5px;
  font-size: .92em;
}

:focus-visible {
  outline: 3px solid var(--cyan);
  outline-offset: 2px;
}

::selection { background: var(--accent); color: var(--accent-ink); }

/* ============================== primitives ============================= */

.panel {
  background: var(--bg-panel);
  border: var(--px) solid var(--grid-line-strong);
  box-shadow:
    inset 2px 2px 0 var(--bevel-light),
    inset -2px -2px 0 var(--bevel-dark);
}

.panel--inset {
  background: var(--bg-inset);
  box-shadow:
    inset 2px 2px 0 var(--bevel-dark),
    inset -2px -2px 0 var(--bevel-light);
}

.panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  background: var(--bg-panel-2);
  border-bottom: var(--px) solid var(--grid-line-strong);
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.panel__body { padding: 10px; }

/* --- buttons --- */
.btn {
  --btn-bg: var(--bg-panel-2);
  --btn-fg: var(--fg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--btn-fg);
  background: var(--btn-bg);
  border: 3px solid var(--fg);
  border-bottom-width: 6px;
  cursor: pointer;
  user-select: none;
  transition: transform var(--snap), filter var(--ease);
}
.btn:hover { filter: brightness(1.14); }
.btn:active {
  transform: translateY(3px);
  border-bottom-width: 3px;
}
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .42;
  cursor: not-allowed;
  transform: none;
}

.btn--primary {
  --btn-bg: var(--accent);
  --btn-fg: var(--accent-ink);
  border-color: var(--accent-2);
  text-shadow: none;
}
.btn--primary:not([disabled]) {
  box-shadow: 0 0 calc(18px * var(--glow-strength)) var(--accent-glow);
}

.btn--ghost {
  --btn-bg: transparent;
  border-color: var(--grid-line-strong);
  border-bottom-width: 3px;
  color: var(--fg-dim);
}
.btn--ghost:hover { color: var(--fg); border-color: var(--accent); }

.btn--sm { padding: 6px 10px; font-size: calc(9 * var(--fs)); border-width: 2px; border-bottom-width: 4px; }
.btn--icon { padding: 8px; min-width: 34px; }

/* --- form controls --- */
.field { display: block; margin-bottom: 14px; }

.field__label {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 6px;
}

.field__hint { font-family: var(--font-body); font-size: calc(12 * var(--fs)); color: var(--fg-faint); text-transform: none; letter-spacing: 0; }

.input, .select, .textarea {
  width: 100%;
  padding: 9px 10px;
  font-family: var(--font-body);
  font-size: calc(13 * var(--fs));
  color: var(--fg);
  background: var(--bg-inset);
  border: 3px solid var(--grid-line-strong);
  box-shadow: inset 2px 2px 0 var(--bevel-dark);
}
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--accent);
  outline: none;
}
.textarea { resize: vertical; min-height: 76px; line-height: 1.5; }
.select { appearance: none; cursor: pointer; padding-right: 28px;
  background-image: linear-gradient(45deg, transparent 50%, var(--fg-dim) 50%),
                    linear-gradient(135deg, var(--fg-dim) 50%, transparent 50%);
  background-position: right 14px top 55%, right 8px top 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
/* --- inline validation, shown against the offending control --- */
.input--invalid, .textarea--invalid, .select--invalid {
  border-color: var(--bad);
  box-shadow: inset 2px 2px 0 var(--bevel-dark), 0 0 0 2px var(--bad);
}
.field__error {
  display: none;
  align-items: flex-start;
  gap: 6px;
  margin-top: 5px;
  color: var(--bad);
  font-size: calc(11 * var(--fs));
  line-height: 1.45;
}
.field__error::before {
  content: "\25B2";
  font-size: calc(8 * var(--fs));
  line-height: 1.7;
  flex: none;
}
.field.is-invalid .field__error { display: flex; }
.field.is-invalid .field__label { color: var(--bad); }

/* --- badges / chips --- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 7px;
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .07em;
  text-transform: uppercase;
  border: 2px solid currentColor;
  color: var(--fg-dim);
}
.badge--accent { color: var(--accent); }
.badge--ok { color: var(--ok); }
.badge--bad { color: var(--bad); }
.badge--cyan { color: var(--cyan); }
.badge--amber { color: var(--amber); }

/* --- X profile chip: the mark plus the handle, whole thing clickable --- */
.x-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border: 2px solid var(--grid-line-strong);
  background: var(--bg-inset);
  color: var(--fg);
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .06em;
  cursor: pointer;
  transition: color var(--ease), border-color var(--ease), background var(--ease);
}
.x-link:hover,
.x-link:focus-visible {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}
.x-mark {
  display: block;
  width: calc(11 * var(--fs));
  height: calc(11 * var(--fs));
  flex: none;
}

/* --- stat readouts --- */
.stat { display: flex; flex-direction: column; gap: 1px; }
.stat__k {
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.stat__v {
  font-family: var(--font-num);
  font-size: calc(26 * var(--fs));
  line-height: 1;
  color: var(--fg);
}
.stat__v--accent { color: var(--accent); text-shadow: 0 0 calc(12px * var(--glow-strength)) var(--accent-glow); }
.stat__v--cyan { color: var(--cyan); }
.stat__v--lime { color: var(--lime); }

/* --- live dot --- */
.live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .1em;
  color: var(--lime);
  text-transform: uppercase;
}
.live::before {
  content: "";
  width: 7px; height: 7px;
  background: var(--lime);
  animation: blink 1.4s steps(2, end) infinite;
}
@keyframes blink { 50% { opacity: .15; } }

/* --- tables --- */
.table { width: 100%; border-collapse: collapse; font-size: calc(13 * var(--fs)); }
.table th {
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .08em;
  text-transform: uppercase;
  text-align: left;
  color: var(--fg-faint);
  padding: 8px 10px;
  border-bottom: 3px solid var(--grid-line-strong);
  white-space: nowrap;
}
.table td { padding: 9px 10px; border-bottom: 2px solid var(--grid-line); vertical-align: middle; }
.table tbody tr:hover { background: var(--bg-panel-2); }
.table .num { font-family: var(--font-num); font-size: calc(18 * var(--fs)); }

/* ============================== app shell ============================== */

.site {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 14px;
  height: var(--header-h);
  padding: 0 16px;
  background: var(--bg-void);
  border-bottom: var(--px) solid var(--accent-2);
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-display);
  font-size: calc(13 * var(--fs));
  color: var(--fg);
  border: 0;
  white-space: nowrap;
}
.brand:hover { border: 0; }
/* Brand mark is the logo artwork itself; sized by height so the intrinsic
   150x160 ratio keeps its own width. `pixelated` because the mark is a
   hard-edged block motif — smooth downscaling muddies the bevels. */
.brand__mark {
  height: 22px;
  width: auto;
  flex: none;
  image-rendering: pixelated;
  filter: drop-shadow(0 0 calc(5px * var(--glow-strength)) var(--accent-glow));
}
.brand__lol { color: var(--accent); }

.nav { display: flex; align-items: center; gap: 4px; margin-left: 8px; }
.nav a {
  padding: 8px 10px;
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--fg-dim);
  border: 0;
}
.nav a:hover { color: var(--fg); background: var(--bg-panel); border: 0; }
.nav a[aria-current="page"] { color: var(--accent); background: var(--bg-panel); }

.topbar__spacer { flex: 1; }

.topbar__price {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 5px 10px;
  border: 2px solid var(--grid-line-strong);
  background: var(--bg-panel);
  white-space: nowrap;
}
.topbar__price b { font-family: var(--font-num); font-size: calc(21 * var(--fs)); color: var(--amber); line-height: 1; }
.topbar__price span { font-family: var(--font-display); font-size: calc(9 * var(--fs)); color: var(--fg-faint); letter-spacing: .08em; }

.theme-toggle {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  background: var(--bg-panel);
  border: 3px solid var(--grid-line-strong);
  color: var(--amber);
  cursor: pointer;
  font-size: calc(15 * var(--fs));
  line-height: 1;
}
.theme-toggle:hover { border-color: var(--accent); }


/* ================================ footer =============================== */

.footer {
  margin-top: auto;
  background: var(--bg-void);
  border-top: var(--px) solid var(--accent-2);
  color: var(--fg-dim);
  font-size: calc(12 * var(--fs));
}

/* pixel sawtooth ridge, drawn with gradients so it costs no assets */
.footer__ridge {
  height: 8px;
  background:
    repeating-linear-gradient(90deg,
      var(--accent) 0 8px, transparent 8px 16px,
      var(--cyan) 16px 24px, transparent 24px 32px);
  opacity: .55;
}

.footer__grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: 26px;
  max-width: 1180px;
  margin: 0 auto;
  padding: 26px 16px 20px;
}

.footer__col h4 {
  font-size: calc(10 * var(--fs));
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 10px;
}
.footer__col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.footer__col a { color: var(--fg-dim); border: 0; }
.footer__col a:hover { color: var(--accent); border: 0; }

.footer__tag {
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  line-height: 1.7;
  color: var(--fg);
  margin: 10px 0 12px;
}
.footer__tag b { color: var(--accent); }

.footer__meter {
  display: flex;
  gap: 2px;
  margin-bottom: 10px;
}
.footer__meter i {
  width: 10px; height: 10px;
  background: var(--grid-line-strong);
}
.footer__meter i[data-on="1"] { background: var(--accent); }

.footer__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  max-width: 1180px;
  margin: 0 auto;
  padding: 12px 16px 26px;
  border-top: 2px solid var(--grid-line);
  color: var(--fg-faint);
  font-size: calc(11 * var(--fs));
}
.footer__coin {
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  color: var(--amber);
  letter-spacing: .08em;
}

/* Audience stats (DataFast) — "Total Visitors" and "Online now", in the
   canvas overlay next to the search box, and again at the top of the block
   detail modal. A pill in this theme is a bevelled panel, not a rounded
   capsule: --radius is 0 everywhere on purpose. Numbers use --font-num so they
   line up with every other figure on the site. */
.audience-slot[hidden] { display: none; }

/* .stage__row is space-between (search left, toolbar right). An auto margin
   beats that distribution, so the pill sits against the search box instead of
   floating in the middle of the canvas. align-self because the search box is
   taller than the pill: top-aligned they stair-step down the row. */
.stage__row > .audience-slot { margin-right: auto; align-self: center; }

.audience {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 4px 10px;
  background: var(--bg-panel);
  border: 2px solid var(--grid-line-strong);
  color: var(--fg-dim);
  white-space: nowrap;
}

/* Number over label, not beside it: two stats side by side with inline labels
   ("1,419,567 Total Visitors · 23 Online now") is wide enough to crowd the
   toolbar off the row, and stacked pairs read as two stats rather than one
   run-on string. align-items:flex-start so both numbers share a baseline
   regardless of label width. */
.audience__stat {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.audience__stat[hidden] { display: none; }

.audience__n {
  font-family: var(--font-num);
  font-size: calc(17 * var(--fs));
  line-height: 1;
  color: var(--fg);
}

.audience__u {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  line-height: 1;
}

/* Live dot on "Online now". Square, like .live::before and everything else
   here — --radius is 0 across the theme, so a round dot would be the one
   circle on the page.

   The glow is a box-shadow on the BASE rule, not inside the keyframes: the
   global prefers-reduced-motion rule (top of this file) zeroes every animation
   duration, which freezes an element on its base state. Animating the glow in
   would therefore leave a reduced-motion visitor with a flat dot; animating
   its INTENSITY leaves them the lit one, minus the pulse. */
.audience__dot {
  width: 7px;
  height: 7px;
  flex: none;
  background: var(--lime);
  box-shadow: 0 0 6px var(--lime), 0 0 2px var(--lime);
  animation: audience-pulse 1.8s ease-in-out infinite;
}
@keyframes audience-pulse {
  50% { box-shadow: 0 0 10px var(--lime), 0 0 4px var(--lime); opacity: .78; }
}

/* A full-height rule between stats rather than a middot: the stats are two
   lines tall now, and a centred dot between them reads as decoration. */
.audience__sep {
  align-self: stretch;
  width: 2px;
  background: var(--grid-line-strong);
}
.audience__sep[hidden] { display: none; }

/* In the block detail modal the same pill spans the body: it is the first
   thing above the product hero, so a left-hugging pill would leave a ragged
   gap where the heading starts. */
.audience-slot--modal { display: block; margin-bottom: 14px; }
.audience-slot--modal .audience { display: flex; gap: 14px; }

/* Builder credit (X / @saas_buildr), beside the live tally on every public
   page. Deliberately styled as chrome, not a CTA: it inherits the footer's
   dim colour and only brightens on hover, so it cannot compete with the buy
   flow. The glyph is the shared .x-mark from ui.js X_MARK, which already
   sizes itself and sets flex:none, and is filled with currentColor — so
   neither theme nor :hover needs an icon rule of its own. */
.footer__builder {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  color: var(--fg-dim);
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .06em;
  white-space: nowrap;
}
.footer__builder:hover,
.footer__builder:focus-visible { color: var(--accent); border: 0; }

/* ============================== layout utils =========================== */

.wrap { width: 100%; max-width: 1180px; margin: 0 auto; padding: 24px 16px 48px; }
.wrap--wide { max-width: 1480px; }
.stack { display: flex; flex-direction: column; }
.row { display: flex; align-items: center; }
.gap-4 { gap: 4px; } .gap-6 { gap: 6px; } .gap-8 { gap: 8px; }
.gap-10 { gap: 10px; } .gap-14 { gap: 14px; } .gap-20 { gap: 20px; }
.wrapf { flex-wrap: wrap; }
.mt-10 { margin-top: 10px; } .mt-20 { margin-top: 20px; }
.mb-10 { margin-bottom: 10px; } .mb-20 { margin-bottom: 20px; }
.grow { flex: 1; min-width: 0; }
.dim { color: var(--fg-dim); }
.faint { color: var(--fg-faint); }
.small { font-size: calc(12 * var(--fs)); }
.num { font-family: var(--font-num); }
.center { text-align: center; }
.hide { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.section-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.section-title::after {
  content: "";
  flex: 1;
  height: var(--px);
  background: repeating-linear-gradient(to right, var(--grid-line-strong) 0 8px, transparent 8px 12px);
}

/* ============================== tower view ============================= */

.tower-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 316px;
  gap: 14px;
  align-items: start;
  padding: 14px;
}

.stage {
  position: relative;
  height: calc(100vh - var(--header-h) - 28px);
  min-height: 460px;
  background: var(--bg-void);
  border: var(--px) solid var(--grid-line-strong);
  overflow: hidden;
}

#tower-canvas { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; }
#tower-canvas.is-dragging { cursor: grabbing; }
#tower-canvas.is-placing { cursor: crosshair; }

.stage__overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 10px;
}
/* pointer-events is an inherited property, so the overlay opts out wholesale
   and only the HUD widgets opt back in.

   This used to be `.stage__overlay > * { pointer-events: auto }`, but the
   overlay's direct children are .stage__row layout containers, not widgets:
   full-width flex rows whose height comes from their tallest child (the 300px
   minimap). Granting them `auto` laid two invisible mats over the canvas — a
   44px strip at the top and a 283px strip at the bottom — which swallowed
   every pointer event across the full width, including the empty gap between
   the search box and the toolbar. Blocks under a strip could not be hovered
   or clicked while blocks between them worked, which is what made the dead
   zones look random; click-to-place on /buy was lost in the same bands.

   So the rows stay transparent and only their widget children take input. */
.stage__row { pointer-events: none; }
.stage__row > * { pointer-events: auto; }

.stage__tl, .stage__bl, .stage__br, .stage__tr { display: flex; gap: 8px; align-items: flex-start; }
.stage__row { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
/* The bottom row holds only the minimap now, and space-between would park a
   lone child on the LEFT — over the tower. Pin it to the right corner. */
.stage__row--bottom { align-items: flex-end; justify-content: flex-end; }

.toolbar {
  display: flex;
  gap: 3px;
  padding: 4px;
  background: var(--bg-panel);
  border: 3px solid var(--grid-line-strong);
}
.toolbar button {
  display: grid;
  place-items: center;
  min-width: 30px; height: 30px;
  padding: 0 7px;
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  cursor: pointer;
}
.toolbar button:hover { background: var(--bg-panel-2); color: var(--fg); }
.toolbar button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.toolbar__sep { width: 2px; background: var(--grid-line-strong); margin: 3px 2px; }

.zoom-readout {
  font-family: var(--font-num);
  font-size: calc(17 * var(--fs));
  color: var(--cyan);
  padding: 0 6px;
  align-self: center;
  min-width: 52px;
  text-align: center;
}

.search {
  position: relative;
  width: 260px;
}
.search .input { padding-left: 30px; font-size: calc(12 * var(--fs)); }
.search::before {
  content: "\1F50D";
  position: absolute;
  left: 8px; top: 50%;
  transform: translateY(-50%);
  font-size: calc(12 * var(--fs));
  opacity: .7;
  pointer-events: none;
}
.search__results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0; right: 0;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg-panel);
  border: 3px solid var(--accent-2);
  z-index: 40;
}
.search__item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px 9px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid var(--grid-line);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: calc(12 * var(--fs));
  text-align: left;
  cursor: pointer;
}
.search__item:hover, .search__item[data-active="true"] { background: var(--accent); color: var(--accent-ink); }
.search__item .faint { color: inherit; opacity: .7; }
.search__swatch { width: 20px; height: 20px; border: 2px solid var(--bevel-dark); flex: none; }
.search__empty { padding: 12px; color: var(--fg-faint); font-size: calc(12 * var(--fs)); }

/* The minimap is a movement HUD, not permanent furniture: it appears while the
   camera is moving and fades once the view settles (app.js toggles .is-moving).
   pointer-events must follow opacity, otherwise an invisible 74px strip keeps
   swallowing clicks aimed at the tower behind it. Because it is only clickable
   while visible, app.js keeps it alive on pointerenter — that pair is what makes
   it reachable instead of something that vanishes as you reach for it. */
.minimap-wrap {
  width: 74px;
  background: var(--bg-panel);
  border: 3px solid var(--grid-line-strong);
  padding: 4px;
  opacity: 0;
  pointer-events: none;
  transform: translateY(6px);
  transition: opacity .2s ease, transform .2s ease;
}
.minimap-wrap.is-moving {
  opacity: 1;
  pointer-events: auto;
  transform: none;
}
/* PRD §56: motion is decoration here, so drop the slide and cross-fade only. */
@media (prefers-reduced-motion: reduce) {
  .minimap-wrap { transform: none; transition: opacity .12s linear; }
}
#minimap { display: block; width: 100%; height: 252px; cursor: pointer; }
.minimap-wrap__label {
  font-family: var(--font-display);
  font-size: calc(8 * var(--fs));
  letter-spacing: .1em;
  color: var(--fg-faint);
  text-align: center;
  padding-bottom: 3px;
  text-transform: uppercase;
}

/* opening-shot caption, fades out the moment the visitor takes over */
.intro-hint {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 25;
  padding: 7px 12px;
  max-width: calc(100% - 24px);
  text-align: center;
  background: var(--bg-panel);
  border: 3px solid var(--accent);
  color: var(--fg-dim);
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .05em;
  pointer-events: none;
  box-shadow: var(--shadow-hard);
  animation: fade var(--ease);
  transition: opacity 380ms ease, transform 380ms ease;
}
.intro-hint.is-out {
  opacity: 0;
  transform: translateX(-50%) translateY(8px);
}

/* First-visit explainer, shown once over the canvas until dismissed or the
   visitor interacts with the tower (onboarding.js owns the timing + the
   localStorage flag). Sits above the HUD widgets (.hover-card is 30,
   .search__results is 40) so it reads as the topmost thing on the stage,
   but stays well under the modal layer (200) since it is not modal. */
.intro {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 16px;
  background: color-mix(in srgb, var(--bg-void) 55%, transparent);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 200ms ease, transform 200ms ease;
}
.intro.is-in { opacity: 1; transform: none; }
.intro.is-out {
  opacity: 0;
  transform: translateY(-6px);
  transition-duration: 300ms;
}
@media (prefers-reduced-motion: reduce) {
  .intro { transform: none; transition: opacity 200ms linear; }
  .intro.is-out { transform: none; transition-duration: 300ms; }
}
/* This is a hint, not a lightbox: the tower underneath must stay usable
   while it's up, so the scrim opts pointer-events out wholesale and only
   the panel itself opts back in — same pattern as .stage__overlay above.
   A full-bleed click mat would kill hover/drag/tap on every block behind
   it just to catch "dismiss on backdrop click", which isn't worth it. */
.intro { pointer-events: none; }
.intro__panel { pointer-events: auto; }

/* `display: grid` above outranks the UA's `[hidden] { display: none }`, so the
   panel a returning visitor never gets shown was still being laid out: opacity
   0, pointer-events none on the scrim — but .intro__panel opts back IN, and
   below 1080px that panel is a full-width bottom sheet 88vh tall. An invisible
   mat sat over the lower half of the phone viewport and swallowed every tap
   that landed on it: the canvas controls in their bottom corner, and dragging
   or tapping a block down there. onboarding.js removes the element outright
   once it HAS been shown, which is why this only ever bit repeat visits. */
.intro[hidden] { display: none; }

.intro__panel {
  position: relative;
  width: 100%;
  max-width: 460px;
  max-height: calc(100% - 24px);
  overflow-y: auto;
  padding: 20px 20px 16px;
  background: var(--bg-panel);
  border: var(--px) solid var(--accent);
  box-shadow: var(--shadow-hard);
}

.intro__x {
  position: absolute;
  top: 8px;
  right: 8px;
  min-width: 32px;
  min-height: 32px;
  display: grid;
  place-items: center;
  background: transparent;
  border: 0;
  color: var(--fg-faint);
  font-family: var(--font-display);
  font-size: calc(16 * var(--fs));
  line-height: 1;
  cursor: pointer;
}
.intro__x:hover { color: var(--fg); }

.intro__eyebrow {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--cyan);
}

.intro__title { font-size: calc(16 * var(--fs)); line-height: 1.2; margin: 0 0 14px; padding-right: 22px; }

.intro__dgm {
  display: block;
  width: 100%;
  max-width: 300px;
  height: auto;
  margin: 0 auto 14px;
  background: var(--bg-inset);
  border: 1px solid var(--grid-line-strong);
}
.intro__c { stroke: var(--bg-panel); stroke-width: 1; }
.intro__c--sold { fill: color-mix(in srgb, var(--fg) 52%, var(--bg-inset)); }
.intro__c--new { fill: var(--accent); }
.intro__c--empty { fill: var(--bg-inset); }
.intro__ground { fill: var(--fg-dim); }

.intro__steps {
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
  counter-reset: intro-step;
  display: grid;
  gap: 7px;
  font-family: var(--font-body);
  font-size: calc(12 * var(--fs));
  color: var(--fg-dim);
}
.intro__steps li {
  counter-increment: intro-step;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.intro__steps li::before {
  content: counter(intro-step);
  flex: none;
  font-family: var(--font-num);
  font-size: calc(15 * var(--fs));
  color: var(--accent);
  min-width: 1.2em;
}
.intro__steps b { color: var(--fg); }

.intro__acts { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
.intro__acts .btn { flex: 1; min-width: 140px; }

.intro__nav {
  margin: 0;
  padding-top: 10px;
  border-top: 2px solid var(--grid-line);
  font-size: calc(10 * var(--fs));
  color: var(--fg-faint);
  text-align: center;
}

/* one-time gesture affordance for touch visitors, appended by onboarding.js
   right after the intro is dismissed. Offset above .intro-hint's 16px so the
   two never overlap during the brief window the opening caption is still
   fading out. Cyan (not accent) so it reads as a distinct, secondary cue. */
.touch-hint {
  position: absolute;
  left: 50%;
  bottom: 54px;
  transform: translateX(-50%);
  z-index: 26;
  padding: 7px 12px;
  max-width: calc(100% - 24px);
  text-align: center;
  background: var(--bg-panel);
  border: 3px solid var(--cyan);
  color: var(--fg-dim);
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .05em;
  pointer-events: none;
  box-shadow: var(--shadow-hard);
  transition: opacity 320ms ease, transform 320ms ease;
}
.touch-hint.is-out {
  opacity: 0;
  transform: translateX(-50%) translateY(8px);
}
@media (prefers-reduced-motion: reduce) {
  .touch-hint { transition: opacity 320ms linear; }
  .touch-hint.is-out { transform: translateX(-50%); }
}

/* mobile sticky action bar — keeps the price + CTA reachable without scrolling
   back to the topbar. Hidden by default; the 1080px breakpoint below (shared
   with the rest of the mobile layout) turns it on. */
.mbar { display: none; }
.mbar__price {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-body);
  font-size: calc(11 * var(--fs));
  color: var(--fg-dim);
}
.mbar__label {
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.mbar__price b { font-family: var(--font-num); font-size: calc(18 * var(--fs)); color: var(--accent); }
.mbar__cta { flex: none; margin-left: auto; }
.mbar__how { flex: none; font-size: calc(11 * var(--fs)); color: var(--fg-dim); }


.hover-card {
  position: absolute;
  z-index: 30;
  min-width: 150px;
  max-width: 240px;
  padding: 7px 9px;
  background: var(--bg-panel);
  border: 3px solid var(--accent);
  pointer-events: none;
  font-size: calc(12 * var(--fs));
  box-shadow: var(--shadow-hard);
}
.hover-card b { display: block; font-family: var(--font-display); font-size: calc(10 * var(--fs)); margin-bottom: 3px; }

.sidebar { display: flex; flex-direction: column; gap: 12px; }

.stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  background: var(--grid-line);
}
.stat-grid .stat { padding: 9px 10px; background: var(--bg-panel); }

.feed { max-height: 268px; overflow-y: auto; }
.feed__item {
  display: flex;
  gap: 8px;
  padding: 7px 10px;
  border-bottom: 2px solid var(--grid-line);
  font-size: calc(12 * var(--fs));
  animation: slide-in 240ms ease;
}
@keyframes slide-in { from { opacity: 0; transform: translateX(-10px); } }
.feed__dot { width: 7px; height: 7px; margin-top: 5px; flex: none; background: var(--fg-faint); }
.feed__dot--buy { background: var(--lime); }
.feed__dot--row { background: var(--amber); }
.feed__dot--price { background: var(--rose); }
.feed__dot--reserve { background: var(--cyan); }
.feed__dot--milestone { background: var(--accent); }
.feed__t { font-family: var(--font-num); font-size: calc(14 * var(--fs)); color: var(--fg-faint); margin-left: auto; flex: none; }

/* ============================== modal ================================== */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(6, 4, 14, .78);
  animation: fade var(--ease);
}
[data-theme="light"] .modal-backdrop { background: rgba(60, 50, 30, .55); }
@keyframes fade { from { opacity: 0; } }

.modal {
  width: 100%;
  max-width: 520px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--bg-panel);
  border: var(--px) solid var(--accent);
  box-shadow: 0 0 calc(46px * var(--glow-strength)) var(--accent-glow);
  animation: pop 160ms cubic-bezier(.2, 1.4, .4, 1);
}
.modal--wide { max-width: 680px; }
@keyframes pop { from { transform: scale(.9); opacity: 0; } }

.modal__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: var(--bg-panel-2);
  border-bottom: var(--px) solid var(--accent-2);
}
.modal__close {
  margin-left: auto;
  width: 32px; height: 32px;
  background: transparent;
  border: 3px solid var(--grid-line-strong);
  color: var(--fg-dim);
  font-family: var(--font-display);
  font-size: calc(11 * var(--fs));
  cursor: pointer;
  flex: none;
}
.modal__close:hover { border-color: var(--bad); color: var(--bad); }
.modal__body { padding: 14px; }
.modal__foot {
  display: flex;
  gap: 8px;
  padding: 12px 14px;
  border-top: var(--px) solid var(--grid-line-strong);
  background: var(--bg-panel-2);
  flex-wrap: wrap;
}

.product-hero { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 14px; }
.product-hero__logo {
  width: 88px; height: 88px;
  flex: none;
  display: grid;
  place-items: center;
  border: 3px solid var(--bevel-dark);
  font-family: var(--font-display);
  font-size: calc(22 * var(--fs));
  overflow: hidden;
}
.product-hero__logo img { width: 100%; height: 100%; object-fit: contain; }

.kv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: 2px;
  background: var(--grid-line);
  border: 2px solid var(--grid-line);
}
.kv { padding: 8px 10px; background: var(--bg-inset); }
.kv__k { font-family: var(--font-display); font-size: calc(9 * var(--fs)); letter-spacing: .08em; text-transform: uppercase; color: var(--fg-faint); }
.kv__v { font-family: var(--font-num); font-size: calc(20 * var(--fs)); line-height: 1.1; }

.shape-mini { display: grid; gap: 1px; }
.shape-mini i { width: 8px; height: 8px; }
.shape-mini i[data-on="1"] { background: var(--accent); }
.shape-mini i[data-on="0"] { background: var(--grid-line); }

/* ============================== designer =============================== */

.buy-layout {
  display: grid;
  grid-template-columns: 330px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
  padding: 16px;
}
.buy-layout--place { grid-template-columns: 288px minmax(0, 1fr); }

.steps {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin-bottom: 14px;
  background: var(--grid-line);
  border: 2px solid var(--grid-line);
  overflow-x: auto;
}
.step-chip {
  flex: 1;
  min-width: 74px;
  padding: 9px 8px;
  background: var(--bg-panel);
  border: 0;
  color: var(--fg-faint);
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}
.step-chip b { display: block; font-family: var(--font-num); font-size: calc(18 * var(--fs)); line-height: 1; margin-bottom: 2px; }
.step-chip[aria-current="true"] { background: var(--accent); color: var(--accent-ink); }
.step-chip[data-done="true"] { color: var(--lime); }
.step-chip[disabled] { cursor: not-allowed; opacity: .55; }

.designer-grid {
  display: grid;
  gap: 2px;
  background: var(--grid-line);
  border: 3px solid var(--grid-line-strong);
  padding: 2px;
  width: max-content;
  margin: 0 auto;
  touch-action: none;
}
.designer-cell {
  width: 26px; height: 26px;
  background: var(--bg-inset);
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: background var(--snap);
}
.designer-cell:hover { background: var(--bg-panel-2); box-shadow: inset 0 0 0 2px var(--accent); }
.designer-cell[data-on="1"] {
  background: var(--accent);
  box-shadow: inset 2px 2px 0 var(--bevel-light), inset -2px -2px 0 var(--bevel-dark);
}
.designer-cell[data-on="1"]:hover { background: var(--accent-2); }
.designer-cell[data-orphan="1"] { background: var(--bad); }

.swatches { display: flex; flex-wrap: wrap; gap: 5px; }
.swatch {
  width: 28px; height: 28px;
  border: 3px solid var(--grid-line-strong);
  cursor: pointer;
  padding: 0;
}
.swatch[aria-pressed="true"] { border-color: var(--fg); box-shadow: 0 0 0 2px var(--accent); }

.dropzone {
  display: grid;
  place-items: center;
  gap: 6px;
  padding: 18px 12px;
  text-align: center;
  background: var(--bg-inset);
  border: 3px dashed var(--grid-line-strong);
  color: var(--fg-faint);
  font-size: calc(12 * var(--fs));
  cursor: pointer;
}
.dropzone:hover, .dropzone.is-over { border-color: var(--accent); color: var(--fg-dim); }
.dropzone__preview { width: 62px; height: 62px; object-fit: contain; }

.price-lines { border: 2px solid var(--grid-line); }
.price-line {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-bottom: 2px solid var(--grid-line);
  font-size: calc(13 * var(--fs));
}
.price-line:last-child { border-bottom: 0; }
.price-line--total {
  background: var(--bg-panel-2);
  font-family: var(--font-display);
  font-size: calc(11 * var(--fs));
  align-items: center;
}
.price-line--total b { font-family: var(--font-num); font-size: calc(27 * var(--fs)); color: var(--amber); line-height: 1; }

.validity {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 9px 10px;
  border: 3px solid var(--grid-line-strong);
  background: var(--bg-inset);
  font-size: calc(12 * var(--fs));
}
.validity--ok { border-color: var(--ok); color: var(--ok); }
.validity--bad { border-color: var(--bad); color: var(--bad); }
.validity__icon { font-family: var(--font-display); font-size: calc(12 * var(--fs)); flex: none; line-height: 1.3; }

.countdown {
  font-family: var(--font-num);
  font-size: calc(40 * var(--fs));
  line-height: 1;
  color: var(--amber);
  letter-spacing: .04em;
}
.countdown--urgent { color: var(--bad); animation: blink .7s steps(2, end) infinite; }

.legal-check { display: flex; gap: 9px; align-items: flex-start; font-size: calc(12 * var(--fs)); margin-bottom: 12px; }
.legal-check input { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--accent); flex: none; }

.preview-tile {
  display: grid;
  place-items: center;
  padding: 14px;
  background: var(--bg-inset);
  border: 3px solid var(--grid-line-strong);
}

.notice {
  padding: 9px 11px;
  border-left: 6px solid var(--amber);
  background: var(--bg-panel-2);
  color: var(--fg-dim);
  font-size: calc(12 * var(--fs));
}
.notice--bad { border-left-color: var(--bad); }
.notice--ok { border-left-color: var(--ok); }

/* toast */
.toasts {
  position: fixed;
  bottom: 16px; left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;
}
.toast {
  padding: 10px 16px;
  background: var(--bg-panel);
  border: 3px solid var(--accent);
  color: var(--fg);
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .05em;
  box-shadow: var(--shadow-hard);
  animation: pop 160ms cubic-bezier(.2, 1.4, .4, 1);
  max-width: 88vw;
  text-align: center;
}
.toast--bad { border-color: var(--bad); }
.toast--ok { border-color: var(--ok); }

/* how-it-works / content pages */
.prose { max-width: 720px; }
.prose h2 { margin: 28px 0 12px; color: var(--accent); }
.prose h3 { margin: 20px 0 8px; }
.prose ul, .prose ol { margin: 0 0 1em; padding-left: 22px; }
.prose li { margin-bottom: 6px; }
.prose pre {
  padding: 12px;
  background: var(--bg-inset);
  border: 3px solid var(--grid-line-strong);
  overflow-x: auto;
  font-size: calc(12 * var(--fs));
  line-height: 1.5;
}

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

.tabs { display: flex; gap: 2px; background: var(--grid-line); border: 2px solid var(--grid-line); margin-bottom: 14px; width: max-content; }
.tab {
  padding: 9px 15px;
  background: var(--bg-panel);
  border: 0;
  color: var(--fg-dim);
  font-family: var(--font-display);
  font-size: calc(10 * var(--fs));
  letter-spacing: .06em;
  text-transform: uppercase;
  cursor: pointer;
}
.tab[aria-selected="true"] { background: var(--accent); color: var(--accent-ink); }

.rank {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  font-family: var(--font-display);
  font-size: calc(12 * var(--fs));
  border: 2px solid var(--grid-line-strong);
  color: var(--fg-dim);
}
.rank--1 { border-color: var(--amber); color: var(--amber); }
.rank--2 { border-color: var(--cyan); color: var(--cyan); }
.rank--3 { border-color: var(--rose); color: var(--rose); }

/* clickable logo + product name, used in the directory and both boards */
.brand-cell {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 3px 6px 3px 3px;
  margin: -3px 0 -3px -3px;
  border: 2px solid transparent;
  color: var(--fg);
}
.brand-cell:hover,
.brand-cell:focus-visible {
  border-color: var(--accent);
  background: var(--bg-panel-2);
  color: var(--fg);
}
.brand-cell:hover .logo-chip,
.brand-cell:focus-visible .logo-chip { border-color: var(--accent); }
.brand-cell:hover b { color: var(--accent); }
/* Sized by its content, capped only for pathologically long names. The floor
   stops the column collapsing to an ellipsis when the table is squeezed. */
.brand-cell__text {
  display: grid;
  gap: 1px;
  min-width: 12ch;
}
.brand-cell__text b,
.brand-cell__text span {
  max-width: 26ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.logo-chip {
  width: 30px; height: 30px;
  flex: none;
  display: grid;
  place-items: center;
  border: 2px solid var(--bevel-dark);
  font-family: var(--font-display);
  font-size: calc(11 * var(--fs));
  overflow: hidden;
}
.logo-chip img { width: 100%; height: 100%; object-fit: contain; }

/* ---------------------- how-it-works explainer diagrams ----------------------
   The SVGs carry no colour of their own: every square is a class, and the fill
   comes from the same tokens as the rest of the page, so one set of markup
   works in both themes. Widths are fluid rather than fixed so a diagram never
   forces the prose column to scroll sideways. */
.dgm { margin: 18px 0 24px; }
.dgm__svg {
  display: block;
  width: 100%;
  height: auto;
  background: var(--bg-inset);
  border: var(--px) solid var(--grid-line-strong);
  box-shadow:
    inset 2px 2px 0 var(--bevel-dark),
    inset -2px -2px 0 var(--bevel-light);
}
.dgm__c { stroke: var(--grid-line); stroke-width: 1; }
.dgm__c.is-empty { fill: var(--bg-inset); }
/* Three neutral tints, so two adjacent sold blocks read as two blocks rather
   than one merged mass. Deliberately not accent/rose/amber — those three are
   reserved for "yours", "illegal" and "sealed".
   Mixed against --bg-inset rather than picked from the palette directly: a
   fixed token that separates cleanly from the empty squares in the dark theme
   (--grid-line-strong) lands within a few percent of them in the light one,
   which erased whole blocks. Mixing keeps all three steps a fixed distance
   from whatever the empty square currently is. */
.dgm__c.is-sold-1 { fill: color-mix(in srgb, var(--fg) 34%, var(--bg-inset)); }
.dgm__c.is-sold-2 { fill: color-mix(in srgb, var(--fg) 52%, var(--bg-inset)); }
.dgm__c.is-sold-3 { fill: color-mix(in srgb, var(--fg) 70%, var(--bg-inset)); }
.dgm__c.is-new { fill: var(--accent); }
.dgm__c.is-bad { fill: var(--bad); }
.dgm__c.is-void { fill: var(--amber); }
.dgm__ground { fill: var(--accent); }

.dgm__pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  align-items: start;
}
.dgm__case { min-width: 0; }
/* capped so a four-square diagram does not blow up to half the prose column */
.dgm__pair .dgm__svg { max-width: 220px; }
.dgm__label {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: calc(9 * var(--fs));
  letter-spacing: .06em;
}
.dgm__label--ok { color: var(--ok); }
.dgm__label--bad { color: var(--bad); }

.dgm__cap {
  margin-top: 10px;
  color: var(--fg-dim);
  font-size: calc(12 * var(--fs));
  line-height: 1.55;
}
.dgm__cap b { color: var(--fg); }

.dgm__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin-top: 10px;
  color: var(--fg-dim);
  font-size: calc(12 * var(--fs));
}
.dgm__key { display: inline-flex; align-items: center; gap: 7px; }
.dgm__sw {
  width: 13px;
  height: 13px;
  border: 2px solid var(--grid-line-strong);
  flex: none;
}
.dgm__sw--sold { background: color-mix(in srgb, var(--fg) 52%, var(--bg-inset)); }
.dgm__sw--new { background: var(--accent); }
.dgm__sw--empty { background: var(--bg-inset); }

@media (max-width: 560px) {
  /* side by side stops teaching anything once each case is 120px wide */
  .dgm__pair { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .dgm__pair .dgm__svg { max-width: 200px; }
}

/* ============================== responsive ============================= */

@media (max-width: 1080px) {
  /* Local var so the fixed bar's own height and the page's bottom padding
     (below) can never drift apart. */
  :root { --mbar-h: 60px; }
  .tower-layout { grid-template-columns: minmax(0, 1fr); }
  /* leaves ~50vh comfortably under --header-h + the mbar for the canvas,
     down from 62vh, so the sticky bar never eats into the tower view */
  .stage { height: 54vh; }
  .buy-layout, .buy-layout--place { grid-template-columns: minmax(0, 1fr); }
  .minimap-wrap { display: none; }
  /* Once the tower is a single column the stage is narrower than the four HUD
     widgets laid end to end, and the only shrinkable one is the search box —
     it collapsed to its 🔍 while the toolbar's right half was clipped by the
     canvas edge. The toolbar leaves the row for the corner the minimap just
     vacated (the row ruler and the ground label are drawn down the LEFT edge,
     so the right corner is the empty one), and the search box gets a floor.

     Scoped to the toolbar that shares a row with the search box, i.e. the home
     stage: /buy's placement stage puts the toolbar alone in the top row (so it
     never stacked) and parks its "tap a spot" hint in the bottom one, which an
     unscoped rule would sit right on top of. */
  .search ~ .toolbar { position: absolute; right: 10px; bottom: 10px; }
  .search { flex: 1 1 160px; width: auto; min-width: 0; }
  /* Both captions are centred on the stage's bottom edge and run nearly its
     full width here, so the toolbar's new corner is underneath them. Lift them
     clear: 60px clears a 44px toolbar on its 10px inset (42px on the phone's
     6px one), and the touch hint keeps its own offset above the caption. */
  .intro-hint { bottom: 60px; }
  .touch-hint { bottom: 104px; }
  .footer__grid { grid-template-columns: 1fr 1fr; gap: 20px; }
  .mbar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 120;
    align-items: center;
    gap: 10px;
    height: var(--mbar-h);
    padding: 0 12px;
    background: var(--bg-panel);
    border-top: 3px solid var(--accent-2);
    box-shadow: 0 calc(-1 * var(--px)) 0 0 var(--bg-void);
  }
  /* .site has no scroll container of its own (min-height only), so the
     document/body is what actually scrolls — the fixed bar has to steal
     its clearance from body padding, or the footer's last row hides
     underneath it. */
  body { padding-bottom: var(--mbar-h); }
  /* At this width .stage is still boxed to 54vh (min 460px), but the intro
     panel's desktop content runs roughly 565px tall (36px padding + 21px
     eyebrow + 44px title + 300px diagram + 74px steps + 52px CTA row + 24px
     nav + 3px border) — more than either the stage's floor or the panel's
     own `calc(100% - 24)` ceiling. Rather than let it fall back to a
     silent inner scroll here too, give it the same fixed bottom-sheet
     treatment as the 720px breakpoint below; the tighter diagram/type
     shrink stays scoped to that narrower breakpoint since there is more
     room to spare up here. */
  .intro {
    position: fixed;
    inset: 0;
    z-index: 150;
    place-items: end stretch;
    padding: 0;
    transform: translateY(40px);
  }
  .intro.is-in { transform: none; }
  .intro.is-out { transform: translateY(24px); }
  .intro__panel {
    max-width: none;
    max-height: 88vh;
    border-width: var(--px) 0 0;
  }
}

/* .intro's own prefers-reduced-motion block (above) only ever ran under the
   base translateY(10px)/(-6px) slide; scope the same "cross-fade only" rule
   to the sheet's larger translateY(40px)/(24px) so a reduced-motion visitor
   still gets no slide at this width. */
@media (max-width: 1080px) and (prefers-reduced-motion: reduce) {
  .intro { transform: none; }
  .intro.is-out { transform: none; }
}

@media (max-width: 720px) {
  :root { --header-h: 56px; }
  .nav { display: none; }
  .topbar__price b { font-size: calc(17 * var(--fs)); }
  .stage__overlay { padding: 6px; }
  .stage__row { flex-wrap: wrap; }
  /* Three HUD blocks stacked down the top-left corner (search, then the
     counters, then the controls) ate ~150px off the top of a canvas that is
     only 54vh tall. The toolbar moved to the bottom corner at the 1080px
     breakpoint above and the widget went at 800px, so the search box and the
     badge are the whole of the top line here.

     90px basis rather than the 160px above: the badge is ~158px (no "see
     stats" label at this width) and the row is 272px inside a 320px phone, so
     anything larger wraps it onto a second line — the stacking this breakpoint
     exists to undo. The box grows back with the viewport (176px at 390). */
  .search { flex: 1 1 90px; }
  /* The results panel is pinned to both edges of a box that is now 106px wide
     on the smallest phone, which would ellipsise every product name. Unpin the
     right edge and let it spill under the badge instead, capped so it never
     runs past the canvas. */
  .search__results { right: auto; min-width: min(280px, calc(100vw - 40px)); }
  /* the auto margin exists to beat space-between on desktop; here the search
     box is the flexible child, so it would only add a gap of its own */
  .stage__row > .audience-slot { margin-right: 0; }
  /* the corner inset follows .stage__overlay's smaller padding above, so the
     toolbar stays flush with the search box; the tighter gap/padding (and the
     button sizes below, which /buy's toolbar wants too) buy back the ~4px that
     otherwise wraps a lone "#" onto a second line at 320px */
  .search ~ .toolbar { right: 6px; bottom: 6px; }
  .toolbar { gap: 2px; padding: 3px; }
  .toolbar button { min-width: 26px; padding: 0 5px; }
  .zoom-readout { min-width: 44px; padding: 0 3px; }
  /* the fixed sheet (1080px breakpoint above) gets its own width at 390px
     phones, so shrink the diagram/type further than that band needs to and
     stack the CTAs — two 358px-wide buttons each hit their 140px min-width
     and wrap anyway, which just re-introduces the scroll this breakpoint
     exists to remove. */
  .intro__panel { padding: 16px 16px 12px; }
  .intro__eyebrow { margin-bottom: 4px; }
  .intro__title { font-size: calc(13 * var(--fs)); margin-bottom: 10px; }
  .intro__dgm { max-width: 210px; margin-bottom: 10px; }
  .intro__steps { gap: 5px; margin-bottom: 12px; }
  .intro__acts { flex-direction: column; margin-bottom: 10px; }
  .intro__acts .btn { width: 100%; min-width: 0; }
  /* Tighter than the desktop pill so both counters clear a 390px canvas — the
     overlay clips rather than scrolls. Nothing is dropped at this width any
     more: with the "see stats" link gone the pill is only two short stats. */
  .audience { gap: 8px; padding: 4px 7px; }
  .audience-slot--modal .audience { gap: 10px; }
  /* wide records tables scroll inside their panel instead of the page */
  .wrap, .wrap--wide { padding-left: 10px; padding-right: 10px; }
  .panel { overflow-x: auto; }
  /* size to content and scroll inside the panel, rather than squeezing
     columns until the product names become ellipses */
  .table { font-size: calc(12 * var(--fs)); width: max-content; min-width: 100%; }
  .table th { font-size: calc(9 * var(--fs)); }
  .table th, .table td { white-space: nowrap; }
  .modal__foot .btn { flex: 1; }
  h1 { font-size: calc(15 * var(--fs)); }
  h2 { font-size: calc(12 * var(--fs)); }
  .stat__v { font-size: calc(21 * var(--fs)); }
  .designer-cell { width: 22px; height: 22px; }
  .mobile-hint { display: block; }
  .footer__grid { grid-template-columns: 1fr; gap: 18px; padding-bottom: 14px; }
  /* the temporary font picker lives in the footer on small screens, so the
     topbar keeps room for the wordmark and the live price */
  .topbar { gap: 8px; padding: 0 10px; }
  .brand { font-size: calc(11 * var(--fs)); gap: 6px; }
  .brand__mark { height: 18px; }
  .topbar__price { padding: 4px 7px; }
  .topbar__price span { display: none; }
  .theme-toggle { width: 34px; height: 34px; }
  /* Same trade as .topbar__price span just above: "Next square" plus the
     price plus the buy button don't fit a 360px bar, and the number is what
     a buyer actually needs — the label goes, the price and CTA survive. */
  .mbar { gap: 8px; padding: 0 10px; }
  .mbar__label { display: none; }
}

.mobile-hint { display: none; }
