/* The rooms page (floorplan-view.mjs, GAME_RULES.md "Runs: servers as rooms"): the server strip on top (Wait and Jack out at its right), and
   the room map filling the rest, with the network map's pop-up card (.map-pop, style.css) beside a room. docs/ui.md tokens: amber is structure and what you can click,
   teal is you, hot is what bites (a live virus, the hunter), gray is down or out of sight.
   The map is Schematic HD: chips and traces, its tokens below. */
.page:has(> .fp) { overflow: hidden; }
.fp { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); gap: var(--s5); height: 100%; min-height: 0; }
.fp > * { min-width: 0; min-height: 0; }
.fp > .fp-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.shell-on .page:has(> .fp) { padding-top: var(--s4); }
.fp .ico { width: 13px; height: 13px; flex: none; background: currentColor; -webkit-mask: var(--icon) center/contain no-repeat; mask: var(--icon) center/contain no-repeat; }
.fp .lbl { font: var(--label); letter-spacing: var(--label-track); text-transform: uppercase; color: var(--dim); }
.fp-ic { flex: none; width: 18px; height: 18px; color: var(--amber-soft); overflow: visible; }
.fp-ic text { font: 600 15px var(--mono); fill: var(--gray); }
.fp-ic.hot { color: var(--hot); }

/* ---- the strip: the server | Signal | Rooms | keys, hunter, pack | boss rule, scripts ---- */
.fp-hud { grid-column: 1 / -1; position: relative; z-index: 6; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4) var(--s5); padding: var(--s4) var(--s6); background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-card); }
.fp-id { display: inline-flex; align-items: baseline; gap: var(--s3); color: var(--amber-soft); cursor: help; min-width: 0; }
.fp-id .ico { align-self: center; }
.fp-id b { font: 600 13px var(--mono); letter-spacing: 1.5px; color: var(--bright); white-space: nowrap; }
.fp-id small { font: 11px var(--mono); color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.fp-sig { display: flex; align-items: center; gap: var(--s4); font-variant-numeric: tabular-nums; flex: 0 1 200px; min-width: 170px; cursor: help; }
.fp-sig .sigbar { flex: 1; width: auto; min-width: 60px; height: 10px; }
.fp-sig strong { font: 600 18px var(--mono); color: var(--you); line-height: 1; }
.fp-sig small { color: var(--muted); font-family: var(--mono); margin-left: calc(-1 * var(--s3)); }
.fp-sig.mid .sigbar { border-color: var(--next); } .fp-sig.mid .sigbar span { background: var(--next); } .fp-sig.mid strong { color: var(--next); }
.fp-sig.low .sigbar { border-color: var(--hot); } .fp-sig.low .sigbar span { background: var(--hot); } .fp-sig.low strong { color: var(--hot); }
.fp-prog { display: inline-flex; align-items: center; gap: var(--s3); font-variant-numeric: tabular-nums; cursor: help; }
.fp-prog .pbar { position: relative; width: 80px; height: 7px; border: 1px solid var(--line-strong); border-radius: 2px; background: var(--inset); overflow: hidden; }
.fp-prog .pbar span { position: absolute; inset: 0 auto 0 0; background: var(--you); }
.fp-prog .pbar i { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--muted); }
.fp-prog b { font: 600 15px var(--mono); color: var(--bright); }
.fp-mapped { font: 700 10px var(--mono); letter-spacing: 1px; color: var(--you); border: 1px solid var(--you); padding: 0 4px; border-radius: var(--r-tag); }
.fp-stat { display: inline-flex; align-items: center; gap: var(--s3); font-variant-numeric: tabular-nums; cursor: help; min-width: 0; }
.fp-stat b { font: 600 13px var(--mono); color: var(--bright); white-space: nowrap; }
.fp-stat .fp-ic { width: 15px; height: 15px; }
.fp-keys .fp-ic { color: var(--next); } .fp-keys b { color: var(--next); }
.fp-hunt .fp-ic, .fp-hunt b { color: var(--hot); }
.fp-hunt.out .fp-ic, .fp-hunt.out b { color: var(--gray); }
.fp-exfilstat .fp-ic, .fp-exfilstat b { color: var(--next); }
.fp-tools { margin-left: auto; display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--s4) var(--s5); min-width: 0; }
.fp-rules { display: inline-flex; align-items: center; gap: var(--s2); }
.fp-rules .lbl { margin-right: var(--s2); cursor: help; }
.fp-rule { height: var(--h-chip); min-width: 22px; padding: 0 var(--s3); border: 1px solid var(--line-strong); border-radius: var(--r-tag); background: transparent; color: var(--muted); font: 600 11px var(--mono); cursor: pointer; }
.fp-rule.on { border-color: var(--hot); color: var(--hot); background: var(--hot-soft); }
.fp-rule:hover:not(.on) { border-color: var(--amber); color: var(--amber); }
.fp-perks { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); min-width: 0; }
.fp-perks .lbl { margin-right: var(--s2); cursor: help; }
.fp-perk { flex: none; display: inline-flex; align-items: center; height: var(--h-chip); padding: 0 var(--s3); border: 1px solid var(--line); border-left: 2px solid var(--r-stock); border-radius: var(--r-tag); font: 600 11px var(--mono); color: var(--bright); white-space: nowrap; cursor: help; }
.fp-perk.r-tuned { border-left-color: var(--r-tuned); } .fp-perk.r-custom { border-left-color: var(--r-custom); }
/* Wait and Jack out (Connect at home): the strip's last thing on a wide screen, beside the server's name on a phone */
.fp-acts { display: inline-flex; gap: var(--s3); flex: none; }
.fp-acts .btn { white-space: nowrap; }

/* ---- the map: Schematic HD (floorplan-view.mjs). Rooms are chips, links are traces; teal is only ever you, red a live
   threat, amber loot and structure, gray cleared or used. ---- */
.fp-map { position: relative; display: flex; flex-direction: column; overflow: visible; }
.fp-scroll { position: relative; flex: 1; min-height: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; padding: var(--s5);
  background: radial-gradient(circle at 1px 1px, rgba(241, 185, 47, 0.09) 0.8px, transparent 0) 0 0 / 14px 14px, #0a0703; }
.fp-scroll::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(ellipse at center, transparent 55%, rgba(0, 0, 0, 0.5)); }
.fp-svg { flex: none; overflow: hidden; user-select: none; -webkit-user-select: none; }
.fp-svg.wide { width: 100%; height: 100%; max-width: calc(var(--w) * 1.3); max-height: calc(var(--h) * 1.3); }
.fp-svg.tall { display: none; }
.fp-svg.zoomed { cursor: grab; touch-action: none; }
.fp-zoom { position: absolute; right: var(--s5); top: var(--s5); z-index: 4; min-width: 30px; }
/* the traceroute (floorplan-view.mjs traceMarkup): the last few hops at the map's foot, older ones dimmer */
.fp-trace { position: absolute; left: var(--s5); bottom: var(--s4); z-index: 3; display: grid; gap: 2px; font: 10px var(--mono); color: var(--dim); font-variant-numeric: tabular-nums; cursor: help; }
.fp-trace .hop { display: grid; grid-template-columns: 7ch 9ch 6ch; gap: var(--s3); opacity: calc(1 - var(--age) * 0.3); }
.fp-trace .hop.last { color: var(--gray); }
.fp-trace .ms { text-align: right; }
.fp-trace .ms.hot { color: var(--hot); }
.fp-map.pz-on .fp-trace { visibility: hidden; }
@media (max-width: 700px) { .fp-trace { display: none; } }
/* traces: a dark bed and a thin line */
.fp-bed { fill: none; stroke: #050302; stroke-width: 9; stroke-linecap: round; }
.fp-bed.beat { stroke: #260b06; }
.fp-under { fill: none; stroke: rgba(241, 185, 47, 0.25); stroke-width: 5; stroke-linecap: round; }
.fp-link { fill: none; stroke-width: 1.6; }
.fp-link.seen { stroke: rgba(241, 185, 47, 0.35); }
.fp-link.walked { stroke: rgba(241, 185, 47, 0.5); }
.fp-link.fog { stroke: #3a2c14; stroke-dasharray: 2 6; }
.fp-link.live { stroke: var(--amber); stroke-width: 2.2; }
/* the hunter's next step */
.fp-route { fill: none; stroke: var(--hot); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 6 8; opacity: 0.9; pointer-events: none; }
.fp-arrowhead { fill: var(--hot); }
/* the Exfil's next step: amber, never the hunter's red */
.fp-route.exfil { stroke: var(--amber); }
.fp-arrowhead.exfil { fill: var(--amber); }
/* a chip: --t is its colour */
.fp-room { --t: var(--amber); }
.fp-room.t-hot { --t: var(--hot); } .fp-room.t-gray { --t: var(--gray); } .fp-room.t-you { --t: var(--you); }
.fp-room text { font-family: var(--mono); pointer-events: none; }
.fp-body { fill: url(#fp-g-node); stroke: var(--t); stroke-width: 2.2; }
.fp-room.t-hot .fp-body { fill: url(#fp-g-hot); }
.fp-room.t-you .fp-body { fill: url(#fp-g-here); stroke-width: 3; }
/* an unknown room (never been in): a ghost, no kind colour; one you can walk into a touch brighter, its cost badge on it */
.fp-room.k-fog .fp-body { fill: #0b0804; stroke: #4a3c22; stroke-width: 1; stroke-dasharray: 4 5; }
.fp-room.k-fog.reach .fp-body { stroke: #8a7346; stroke-width: 1.3; }
.fp-room.k-fog.reach .fp-q { fill: #8a7346; }
.fp-hair { fill: none; stroke: var(--t); stroke-width: 1; opacity: 0.35; pointer-events: none; }
/* a room's kind as its hue (floorplan-view.mjs lookOf), while it's live or unused: cleared stays gray, yours teal.
   The rooms map is the one place these hues mean a kind: magenta an elite, orange a socket, green the rest room,
   gold the vault, white the entry and the den; red is still a process and the Core, amber a cache. */
.fp-room.look-elite:is(.t-hot, .t-amber) { --t: #ff4fd8; }
.fp-room.look-core:is(.t-hot, .t-amber) { --t: #ff2626; }
.fp-room.look-socket:is(.t-hot, .t-amber) { --t: #ff9a2a; }
.fp-room.look-vault:is(.t-hot, .t-amber) { --t: #ffe066; }
.fp-room.look-rest:is(.t-hot, .t-amber) { --t: #7fdc7a; }
.fp-room.look-hall:is(.t-hot, .t-amber) { --t: #e6e1d6; }
.fp-room:is(.look-elite, .look-core, .look-socket, .look-vault, .look-rest, .look-hall):is(.t-hot, .t-amber) .fp-body { fill: color-mix(in srgb, var(--t) 12%, #120d08); }
.fp-icon { color: var(--t); pointer-events: none; }
.fp-room.t-gray .fp-icon { opacity: 0.6; }
.fp-plate { fill: rgba(0, 0, 0, 0.45); pointer-events: none; }
.fp-name { fill: var(--text); }
.fp-room.t-gray .fp-name { fill: var(--gray); }
.fp-q { font-size: 24px; fill: #5a4a2c; }
/* you: a soft glow and a pulsing outline */
.fp-you-glow { fill: none; stroke: var(--you); stroke-width: 6; opacity: 0.25; pointer-events: none; }
.fp-you-ring { fill: none; stroke: var(--you); stroke-width: 2; pointer-events: none; }
.fp-near { fill: none; stroke: var(--hot); stroke-width: 2; opacity: 0.7; pointer-events: stroke; cursor: help; }
/* what you can click: a room you can walk into, a room you've seen */
.fp-room.reach, .fp-room[data-fp-sel] { cursor: pointer; }
.fp-room.reach:hover .fp-body, .fp-room[data-fp-sel]:hover .fp-body, .fp-room:focus-visible .fp-body { filter: brightness(1.45); }
.fp-room:focus { outline: none; } /* the browser's box: a focused room brightens instead */
.fp-room:focus-visible { outline: none; }
.fp-room:focus-visible .fp-hair { stroke: var(--bright); opacity: 0.9; stroke-width: 1; }
.fp-room.sel:not(.here) .fp-body { stroke: var(--bright); stroke-width: 2; }
/* badges */
.fp-tag rect { fill: #0a0703; stroke: var(--muted); stroke-width: 1; }
.fp-tag text { font-size: 9px; letter-spacing: 0.5px; fill: var(--muted); }
.fp-tag.key rect { fill: var(--next); stroke: none; }
.fp-tag.key text { fill: #1a1400; font-weight: 700; }
.fp-tag.hot rect { stroke: var(--hot); } .fp-tag.hot text { fill: var(--hot); }
.fp-tag.rare rect { stroke: var(--next); } .fp-tag.rare text { fill: var(--next); font-size: 10px; }
.fp-cost rect { fill: #0a0703; stroke: var(--amber); stroke-width: 1; }
.fp-cost text { font-size: 10px; fill: var(--amber); font-variant-numeric: tabular-nums; }
.fp-you-tab rect { fill: var(--you); }
.fp-you-tab text { font-size: 10px; font-weight: 700; letter-spacing: 1px; fill: #06100e; }
.fp-live { fill: var(--hot); }
.fp-respawn .bg { fill: #0a0703; }
.fp-respawn .track { fill: none; stroke: #3a2c14; stroke-width: 3; }
.fp-respawn .arc { fill: none; stroke: var(--hot); stroke-width: 3; stroke-dasharray: 100; stroke-dashoffset: 100; }
.fp-respawn.amber .arc { stroke: var(--amber); }
.fp-respawn text { font-size: 10px; fill: var(--muted); font-variant-numeric: tabular-nums; }
.fp-skull .bg { fill: #140503; stroke: var(--hot); stroke-width: 1.2; }
.fp-skull .px { fill: var(--hot); }
.fp-skull.asleep { opacity: 0.55; }
/* the Exfil: a gold archive with an upload arrow in a small dark badge (filled once cornered) */
.fp-exfil .bg { fill: #1a1404; stroke: var(--next); stroke-width: 1.2; }
.fp-exfil use { color: var(--next); }
.fp-exfil.cornered .bg { fill: var(--next); } .fp-exfil.cornered use { color: #1a1404; }
/* sockets (R2): a program's badge bottom left (the slot it sits in, or on a held Core the program), a Tunnel's arc */
.fp-prog rect { fill: #0a0703; stroke: var(--amber); stroke-width: 1.2; }
.fp-prog use { color: var(--amber); }
.fp-room.prog .fp-icon { color: var(--bright); }
.fp-room.prog.off .fp-icon, .fp-room.prog.off .fp-name { opacity: 0.45; }
.fp-room.k-socket.open:not(.prog) .fp-hair { stroke-dasharray: 3 3; }
.fp-tunnel { fill: none; stroke: var(--amber); stroke-width: 2; stroke-dasharray: 2 5; stroke-linecap: round; opacity: 0.85; }
.fp-oneway path { fill: none; stroke: var(--amber); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.fp-oneway { pointer-events: visibleStroke; }
/* a room's grid address (C2): a dim corner label on every chip, an unknown one too; amber with a contract's mark when a
   job points at the room. Never teal (teal is you). */
.fp-addr rect { fill: #0a0703; }
.fp-addr text { fill: var(--dim); letter-spacing: 0.5px; font-variant-numeric: tabular-nums; }
.fp-room.k-fog .fp-addr text { fill: #806b42; }
.fp-addr.job rect { stroke: var(--amber); stroke-width: 1; }
.fp-addr.job text { fill: var(--amber); font-weight: 700; }
.fp-addr.job use { color: var(--amber); }
.fp-kick .fp-at { color: var(--dim); }
.fp-note.fp-job { display: flex; gap: var(--s3); align-items: flex-start; color: var(--amber); }
.fp-note.fp-job .fp-ic { flex: none; width: 16px; height: 16px; color: var(--amber); margin-top: 1px; }
.fp-jobs .fp-ic { color: var(--amber); }
.fp-jobs.ready b { color: var(--next); }
/* an Empty room: a hollow mark, quieter than anything else on the map */
.fp-room.k-empty .fp-icon { opacity: 0.7; }
/* the 1s and 0s drifting off your room: none with reduced motion */
.fp-bits text { font-size: 8px; font-weight: 500; fill: var(--you); opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
  .fp-link.live { stroke-dasharray: 6 8; animation: fp-flow 1.2s linear infinite; }
  .fp-route { animation: fp-flow 2.4s linear infinite; }
  .fp-you-ring, .fp-near { animation: fp-ring 2s ease-out infinite; transform-box: fill-box; transform-origin: center; }
  .fp-live { animation: fp-blink 1.6s ease-in-out infinite; }
  .fp-bits text { animation: fp-bit var(--t, 2.6s) linear infinite; animation-delay: var(--d, 0s); }
}
@keyframes fp-flow { to { stroke-dashoffset: -28; } }
@keyframes fp-ring { 0% { opacity: 0.9; transform: scale(1); } 100% { opacity: 0; transform: scale(1.18); } }
@keyframes fp-blink { 50% { opacity: 0.45; } }
@keyframes fp-bit { 0% { opacity: 0; transform: translate(0, 0); } 15% { opacity: 0.45; } 70% { opacity: 0.2; } 100% { opacity: 0; transform: translate(var(--dx), var(--dy)); } }

/* a step between rooms (travelfx.mjs): you travel the trace as teal code, then a scan line loads you into the room */
.fp-travel { pointer-events: none; }
.fp-travel text { font-size: 9px; font-weight: 600; fill: var(--you); filter: drop-shadow(0 0 2px var(--you)); }
.fp-travel-scan { stroke: var(--you); stroke-width: 1.4; opacity: 0; filter: drop-shadow(0 0 3px var(--you)); }
/* the hunter's step, when you see it: a glitching red head drags a lit trace */
.fp-travel-lit { fill: none; stroke: var(--hot); stroke-width: 2; stroke-linecap: round; filter: drop-shadow(0 0 3px var(--hot)); }
.fp-travel-head { fill: #ffb4a0; filter: drop-shadow(0 0 4px var(--hot)); }
/* the server's own traffic (traffic.mjs): faint gray packets on traces you aren't on; a Mailhub's are envelopes */
.fp-pkt { fill: var(--gray); pointer-events: none; }
.fp-pkt.heavy { fill: #8d8778; }
.fp-pkt.mail { fill: #15110a; stroke: var(--gray); stroke-width: 0.9; stroke-linejoin: round; }

/* ---- the pop-up: the room you're in, a room you clicked, home ---- */
.fp-map .fp-pop { z-index: 5; width: 320px; }
.fp-pop .fp-screen > :first-child { padding-right: 34px; }
.fp-screen { border-color: var(--line-strong); box-shadow: inset 0 2px 0 var(--amber); display: grid; gap: var(--s4); }
.fp-screen.focus { box-shadow: inset 0 2px 0 var(--hot); }
.fp-screen h1 { font: 600 21px/1.15 var(--sans); overflow-wrap: anywhere; }
.fp-screen h2 { color: var(--amber-soft); overflow-wrap: anywhere; }
.fp-kick.hot { color: var(--hot); } .fp-kick.you { color: var(--you); } .fp-kick.next { color: var(--next); } .fp-kick.dim { color: var(--dim); }
.fp-screen .row.acts { display: flex; flex-wrap: wrap; gap: var(--s3); }
.fp-screen .row.acts .btn { flex: 1 1 auto; }
.fp-screen .btn small { margin-left: var(--s3); font: 11px var(--mono); color: var(--dim); }
.fp-note { margin: 0; font-size: 13px; color: var(--text); }
.fp-note.dim { color: var(--dim); font-size: 12px; }
.fp-note.hot { color: var(--hot); }
.fp-virus { display: grid; grid-template-columns: 26px minmax(0, 1fr); align-items: center; gap: var(--s4); padding: var(--s4) var(--s5); border: 1px solid var(--line); border-left: 2px solid var(--hot); border-radius: var(--r-btn); background: var(--inset); }
.fp-virus[title] { cursor: help; }
.fp-virus .fp-ic { width: 24px; height: 24px; color: var(--hot); }
.fp-virus.down { border-left-color: var(--gray); } .fp-virus.down .fp-ic { color: var(--gray); }
.fp-virus.exfil { border-left-color: var(--next); } .fp-virus.exfil .fp-ic { color: var(--next); }
.fp-virus.prog { border-left-color: var(--amber); } .fp-virus.prog .fp-ic { color: var(--amber); }
/* the Build card: a row a codebase you know (its icon, name, rule, what it costs here and what you hold), and Build */
.bp-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); max-height: min(52vh, 420px); overflow-y: auto; }
.bp-prog { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; gap: var(--s4); padding: var(--s3) var(--s4); border: 1px solid var(--line); border-radius: var(--r-btn); background: var(--inset); }
.bp-prog .bp-ic .fp-ic { width: 20px; height: 20px; color: var(--amber); }
.bp-prog.short .bp-ic .fp-ic { color: var(--gray); }
.bp-txt { display: grid; gap: 1px; min-width: 0; }
.bp-txt b { font: 600 13px var(--sans); color: var(--bright); }
.bp-txt small { font-size: 11px; line-height: 1.3; color: var(--muted); }
.bp-cost { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); margin-top: var(--s1); }
.bp-cost .bc { display: inline-flex; align-items: center; gap: 2px; font: 600 10px var(--mono); color: var(--hot); cursor: help; }
.bp-cost .bc.ok { color: var(--amber-soft); }
/* The Server page's codebases (view.mjs programsCard): one tight row each, its rule under it. */
.cb-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.cb-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: baseline; gap: 0 var(--s3); padding: var(--s2) var(--s4); border-left: 2px solid var(--line); background: var(--inset); }
.cb-row.on { border-left-color: var(--amber); }
.cb-name { display: inline-flex; align-items: center; gap: var(--s2); font-size: 13px; color: var(--text); }
.cb-name .glyph { width: 13px; height: 13px; color: var(--dim); }
.cb-row.on .cb-name .glyph { color: var(--amber); }
.cb-w { font: 600 10px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--dim); }
.cb-at { font: 600 11px var(--mono); color: var(--amber); cursor: help; }
.cb-rule { grid-column: 1 / -1; color: var(--muted); font-size: 12px; line-height: 1.35; }
.bp-cost .bc .glyph, .bp-cost .bc svg { width: 11px; height: 11px; }
.fp-virus b { display: block; font: 600 15px var(--sans); color: var(--bright); overflow-wrap: anywhere; }
.fp-virus small { font: 11px var(--mono); color: var(--muted); }
.fp-tag-inline { display: inline-flex; align-items: center; height: 16px; padding: 0 4px; border: 1px solid currentColor; border-radius: var(--r-tag); font: 600 10px var(--mono); letter-spacing: 0.5px; text-transform: uppercase; white-space: nowrap; }
.fp-tag-inline.hot { color: var(--hot); } .fp-tag-inline.next { color: var(--next); }
.fp-sec { margin: 0; font: var(--kicker); letter-spacing: var(--kicker-track); text-transform: uppercase; color: var(--muted); display: flex; gap: var(--s3); align-items: baseline; }
.fp-sec small { font: 11px var(--mono); color: var(--dim); letter-spacing: 0; text-transform: none; }
.fp-lines { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.fp-lines li { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: var(--s4); font-size: 13px; }
.fp-lines li.hot span:last-child { color: var(--hot); }
.fp-items { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.fp-items li { display: grid; gap: 0; font-size: 13px; }
.fp-items small { color: var(--muted); font-size: 12px; }
.fp-items .fp-stats { display: grid; } /* one stat a line */
/* What happens on the server pops up in the map's bottom-right corner, over the command line (a fight keeps the corner
   toast, off the board): clear of the rooms you're reading, it lets clicks through, and a long one (ls, map) that stays
   until your next move closes on a click. */
.rooms-page .notice { position: absolute; top: auto; left: auto; right: var(--s4); bottom: calc(100% + var(--s4)); transform: none; max-width: min(420px, calc(100vw - 32px)); padding: var(--s4) var(--s5); border-radius: var(--r-tag); background: var(--panel-2); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6); font-size: 14px; line-height: 1.45; text-align: left; white-space: pre-line; z-index: 60; pointer-events: none; animation: fp-notice 0.18s ease-out; }
.rooms-page .notice.sticky { pointer-events: auto; cursor: pointer; }
@keyframes fp-notice { from { opacity: 0; transform: translateY(6px); } }
.no-motion.rooms-page .notice { animation: none; }

/* ---- the interrupt on the fight board (view.mjs), room fights only ---- */
.part-bar:has(.ptick) { overflow: visible; }
.part-bar .ptick { position: absolute; top: -3px; bottom: -3px; width: 4px; height: auto; margin-left: -2px; background: var(--bright); border-radius: 1px; box-shadow: 0 0 0 1px var(--bg); cursor: help; }
.part-bar .ptick.spent { background: var(--bg); box-shadow: inset 0 0 0 1px var(--gray), 0 0 0 1px var(--bg); }
.part-bar .ptick.phase { background: var(--hot); }
.part-bar .ptick.phase.spent { background: var(--bg); box-shadow: inset 0 0 0 1px var(--hot), 0 0 0 1px var(--bg); }
.intent .cuts { color: var(--you); font: 600 10px var(--mono); }
.intent .cuts.waste { color: var(--gray); }
.cut-pips { letter-spacing: 1px; } .cut-pips i { font-style: normal; color: var(--gray); }
#tray .ability.wiped { border-color: var(--hot); }
#tray .ability.wiped .name { color: transparent; text-shadow: none; }
#tray .ability.wiped .ico { background: var(--hot); }
#tray .ability.script { border-left: 2px solid var(--r-stock); }
#tray .ability.script.r-tuned { border-left-color: var(--r-tuned); } #tray .ability.script.r-custom { border-left-color: var(--r-custom); }

/* the rooms playtest: the old game's tabs and meters step aside */
.rooms-mode .modules button[data-module="map"], .rooms-mode .modules button[data-module="mail"], .rooms-mode .modules button[data-module="server"], .rooms-mode .modules button[data-module="craft"],
.rooms-mode #meter-integrity, .rooms-mode .meter:has(#vault-value), .rooms-mode #meter-signal { display: none; }

/* ---- puzzle rooms (puzzle-view.mjs): the board over the map, in the map's own look. Amber structure and what you can
   use, red what bites (the worm, the Tracker, a charge about to go), steel what protects (a firewall), gold what you're
   after (a cache, a reading), teal only you. ---- */
.fp-map.pz-on .fp-scroll { filter: saturate(0.5) brightness(0.38); transition: filter 0.25s; }
.fp-map.pz-on .fp-zoom { visibility: hidden; }
.pz-panel { position: absolute; inset: var(--s4); z-index: 7; display: grid; place-items: center; pointer-events: none; container-type: size; }
.pz-card { position: relative; pointer-events: auto; width: min(760px, 100%); max-height: 100%; overflow: auto; display: grid; gap: var(--s4); padding: var(--s5) var(--s6) var(--s6);
  background: linear-gradient(180deg, rgba(22, 15, 6, 0.97), rgba(11, 7, 3, 0.97)); border: 1px solid var(--line-strong);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 18px 60px rgba(0, 0, 0, 0.7), 0 0 40px rgba(241, 185, 47, 0.1), inset 0 2px 0 var(--amber); }
.pz-panel.k-chase .pz-card { box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 18px 60px rgba(0, 0, 0, 0.7), 0 0 40px rgba(255, 91, 61, 0.1), inset 0 2px 0 var(--hot); }
.pz-head { position: relative; display: grid; gap: var(--s1); padding-right: 36px; }
.pz-kick { font: var(--kicker); letter-spacing: var(--kicker-track); text-transform: uppercase; color: var(--amber-soft); }
.pz-kick .pz-at { color: var(--dim); } .pz-kick .pz-var { color: var(--hot); }
.pz-head h1 { font: 600 22px/1.15 var(--sans); color: var(--bright); letter-spacing: 0.2px; }
.pz-rule { font-size: 13px; color: var(--muted); max-width: 64ch; }
.pz-x { position: absolute; right: 0; top: 0; min-width: 30px; }
.pz-boot { font: 11px var(--mono); color: var(--dim); letter-spacing: 0.4px; min-height: 1.3em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pz-boot.ok::after { content: ' · ok'; color: var(--amber); }
.pz-stats { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4) var(--s6); min-height: var(--h-btn-sm); }
.pz-stat { display: inline-flex; align-items: center; gap: var(--s3); font-variant-numeric: tabular-nums; cursor: default; }
.pz-stat[title] { cursor: help; }
.pz-stat .lbl { font: var(--label); letter-spacing: var(--label-track); text-transform: uppercase; color: var(--dim); }
.pz-stat b { font: 600 15px var(--mono); color: var(--bright); }
.pz-stat b small { font-size: 12px; color: var(--muted); font-weight: 500; }
.pz-stat.steel b { color: var(--steel); } .pz-stat.dim b { color: var(--muted); }
.pz-safe .pz-bar { position: relative; width: 96px; height: 8px; border: 1px solid var(--line-strong); border-radius: 2px; background: var(--inset); overflow: hidden; }
.pz-safe .pz-bar span { position: absolute; inset: 0 auto 0 0; background: var(--amber); transition: width 0.4s; }
.pz-safe .pz-bar i { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--bright); opacity: 0.7; }
.pz-safe.low .pz-bar span { background: var(--hot); } .pz-safe.low b { color: var(--hot); }
.pz-pips { display: inline-flex; gap: 3px; }
.pz-pips i { width: 10px; height: 10px; border: 1px solid var(--line-strong); border-radius: 1px; background: var(--inset); }
.pz-pips i.on { background: var(--amber); border-color: var(--amber); box-shadow: 0 0 6px rgba(241, 185, 47, 0.45); }
.pz-pips.hot i.on { background: var(--hot); border-color: var(--hot); box-shadow: 0 0 6px rgba(255, 91, 61, 0.5); }
.pz-ctrls { display: inline-flex; gap: var(--s3); margin-left: auto; }
.pz-seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: var(--r-btn); overflow: hidden; }
.pz-seg .btn { border: 0; border-radius: 0; min-width: 58px; color: var(--muted); }
.pz-seg .btn.on { background: var(--amber); color: #140d02; font-weight: 600; }
.pz-well { position: relative; padding: var(--s4); border: 1px solid var(--line); border-radius: var(--r-card);
  background: radial-gradient(circle at 1px 1px, rgba(241, 185, 47, 0.09) 0.8px, transparent 0) 0 0 / 14px 14px, #0a0703; }
.pz-well::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(ellipse at center, transparent 55%, rgba(0, 0, 0, 0.55)); }
.pz-svg { display: block; width: 100%; height: auto; max-height: min(540px, calc(100cqh - 250px)); margin: 0 auto; touch-action: manipulation; user-select: none; -webkit-user-select: none; overflow: visible; }
.pz-q.tall { display: none; }
.pz-busy .pz-well { pointer-events: none; }
.pz-svg text { font-family: var(--mono); pointer-events: none; }
.pz-hit { fill: transparent; }
.pz-n { transform-box: fill-box; transform-origin: center; }
.pz-n .pz-chip { fill: url(#fp-g-node); stroke: #5a4420; stroke-width: 1.4; transition: stroke 0.15s, filter 0.15s; }
.pz-n .pz-hair { fill: none; stroke: var(--amber-soft); stroke-width: 0.6; opacity: 0.3; pointer-events: none; }
.pz-n[data-rooms] { cursor: pointer; }
.pz-n[data-rooms]:hover .pz-chip, .pz-n[data-rooms]:focus-visible .pz-chip { stroke: var(--amber); filter: brightness(1.5); }
.pz-n:focus { outline: none; }
.pz-n:focus-visible .pz-hair { stroke: var(--bright); opacity: 0.95; stroke-width: 1.2; }
.pz-bed { stroke: #050302; stroke-width: 8; stroke-linecap: round; }
.pz-bed.hot { stroke: #2a0c06; }
.pz-trace { stroke: rgba(241, 185, 47, 0.3); stroke-width: 1.6; }
.pz-trace.hot { stroke: var(--hot); opacity: 0.75; }
.pz-trace.cut { stroke: rgba(134, 182, 198, 0.22); stroke-dasharray: 2 5; }
/* Quarantine */
.pz-q .s-open.data .pz-chip { stroke: var(--amber); }
.pz-star { fill: var(--amber); filter: url(#fp-glow); pointer-events: none; }
.pz-q .s-inf .pz-chip { fill: url(#fp-g-hot); stroke: var(--hot); stroke-width: 1.8; }
.pz-q .s-inf .pz-hair { stroke: var(--hot); }
.pz-q .s-inf .pz-star { fill: #a8402a; filter: none; }
.pz-core { fill: var(--hot); filter: url(#fp-glow); pointer-events: none; }
.pz-halo { fill: none; stroke: var(--hot); stroke-width: 1.2; opacity: 0.3; pointer-events: none; }
.pz-q .s-wall .pz-chip { fill: #0c181c; stroke: var(--steel); stroke-width: 1.8; }
.pz-q .s-wall .pz-hair { stroke: var(--steel); }
.pz-brick { fill: none; stroke: var(--steel); stroke-width: 1.8; stroke-linecap: round; filter: url(#fp-glow); pointer-events: none; }
.pz-q .s-dead .pz-chip { fill: none; stroke: #2a2010; stroke-dasharray: 3 3; }
.pz-next polygon { fill: none; stroke: var(--hot); stroke-width: 2.2; stroke-dasharray: 6 5; pointer-events: none; }
.pz-next text { font-size: 12px; font-weight: 700; fill: var(--hot); }
/* The chase */
.pz-frame { fill: none; stroke: var(--amber-deep); stroke-width: 3; }
.pz-room .pz-chip { fill: #0d0904; stroke: #2a2010; stroke-width: 1; }
.pz-room.reach .pz-chip { fill: url(#fp-g-node); stroke: var(--amber-soft); stroke-width: 1.4; }
.pz-room.reach.danger .pz-chip { fill: url(#fp-g-hot); stroke: rgba(255, 91, 61, 0.75); }
.pz-room.reach.danger:hover .pz-chip { stroke: var(--hot); }
.pz-wall { stroke: var(--amber); stroke-width: 5; stroke-linecap: square; }
.pz-wallglow { stroke: rgba(241, 185, 47, 0.22); stroke-width: 14; stroke-linecap: round; }
.pz-doorway { fill: rgba(241, 185, 47, 0.06); stroke: var(--amber); stroke-width: 1.4; stroke-dasharray: 4 4; pointer-events: none; }
.pz-gap { stroke: #0a0703; stroke-width: 9; }
.pz-door { stroke: var(--amber); stroke-width: 2.2; stroke-dasharray: 4 4; }
.pz-doorword { font-size: 11px; font-weight: 700; letter-spacing: 2px; fill: var(--amber); }
.pz-you { pointer-events: none; }
.pz-you .body { fill: url(#fp-g-here); stroke: var(--you); stroke-width: 2; }
.pz-you .ring { fill: none; stroke: var(--you); stroke-width: 1.6; opacity: 0.45; }
.pz-you text { font-size: 12px; font-weight: 700; letter-spacing: 1px; fill: var(--you); }
.pz-trk { pointer-events: none; }
.pz-trk .bg { fill: #140503; stroke: var(--hot); stroke-width: 1.4; }
.pz-trk .px { fill: var(--hot); }
.pz-trk.caught .bg { filter: url(#fp-glow); stroke-width: 2.4; }
.pz-trail { fill: none; stroke: var(--hot); stroke-width: 2; stroke-dasharray: 3 5; opacity: 0.5; pointer-events: none; }
.pz-ghostpath { fill: none; stroke: var(--hot); stroke-width: 2; stroke-dasharray: 4 4; opacity: 0.9; pointer-events: none; }
.pz-ghostbox { fill: none; stroke: var(--hot); stroke-width: 2; stroke-dasharray: 4 3; pointer-events: none; }
.pz-ghostbox.caught { stroke-dasharray: none; stroke-width: 3; filter: url(#fp-glow); }
.pz-keys { font: 11px var(--mono); color: var(--dim); display: flex; gap: var(--s2); align-items: center; }
.pz-keys kbd { font: 11px var(--mono); border: 1px solid var(--line-strong); color: var(--muted); border-radius: 2px; padding: 0 5px; }
/* The cascade */
.pz-c .v1 .pz-chip { stroke: #6a5224; }
.pz-c .v2 .pz-chip { stroke: var(--amber-soft); fill: #20160a; }
.pz-c .v3 .pz-chip { stroke: var(--hot); fill: url(#fp-g-hot); stroke-width: 1.8; }
.pz-c .v3 .pz-hair { stroke: var(--hot); }
.pz-num { font-size: 19px; font-weight: 700; fill: var(--bright); }
.pz-c .v3 .pz-num { fill: #ffd2c8; }
.pz-pip { fill: #2a2011; pointer-events: none; }
.pz-pip.on { fill: var(--amber); }
.pz-c .v3 .pz-pip.on { fill: var(--hot); }
.pz-slot { fill: #120d07; stroke: #2a2010; }
/* The ping sweep */
.pz-cell .pz-chip { fill: #0c0905; stroke: #241b0e; stroke-width: 1; }
.pz-cell[data-rooms]:hover .pz-chip { stroke: var(--amber); filter: none; fill: #1a1309; }
.pz-static { fill: none; stroke: #5a4a2c; stroke-width: 1.4; stroke-dasharray: 2 2; pointer-events: none; }
.pz-ring { fill: rgba(255, 216, 92, 0.06); stroke: rgba(255, 216, 92, 0.16); stroke-width: 1; pointer-events: none; }
.pz-ring.last { fill: rgba(255, 216, 92, 0.1); stroke: rgba(255, 216, 92, 0.32); }
.pz-ping { pointer-events: none; transform-box: fill-box; transform-origin: center; }
.pz-ping rect { fill: #1d1606; stroke: var(--next); stroke-width: 1.5; }
.pz-ping text { font-size: 21px; font-weight: 700; fill: var(--next); }
.pz-dug, .pz-cache { pointer-events: none; transform-box: fill-box; transform-origin: center; }
.pz-dug rect { fill: #140806; stroke: rgba(255, 91, 61, 0.6); stroke-width: 1.2; }
.pz-dug path { stroke: var(--hot); stroke-width: 2.6; stroke-linecap: round; }
.pz-cache path { fill: var(--next); stroke: #2a1d03; stroke-width: 1; }
.pz-cache .glow { fill: rgba(255, 216, 92, 0.16); filter: url(#fp-glow); }
.pz-cache.missed path { fill: none; stroke: var(--next); stroke-dasharray: 3 3; opacity: 0.7; }
/* what just happened (createPuzzleFx) */
.pz-fx { pointer-events: none; }
.pz-burst { fill: none; stroke-width: 2.5; transform-box: fill-box; transform-origin: center; }
.pz-spark { stroke-width: 2; stroke-linecap: round; }
.pz-burst.steel, .pz-spark.steel { stroke: var(--steel); }
.pz-burst.hot, .pz-spark.hot { stroke: var(--hot); }
.pz-burst.amber, .pz-spark.amber { stroke: var(--bright); }
.pz-burst.gold, .pz-spark.gold { stroke: var(--next); }
.pz-runner { fill: var(--hot); filter: url(#fp-glow); }
.pz-wave { fill: none; stroke: var(--next); stroke-width: 2; }
/* how it went */
.pz-result { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s5); padding: var(--s4) var(--s5); border: 1px solid; border-radius: var(--r-btn); }
.pz-result b { font: 700 13px var(--mono); letter-spacing: 2px; text-transform: uppercase; }
.pz-result span { font: 12px var(--mono); color: var(--muted); }
.pz-result .btn { margin-left: auto; }
.pz-result.win { border-color: var(--amber); background: var(--amber-wash); } .pz-result.win b { color: var(--amber); text-shadow: 0 0 12px rgba(241, 185, 47, 0.4); }
.pz-result.lose { border-color: var(--hot); background: var(--hot-soft); } .pz-result.lose b { color: var(--hot); }
.fp-virus.pz { border-left-color: var(--amber); } .fp-virus.pz .fp-ic { color: var(--amber); }
.fp-tag.done text { fill: var(--gray); } .fp-tag.done rect { stroke: var(--gray); }
@media (prefers-reduced-motion: no-preference) {
  body:not(.no-motion) .pz-next polygon { animation: fp-flow 1.1s linear infinite; }
  body:not(.no-motion) .pz-halo { animation: pz-pulse 1.8s ease-out infinite; transform-box: fill-box; transform-origin: center; }
  body:not(.no-motion) .pz-you .ring { animation: fp-ring 2s ease-out infinite; transform-box: fill-box; transform-origin: center; }
  body:not(.no-motion) .pz-c .v3 .pz-chip { animation: pz-hum 1.4s ease-in-out infinite; }
}
@keyframes pz-pulse { 0% { opacity: 0.55; transform: scale(0.6); } 100% { opacity: 0; transform: scale(1.5); } }
@keyframes pz-hum { 50% { stroke: #ff8a6e; } }

/* ---- phone: one column; the map stands up (the spine runs top to bottom) ---- */
@media (max-width: 900px) {
  .page:has(> .fp) { overflow: visible; }
  .fp { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; height: auto; gap: var(--s4); }
  .fp-hud { position: sticky; top: 0; gap: var(--s3) var(--s5); padding: var(--s3) var(--s5); box-shadow: 0 6px 14px rgba(0, 0, 0, 0.55); }
  .fp-id { order: -2; flex: 1 1 0; flex-wrap: wrap; row-gap: 0; }
  .fp-acts { order: -1; }
  .fp-id small { overflow: visible; white-space: normal; }
  .fp-sig { flex: 1 1 100%; min-width: 0; }
  .fp-tools { display: contents; }
  .fp-map { overflow: visible; }
  .fp-scroll { padding: var(--s4) var(--s2); }
  .fp-svg.wide { height: auto; max-height: none; }
  .fp-map .fp-pop { width: auto; scroll-margin: 150px 0 90px; }
  /* a puzzle's board takes the map's place: in the page's flow, full width */
  .fp-map.pz-on .fp-scroll { display: none; }
  .pz-panel { position: static; inset: auto; display: block; container-type: normal; }
  .pz-card { width: 100%; max-height: none; overflow: visible; padding: var(--s4) var(--s4) var(--s5); }
  .pz-svg { max-height: none; }
}
@media (max-width: 640px) {
  .fp-svg.wide { display: none; }
  .fp-svg.tall { display: block; width: 100%; height: auto; max-width: calc(var(--w) * 1.2); }
  .fp-hud .fp-rules .lbl { display: none; }
  /* Quarantine stands on its side: 7 nodes across, bigger under a thumb */
  .pz-q.wide { display: none; } .pz-q.tall { display: block; }
  .pz-well { padding: var(--s2); }
  .pz-head h1 { font-size: 19px; }
  .pz-stats { gap: var(--s3) var(--s5); }
  .pz-ctrls { margin-left: 0; width: 100%; } .pz-ctrls .btn { flex: 1; min-height: 36px; }
  .pz-seg .btn { min-height: 34px; min-width: 70px; }
}

/* ---- Packet routing: lit wire amber and flowing, the sink gold once it's joined ---- */
.rt-svg .pz-bed { stroke-linecap: butt; }
.rt-wire { stroke: rgba(241, 185, 47, 0.32); stroke-width: 2.4; }
.rt-via { fill: #0a0703; stroke: rgba(241, 185, 47, 0.35); stroke-width: 1.2; }
.rt-tile.on .rt-wire, .rt-wire.lead.on { stroke: var(--amber); stroke-width: 3; }
.rt-halo { stroke: rgba(241, 185, 47, 0.22); stroke-width: 9; pointer-events: none; }
.rt-svg.won .rt-halo, .rt-halo.gold { stroke: rgba(255, 216, 92, 0.24); }
.rt-tile.on .rt-via { stroke: var(--amber); fill: #2a1d06; }
.rt-tile.on .pz-chip { stroke: var(--amber-deep); }
.rt-wire.lead.gold, .rt-svg.won .rt-tile.on .rt-wire { stroke: var(--next); }
.rt-flow { stroke: var(--bright); stroke-width: 2; stroke-dasharray: 2 12; stroke-linecap: round; opacity: 0.7; pointer-events: none; }
.rt-empty .pz-chip { fill: none; stroke: #2a2010; stroke-dasharray: 3 3; }
.rt-end .pz-chip { stroke-width: 1.8; }
.rt-end text { font: 600 10px var(--mono); letter-spacing: 1px; }
.rt-end.src .pz-chip { fill: #2a1d06; stroke: var(--amber); } .rt-end.src text { fill: var(--amber); }
.rt-end.snk .pz-chip { fill: #1a1405; stroke: rgba(255, 216, 92, 0.55); stroke-dasharray: 4 3; } .rt-end.snk text { fill: rgba(255, 216, 92, 0.7); }
.rt-end.snk.won .pz-chip { fill: #3a2e08; stroke: var(--next); stroke-dasharray: none; } .rt-end.snk.won text { fill: var(--next); }
.rt-ghost { fill: none; stroke: var(--next); stroke-width: 2; stroke-dasharray: 3 6; opacity: 0.6; pointer-events: none; }
/* ---- Deadlock: held locks steel, wanted ones dashed, a give you can make amber; a give that finishes, gold ---- */
.dl-svg.tall { display: none; }
.dl-ring { fill: none; stroke: rgba(241, 185, 47, 0.28); stroke-width: 1.6; stroke-dasharray: 6 8; }
.dl-name { font: 600 12px var(--mono); fill: var(--bright); letter-spacing: 0.5px; }
.dl-count { font: 600 11px var(--mono); fill: var(--muted); }
.dl-proc.done .pz-chip { fill: #1c1406; stroke: var(--amber-deep); }
.dl-proc.done .dl-name { fill: var(--muted); }
.dl-exit { font: 600 12px var(--mono); fill: var(--amber); letter-spacing: 1px; }
.dl-proc.stuck .pz-chip { stroke: rgba(255, 91, 61, 0.6); fill: url(#fp-g-hot); }
.dl-proc:has(.dl-lock.can:hover, .dl-lock.can:focus-visible) .pz-chip { stroke: var(--amber); }
.dl-proc:has(.dl-lock.can[data-fin]:hover, .dl-lock.can[data-fin]:focus-visible) .pz-chip { stroke: var(--next); filter: url(#fp-glow); }
.dl-lock .sh { fill: none; stroke-width: 2; }
.dl-lock .bd { stroke-width: 1.4; }
.dl-lock .bd, .dl-lock .sh { pointer-events: all; }
.dl-lock text { font: 700 11px var(--mono); }
.dl-lock.held .sh, .dl-lock.free .sh { stroke: var(--steel); } .dl-lock.held .bd, .dl-lock.free .bd { fill: #0c181c; stroke: var(--steel); } .dl-lock.held text, .dl-lock.free text { fill: var(--steel); }
.dl-lock.want .sh { stroke: #4a3c22; stroke-dasharray: 2 2; } .dl-lock.want .bd { fill: none; stroke: #4a3c22; stroke-dasharray: 3 2; } .dl-lock.want text { fill: var(--dim); }
.dl-lock.want.can { cursor: pointer; }
.dl-lock.want.can .sh, .dl-lock.want.can .bd { stroke: var(--amber-soft); } .dl-lock.want.can text { fill: var(--amber); }
.dl-lock.can:hover .bd, .dl-lock.can:focus-visible .bd { stroke: var(--amber); stroke-dasharray: none; fill: #2a1d06; }
.dl-lock.can:hover .sh, .dl-lock.can:focus-visible .sh { stroke: var(--amber); stroke-dasharray: none; }
.dl-lock.can[data-fin]:hover .bd, .dl-lock.can[data-fin]:hover .sh, .dl-lock.can[data-fin]:focus-visible .bd { stroke: var(--next); }
.dl-lock.can[data-fin]:hover text { fill: var(--next); }
.dl-lock:focus { outline: none; }
.dl-pool .pz-chip { fill: #0c1114; stroke: rgba(134, 182, 198, 0.45); stroke-width: 1.4; stroke-dasharray: 4 3; }
.dl-free { font: 600 10px var(--mono); letter-spacing: 2px; fill: var(--steel); opacity: 0.8; }
.dl-pool.lost .pz-chip { stroke: var(--hot); stroke-dasharray: none; fill: #1e0a06; }
.dl-pool.lost .dl-free { fill: var(--hot); opacity: 1; font-size: 12px; }
.dl-none { font: 600 14px var(--mono); fill: var(--dim); }
.dl-token rect { fill: #0c181c; stroke: var(--steel); stroke-width: 1.4; filter: url(#fp-glow); }
.dl-token text { font: 700 10px var(--mono); fill: var(--steel); }
/* ---- Cryptogram: your key's letters dim amber, this note's bright ---- */
.cg-board { display: grid; gap: var(--s6); padding: var(--s5) var(--s4) var(--s4); user-select: none; -webkit-user-select: none; }
.cg-note { display: flex; flex-wrap: wrap; gap: var(--s5) 18px; align-items: flex-end; margin: 0; }
.cg-w { display: inline-flex; gap: 2px; }
.cg-c { display: inline-grid; grid-template-rows: 22px 14px; justify-items: center; width: 22px; padding: 0; border: 0; border-radius: 2px; background: none; color: var(--bright); cursor: pointer; }
.cg-c b { align-self: end; width: 100%; font: 600 18px/22px var(--mono); text-align: center; border-bottom: 1px solid rgba(241, 185, 47, 0.3); }
.cg-c i { font: 500 10px/14px var(--mono); font-style: normal; color: var(--dim); }
.cg-c.car b { color: var(--amber-soft); border-bottom-color: transparent; }
.cg-c.sol b { color: var(--amber); text-shadow: 0 0 10px rgba(241, 185, 47, 0.45); border-bottom-color: transparent; }
.cg-c.car, .cg-c.sol { cursor: default; }
.cg-c.open:hover { background: var(--amber-wash); }
.cg-c.sel { background: rgba(241, 185, 47, 0.16); box-shadow: inset 0 0 0 1px var(--amber); }
.cg-c.sel b { border-bottom-color: var(--amber); } .cg-c.sel i { color: var(--amber); }
.cg-c.miss { animation: cg-miss 0.9s ease-out both; }
@keyframes cg-miss { 0%, 40% { box-shadow: inset 0 0 0 1px var(--hot); background: var(--hot-soft); transform: translateX(-2px); } 20% { transform: translateX(2px); } 100% { transform: none; } }
.cg-p { font: 600 18px/22px var(--mono); color: var(--muted); align-self: start; padding-bottom: 14px; }
.cg-sig { font: 500 14px var(--mono); color: var(--dim); align-self: center; margin-left: var(--s4); }
.cg-keys { display: grid; grid-template-columns: repeat(13, 1fr); gap: var(--s2); }
.cg-k { min-height: 34px; padding: 0; font: 600 15px var(--mono); color: var(--bright); border: 1px solid var(--line-strong); border-radius: var(--r-btn); background: #0e0a04; }
.cg-k:hover:not(:disabled) { border-color: var(--amber); color: var(--amber); }
.cg-k:disabled { opacity: 1; cursor: default; color: var(--muted); }
.cg-board.done .cg-k:not(.car):not(.sol) { color: var(--dim); border-color: var(--line); background: none; }
.cg-k.car { color: var(--amber-soft); border-color: transparent; background: none; opacity: 0.55; }
.cg-k.sol { color: var(--amber); border-color: transparent; background: none; opacity: 0.55; }
.cg-k.tried { color: var(--hot); border-color: rgba(255, 91, 61, 0.4); text-decoration: line-through; }
.cg-keybar { display: inline-flex; gap: 1px; }
.cg-keybar i { width: 4px; height: 10px; background: var(--inset); border: 1px solid var(--line); }
.cg-keybar i.car { background: var(--amber-soft); border-color: var(--amber-soft); opacity: 0.6; }
.cg-keybar i.sol { background: var(--amber); border-color: var(--amber); }
.pz-stat b small { font-size: 12px; color: var(--muted); font-weight: 500; }
@media (prefers-reduced-motion: no-preference) {
  body:not(.no-motion) .rt-flow { animation: rt-flow 0.9s linear infinite; }
  body:not(.no-motion) .dl-ring { animation: fp-flow 2.4s linear infinite; }
}
@keyframes rt-flow { to { stroke-dashoffset: -14; } }
@media (max-width: 640px) {
  .dl-svg.wide { display: none; } .dl-svg.tall { display: block; }
  .cg-c { width: 18px; } .cg-c b { font-size: 15px; }
  .cg-note { gap: var(--s4) 12px; }
  .cg-board { padding: var(--s3) 0; }
  .cg-keys .cg-k { min-height: 38px; font-size: 14px; }
}

/* ---- Threads (threads-view.mjs): quests found in rooms. Amber is a thread's colour; teal is only ever you. ---- */
/* the mark over a room: a filled amber badge, ! where one starts, ? where the step you're on sends you */
.fp-quest { pointer-events: auto; cursor: help; }
.fp-quest .bg { fill: var(--amber); stroke: #2a1b04; stroke-width: 1.2; filter: drop-shadow(0 0 4px rgba(241, 185, 47, 0.75)); }
.fp-quest text { font: 700 13px var(--mono); fill: #1a1002; }
.fp-quest.m-goal .bg { fill: #1a1103; stroke: var(--amber); stroke-width: 1.6; }
.fp-quest.m-goal text { fill: var(--amber); }
@media (prefers-reduced-motion: no-preference) {
  .fp-quest.m-start { animation: th-bob 2.4s ease-in-out infinite; transform-box: fill-box; }
  .fp-file.waits .bg { animation: th-wait 1.2s ease-in-out infinite; }
}
@keyframes th-bob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -2px; } }
@keyframes th-wait { 0%, 100% { stroke-opacity: 1; } 50% { stroke-opacity: 0.35; } }
/* a file walking: its badge on the room's bottom edge and its Integrity under it */
.fp-file { pointer-events: auto; cursor: help; }
.fp-file .bg { fill: #1a1103; stroke: var(--amber); stroke-width: 1.4; }
.fp-file use { color: var(--amber); }
.fp-file .hpbg { fill: #0a0703; stroke: rgba(241, 185, 47, 0.35); stroke-width: 0.8; }
.fp-file .hp { fill: var(--amber); }
.fp-file.low .hp { fill: var(--hot); } .fp-file.low .bg { stroke: var(--hot); } .fp-file.low use { color: var(--hot); }
/* a thread's room (a nook): its icon amber while it has something for you, gray once it's done */
.fp-room.k-empty.t-amber .fp-body { stroke-dasharray: none; }
/* the strip: the thread you track, and a file walking here */
.fp-qmini { display: inline-grid; place-items: center; width: 15px; height: 17px; clip-path: polygon(50% 0, 100% 24%, 100% 76%, 50% 100%, 0 76%, 0 24%); background: var(--amber); color: #1a1002; font: 700 11px var(--mono); flex: none; }
.fp-thread b { color: var(--amber); max-width: 18ch; overflow: hidden; text-overflow: ellipsis; }
.fp-thread small, .fp-filestat small { font: 11px var(--mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 30ch; }
.fp-filestat .fp-ic { color: var(--amber); }
.fp-filestat .lbl { font: var(--label); letter-spacing: var(--label-track); text-transform: none; color: var(--dim); }
.fp-filestat .fbar { position: relative; width: 44px; height: 6px; border: 1px solid var(--line-strong); border-radius: 2px; background: var(--inset); overflow: hidden; }
.fp-filestat .fbar span { position: absolute; inset: 0 auto 0 0; background: var(--amber); }
.fp-filestat.low .fbar span { background: var(--hot); } .fp-filestat.low b { color: var(--hot); }
/* the room's card */
.fp-virus.th { border-left-color: var(--amber); } .fp-virus.th .fp-ic { color: var(--amber); }
.fp-note.th-note { display: flex; gap: var(--s3); align-items: flex-start; color: var(--amber); }
.fp-note.th-note .fp-ic { flex: none; width: 16px; height: 16px; color: var(--amber); margin-top: 1px; }
/* the dialogue, over the map like a puzzle's board (quest-view.mjs, quest.css): its choice of two */
.th-choices { display: grid; gap: var(--s3); }
.th-choice { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--s4); text-align: left; min-height: var(--h-btn); border: 1px solid var(--line-strong); background: var(--inset); }
.th-choice:hover:not(:disabled) { border-color: var(--amber); background: var(--amber-wash); }
.th-choice kbd { display: inline-grid; place-items: center; width: 20px; height: 20px; border: 1px solid var(--amber); border-radius: 2px; font: 600 11px var(--mono); color: var(--amber); }
.th-acts { justify-content: flex-end; }
@media (max-width: 640px) { .fp-thread small, .fp-filestat .lbl, .fp-filestat small { display: none; } .fp-thread b { max-width: 14ch; } }

/* A hidden room that just opened (a Ghostroot server's): its chip and its trace fade in. Nothing else moves. */
@media (prefers-reduced-motion: no-preference) {
  body:not(.no-motion) .fp-room.reveal, body:not(.no-motion) .fp-svg g.reveal { animation: fp-reveal 1.4s ease-out both; }
}
@keyframes fp-reveal { from { opacity: 0; } to { opacity: 1; } }
/* a room you've just seen or just discovered resolves top to bottom, a line at a time (app.js adds .resolve) */
@media (prefers-reduced-motion: no-preference) {
  body:not(.no-motion) .fp-room.resolve { animation: fp-resolve 0.42s steps(8, end) both; }
}
@keyframes fp-resolve { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
/* A scan's card as the notice (app.js noticeCard): the notice is its frame. */
.rooms-page .notice .scan-card { margin: 0; padding: 0; border: 0; background: none; white-space: normal; }
/* SCAN on a room next to you that scan reads (floorplan-view.mjs): on the chip's bottom-right edge, where a down room's
   ring sits, shown on hover or focus. Hidden, it takes no clicks, so the chip walks in from anywhere but the button.
   On a phone's map (no hover) it always shows. Neutral: not teal (you), red (threat) or amber (loot). */
.fp-scanbtn { opacity: 0; pointer-events: none; cursor: pointer; transition: opacity 0.12s; }
.fp-scanbtn rect { fill: #0a0703; stroke: var(--muted); stroke-width: 1; }
.fp-scanbtn text { font-size: 9px; letter-spacing: 0.5px; fill: var(--bright); font-variant-numeric: tabular-nums; }
.fp-room:hover .fp-scanbtn, .fp-room:focus-within .fp-scanbtn, .fp-scanbtn.tip-target, .fp-svg.tall .fp-scanbtn { opacity: 1; pointer-events: auto; }
.fp-scanbtn:hover rect, .fp-scanbtn:focus-visible rect { stroke: var(--bright); }
.fp-scanbtn:focus { outline: none; }
.fp-scanbtn.off { cursor: default; }
.fp-scanbtn.off text { fill: var(--muted); }
@media (hover: none) { .fp-scanbtn { opacity: 1; pointer-events: auto; } }
/* rivals (rivals.mjs): red for the rival, its route and its lineup; amber for the stakes; the crew's colour only on its byline */
.fp-rvstat .fp-ic, .fp-rvstat b { color: var(--hot); }
.fp-rv-route { fill: none; stroke: var(--hot); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 0.5 7; opacity: 0.9; pointer-events: none; }
.fp-rv-vault { color: var(--amber); opacity: 0.75; }
.fp-rv-ring { fill: none; stroke: var(--hot); stroke-width: 2; pointer-events: none; }
.fp-rv-tab rect { fill: var(--hot); }
.fp-rv-tab text { font-size: 10px; font-weight: 700; letter-spacing: 1px; fill: #140503; }
.fp-rv-lineup rect { fill: #140503; stroke: var(--hot); stroke-width: 1; }
.fp-rv-lineup text { font-size: 9px; font-weight: 700; fill: var(--hot); }
.fp-rv-lineup .next rect { fill: var(--hot); } .fp-rv-lineup .next text { fill: #140503; }
.fp-rv-lineup .done rect, .fp-rv-lineup .done line { stroke: var(--gray); } .fp-rv-lineup .done text { fill: var(--gray); }
.fp-tag.race-you rect { stroke: var(--you); } .fp-tag.race-you text { fill: var(--you); font-weight: 700; }
.fp-tag.race-it rect { stroke: var(--hot); } .fp-tag.race-it text { fill: var(--hot); font-weight: 700; }
.fp-rv-by { margin: 0; font-size: 12px; letter-spacing: 0.5px; color: var(--dim); }
.fp-rv-by span[title] { cursor: help; }
.fp-rv-says { margin: 0; font-size: 14px; color: var(--hot); font-style: italic; }
.fp-rv-says.quote { font: 13px var(--mono, monospace); font-style: normal; }
.fp-rv-line { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--s2); }
.fp-rv-line li { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; gap: var(--s3); padding: var(--s2) var(--s4); border: 1px solid var(--line); border-left: 2px solid var(--hot); border-radius: var(--r-btn); background: var(--inset); font-size: 13px; color: var(--bright); }
.fp-rv-line li b { color: var(--hot); font-variant-numeric: tabular-nums; }
.fp-rv-line li.beaten { border-left-color: var(--gray); color: var(--gray); } .fp-rv-line li.beaten span { text-decoration: line-through; } .fp-rv-line li.beaten b { color: var(--gray); }
.fp-rv-add { font-style: normal; font-size: 12px; color: var(--hot); cursor: help; }
.fp-rv-read { margin: 0; font-size: 14px; color: var(--text); }
.fp-rv-read b { color: var(--hot); cursor: help; }
.fp-rv-sig { display: flex; align-items: center; gap: var(--s3); margin: 0; font-size: 13px; color: var(--dim); }
.fp-rv-sig .fp-ic { color: var(--you); } .fp-rv-sig b { color: var(--you); font-variant-numeric: tabular-nums; }
.fp-rv-stakes { display: flex; align-items: center; gap: var(--s3); margin: 0; font-size: 13px; color: var(--amber); cursor: help; }
.fp-rv-stakes .fp-ic { color: var(--amber); }
.fp-rv-wick { margin: 0; font-size: 12px; color: var(--dim); font-style: italic; }
.fp-rv-wick span { font-style: normal; color: var(--muted); margin-right: var(--s3); text-transform: lowercase; }
/* An objective (objectives.mjs): its tag on the map (icon and number) and its line on the room card, with what it pays. */
.fp-tag.obj rect { stroke: var(--next); } .fp-tag.obj text { fill: var(--next); font-size: 10px; font-weight: 700; } .fp-tag.obj use { color: var(--next); }
.fp-tag-inline.fp-obj { gap: 3px; } .fp-tag-inline.fp-obj .fp-ic { width: 11px; height: 11px; }
.fp-obj-pay { display: flex; gap: 6px; margin-top: 5px; }
.fp-obj-pay .rw { padding: 1px 6px; } .fp-obj-pay .rw b { font: 600 11px var(--mono); }
.part-bar .ptick.phase.held { background: var(--bg); box-shadow: inset 0 0 0 2px var(--hot), 0 0 0 1px var(--bg); } /* a P4 boss's ♛: leave it above this tick until the beat plays (acts.mjs) */

/* Two racks (a deeper cascade): each in its frame; a slot on its rack's edge carries a notch (it charges its twin). */
.pz-rackframe { fill: none; stroke: var(--line-strong); stroke-dasharray: 4 4; }
.pz-twin { stroke: var(--amber-soft); stroke-width: 2.4; stroke-linecap: round; pointer-events: none; }
.pz-c .v3 .pz-twin { stroke: var(--hot); }
/* The terminal: the log sweep's log in the well. */
.ls-board .sweep { margin: 0; border: 0; background: none; }
.ls-board.done .sw-line { pointer-events: none; }
/* Run cards (runcards.mjs): the director's violet for a surge and a courier, red for the pack. */
.fp-card-ring { fill: none; stroke-width: 2; pointer-events: none; }
.fp-card-ring.surge { stroke: var(--daemon); }
.fp-card-ring.pack { stroke: var(--hot); }
.fp-card-tab rect { stroke-width: 1; }
.fp-card-tab text { font-size: 9px; font-weight: 700; letter-spacing: 1px; }
.fp-card-tab.surge rect { fill: #120d1c; stroke: var(--daemon); } .fp-card-tab.surge text { fill: var(--daemon); }
.fp-card-tab.pack rect { fill: #140503; stroke: var(--hot); } .fp-card-tab.pack text { fill: var(--hot); }
.fp-card-bar rect { fill: #120d1c; stroke: var(--daemon); stroke-width: 1; }
.fp-card-bar rect.fill { fill: var(--daemon); stroke: none; }
.fp-courier rect { fill: #0d0a06; stroke: var(--daemon); stroke-width: 1.5; }
.fp-courier use { color: var(--daemon); }
.fp-cr-route { fill: none; stroke: var(--daemon); stroke-width: 3; stroke-linecap: round; stroke-dasharray: 0.5 7; opacity: 0.9; pointer-events: none; }
.fp-pk-route { fill: none; stroke: var(--hot); stroke-width: 3; stroke-dasharray: 8 6; opacity: 0.85; pointer-events: none; }
.fp-cardstat.surge .lbl, .fp-cardstat.surge b { color: var(--daemon); }
.fp-cardstat.pack .lbl, .fp-cardstat.pack b { color: var(--hot); }
.fp-cardbar { display: inline-block; width: 56px; height: 7px; margin: 0 6px; vertical-align: middle; border: 1px solid var(--daemon); background: linear-gradient(90deg, var(--daemon) calc(var(--f, 0) * 100%), transparent 0); }
