/* Flemmings — UI overlay styling (GDD §15). Dark metal bottom bar (88 px),
   64×64 skill buttons with count badges, HUD + results + level-select map.
   No external assets; system fonts; colorblind-safe (shape + number + color). */

:root {
  --fb-metal: #232a33;
  --fb-metal-hi: #313a46;
  --fb-metal-lo: #171d24;
  --fb-edge: #0c1014;
  --fb-text: #e8f0e8;
  --fb-dim: #9fb0a8;
  --fb-accent: #ffd27a;
  --fb-good: #7fd67a;
  --fb-bad: #ff6b5e;
}

/* ---------------- bottom control panel ---------------- */
#panel.fb-bottom-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  height: 88px; display: flex; align-items: center; gap: 10px;
  padding: 0 12px; box-sizing: border-box;
  background: linear-gradient(180deg, var(--fb-metal-hi), var(--fb-metal) 55%, var(--fb-metal-lo));
  border-top: 2px solid var(--fb-edge);
  box-shadow: 0 -4px 18px rgba(0,0,0,.45);
  user-select: none;
}
.fb-cluster { display: flex; align-items: center; gap: 4px; }
.fb-btn {
  background: var(--fb-metal-hi); color: var(--fb-text);
  border: 1px solid var(--fb-edge); border-radius: 6px;
  font: 700 13px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  cursor: pointer; padding: 8px 10px;
}
.fb-btn:hover { filter: brightness(1.15); }
.fb-btn:active { transform: translateY(1px); }

/* RESTART — the only control in the bar with a WORD on it (owner, 29 Sep: "an
   obvious button to restart a level"). Every other button is a bare glyph
   (☠ ❚❚ ☰ ▾ ▲), which is why a fifth icon would not have fixed "obvious".
   Sits first in the bar, far from the destructive nuke at the other end.
   Deliberately quieter than the skill row: the accent glow is an ARMED SKILL'S
   state, and a restart that glows would compete with the primary control
   surface. It earns prominence from being labelled and outlined, not louder. */
.fb-restart {
  flex: 0 0 auto;
  height: 64px;
  padding: 0 13px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px;
  background: linear-gradient(180deg, #3d4857, #2b333e);
  border: 1px solid #61718266;
  border-radius: 7px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07), 0 2px 6px rgba(0, 0, 0, .35);
}
.fb-restart-glyph { font: 700 19px/1 ui-monospace, monospace; color: var(--fb-good); }
.fb-restart-label {
  font: 700 9.5px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 1.1px; color: var(--fb-dim);
}
.fb-restart:hover .fb-restart-label { color: var(--fb-text); }
/* One-shot ack, so a click is never ambiguous with the reload it triggers. */
.fb-restart.fb-restart-ack { animation: fb-restart-ack .45s ease; }
@keyframes fb-restart-ack {
  0% { box-shadow: 0 0 0 2px var(--fb-good), inset 0 1px 0 rgba(255, 255, 255, .07); }
  100% { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07), 0 2px 6px rgba(0, 0, 0, .35); }
}

.fb-rate { flex-direction: row; }
.fb-rate-btn { width: 30px; height: 40px; font-size: 12px; }
.fb-rate-readout {
  min-width: 44px; text-align: center; padding: 9px 6px;
  background: var(--fb-metal-lo); border: 1px solid var(--fb-edge); border-radius: 6px;
  font: 700 18px/1 ui-monospace, monospace; color: var(--fb-accent);
}
.fb-rate-readout.fb-clamp-flash { animation: fb-clamp .5s ease; }
@keyframes fb-clamp { 0%, 60% { color: #ff5a4e; box-shadow: 0 0 10px rgba(255,90,78,.8); } 100% { color: var(--fb-accent); } }

.fb-timer { margin: 0 4px; }
.fb-timer-text { font: 700 20px/1 ui-monospace, monospace; color: var(--fb-text); }
.fb-timer-low { color: #ff5a4e; animation: fb-pulse 1s steps(2) infinite; }
.fb-timer-warn { color: #ffb347; }
/* Zero: the crew is being detonated. Shake the readout — the sim is mid-cascade. */
.fb-timer-zero { color: #ff3b2e; animation: fb-det-shake 0.12s steps(2) infinite; }
@keyframes fb-det-shake { 50% { transform: translateX(1px) scale(1.08); } }
@keyframes fb-pulse { 50% { opacity: .45; } }

.fb-counters { flex-direction: column; gap: 2px; }
.fb-counter { display: flex; gap: 6px; align-items: baseline; }
.fb-counter-label { font: 600 9px/1 ui-monospace, monospace; color: var(--fb-dim); letter-spacing: 1px; }
.fb-counter-value { font: 700 14px/1 ui-monospace, monospace; min-width: 22px; text-align: right; }

.fb-skills { display: flex; gap: 6px; flex: 0 0 auto; }
.fb-skill {
  width: 64px; height: 64px; padding: 4px 2px;
  display: flex; flex-direction: column; align-items: center; justify-content: space-between;
  position: relative; box-sizing: border-box;
}
.fb-skill-icon { width: 30px; height: 30px; color: var(--fb-text); }
.fb-skill-name { font: 600 8.5px/1 ui-monospace, monospace; color: var(--fb-dim); letter-spacing: .4px; }
.fb-skill-count {
  position: absolute; top: -6px; right: -4px;
  min-width: 18px; height: 18px; padding: 0 4px; border-radius: 9px;
  background: var(--fb-accent); color: #1a1206;
  font: 700 11px/18px ui-monospace, monospace; text-align: center;
  box-shadow: 0 1px 3px rgba(0,0,0,.5);
}
.fb-skill.fb-empty {
  /* Was opacity: .4 on the whole tile. Measured at 1280x800 gameplay scale in a
     played level, seven of eight tiles carry .fb-empty at the first frame, so the
     row a player must READ to decide anything was rendered at 40 percent: the
     glyphs went to mush while the four control tiles, which are never empty,
     stayed crisp. That is the actual reason 'twelve tiles read as one set' was
     false by eye while geometry said it was true - one stroke system, two visual
     weights. "None left" is the badge's job, not the glyph's. */
  opacity: .66;
}
.fb-skill.fb-empty .fb-skill-count { background: #55606b; color: #d7dee3; }
.fb-skill.fb-armed {
  transform: translateY(-4px);
  box-shadow: 0 0 0 2px var(--fb-accent), 0 0 14px rgba(255, 210, 122, .55);
  background: #3b4653;
}
.fb-skill.fb-deny-flash { animation: fb-deny .45s ease; }
@keyframes fb-deny { 0%, 60% { box-shadow: 0 0 0 2px #ff5a4e; } 100% { box-shadow: none; } }

/* ---------------------------------------------------------------------------
   Right-edge control tiles (was: raw text glyphs on three sizes, ~120px dead).
   One set with the skill row: same 46x64 tile, same 30px icon box, same 2.2
   stroke system. `.fb-skill-icon` and `.fb-bar-icon` share every metric so the
   eight skills and the four controls cannot drift apart again.
   --------------------------------------------------------------------------- */
.fb-bar-icon { width: 30px; height: 30px; display: block; }
/* One stroke system, with filled masses declared explicitly rather than inferred
   from path data: an attribute-substring selector like path[d^="M11 7"] breaks the
   moment a coordinate is nudged, which is the normal thing to do to an icon. */
.fb-bar-icon { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: butt; stroke-linejoin: miter; }
.fb-bar-icon .fb-fill { fill: currentColor; stroke: none; }
.fb-bar-icon rect, .fb-bar-icon circle { fill: currentColor; stroke: none; }

/* One tile size for the four controls. */
.fb-nuke, .fb-pause, .fb-speed, .fb-menu { width: 46px; height: 64px; font-size: 0; }
.fb-speed {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px;
}
.fb-speed-val { font: 700 11px/1 ui-monospace, monospace; letter-spacing: .3px; }
/* THE FAST-FORWARD STATE USED TO HAVE NO RULE AT ALL. setSpeed() toggles this class
   (bottom-bar.js) and nothing styled it, so engaging 2x changed 37 px of the whole
   1280x88 bar - only the 1x/2x text - and a player could not tell 1x from 2x from
   the tiles. Same treatment as .fb-pause.fb-paused, deliberately: two latched
   transport states, one visual language. */
.fb-speed.fb-speed-on { color: var(--fb-accent); box-shadow: 0 0 0 2px var(--fb-accent); }
.fb-speed.fb-speed-on .fb-speed-val { color: var(--fb-accent); }

/* Pause reports its own state: bars when running, triangle when paused. */
.fb-pause .fb-ico-play { display: none; }
.fb-pause .fb-ico-pause { display: block; }
.fb-pause.fb-paused .fb-ico-pause { display: none; }
.fb-pause.fb-paused .fb-ico-play { display: block; }

/* Nuke was font-size:24 on a text glyph; the icon carries its own size now.
   The colour is kept from the old rule (it was on the same declaration I replaced). */
.fb-nuke { width: 46px; color: #cfd8dc; }
.fb-nuke-glyph { display: block; }
.fb-nuke.fb-nuke-armed {
  color: #ff5a4e; animation: fb-nuke-flash .4s steps(2) infinite;
  box-shadow: 0 0 0 2px #ff5a4e;
}
@keyframes fb-nuke-flash { 50% { background: #4a1f1c; } }
.fb-pause { width: 46px; height: 64px; }
.fb-pause.fb-paused { color: var(--fb-accent); box-shadow: 0 0 0 2px var(--fb-accent); }
.fb-menu { width: 46px; height: 64px; }

body.fb-armed-cursor, body.fb-armed-cursor #game { cursor: crosshair; }

/* ---------------- top HUD ---------------- */
#top-hud {
  position: fixed; top: 0; left: 0; right: 0; z-index: 15;
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 12px; pointer-events: none;
  font: 700 14px/1.2 ui-monospace, monospace; color: var(--fb-text);
  text-shadow: 0 1px 3px rgba(0,0,0,.8);
}
#top-hud[style*="display: none"] { display: none !important; }
.fb-hud-left { display: flex; align-items: center; gap: 8px; }
.fb-tier-badge {
  font: 700 10px/1 ui-monospace, monospace; letter-spacing: 1px;
  background: var(--fb-accent); color: #1a1206;
  padding: 3px 7px; border-radius: 4px;
}
.fb-progress {
  position: relative; width: 220px; height: 14px;
  background: rgba(12, 16, 22, .72); border: 1px solid var(--fb-edge); border-radius: 7px;
  overflow: hidden;
}
#progress-fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0%;
  background: #6b7f74; transition: width .25s ease;
}
#progress-fill.fb-progress-passing { background: var(--fb-good); }
#progress-tick {
  position: absolute; top: -2px; bottom: -2px; left: 50%; width: 2px;
  background: #ffffff; box-shadow: 0 0 4px rgba(0,0,0,.9);
}

/* ---------------- overlays ---------------- */
#paused, #results, #level-select {
  position: fixed; inset: 0; z-index: 30; display: none;
  align-items: center; justify-content: center;
  background: rgba(8, 10, 14, .6);
}
/* The map used to sit over the live game at 60% opacity. Stacking order was never
   the problem - #level-select is already above #top-hud (30 vs 15) and #panel (20) -
   the scrim was simply thin enough that a running level stayed readable underneath
   it, so the dossier appeared on top of a bottom bar, a timer and an OUT/SAVED
   counter that are not part of this screen.
   Near-opaque + blur keeps a hint of depth without leaving a legible HUD behind it. */
#level-select {
  background: rgba(6, 8, 11, 0.94);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
#paused { font: 700 30px/1 ui-monospace, monospace; letter-spacing: 6px; color: #fff; }

.fb-results-card, .fb-map {
  background: linear-gradient(180deg, var(--fb-metal-hi), var(--fb-metal-lo));
  border: 2px solid var(--fb-edge); border-radius: 12px;
  padding: 28px 40px; text-align: center; color: var(--fb-text);
  font: 600 15px/1.5 ui-monospace, monospace;
  box-shadow: 0 12px 48px rgba(0,0,0,.6);
  max-height: 92vh; overflow: auto;
}
.fb-results-card h1 { font-size: 24px; letter-spacing: 2px; margin: 0 0 12px; }
.fb-results-card h1.fb-win { color: var(--fb-good); }
.fb-results-card h1.fb-lose { color: #ff5a4e; }
.fb-results-pct { display: flex; gap: 18px; justify-content: center; align-items: baseline; }
.fb-results-saved { font-size: 52px; font-weight: 800; color: var(--fb-accent); }
.fb-results-required { color: var(--fb-dim); font-size: 14px; }
.fb-results-stars { font-size: 40px; margin: 8px 0; letter-spacing: 8px; }
.fb-star { color: #45505c; display: inline-block; }
.fb-star-earned { color: var(--fb-accent); animation: fb-star-pop .5s cubic-bezier(.2, 2.4, .4, 1) both; text-shadow: 0 0 18px rgba(255, 210, 122, .8); }
@keyframes fb-star-pop { from { transform: scale(0); } to { transform: scale(1); } }
.fb-results-tally { display: flex; gap: 16px; justify-content: center; color: var(--fb-dim); margin: 6px 0; flex-wrap: wrap; }
.fb-cause i { font-style: normal; margin-right: 4px; }
.fb-results-score { margin: 8px 0 16px; color: var(--fb-text); }
.fb-results-buttons { display: flex; gap: 10px; justify-content: center; }
.fb-results-buttons .fb-btn { padding: 10px 18px; }
.fb-next { background: var(--fb-accent); color: #1a1206; }

/* ---------------- level-select: operations dossier ----------------
   Three versions, and each one was a reaction to looking at the last:

   1. five pastel gradients differing only in hue, 119 identical grey tiles each
      carrying a 🔒 emoji, and a 1080px card marooned in a viewport it never filled.
   2. restraint — tier identity by sigil SHAPE + accent hairline + code, redacted
      locked slots, the card widened. Correct, and still a spreadsheet in a void.
   3. THIS ONE ("No, you go ahead and make it beautiful."). Three moves: a DETAIL
      PANEL, so a tile can say its own name and what it costs; DEPTH, so the board
      is a lit surface with raised cards rather than flat ink; and SCALE, so the
      thing fills the screen it is on.

   The palette is unchanged. Every colour below is either an existing token, the
   tier's own --region-accent, or a neutral derived from the stock, because the
   five accents are the only saturated things on the board and that is what makes
   a five-column grid readable at a glance. */

.fb-map {
  position: relative;
  max-width: 1760px; width: min(97vw, 1760px);
  /* HUGS ITS CONTENT. A min-height here forced ~250px of dead board between the
     detail panel and the footer at 1440p — the card was tall and the screen looked
     empty, which is the opposite of what filling the viewport was for. The overlay
     already centres it. */
  display: flex; flex-direction: column;
  padding: 20px 24px 12px; text-align: left;
  border: 1px solid #05080b; border-radius: 6px;
  /* Dossier stock: a vertical wash for depth, a 4px scanline so large empty areas
     read as printed surface, and a faint blueprint grid so the board is a surface
     rather than a void. */
  background:
    repeating-linear-gradient(0deg, rgba(255,255,255,.016) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(90deg, rgba(255,255,255,.012) 0 1px, transparent 1px 28px),
    linear-gradient(180deg, #232c35 0%, #161c23 46%, #0f141a 100%);
  box-shadow:
    0 26px 80px rgba(0,0,0,.72),
    0 2px 0 rgba(255,255,255,.04) inset,
    0 0 0 1px rgba(255,255,255,.03);
  overflow: hidden;
}

/* A lamp over the board: one soft warm pool at the top centre, so the header is the
   brightest part of the screen and the grid falls away toward the footer. Pure
   decoration, and it never intercepts a click. */
.fb-map-scrim {
  position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(120% 58% at 50% -12%, rgba(255, 226, 168, .085), transparent 62%),
    radial-gradient(140% 90% at 50% 118%, rgba(0, 0, 0, .5), transparent 58%);
}
.fb-map > *:not(.fb-map-scrim) { position: relative; z-index: 1; }

.fb-map-head {
  display: flex; align-items: flex-end; gap: 22px;
  padding-bottom: 12px; margin-bottom: 14px;
  border-bottom: 1px solid rgba(255,255,255,.09);
  box-shadow: 0 1px 0 rgba(0,0,0,.5);
}
.fb-map-title { flex: 1; display: block; }
.fb-map-head h1 {
  text-align: left; font-size: 30px; letter-spacing: 11px; margin: 0;
  color: #f6faf6; font-weight: 700; text-transform: uppercase;
  text-shadow: 0 2px 14px rgba(255, 226, 168, .18), 0 1px 0 rgba(0,0,0,.7);
}
.fb-map-sub {
  display: block; margin-top: 5px;
  font-size: 10px; letter-spacing: 3px; color: var(--fb-dim); font-weight: 600;
}
.fb-map-profile { display: flex; gap: 10px; align-items: stretch; }
.fb-stat {
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 2px; min-width: 74px;
  padding: 6px 11px; border-radius: 4px;
  background: linear-gradient(180deg, rgba(255,255,255,.055), rgba(0,0,0,.24));
  border: 1px solid rgba(255,255,255,.07);
  font-size: 15px; font-weight: 700; color: var(--fb-accent); letter-spacing: .5px;
  font-variant-numeric: tabular-nums;
}
.fb-stat b {
  display: block; font-size: 8px; letter-spacing: 1.7px;
  color: var(--fb-dim); font-weight: 600;
}
.fb-map-close { margin-left: 0; align-self: flex-start; }

.fb-map-regions {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 13px;
  align-items: start;
}
@media (max-width: 900px) { .fb-map-regions { grid-template-columns: repeat(2, 1fr); } }

.fb-region {
  position: relative; overflow: hidden;
  /* A RAISED CARD, not a tinted rectangle: an accent crown, a lit top edge, a real
     cast shadow, and a body that lightens toward its own header. */
  background: linear-gradient(180deg, #2b343e 0%, #1d242c 55%, #161c23 100%);
  border: 1px solid #0a0e12; border-top: 3px solid var(--region-accent);
  border-radius: 5px; padding: 11px 10px 12px;
  box-shadow:
    0 10px 26px rgba(0,0,0,.5),
    inset 0 1px 0 rgba(255,255,255,.06);
}
/* The tier's sigil, blown up and dropped to a watermark behind its own tiles. The
   shape (not the hue) is what identifies a tier, so the watermark has to be the
   SHAPE — a large tinted rectangle would just be a smudge. */
.fb-region::before {
  content: ''; position: absolute; right: -18px; bottom: -22px;
  width: 150px; height: 150px; background: var(--region-accent);
  opacity: .055; pointer-events: none;
}
.fb-region[data-sigil="diamond"]::before  { transform: rotate(45deg) scale(.62); }
.fb-region[data-sigil="chevron"]::before  { clip-path: polygon(0 0, 50% 50%, 100% 0, 100% 38%, 50% 88%, 0 38%); }
.fb-region[data-sigil="cross"]::before    { clip-path: polygon(34% 0,66% 0,66% 34%,100% 34%,100% 66%,66% 66%,66% 100%,34% 100%,34% 66%,0 66%,0 34%,34% 34%); }
.fb-region[data-sigil="triangle"]::before { clip-path: polygon(50% 0,100% 100%,0 100%); }
.fb-region[data-sigil="bars"]::before {
  background: repeating-linear-gradient(90deg, var(--region-accent) 0 14px, transparent 14px 30px);
}

.fb-region-head {
  position: relative;
  display: grid; grid-template-columns: 13px auto; align-items: center;
  gap: 2px 7px; margin-bottom: 7px;
}
.fb-region-sigil {
  grid-row: 1 / 3; width: 12px; height: 12px; justify-self: center;
  background: var(--region-accent);
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--region-accent) 55%, transparent));
}
/* Shape is the differentiator, per tier — verified as four distinct silhouettes
   rather than four tints of one square. */
.fb-region[data-sigil="diamond"]  .fb-region-sigil { transform: rotate(45deg) scale(.82); }
.fb-region[data-sigil="chevron"]  .fb-region-sigil {
  background: none; border-left: 3px solid var(--region-accent);
  border-bottom: 3px solid var(--region-accent); transform: rotate(-45deg) scale(.7);
}
.fb-region[data-sigil="cross"]    .fb-region-sigil {
  background: none;
  box-shadow: inset 0 0 0 0 var(--region-accent), 0 0 0 0 var(--region-accent);
  clip-path: polygon(35% 0,65% 0,65% 35%,100% 35%,100% 65%,65% 65%,65% 100%,35% 100%,35% 65%,0 65%,0 35%,35% 35%);
  background: var(--region-accent);
}
.fb-region[data-sigil="triangle"] .fb-region-sigil {
  background: var(--region-accent);
  clip-path: polygon(50% 0,100% 100%,0 100%);
}
.fb-region[data-sigil="bars"]     .fb-region-sigil {
  background: repeating-linear-gradient(90deg, var(--region-accent) 0 2.5px, transparent 2.5px 5px);
}

.fb-region-code {
  font-size: 8.5px; letter-spacing: 1.8px; color: var(--region-accent);
  font-weight: 700; align-self: end;
}
.fb-region-head h2 {
  margin: 0; font-size: 14px; letter-spacing: 3px; color: #f2f7f2;
  font-weight: 700; text-transform: uppercase; align-self: start;
  text-shadow: 0 1px 0 rgba(0,0,0,.6);
}
.fb-region-tag {
  grid-column: 2; font-size: 8px; letter-spacing: 1.6px; color: #84938e;
  font-weight: 600;
}

/* Clearance bar — replaces the 🔒 sentence that repeated itself four times. */
.fb-region-gate {
  position: relative;
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: 6px; margin-bottom: 9px; padding: 4px 6px;
  background: rgba(0,0,0,.36); border-radius: 3px;
  box-shadow: inset 0 1px 2px rgba(0,0,0,.5);
}
.fb-gate-key { font-size: 7.5px; letter-spacing: 1.3px; color: #78867f; font-weight: 700; }
.fb-gate-bar { height: 4px; background: rgba(255,255,255,.08); border-radius: 2px; overflow: hidden; }
.fb-gate-bar i {
  display: block; height: 100%; background: var(--region-accent);
  box-shadow: 0 0 8px color-mix(in srgb, var(--region-accent) 60%, transparent);
}
.fb-gate-num { font-size: 8.5px; color: #93a29c; font-weight: 700; font-variant-numeric: tabular-nums; }
.fb-gate-open .fb-gate-num { color: var(--fb-good); }
.fb-gate-need {
  grid-column: 1 / -1; font-size: 8px; letter-spacing: .6px; color: #c99; font-weight: 600;
}

.fb-region-nodes { position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }

.fb-node {
  position: relative; aspect-ratio: 1 / .92; border-radius: 3px;
  border: 1px solid #090d11; color: var(--fb-text);
  font: 700 14px/1.2 ui-monospace, monospace;
  --node-gutter: 3px;
  cursor: pointer;
  padding: var(--node-gutter) var(--node-gutter) calc(var(--node-gutter) + .62em);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  transition: transform .09s ease, filter .12s ease, box-shadow .12s ease;
}

/* LOCKED = redacted dossier slot. No glyph, no emoji: a black bar over the place
   a title would be, which is what a censored line actually looks like. The file
   number stays legible, so 119 copies of one shape become 119 distinct rows.
   A diagonal hatch was added in the beauty pass so a locked tile reads as a
   physical folder with a seal on it rather than as a dead grey button. */
.fb-node-locked {
  cursor: default; color: #4b575f;
  background:
    repeating-linear-gradient(135deg, rgba(255,255,255,.022) 0 3px, transparent 3px 7px),
    linear-gradient(180deg, #1a2027, #12171c);
  border-color: #0c1116;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.03);
}
.fb-node-locked .fb-node-num { display: none; }
.fb-node-locked .fb-node-stars { display: none; }
.fb-node-locked::after {
  content: ''; position: absolute; left: 16%; right: 16%; top: 52%; height: 3px;
  background: #0b0f13; box-shadow: 0 -6px 0 #0b0f13, 0 6px 0 1px #0b0f13;
}
/* 119 locked tiles drew the IDENTICAL three-bar redaction, so the grid read as one
   repeated pattern rather than a cabinet of separate files. The variants below are
   selected per tile by level index + tier (see redactVariant() in level-select.js);
   keeping them as classes rather than 120 inline styles means a capture can name
   which variant a tile is using. Variant 0 is the plain rule above, so removing the
   JS class is a visible, and therefore testable, change. */
.fb-node-locked.fb-redact-1::after {
  left: 22%; right: 30%; top: 48%; height: 2.5px;
  box-shadow: 0 -5.5px 0 0 #0b0f13;
}
.fb-node-locked.fb-redact-2::after {
  left: 14%; right: 22%; top: 56%; height: 3.5px;
  box-shadow: 0 -6.5px 0 0 #0b0f13, 0 6px 0 1.5px #0b0f13, 0 12px 0 0 #0b0f13;
}
.fb-node-locked.fb-redact-3::after {
  left: 26%; right: 14%; top: 50%; height: 3px;
  box-shadow: 0 -6px 0 0 #0b0f13, 0 6.5px 0 0.5px #0b0f13;
}

/* The dossier file number, top-left, quiet. Present on every tile, which is what
   makes an OPEN tile's bare number read as a deliberate exception. */
.fb-node-file {
  position: absolute; top: 3px; left: 4px;
  font-size: 7px; letter-spacing: .4px; color: #5a6870; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* An OPEN tile is the only bright thing on the board: a lit face, a top highlight
   and a real drop shadow, so it reads as pressable at a glance. */
.fb-node-open {
  background: linear-gradient(180deg, #46535f 0%, #313b46 52%, #262e37 100%);
  border-color: #0a0e12;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 2px 5px rgba(0,0,0,.45);
}
.fb-node-open .fb-node-num {
  font-size: 17px; color: #f7fbf7; text-shadow: 0 1px 2px rgba(0,0,0,.6);
}
.fb-node-open:hover {
  filter: brightness(1.3);
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 6px 14px rgba(0,0,0,.55);
}

/* Debriefed: identity moves to the accent edge, stars become readable, and the
   number dims — the result matters more than the ordinal. */
.fb-node-won {
  border-left: 2px solid var(--fb-good);
  background: linear-gradient(180deg, #3b4650 0%, #2b333c 55%, #222932 100%);
}
.fb-node-won .fb-node-num { font-size: 13px; color: #bcc9c2; }
/* STARS ARE THE PAYOFF. At 10px and a plain amber they were the quietest thing on a
   tile whose whole point is that you finished it — the earned result has to outrank
   the ordinal next to it, which is why the number dims and these do not. Sized up,
   brightened, and given a warm bloom so a debriefed tile reads as a small trophy
   from across the room. */
.fb-node-stars { font-size: 11.5px; color: #ffd98a; letter-spacing: 1.5px; line-height: 1; }
.fb-node-won .fb-node-stars {
  text-shadow: 0 0 6px rgba(255, 200, 90, .75), 0 0 14px rgba(255, 180, 60, .35);
}
.fb-node-won {
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 2px 6px rgba(0,0,0,.45),
              0 0 0 1px rgba(127,214,122,.12);
}

/* Exactly one of these exists on the board (asserted in tests/ui.test.mjs). It is
   the only animated tile: a slow amber breath, which is how the eye finds "where
   was I" on a 120-tile board without a pointer. */
.fb-node-next {
  border: 1px solid var(--fb-accent);
  background: linear-gradient(180deg, #55492f 0%, #3a2f1f 60%, #2b2317 100%);
  box-shadow: 0 0 0 1px rgba(255,210,122,.45), 0 0 18px rgba(255,210,122,.3);
  animation: fb-next-breath 2.6s ease-in-out infinite;
}
@keyframes fb-next-breath {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255,210,122,.45), 0 0 14px rgba(255,210,122,.22); }
  50%      { box-shadow: 0 0 0 1px rgba(255,210,122,.75), 0 0 26px rgba(255,210,122,.42); }
}
.fb-node-flag {
  /* Inset by the tile's own gutter, not a hand-matched pixel. font-size in em makes
     the flag scale with the tile it lives in, and the gutter keeps its border off
     the tile border at any tile size. */
  position: absolute; bottom: var(--node-gutter, 3px); right: var(--node-gutter, 3px);
  font-size: .5em; letter-spacing: 1px; color: #1a1206;
  background: var(--fb-accent); padding: 0 .3em; border-radius: 2px; font-weight: 800;
}
.fb-node-cursor { outline: 2px solid #fff; outline-offset: 2px; }

/* ---------------- detail panel ----------------
   The board was 120 anonymous boxes. This is the thing that makes it a dossier: it
   names the file under the pointer (or the cursor), says what the level wants and
   what it will cost, and draws the level's OWN terrain from its own JSON — so the
   thumbnail cannot disagree with the level it describes. */
.fb-detail {
  margin-top: 14px; min-height: 118px;
  border-radius: 5px;
  background: linear-gradient(180deg, rgba(255,255,255,.045), rgba(0,0,0,.3));
  border: 1px solid rgba(255,255,255,.07);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05), 0 6px 18px rgba(0,0,0,.4);
  overflow: hidden;
}
.fb-detail-inner {
  display: grid; grid-template-columns: 300px 1fr auto; gap: 18px;
  align-items: center; padding: 14px 18px;
  border-left: 3px solid var(--region-accent, #7fd6a8);
}
.fb-detail-media {
  display: flex; align-items: center; justify-content: center;
  height: 112px; border-radius: 4px;
  background:
    repeating-linear-gradient(0deg, rgba(255,255,255,.02) 0 1px, transparent 1px 3px),
    rgba(0,0,0,.42);
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: inset 0 2px 8px rgba(0,0,0,.6);
}
canvas.fb-thumb { display: block; image-rendering: pixelated; }
.fb-detail-text { min-width: 0; }
.fb-detail-top { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.fb-detail-code {
  font: 700 10px/1 ui-monospace, monospace; letter-spacing: 2px;
  color: var(--region-accent, #7fd6a8);
}
.fb-detail-tier {
  font: 600 9px/1 ui-monospace, monospace; letter-spacing: 1.8px; color: #84938e;
}
.fb-detail-state {
  margin-left: auto; font: 800 8.5px/1 ui-monospace, monospace;
  letter-spacing: 2px; padding: 3px 7px; border-radius: 2px;
}
.fb-state-open    { color: #0f1a12; background: var(--fb-good); }
.fb-state-sealed  { color: #d9b0a8; background: rgba(226,112,95,.16); border: 1px solid rgba(226,112,95,.4); }
.fb-state-pending { color: #cbb98a; background: rgba(255,210,122,.12); border: 1px solid rgba(255,210,122,.3); }
.fb-state-locked  { color: #b9c6c0; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.18); }
.fb-detail-name {
  margin: 0 0 8px; font-size: 20px; font-weight: 700; letter-spacing: .5px;
  color: #f4f9f4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fb-detail-stats { display: flex; gap: 20px; align-items: baseline; margin-bottom: 7px; }
.fb-dstat {
  display: flex; flex-direction: column; gap: 1px;
  font: 600 8.5px/1 ui-monospace, monospace; letter-spacing: 1.5px; color: var(--fb-dim);
}
.fb-dstat b {
  font-size: 17px; color: var(--fb-accent); letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.fb-detail-skills { display: flex; flex-wrap: wrap; gap: 5px; }
.fb-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 7px 3px 4px; border-radius: 3px;
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
  font: 700 8.5px/1 ui-monospace, monospace; letter-spacing: 1.1px; color: #cfdad4;
}
.fb-chip b {
  min-width: 15px; text-align: center; padding: 2px 3px; border-radius: 2px;
  background: rgba(255,210,122,.9); color: #1a1206; font-size: 9px;
}
.fb-chip-none { color: #b9a68a; border-color: rgba(255,210,122,.28); }
.fb-detail-note { margin: 0; font-size: 12px; color: #a9b6b0; line-height: 1.5; }
.fb-detail-note b { color: var(--fb-text); }
.fb-detail-loading { font: 600 11px/1 ui-monospace, monospace; letter-spacing: 2px; color: #6d7b76; }
.fb-detail-stars {
  font-size: 22px; letter-spacing: 4px; color: #3d4854;
  text-shadow: 0 1px 0 rgba(0,0,0,.5);
}
@media (max-width: 1100px) {
  .fb-detail-inner { grid-template-columns: 1fr auto; }
  .fb-detail-media { display: none; }
}

.fb-map-foot {
  margin-top: auto; padding-top: 10px; color: var(--fb-dim); font-size: 10.5px;
  letter-spacing: .8px; border-top: 1px solid rgba(255,255,255,.06);
  display: flex; gap: 14px; align-items: center;
}
.fb-foot-keys { margin-left: auto; color: #7d8b86; }

/* ---------------- tooltip + deny puff ---------------- */
#hover-tooltip {
  position: fixed; z-index: 25; display: none;
  background: rgba(12, 16, 22, .85); color: var(--fb-text);
  border: 1px solid var(--fb-edge); border-radius: 5px;
  font: 600 11px/1 ui-monospace, monospace; letter-spacing: .5px;
  padding: 5px 8px; pointer-events: none;
}
.fb-deny-puff {
  position: fixed; z-index: 24; transform: translate(-50%, -50%);
  color: #ff5a4e; font: 800 22px/1 ui-monospace, monospace;
  text-shadow: 0 0 8px rgba(255, 90, 78, .9); pointer-events: none;
  animation: fb-puff .7s ease-out forwards;
}
@keyframes fb-puff { from { opacity: 1; margin-top: 0; } to { opacity: 0; margin-top: -34px; } }

/* Keep the old dev hud out of the way of the panel (bottom bar ~98px tall).
   NOTE this is the line that put it on top of the audio pill: the pill is
   left:12 / bottom:104 and ~33px tall, so bottom:98 sat inside it. When the dev
   readout is explicitly opted into (main.js DEV_HUD), it clears BOTH the panel and
   the pill. index.html keeps it display:none, so none of this is visible in
   production. */
#hud { bottom: 98px; }
#hud.fb-dev-on { display: block; bottom: 146px; }

/* ---------------------------------------------------------------------------
   Spy crew (DECISIONS.md #15): speech bubbles + mission-debrief results.
   --------------------------------------------------------------------------- */
.fb-bubble {
  position: fixed; z-index: 24; transform: translate(-50%, -90%);
  top: 38vh; padding: 7px 12px; border-radius: 10px;
  background: rgba(16, 20, 26, 0.92); color: #ffec9e;
  border: 1px solid rgba(255, 236, 158, 0.55);
  font: 700 13px/1.25 ui-monospace, Menlo, monospace; letter-spacing: .02em;
  pointer-events: none; white-space: nowrap;
  animation: fb-bubble-pop 2.2s ease-out forwards;
}
.fb-bubble::after {
  content: ''; position: absolute; left: 50%; bottom: -6px;
  width: 10px; height: 10px; transform: translateX(-50%) rotate(45deg);
  background: rgba(16, 20, 26, 0.92); border-right: 1px solid rgba(255, 236, 158, .55);
  border-bottom: 1px solid rgba(255, 236, 158, .55);
}
@keyframes fb-bubble-pop {
  0% { opacity: 0; transform: translate(-50%, -70%) scale(.8); }
  8% { opacity: 1; transform: translate(-50%, -90%) scale(1); }
  85% { opacity: 1; } 100% { opacity: 0; transform: translate(-50%, -100%); }
}

.fb-debrief { border-top: 3px solid #ffec9e; }
.fb-debrief-title {
  font: 800 15px/1 ui-monospace, monospace; letter-spacing: .34em;
  color: #9fb4c7; margin: 2px 0 10px;
}
.fb-debrief-report { font-size: 24px; margin: 0 0 6px; }
.fb-debrief-flavor { color: #aab6bf; font-style: italic; font-size: 13px; margin: 0 0 12px; }
.fb-debrief-stamp {
  position: absolute; top: 14px; right: 16px; padding: 4px 10px;
  font: 800 12px/1 ui-monospace, monospace; letter-spacing: .22em;
  border: 2px solid currentColor; border-radius: 4px; transform: rotate(6deg);
  opacity: .9;
}
.fb-stamp-win { color: #7fd67a; }
.fb-stamp-lose { color: #ff5a4e; }
.fb-debrief-causes { display: flex; flex-direction: column; gap: 2px; margin: 8px 0; }
.fb-debrief-causes .fb-cause { color: #aab6bf; font-size: 12px; }

/* --- First-level tutorial arrows (DECISIONS.md #17) --------------------- */
/* Three-band "sticker" pointer. The band colours are load-bearing, not styling:
   measured against the live frame, no single flat colour can reach 4.5:1 against
   both the flat blue sky (L .54) and the dark carved cavity (L .006) — the two
   WCAG bounds do not intersect. White halo + black casing is the only pair whose
   WORST-CASE max-contrast still clears the bar (4.583, at backdrop L = 0.179).
   The magenta core is a hue the scene contains nowhere, so it can never blend in.
   Widths here must match POINTER in src/ui/tutorial.js — the gate asserts both. */
.fb-tutorial {
  position: fixed; inset: 0; z-index: 22; pointer-events: none;
  --fb-tut-halo: #ffffff;   /* outer band — wins against any dark backdrop */
  --fb-tut-case: #07090c;   /* mid band   — wins against any light backdrop */
  --fb-tut-ink: #ff1f8f;    /* core       — 5.85:1 against the casing, absent hue */
}
.fb-tutorial-hidden { display: none; }
.fb-tutorial-arrow {
  position: absolute;
  width: 168px; height: 222px;   /* = POINTER.W / POINTER.H */
  overflow: visible;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, .4));
  animation: fb-tut-pulse 1.1s ease-in-out infinite;
}
.fb-tutorial-bundle > path {
  stroke-linecap: round;
  stroke-linejoin: round;
  /* NO `fill` here. A `fill: currentColor` on this rule silently beat every
     per-band `fill` below — class+type (0,1,1) outranks single class (0,1,0) —
     and the arrowhead rendered BLACK with a coloured outline while every CSS
     variable still read correctly. Measured pixels caught it; reading the
     stylesheet could not. Each band owns its own fill. */
}
.fb-tutorial-halo { stroke: var(--fb-tut-halo); stroke-width: 40; fill: var(--fb-tut-halo); }
.fb-tutorial-case { stroke: var(--fb-tut-case); stroke-width: 28; fill: var(--fb-tut-case); }
.fb-tutorial-ink  { stroke: var(--fb-tut-ink);  stroke-width: 16; fill: var(--fb-tut-ink); }
.fb-tutorial-box {
  position: absolute; display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: 8px;
  background: rgba(7, 9, 12, .92); border: 2px solid var(--fb-tut-halo);
}
.fb-tutorial-label {
  font: 800 17px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .16em; text-transform: uppercase; color: var(--fb-text);
  white-space: nowrap;
}
.fb-tutorial-skip {
  pointer-events: auto; cursor: pointer;
  background: none; border: 1px solid rgba(255, 255, 255, .35); border-radius: 6px;
  color: var(--fb-dim); font: 700 11px/1 ui-monospace, monospace;
  letter-spacing: .12em; padding: 5px 7px;
}
.fb-tutorial-skip:hover { color: var(--fb-text); border-color: var(--fb-text); }
/* The prompt draws attention by PULSING, not by moving.
   It used to be `transform: translateY(-7px)`, which slid the pointer 7 px off
   the thing it was pointing at — 11% of its own core width — so "does the tip
   land on the target" was not a determinable property while the page was alive,
   and a screenshot caught the arrow wherever the compositor happened to be.
   A pointer's whole job is to be on the target; make it brighter instead. */
@keyframes fb-tut-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .68; }
}
@media (prefers-reduced-motion: reduce) {
  .fb-tutorial-arrow { animation: none; }
}
