/* PHREAKNYST — phosphor-on-black, built for a hotel lobby at 2am
   and a phone with 40% battery. */

:root {
  --bg: #050706;
  --bg-2: #0a0e0c;
  --panel: #0d1311;
  --edge: #1d2a25;
  --ink: #7fe0b0;
  --ink-dim: #4e8f74;
  --text: #d6e6df;
  --muted: #7d918a;
  --warn: #f0b429;
  --bad: #f2685c;
  --good: #6ee7a8;
  --act1: #4ade80;
  --act2: #38bdf8;
  --act3: #fbbf24;
  --act4: #f87171;
  --topbar-h: 53px;
  /* Room left for topbar + caption + FOUND list + field bar. The scene is
     sized so all of that still fits on screen. */
  --chrome: 200px;
  --tap: 44px;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.55;
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
}

body::after {
  /* CRT scanlines. Cheap, and it sells the whole thing. */
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(180deg, rgba(255,255,255,0.017) 0 1px, transparent 1px 3px);
  z-index: 9000;
}

a { color: var(--ink); }
button { font: inherit; }

#app { min-height: 100%; display: flex; flex-direction: column; }

/* ------------------------------------------------------------ gate screen */

.gate {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 24px 16px;
}
.gate-inner { width: min(560px, 100%); }
.gate h1 {
  font-size: clamp(28px, 8vw, 46px);
  letter-spacing: 0.16em;
  margin: 0 0 2px;
  color: var(--ink);
  text-shadow: 0 0 24px rgba(127,224,176,0.28);
}
.gate .sub { color: var(--muted); letter-spacing: 0.22em; font-size: 12px; margin-bottom: 26px; }
.gate .lede { color: var(--muted); margin: 0 0 22px; font-size: 14px; }
.gate .lede p { margin: 0 0 8px; }

.tabs { display: flex; gap: 0; margin-bottom: 18px; border-bottom: 1px solid var(--edge); }
.tabs button {
  flex: 1; background: none; border: 0; color: var(--muted);
  padding: 10px 6px; cursor: pointer; letter-spacing: 0.1em; font-size: 12px;
  border-bottom: 2px solid transparent;
}
.tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--ink); }

label { display: block; font-size: 11px; letter-spacing: 0.14em; color: var(--muted); margin: 14px 0 5px; }

input[type="text"], input[type="password"], textarea, select {
  width: 100%; padding: 11px 12px;
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--edge); border-radius: 3px;
  font-family: var(--mono);
  /* 16px is not a style choice: iOS Safari zooms the whole page when you focus
     a control smaller than this, and the player has to pinch back every time
     they answer a puzzle. */
  font-size: 16px;
  outline: none;
  min-height: var(--tap);
}
input:focus, textarea:focus { border-color: var(--ink-dim); box-shadow: 0 0 0 1px rgba(127,224,176,0.16); }
input[inputmode="numeric"] { letter-spacing: 0.4em; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 18px; background: var(--ink); color: #04140d;
  border: 0; border-radius: 3px; cursor: pointer;
  font-weight: 700; letter-spacing: 0.08em; font-size: 13px;
}
.btn:disabled { opacity: 0.45; cursor: default; }
.btn.ghost { background: transparent; color: var(--ink); border: 1px solid var(--ink-dim); font-weight: 400; }
.btn.wide { width: 100%; margin-top: 20px; }
.btn.small { padding: 6px 11px; font-size: 12px; }

.err { color: var(--bad); font-size: 13px; margin-top: 12px; min-height: 19px; }
.ok { color: var(--good); }

/* ------------------------------------------------------------------- chrome */

.topbar {
  position: sticky; top: 0; z-index: 60; min-height: var(--topbar-h);
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px;
  background: rgba(5,7,6,0.94);
  border-bottom: 1px solid var(--edge);
  backdrop-filter: blur(6px);
}
.brand { color: var(--ink); letter-spacing: 0.16em; font-size: 12px; white-space: nowrap; }
.brand b { font-weight: 700; }
.spacer { flex: 1; }
.stat { font-size: 12px; color: var(--muted); white-space: nowrap; }
.stat b { color: var(--text); }
.iconbtn {
  background: none; border: 1px solid var(--edge); color: var(--muted);
  min-width: 36px; height: 34px; border-radius: 3px; cursor: pointer;
  font-size: 16px; line-height: 1; flex: 0 0 auto;
}
.iconbtn.on, .iconbtn:hover { color: var(--ink); border-color: var(--ink-dim); }
/* The walk-through is loud on purpose: every gate in the game is open while it
   is lit, and that must never be mistaken for having actually solved anything. */
.iconbtn.skeleton { color: var(--warn); border-color: var(--warn); background: rgba(240,180,41,.14); }
.iconbtn .dot {
  position: absolute; width: 7px; height: 7px; border-radius: 50%;
  background: var(--warn); transform: translate(11px, -14px);
}

/* --------------------------------------------------------------- the scene */

.scene-wrap { position: relative; flex: 1; display: flex; flex-direction: column; }
.scene {
  position: relative;
  aspect-ratio: 3 / 2;
  /* The frame MUST keep its 3:2 ratio: hotspots are percentages of this box and
     the image is object-fit:cover, so any other ratio slides the art out from
     under the hotspots. So cap by WIDTH derived from the height available —
     never by max-height, which would crop the picture away from its hotspots. */
  width: min(100%, calc((100dvh - var(--chrome)) * 1.5));
  min-width: 300px;
  margin-inline: auto;
  background: #000;
  overflow: hidden; user-select: none;
  touch-action: manipulation;   /* kill the 300ms double-tap-zoom delay */
}
.scene img { width: 100%; height: 100%; object-fit: cover; display: block; }

.hotspot {
  position: absolute; border: 0; border-radius: 2px;
  cursor: pointer; background: transparent; padding: 0;
  -webkit-tap-highlight-color: transparent;
  transition: background 120ms;
}
/* Deliberately invisible: the room is searched, not read. The only feedback is
   a brief flash on press so a tap feels like it landed. */
.hotspot:active { background: rgba(127,224,176,0.16); }
.hotspot:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.hotspot.locked { cursor: not-allowed; }
/* Floor-plan zoom bar. The Level 1 sheet is twice as wide as it is tall, so a
   phone needs to magnify it rather than squint at a fitted strip.

   This block used to be glued to an orphaned `.hotspot.tag-right` selector left
   over from the first commit. Comments are stripped before parsing, so the two
   became `.hotspot.tag-right .plan-zoom` — a descendant selector that matches
   nothing, and the zoom buttons have been unstyled ever since. */
.plan-zoom { display: flex; gap: 6px; margin-bottom: 8px; }
.plan-zoom button { flex: 0 0 auto; padding: 5px 12px; font-size: 12px; letter-spacing: 1px; }
.plan-zoom button.on { border-color: var(--ink); color: var(--ink); }

.caption {
  padding: 10px 14px; border-bottom: 1px solid var(--edge);
  background: var(--bg-2); color: var(--muted); font-size: 13px;
}
.caption .where { color: var(--ink); letter-spacing: 0.14em; font-size: 11px; display: block; margin-bottom: 3px; }

.exits { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 14px 14px; }
.exit {
  background: var(--panel); border: 1px solid var(--edge); color: var(--text);
  padding: 7px 12px; border-radius: 3px; cursor: pointer; font-size: 12px;
}
.exit:hover { border-color: var(--ink-dim); color: var(--ink); }
.exit::before { content: "→ "; color: var(--ink-dim); }

/* ---------------------------------------------------------------- terminal */

.terminal { margin-top: 16px; padding: 12px; background: #050a07; border: 1px solid var(--edge); }
.terminal-output { max-height: 45vh; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; color: var(--ink); }
.terminal-entry { display: flex; align-items: center; gap: 8px; }
.terminal-entry input { flex: 1; min-width: 0; width: 0; font: inherit; }
.terminal-prompt { white-space: pre-wrap; overflow-wrap: anywhere; max-width: 35%; }
.terminal-device { margin-top: 16px; }
.terminal-stage { position: relative; aspect-ratio: 3 / 2; container-type: inline-size; }
.terminal-photo { display: block; width: 100%; height: 100%; object-fit: contain; }
.terminal-on-screen { position: absolute; box-sizing: border-box; display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 10px; border: 0; background: #020302; color: #ffcd69; font: 13px/1.35 var(--mono, monospace); font-size: clamp(12px, 1.7cqw, 16px); overflow: hidden; }
.terminal-on-screen .terminal-output { flex: 1; min-height: 0; max-height: none; margin: 0; color: inherit; scrollbar-width: thin; }
.terminal-on-screen .terminal-entry { flex: none; gap: 4px; }
.terminal-on-screen input { background: #050805; color: inherit; border: 1px solid #5e552e; padding: 6px; }
.terminal-on-screen button { color: inherit; background: #13170f; border-color: #5e552e; padding: 6px; }
.terminal-on-screen .solved-banner, .terminal-on-screen .err { margin: 0; padding: 4px; font-size: 12px; }
.terminal-on-screen :focus-visible { outline: 2px solid #ffcd69; outline-offset: 1px; }
.terminal-stage.is-expanded { height: min(65dvh, 540px); min-height: 280px; aspect-ratio: auto; }
.terminal-stage.is-expanded .terminal-photo { display: none; }
.terminal-stage.is-expanded .terminal-on-screen { inset: 0 !important; width: 100% !important; height: 100% !important; font-size: 16px; }
.terminal-device > button { margin-top: 8px; }

/* Claiming a real-world code lives in the REAL WORLD tab, not in a bar pinned
   to every screen. Six of the eleven stations are typed in; the rest arrive by
   scan. That is not enough traffic to justify permanent furniture under the
   picture, which is the one thing the game is actually made of. */
.claimbox { margin-bottom: 16px; display: grid; gap: 5px; }
.claimbox label { font-size: 10px; letter-spacing: 0.14em; color: var(--muted); }
.claimbox input { flex: 1; }

/* ------------------------------------------------------------------ modals */

.modal-bg {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0,0,0,0.82);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 4vh 12px; overflow-y: auto;
}
.modal {
  width: min(760px, 100%); background: var(--panel);
  border: 1px solid var(--edge); border-radius: 4px;
  box-shadow: 0 30px 90px rgba(0,0,0,0.7);
}
.modal-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--edge);
}
.modal-head h2 { margin: 0; font-size: 15px; letter-spacing: 0.1em; color: var(--ink); flex: 1; }
.modal-head .pts { font-size: 12px; color: var(--warn); }
.modal-body { padding: 16px; }
.modal-body pre, .prose {
  white-space: pre-wrap; word-break: break-word;
  font-family: var(--mono); font-size: 13.5px; line-height: 1.6; margin: 0;
}

/* Aligned blocks: column position carries meaning, so never wrap them.
   fitArt() shrinks each one to fit; below its floor this scrolls instead. */
pre.art {
  white-space: pre;
  word-break: normal;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  max-width: 100%;
  padding-bottom: 2px;
}
pre.art::-webkit-scrollbar { height: 5px; }
pre.art::-webkit-scrollbar-thumb { background: var(--edge); border-radius: 3px; }
/* Only the ones too wide even at the minimum size advertise that they scroll. */
pre.art.scrolls {
  border-right: 1px solid var(--edge);
  background: linear-gradient(to left, rgba(127,224,176,0.07), transparent 24px);
}
.flavor { color: var(--muted); font-style: italic; margin-bottom: 14px; font-size: 13px; }
.prompt-box {
  background: #06090800; border-left: 2px solid var(--edge);
  padding: 2px 0 2px 14px; margin-bottom: 18px; color: var(--text);
}
.close { background: none; border: 0; color: var(--muted); font-size: 22px; cursor: pointer; line-height: 1; padding: 0 4px; }
.close:hover { color: var(--bad); }

.solved-banner {
  border: 1px solid rgba(110,231,168,0.4); background: rgba(110,231,168,0.06);
  color: var(--good); padding: 10px 12px; border-radius: 3px; margin-bottom: 16px; font-size: 13px;
}
.gate-banner {
  border: 1px solid rgba(240,180,41,0.4); background: rgba(240,180,41,0.06);
  color: var(--warn); padding: 10px 12px; border-radius: 3px; margin-bottom: 16px; font-size: 13px;
}

/* -------------------------------------------------------- puzzle controls */

.answer-row { display: flex; gap: 8px; margin-top: 6px; }
.answer-row input { flex: 1; }

.keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; max-width: 260px; margin: 14px 0; }
.keypad button {
  padding: 15px 0; background: var(--bg-2); border: 1px solid var(--edge);
  color: var(--text); border-radius: 3px; cursor: pointer; font-size: 19px;
}
.keypad button:active { background: var(--ink); color: #04140d; }
.keydisp {
  font-size: 30px; letter-spacing: 0.42em; color: var(--ink);
  min-height: 42px; padding: 4px 0 0 6px; border-bottom: 1px solid var(--edge); max-width: 260px;
}

.seq-options { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
.seq-options button {
  background: var(--bg-2); border: 1px solid var(--edge); color: var(--text);
  padding: 9px 13px; border-radius: 3px; cursor: pointer; font-size: 13px; text-align: left;
}
.seq-options button:hover { border-color: var(--ink-dim); }
.seq-options button:disabled { opacity: 0.3; cursor: default; }
.seq-picked {
  min-height: 44px; border: 1px dashed var(--edge); border-radius: 3px;
  padding: 8px 10px; margin: 10px 0; color: var(--ink); font-size: 13px;
}
.seq-picked span { display: inline-block; margin-right: 10px; }
.seq-picked .n { color: var(--muted); }

.wires { display: grid; gap: 10px; margin: 14px 0; }
.wire-row {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: center;
  border: 1px solid var(--edge); border-radius: 3px; padding: 9px 11px; background: var(--bg-2);
}
@media (max-width: 560px) {
  .wire-row { grid-template-columns: 1fr; gap: 6px; }
  .wire-row .arrow { display: none; }
  .wire-row .det { margin-bottom: 4px; }
}
.wire-row .lbl { font-size: 13px; color: var(--ink); }
.wire-row .det { font-size: 11.5px; color: var(--muted); }
.wire-row select {
  background: var(--bg); color: var(--text); border: 1px solid var(--edge);
  border-radius: 3px; padding: 10px; font-family: var(--mono); font-size: 16px; width: 100%;
}
.wire-row .arrow { color: var(--ink-dim); }

.fields { display: grid; gap: 12px; margin: 14px 0; }

.term {
  background: #04070500; border: 1px solid var(--edge); border-radius: 3px;
  height: min(46vh, 380px); overflow: auto; padding: 12px;
  font-size: 13px; white-space: pre; word-break: normal;
  -webkit-overflow-scrolling: touch;
  background-color: #030605;
}
.term > div { min-width: max-content; }
@media (max-width: 560px) { .term { font-size: 11.5px; } }
.term .cmd { color: var(--ink); }
.term .out { color: var(--text); opacity: 0.92; }
.term-input { display: flex; gap: 8px; margin-top: 8px; align-items: center; }
.term-input .ps { color: var(--ink); font-size: 13px; }
.term-input input { flex: 1; }

/* --------------------------------------------------------------- payphone */

.phone { display: grid; gap: 14px; margin-top: 14px; }

/* The phone is the picture. Keys are percentage rects over a close-up of the
   real plate, exactly like a room hotspot, so they stay on their buttons at
   any rendered size. */
.phone-face {
  position: relative; margin: 0 auto;
  /* Sized by HEIGHT, not width. The plate is twice as tall as it is wide, so
     filling the width of a phone would run it off the bottom of the screen;
     filling the height instead gets the keys as big as they can be and still
     be seen all at once. Keys land at ~70px, well over the 44px tap floor. */
  height: min(68dvh, 660px);
  width: auto; max-width: 100%;
  background-repeat: no-repeat; border-radius: 3px;
  box-shadow: 0 0 0 1px var(--edge), 0 10px 34px #000a;
}
@supports not (height: 1dvh) { .phone-face { height: min(68vh, 660px); } }
.phone-key {
  position: absolute; appearance: none; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: 3px;
  transition: background 90ms, box-shadow 90ms;
}
/* Unlit until you go looking, so the plate reads as a photograph first. */
.phone-face:hover .phone-key, .phone-face:focus-within .phone-key { border-color: #7fe0b026; }
/* Touch has no hover to go looking with, and a photograph of a keypad gives a
   thumb nothing to aim at. On a phone the keys are always faintly outlined. */
@media (hover: none) {
  .phone-key { border-color: #7fe0b038; }
}
.phone-key:hover { background: #7fe0b01f; border-color: var(--ink-dim); }
.phone-key:active, .phone-key.lit {
  background: #7fe0b059; border-color: var(--ink);
  box-shadow: 0 0 12px #7fe0b073, inset 0 0 8px #7fe0b04d;
}
.phone-key:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }

.phone-glass {
  position: absolute; display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-weight: 600;
  font-size: clamp(9px, 7.5cqw, 17px);
  letter-spacing: 0.08em; white-space: nowrap; overflow: hidden;
  color: #ffb545; text-shadow: 0 0 6px #ffb54599, 0 0 14px #ff8a0059;
  background: #140b02cc;
}
.phone-face { container-type: inline-size; }
.phone-glass.bare {
  position: static; height: 40px; margin-bottom: 12px; font-size: 15px;
  border: 1px solid var(--edge); border-radius: 3px;
}

.redial-wrap { border-top: 1px solid var(--edge); padding-top: 10px; }
.redial-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 11px; letter-spacing: 0.16em; color: var(--muted); margin-bottom: 7px;
}
.redial { display: flex; flex-wrap: wrap; gap: 6px; }
.redial:empty::after { content: "nothing dialled yet"; color: var(--muted); font-size: 12px; }

.phone-line {
  border: 1px solid var(--edge); border-radius: 3px; background-color: #030605;
  max-height: min(42vh, 340px); overflow: auto; padding: 12px; font-size: 13px;
  -webkit-overflow-scrolling: touch;
}
.phone-line:empty { display: none; }
.phone-line .said { margin-bottom: 12px; }
.phone-line .said:last-child { margin-bottom: 0; }
.phone-line .dialled {
  color: var(--ink); letter-spacing: 0.2em; border-bottom: 1px solid var(--edge);
  padding-bottom: 7px; margin-bottom: 10px;
}
.phone-line .dialtone { color: var(--muted); }
.phone-line .got { color: var(--good); }
.phone-line .note { color: var(--warn); }
.phone-line .err { color: var(--bad); }
.phone-line .solved {
  color: #04140d; background: var(--good); font-weight: 600;
  letter-spacing: 0.1em; padding: 6px 10px; border-radius: 3px; margin: 4px 0 12px;
}
@media (max-width: 560px) { .phone-line { font-size: 12px; } }

/* The grid fallback, for a phone with no close-up art of its own. */
.dialpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; max-width: 280px; margin: 0 auto; }
.dialpad.dtmf { grid-template-columns: repeat(4, 1fr); max-width: 340px; }
.dialpad .dkey {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 8px 0 6px;
  background: var(--bg-2); border: 1px solid var(--edge); color: var(--text);
  border-radius: 3px; cursor: pointer; font-family: var(--mono); line-height: 1.1;
}
.dialpad .dkey .d { font-size: 19px; }
.dialpad .dkey .l { font-size: 8.5px; letter-spacing: 0.1em; color: var(--muted); min-height: 11px; }
.dialpad .dkey:active { background: var(--ink); color: #04140d; }
.dialpad .dkey:active .l { color: #04140d; }
.dialpad .dkey.col4 { border-color: var(--ink-dim); }
.dialpad .dkey.col4 .d { color: var(--ink-dim); }


/* -------------------------------------------------------------- panels */

.panel-list { display: grid; gap: 10px; }
.card {
  border: 1px solid var(--edge); border-radius: 3px; padding: 11px 13px; background: var(--bg-2);
}
.card h4 { margin: 0 0 4px; font-size: 13px; color: var(--text); }
.card .meta { font-size: 11.5px; color: var(--muted); }
.card.done { border-color: rgba(110,231,168,0.3); }
.card.done h4 { color: var(--good); }
.card.locked { opacity: 0.62; }

.tag {
  display: inline-block; font-size: 10px; letter-spacing: 0.1em;
  padding: 2px 6px; border: 1px solid currentColor; border-radius: 2px; margin-right: 6px;
}
.tag.qr { color: #7dd3fc; } .tag.nfc { color: #c4b5fd; } .tag.code { color: #fcd34d; }
.tag.ssid { color: #6ee7a8; } .tag.rolling { color: #fda4af; } .tag.staff { color: #fdba74; }

.map-grid { display: grid; gap: 8px; }
.map-act h3 {
  font-size: 11px; letter-spacing: 0.18em; color: var(--muted);
  margin: 16px 0 8px; border-bottom: 1px solid var(--edge); padding-bottom: 5px;
}
.map-node {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: var(--bg-2); border: 1px solid var(--edge); border-radius: 3px;
  padding: 9px 11px; cursor: pointer; color: var(--text);
}
.map-node:hover { border-color: var(--ink-dim); }
.map-node:disabled { opacity: 0.4; cursor: not-allowed; }
.map-node .nm { flex: 1; font-size: 13px; }
.map-node .pz { font-size: 11px; color: var(--muted); }
.map-node .bar { width: 42px; height: 4px; background: var(--edge); border-radius: 2px; overflow: hidden; }
.map-node .bar i { display: block; height: 100%; background: var(--good); }
.map-node .caret { color: var(--ink-dim); font-size: 10px; width: 9px; }
.map-node[aria-expanded="true"] .caret { color: var(--ink); }

/* The row opens the room; the arrow on the end still walks there. */
.map-row { display: flex; gap: 6px; align-items: stretch; }
.map-row .map-node { flex: 1; border-radius: 3px 0 0 3px; }
.map-row.open .map-node { border-color: var(--ink-dim); border-bottom-color: transparent; border-radius: 3px 0 0 0; }
.map-go {
  flex: none; width: 34px; background: var(--bg-2); border: 1px solid var(--edge);
  border-left: 0; border-radius: 0 3px 3px 0; color: var(--ink-dim);
  cursor: pointer; font-size: 15px; line-height: 1;
}
.map-go:hover { border-color: var(--ink-dim); color: var(--ink); background: #7fe0b014; }
.map-go:disabled { opacity: 0.4; cursor: not-allowed; }
.map-row.open .map-go { border-color: var(--ink-dim); }

.map-detail {
  border: 1px solid var(--ink-dim); border-top: 0; border-radius: 0 0 3px 3px;
  background: var(--panel); padding: 7px; margin: -1px 0 8px; display: grid; gap: 5px;
}
.md-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  background: transparent; border: 1px solid transparent; border-radius: 3px;
  padding: 6px 7px; cursor: pointer; color: var(--text); font-size: 12.5px;
}
.md-item:hover { border-color: var(--edge); background: var(--bg-2); }
.md-mark { color: var(--ink-dim); width: 11px; }
.md-item.done .md-mark { color: var(--good); }
.md-item.locked .md-mark { color: var(--warn); }
.md-title { flex: 1; }
.md-item.done .md-title { color: var(--muted); text-decoration: line-through; }
.md-pts { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.md-empty { font-size: 11.5px; color: var(--muted); padding: 4px 7px; line-height: 1.45; }

table.board { width: 100%; border-collapse: collapse; font-size: 13px; }
table.board th, table.board td { padding: 7px 8px; border-bottom: 1px solid var(--edge); text-align: left; }
table.board th { color: var(--muted); font-size: 10.5px; letter-spacing: 0.14em; font-weight: 400; }
table.board td.n { text-align: right; font-variant-numeric: tabular-nums; }
table.board tr.me { background: rgba(127,224,176,0.07); }
table.board tr.me td { color: var(--ink); }

.feed-item { font-size: 12.5px; padding: 6px 0; border-bottom: 1px solid var(--edge); color: var(--muted); }
.feed-item b { color: var(--ink); font-weight: 400; }
.feed-item .fb { color: var(--warn); }

.toast-wrap { position: fixed; left: 50%; bottom: 74px; transform: translateX(-50%); z-index: 500; width: min(520px, 92vw); }
.toast {
  background: var(--panel); border: 1px solid var(--ink-dim); border-left-width: 3px;
  border-radius: 3px; padding: 10px 13px; margin-top: 8px; font-size: 13px;
  box-shadow: 0 10px 40px rgba(0,0,0,0.6); animation: rise 180ms ease-out;
}
.toast.bad { border-color: var(--bad); color: var(--bad); }
/* Sensor advice runs to several lines of settings navigation. Without this the
   long ones clipped, which is the same as not showing them. */
.toast { line-height: 1.45; white-space: pre-line; overflow-wrap: anywhere; }
.toast.warn { border-color: var(--warn); color: var(--warn); }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }

.announce {
  border: 1px solid var(--warn); border-left-width: 3px; background: rgba(240,180,41,0.07);
  color: var(--warn); padding: 9px 12px; font-size: 12.5px; margin: 10px 14px 0;
}

.empty { color: var(--muted); font-size: 13px; padding: 20px 0; text-align: center; }

/* ------------------------------------------------------------- responsive */

@media (min-width: 900px) {
  .scene-wrap { flex-direction: row; align-items: flex-start; gap: 0; }
  .scene-main { flex: 1 1 auto; min-width: 0; }
  .side {
    width: 340px; flex: 0 0 340px; border-left: 1px solid var(--edge);
    height: calc(100dvh - 49px); position: sticky; top: 49px; overflow-y: auto; padding: 14px;
  }
}
/* Very narrow phones (320px, iPhone SE 1st gen): shed the least useful chrome
   rather than letting the topbar push the page sideways. */
@media (max-width: 374px) {
  .topbar { gap: 7px; padding: 8px 8px; }
  .brand { overflow: hidden; text-overflow: ellipsis; max-width: 38vw; }
  .stat.rank { display: none; }
}

/* Landscape phones and short laptop viewports: less chrome to reserve, or the
   frame collapses to nothing. */
@media (max-height: 620px) {
  :root { --chrome: 106px; }
}

/* ------------------------------------------------- phone, held sideways

   A phone in landscape has about 380px of height and the picture wants all of
   it. Everything that normally sits under the frame — the caption and the
   FOUND HERE list — moves into the hamburger, and the frame is sized by height
   so the whole 3:2 wall fits with nothing cropped and nothing to scroll past.
   The topbar stays: it is the control bar, and it holds the menu. */
@media (orientation: landscape) and (max-height: 560px) and (hover: none) {
  :root { --chrome: 0px; }
  .scene-wrap { flex: 1 1 auto; min-height: 0; }
  .scene-main { display: flex; align-items: center; justify-content: center; }
  .scene {
    width: auto; min-width: 0;
    height: calc(100dvh - var(--topbar-h));
    max-width: 100%;
  }
  /* The turn buttons keep their edges but shrink out of the way. */
  .turnedge { transform: scale(0.85); }
}

@media (max-width: 560px) {
  .topbar { flex-wrap: wrap; }
  .tag { font-size: 11px; }
  .card .meta, .feed-item { font-size: 12.5px; }
  table.board th { font-size: 11px; }
  .caption { font-size: 13.5px; }
  .stat { font-size: 12.5px; }
  .brand { font-size: 12.5px; }
  .modal-bg { padding: 2vh 6px 12px; }
  .modal-body { padding: 12px 10px; }
  .modal-head { padding: 10px 12px; }
  .scene-wrap { flex: 0 0 auto; }
  .exits { padding-bottom: 18px; }
}

@media (max-width: 899px) {
  .side { display: none; }
  .side.open {
    display: block; position: fixed; inset: 49px 0 0 0; z-index: 120;
    background: var(--bg); overflow-y: auto; padding: 14px 14px 90px;
  }
}

.side h3 { font-size: 11px; letter-spacing: 0.18em; color: var(--muted); margin: 0 0 10px; }
.side section { margin-bottom: 26px; }

.sidebar-tabs { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; }
.sidebar-tabs button {
  background: none; border: 1px solid var(--edge); color: var(--muted);
  padding: 5px 9px; border-radius: 3px; font-size: 11px; cursor: pointer; letter-spacing: 0.08em;
}
.sidebar-tabs button[aria-selected="true"] { color: var(--ink); border-color: var(--ink-dim); }

.progress-line { display: flex; justify-content: space-between; font-size: 12px; color: var(--muted); margin-bottom: 5px; }
.progress-bar { height: 5px; background: var(--edge); border-radius: 3px; overflow: hidden; margin-bottom: 14px; }
.progress-bar i { display: block; height: 100%; background: var(--ink); }

/* ------------------------------------------------------------ art labels

   Organizer-only badge naming the art file, top-right of every picture. Sits
   above the hotspots so it stays readable, but ignores the mouse so it can
   never eat a click meant for the scene. */

.arttag {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 6;
  pointer-events: none;
  padding: 3px 7px;
  border: 1px solid rgba(127, 224, 176, .38);
  border-radius: 2px;
  background: rgba(7, 11, 10, .74);
  color: #7fe0b0;
  font: 400 11px/1.35 var(--mono, ui-monospace, monospace);
  letter-spacing: .5px;
  white-space: nowrap;
  text-shadow: 0 1px 2px #000;
}

.shotwrap { position: relative; display: block; }
.shotwrap .zoomshot { display: block; }

/* ---------------------------------------------------- clickable regions (H)

   Outlines every hit target on the scene and names it from data-id. Colour
   carries the state, because "this box is in the wrong place" and "this box
   has already been found" are different notes to take:
     green  = not yet found      amber = found      blue = solved
     violet = edge-of-screen turn strip                                    */

.scene.boxes .hotspot {
  border: 1px dashed rgba(127, 224, 176, .85);
  background: rgba(127, 224, 176, .09);
}
.scene.boxes .hotspot.found { border-color: rgba(232, 196, 112, .9); background: rgba(232, 196, 112, .09); }
.scene.boxes .hotspot.solved { border-color: rgba(120, 196, 255, .9); background: rgba(120, 196, 255, .09); }
.scene.boxes .hotspot::after {
  content: attr(data-id);
  position: absolute;
  left: 0;
  top: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 1px 4px;
  background: rgba(7, 11, 10, .82);
  color: #d6f5e6;
  font: 400 10px/1.4 var(--mono, ui-monospace, monospace);
  letter-spacing: .3px;
  pointer-events: none;
}
.scene.boxes .hotspot.found::after { color: #f0dcae; }
.scene.boxes .hotspot.solved::after { color: #c2e2ff; }

/* --------------------------------------------------- floor plan editor */

.planner-body { overflow: hidden; }
.planner-body #app { display: flex; flex-direction: column; height: 100dvh; }

.pl-bar, .pl-status {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 10px; border-bottom: 1px solid var(--edge); background: var(--panel);
}
.pl-bar .spacer { flex: 1; }
.pl-tabs { display: flex; gap: 6px; }
.pl-status {
  border-bottom: 0; border-top: 1px solid var(--edge);
  font: 400 11px/1.4 var(--mono, ui-monospace, monospace); letter-spacing: .5px; color: var(--dim);
}
.pl-status .warn { color: #f0b429; }
.pl-status .ok { color: var(--accent); }

.pl-main { flex: 1; display: flex; min-height: 0; }
.pl-canvaswrap { flex: 1; min-width: 0; background: #070b0a; position: relative; }
.pl-canvas { width: 100%; height: 100%; display: block; touch-action: none; cursor: grab; }

.pl-g1 { stroke: rgba(127, 224, 176, .07); fill: none; }
.pl-g10 { stroke: rgba(127, 224, 176, .16); fill: none; }
.pl-axis { stroke: rgba(240, 180, 41, .34); fill: none; }

.pl-room rect { fill: rgba(7, 11, 10, .3); stroke: #7fe0b0; cursor: move; }
.pl-room.sel rect { stroke: #f0b429; fill: rgba(240, 180, 41, .10); }
.pl-room.bad rect { stroke: #ff6b6b; }
.pl-room.k-service rect { stroke: #9fc4b4; }
.pl-room.k-hall rect { stroke: #7fe0b0; fill: rgba(127, 224, 176, .10); stroke-dasharray: 5 4; }
.pl-room.k-hall.sel rect { stroke-dasharray: none; }
.pl-room.k-venue rect, .pl-room.k-subroom rect { stroke: #8ad6a8; }
.pl-room.k-atrium rect, .pl-room.k-void rect { stroke: #38bdf8; }
.pl-room.k-exterior rect { stroke: #f0b429; }
.pl-room.k-overlay rect { stroke: #6c7f78; stroke-dasharray: 6 5; }
.pl-room.k-mystery rect { stroke: #c58af0; stroke-dasharray: 6 5; }

/* Doors. The fat transparent hit line is what you actually grab — a 3 ft door
   is a few pixels wide on a 340 ft sheet and would be unhittable otherwise. */
.pl-doorhit { stroke: transparent; cursor: move; }
.pl-doorjamb { stroke: #7fe0b0; stroke-linecap: round; pointer-events: none; }
.pl-doorleaf { stroke: #f0b429; stroke-linecap: round; pointer-events: none; }
.pl-doorarc { stroke: #f0b429; fill: none; stroke-dasharray: 3 3; opacity: .8; pointer-events: none; }
.pl-door.sel .pl-doorjamb { stroke: #fff2cc; }
.pl-door.sel .pl-doorleaf, .pl-door.sel .pl-doorarc { stroke: #ffd97a; opacity: 1; }
.pl-listitem.door b { color: #f0b429; }
.pl-selhead {
  font-size: 11px; letter-spacing: 1.5px; color: #f0b429;
  margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--edge);
}

.pl-label {
  fill: #d6e6df; text-anchor: middle; dominant-baseline: middle;
  font-family: var(--mono, ui-monospace, monospace); pointer-events: none; user-select: none;
}
.pl-label.dim { fill: #7d938b; }
.pl-dim {
  fill: #f0b429; text-anchor: middle; font-family: var(--mono, ui-monospace, monospace);
  pointer-events: none; user-select: none;
}
.pl-handle { fill: #f0b429; stroke: #070b0a; }

.pl-side {
  width: 320px; flex: 0 0 320px; overflow-y: auto; padding: 12px;
  border-left: 1px solid var(--edge); background: var(--panel);
}
.pl-list { margin-bottom: 14px; }
.pl-listhead { font-size: 11px; letter-spacing: 1px; color: var(--dim); margin-bottom: 6px; }
.pl-listbody { max-height: 30vh; overflow-y: auto; border: 1px solid var(--edge); border-radius: 3px; }
.pl-listitem {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 5px 8px; border: 0; border-bottom: 1px solid rgba(127, 224, 176, .08);
  background: transparent; color: var(--ink); text-align: left; cursor: pointer;
  font: 400 12px/1.35 var(--mono, ui-monospace, monospace);
}
.pl-listitem:last-child { border-bottom: 0; }
.pl-listitem:hover { background: rgba(127, 224, 176, .07); }
.pl-listitem.on { background: rgba(240, 180, 41, .14); color: #f0b429; }
.pl-listitem.bad b::after { content: ' ⚠'; color: #ff6b6b; }
.pl-listitem b { flex: 1; font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-listitem i { font-style: normal; color: var(--dim); font-size: 11px; }

.pl-empty { color: var(--dim); font-size: 12px; line-height: 1.5; margin: 0 0 10px; }
.pl-field { display: block; margin-bottom: 10px; }
.pl-field > span { display: block; font-size: 11px; letter-spacing: 1px; color: var(--dim); margin-bottom: 4px; }
.pl-field > i { display: block; font-size: 11px; font-style: normal; color: #6c7f78; margin-top: 3px; }
.pl-field input, .pl-field select, .pl-field textarea { width: 100%; }
.pl-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 10px; }
.pl-rowbtns { display: flex; gap: 8px; margin-top: 12px; }
.pl-rowbtns .btn { flex: 1; }
.btn.danger { border-color: #ff6b6b; color: #ff6b6b; }
.pl-preview { margin-top: 16px; }
.pl-preview summary { cursor: pointer; font-size: 11px; letter-spacing: 1px; color: var(--dim); }
.pl-preview img { width: 100%; margin-top: 8px; border: 1px solid var(--edge); background: #070b0a; }

/* On a phone the inspector goes underneath, because the canvas needs the width
   and this gets used standing in a hotel corridor. */
@media (max-width: 760px) {
  .pl-main { flex-direction: column; }
  .pl-canvaswrap { flex: 1 1 55%; }
  .pl-side { width: auto; flex: 1 1 45%; border-left: 0; border-top: 1px solid var(--edge); }
}

/* ------------------------------------------------------ touch targets

   Last in the file on purpose: these are minimums that must beat the base
   rules above, and they are all equal-specificity single-class selectors.
   Scoped to coarse pointers so desktop keeps its compact chrome. */

@media (pointer: coarse) {
  .btn, .btn.small, .exit, .iconbtn, .close,
  .map-node, .seq-options button, .sidebar-tabs button, .tabs button,
  .keypad button, .card .btn {
    min-height: var(--tap);
  }
  .iconbtn, .close { min-width: var(--tap); width: auto; }
  .close { font-size: 26px; }
  .btn.small { padding: 8px 14px; }
  .exit { padding: 11px 14px; }
  .sidebar-tabs button { padding: 9px 12px; min-width: var(--tap); }
  .seq-options button { padding: 11px 14px; }
  /* Percentage rects land a few px short of 44 on the narrowest phones because
     the frame height varies with viewport width. The content rects are authored
     close enough that this last nudge cannot make neighbours overlap. */
  .hotspot { min-width: 44px; min-height: 44px; }

  /* No hover on touch, so never leave a control in a hover-only state. */
  .hotspot:hover { background: transparent; }
  }

/* ------------------------------------------------------------- found list

   The only place a thing gets named, and only once the player has clicked it. */

.found { padding: 12px 14px calc(16px + env(safe-area-inset-bottom, 0px)); }
.found-head {
  display: flex; align-items: baseline; gap: 10px;
  border-bottom: 1px solid var(--edge); padding-bottom: 7px; margin-bottom: 10px;
}
.found-title { font-size: 11px; letter-spacing: 0.2em; color: var(--muted); }
.found-count { font-size: 11px; color: var(--ink-dim); margin-left: auto; font-variant-numeric: tabular-nums; }
.found-empty { color: var(--muted); font-size: 13px; line-height: 1.6; font-style: italic; }
.found-list { display: grid; gap: 7px; }

.found-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: var(--panel); border: 1px solid var(--edge); border-radius: 3px;
  color: var(--text); padding: 10px 12px; cursor: pointer; font-size: 13.5px;
  min-height: var(--tap);
}
.found-item:hover { border-color: var(--ink-dim); }
.found-item .fi-mark { color: var(--ink-dim); width: 14px; flex: 0 0 auto; text-align: center; }
.found-item .fi-label { flex: 1; }
.found-item .fi-pts { color: var(--warn); font-size: 12px; flex: 0 0 auto; }
.found-item.go .fi-mark { color: #7dd3fc; }
.found-item.go .fi-label { color: #cbe9fb; }
.found-item.puzzle .fi-mark { color: var(--ink); }
.found-item.done { border-color: rgba(110,231,168,0.32); }
.found-item.done .fi-mark, .found-item.done .fi-label { color: var(--good); }
.found-item.locked .fi-mark { color: var(--warn); }
.found-item.locked .fi-label { color: var(--muted); }

/* ---------------------------------------------------------- labyrinth map */

.automap-wrap {
  border: 1px solid var(--edge); border-radius: 3px; background: var(--bg-2);
  padding: 8px; overflow-x: auto;
}
.automap { width: 100%; max-width: 260px; height: auto; color: var(--ink-dim); display: block; margin: 0 auto; }
.automap-cell { cursor: pointer; }
.automap-cell:hover { fill: rgba(127,224,176,0.16) !important; }

/* ------------------------------------------------------- Myst-style cursors

   Two shapes, as in Myst: an open hand over anything you can take hold of, and
   a pointing finger at the screen edges for turning. Inline SVG data URIs so
   there are no asset requests and no external files to lose. The hotspot itself
   stays invisible — the cursor is the only tell, which is the whole point. */

:root {
  /* open hand — over something you can manipulate */
  --cur-grab: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34"><g fill="%23e8fff5" stroke="%2305100c" stroke-width="1.4" stroke-linejoin="round"><path d="M12 20V8.5a2 2 0 1 1 4 0V16"/><path d="M16 15.5V7a2 2 0 1 1 4 0v8.5"/><path d="M20 16V9.5a2 2 0 1 1 4 0V19"/><path d="M24 19v-4a2 2 0 1 1 4 0v9c0 4.5-3 7.5-7.5 7.5h-3C13 31.5 11 29 9.5 26L7 21.5a2 2 0 0 1 3.4-2.1L12 21.5"/></g></svg>') 17 17, grab;
  /* closed hand — the moment you press */
  --cur-grabbing: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34"><g fill="%23e8fff5" stroke="%2305100c" stroke-width="1.4" stroke-linejoin="round"><path d="M9 18c0-4 3-6 8-6h6c4.5 0 7 2.5 7 6.5v5c0 4.5-3 7.5-7.5 7.5h-3C14 31 11 28.5 9.5 25.5Z"/><path d="M12 13.5V10a2 2 0 1 1 4 0v3"/><path d="M16 12.5V9a2 2 0 1 1 4 0v3.5"/><path d="M20 13v-2.5a2 2 0 1 1 4 0V14"/></g></svg>') 17 17, grabbing;
  /* magnifier — something to inspect rather than handle */
  --cur-look: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><g fill="none" stroke="%23e8fff5" stroke-width="2.6"><circle cx="12.5" cy="12.5" r="7.5"/><path d="M18 18l8 8"/></g><g fill="none" stroke="%2305100c" stroke-width="1"><circle cx="12.5" cy="12.5" r="9"/></g></svg>') 12 12, zoom-in;
}

/* pointing finger, rotated per edge */

:root {
  --cur-point-l: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34"><g fill="%23e8fff5" stroke="%2305100c" stroke-width="1.4" stroke-linejoin="round"><path d="M14 17H4"/><path d="M14 12.5c-3 0-5 2-5 4.5s2 4.5 5 4.5h3v6.5c0 2 1.5 3.5 3.5 3.5h6c3 0 5.5-2.5 5.5-5.5v-9c0-2.5-2-4.5-4.5-4.5H17Z"/></g></svg>') 6 17, w-resize;
  --cur-point-r: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34"><g fill="%23e8fff5" stroke="%2305100c" stroke-width="1.4" stroke-linejoin="round"><path d="M20 17h10"/><path d="M20 12.5c3 0 5 2 5 4.5s-2 4.5-5 4.5h-3v6.5c0 2-1.5 3.5-3.5 3.5h-6C4.5 31.5 2 29 2 26v-9c0-2.5 2-4.5 4.5-4.5H17Z"/></g></svg>') 28 17, e-resize;
  --cur-point-u: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34"><g fill="%23e8fff5" stroke="%2305100c" stroke-width="1.4" stroke-linejoin="round"><path d="M17 14V4"/><path d="M12.5 14c0-3 2-5 4.5-5s4.5 2 4.5 5v3h6.5c2 0 3.5 1.5 3.5 3.5v6c0 3-2.5 5.5-5.5 5.5h-9c-2.5 0-4.5-2-4.5-4.5V17Z"/></g></svg>') 17 6, n-resize;
  --cur-point-d: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34"><g fill="%23e8fff5" stroke="%2305100c" stroke-width="1.4" stroke-linejoin="round"><path d="M17 20v10"/><path d="M21.5 20c0 3-2 5-4.5 5s-4.5-2-4.5-5v-3H6c-2 0-3.5-1.5-3.5-3.5v-6C2.5 4.5 5 2 8 2h9c2.5 0 4.5 2 4.5 4.5V17Z"/></g></svg>') 17 28, s-resize;
}

/* Hotspot cursor by what the thing is. */
.hotspot.cur-grab   { cursor: var(--cur-grab); }
.hotspot.cur-grab:active { cursor: var(--cur-grabbing); }
.hotspot.cur-look   { cursor: var(--cur-look); }
.hotspot.cur-go     { cursor: var(--cur-point-u); }

/* Turning. On the frame, at the middle of each edge, and VISIBLE — which is
   the whole difference from what was here before. The old controls were
   invisible strips 13% of the width down each side, so a door or a corridor
   near the edge of a shot sat underneath one and clicking the thing you could
   see turned you away from it. A small button you can actually see takes a
   fraction of the area and tells you what it is. */
.turnedge {
  position: absolute; z-index: 5;
  width: 44px; height: 78px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(127, 224, 176, .45); border-radius: 3px;
  background: rgba(5, 7, 6, .62);
  color: var(--ink); font-size: 30px; line-height: 1;
  cursor: pointer; padding: 0;
  -webkit-tap-highlight-color: transparent;
  backdrop-filter: blur(2px);
  transition: background 120ms, border-color 120ms;
}
.turnedge:hover { background: rgba(5, 7, 6, .85); border-color: var(--ink); }
.turnedge:active { background: rgba(127, 224, 176, .3); }
.turnedge:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.te-left  { left: 8px;  bottom: 8px; }
.te-right { right: 8px; bottom: 8px; }
.te-up    { left: 50%; top: 8px;    transform: translateX(-50%) rotate(90deg); }
.te-down  { left: 50%; bottom: 8px; transform: translateX(-50%) rotate(-90deg); }

.turnbar { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 8px; }

.compass { display: flex; gap: 6px; }
.compass-dot {
  width: 26px; height: 8px; border-radius: 2px; padding: 0;
  border: 1px solid var(--edge); background: transparent; cursor: pointer;
}
.compass-dot.on { background: var(--ink); border-color: var(--ink); }
@media (pointer: coarse) {
  .compass-dot { height: 22px; width: 34px; }
  .turnedge { width: var(--tap); height: 88px; }
}

/* A found thing that lives on another wall of this room. */
.found-item.elsewhere { opacity: 0.62; }
.found-item.elsewhere .fi-pts { color: var(--ink-dim); }

/* A close-up of the thing you just leaned in to look at. */
.zoomshot {
  width: 100%; height: auto; display: block; border-radius: 3px;
  border: 1px solid var(--edge); margin-bottom: 14px; background: #05100c;
}

/* ------------------------------------------------- challenge artifacts -- */
/* Files a puzzle hands the player: a capture, a dump, a photograph with
   something in it. Styled as a thing you are taking, not as a download list. */
.artifacts { margin: 14px 0 4px; border: 1px solid var(--edge); border-radius: 4px; }
.artifacts-head { font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); padding: 7px 10px; border-bottom: 1px solid var(--edge); }
.artifact { display: flex; align-items: center; gap: 10px; padding: 9px 10px; }
.artifact + .artifact { border-top: 1px solid var(--edge); }
.a-meta { flex: 1 1 auto; min-width: 0; }
.a-label { font-size: 13.5px; color: var(--ink); }
.a-sub { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere; }
.a-note { font-size: 11.5px; color: var(--muted); margin-top: 3px; font-style: italic; }
.a-get { flex: 0 0 auto; text-decoration: none; white-space: nowrap; }
.a-missing { flex: 0 0 auto; font-size: 10px; letter-spacing: 0.12em; color: var(--bad); }
@media (max-width: 560px) {
  .artifact { flex-wrap: wrap; }
  .a-get { width: 100%; text-align: center; min-height: 40px; line-height: 26px; }
}

/* ------------------------------------------------------------ zone editor */

/* Admin-only, and it deliberately looks like a tool rather than the game: the
   whole point is to see the boxes you are aiming, over the picture they sit on. */
/* While editing, a drag on the picture is aiming a box, never a page scroll.
   touch-action has to be none on the SCENE, not just on the zones: the browser
   intersects it up the ancestor chain, and a scrollable ancestor that still
   allows panning cancels the pointer mid-gesture — which reads as "drag does
   nothing at all" on a phone, and only on the first touch. */
.scene.zoning { cursor: default; touch-action: none; }
.scene.zoning img { opacity: 0.82; }

.zone {
  position: absolute; box-sizing: border-box;
  border: 1px solid var(--ink-dim); background: #7fe0b014;
  cursor: move; touch-action: none;
}
.zone.moved { border-color: var(--warn); background: #f0b4291f; }
.zone.sel { border-color: var(--ink); background: #7fe0b029; box-shadow: 0 0 0 1px var(--ink), 0 0 18px #7fe0b040; }
.zone-tag {
  position: absolute; left: 0; top: -17px; white-space: nowrap;
  font-size: 10px; letter-spacing: 0.1em; color: var(--ink);
  background: #050706d9; padding: 1px 5px; border-radius: 2px; pointer-events: none;
}
.zone.moved .zone-tag { color: var(--warn); }

/* Grips are invisible until the box is selected, so eight handles per zone do
   not turn the wall into a pincushion. Big hit areas, small marks. */
.zone .grip { position: absolute; width: 22px; height: 22px; touch-action: none; }
.zone.sel .grip::after {
  content: ''; position: absolute; inset: 7px;
  background: var(--ink); border-radius: 1px; box-shadow: 0 0 6px #7fe0b0a6;
}
.zone .g-nw { left: -11px; top: -11px; cursor: nwse-resize; }
.zone .g-n  { left: calc(50% - 11px); top: -11px; cursor: ns-resize; }
.zone .g-ne { right: -11px; top: -11px; cursor: nesw-resize; }
.zone .g-e  { right: -11px; top: calc(50% - 11px); cursor: ew-resize; }
.zone .g-se { right: -11px; bottom: -11px; cursor: nwse-resize; }
.zone .g-s  { left: calc(50% - 11px); bottom: -11px; cursor: ns-resize; }
.zone .g-sw { left: -11px; bottom: -11px; cursor: nesw-resize; }
.zone .g-w  { left: -11px; top: calc(50% - 11px); cursor: ew-resize; }

.zonepanel {
  border: 1px solid var(--ink-dim); border-radius: 3px; background: var(--panel);
  padding: 11px 12px; margin-top: 10px; display: grid; gap: 10px;
}
.zp-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.zp-title { font-size: 11px; letter-spacing: 0.14em; color: var(--ink); }
.zp-count { font-size: 11px; color: var(--muted); }
.zp-empty { font-size: 12px; color: var(--muted); line-height: 1.5; }
.zp-sel { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.zp-sel .btn.small { margin-left: auto; }
.zp-id { color: var(--ink); font-size: 13px; }
.zp-act { color: var(--muted); font-size: 11.5px; }
.zp-nums { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.zp-num { display: grid; gap: 3px; }
.zp-num span { font-size: 10px; letter-spacing: 0.12em; color: var(--muted); }
.zp-num input {
  background: var(--bg); color: var(--text); border: 1px solid var(--edge);
  border-radius: 3px; padding: 7px 8px; font-family: var(--mono); font-size: 14px; width: 100%;
}
.zp-warn { font-size: 11.5px; color: var(--warn); }
.zp-pick { display: flex; flex-wrap: wrap; gap: 6px; }
.zp-pick .moved { border-color: var(--warn); color: var(--warn); }
.zp-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.zp-hint { font-size: 11px; color: var(--muted); }

.zp-live { font-size: 11px; color: var(--muted); letter-spacing: 0.08em; }

/* ---------------------------------------------------- challenge editor */

.admin-strip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 20px; padding-top: 12px; border-top: 1px dashed var(--edge);
}
.as-tag { font-size: 10px; letter-spacing: 0.16em; color: var(--warn); }
.as-id { font-size: 11.5px; color: var(--muted); flex: 1; }

.ce { display: grid; gap: 14px; }
.ce-structure {
  display: grid; gap: 3px; font-size: 12px; color: var(--text);
  border: 1px solid var(--edge); border-radius: 3px; padding: 8px 10px; background: var(--bg-2);
}
.ce-structure b { color: var(--ink); font-weight: 400; }
.ce-ro { font-size: 11px; color: var(--muted); line-height: 1.45; }
.ce-field { display: grid; gap: 4px; }
.ce-field label { font-size: 10px; letter-spacing: 0.14em; color: var(--muted); }
.ce-hint { font-size: 11px; color: var(--muted); line-height: 1.4; }
.ce-field input, .ce-field textarea {
  background: var(--bg); color: var(--text); border: 1px solid var(--edge);
  border-radius: 3px; padding: 8px 9px; font-family: var(--mono); font-size: 13px; width: 100%;
}
.ce-field textarea { resize: vertical; line-height: 1.5; }
.ce-field input:focus, .ce-field textarea:focus { border-color: var(--ink-dim); outline: none; }

/* The room's caption and FOUND list when the menu is holding them (landscape). */
.side-here { margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--edge); }
.side-here .caption { padding: 0 0 10px; }

/* Zone editor: adding, deleting, and re-pointing a zone ---------------------
   A zone that does not exist yet is drawn dashed, so it reads as pending
   rather than as something already in the game. */
.zone.fresh { border-style: dashed; border-color: var(--ink); }
.zone.fresh .zone-tag::before { content: '+ '; }

/* The kind/target selects. Full width because a node id plus its name is long,
   and stacked because on a phone they would otherwise be three across. */
.zp-pickrow {
  display: grid; grid-template-columns: 76px 1fr; align-items: center;
  gap: 8px; margin-top: 6px;
}
.zp-pickrow > span {
  font-size: 10px; letter-spacing: .14em; color: var(--muted); text-transform: uppercase;
}
.zp-pickrow select {
  width: 100%; min-width: 0; padding: 7px 9px;
  font: inherit; font-size: 12px;
  color: var(--text); background: var(--panel-2, #0f1613);
  border: 1px solid var(--edge); border-radius: 3px;
}
.zp-pickrow select:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }

.btn.danger { border-color: var(--bad, #f2685c); color: var(--bad, #f2685c); }
.btn.danger:hover { background: #f2685c1f; }
/* A pending deletion stays in the list, struck through, so it can be taken back
   before save. */
.btn.struck { text-decoration: line-through; opacity: .6; }

/* Lights that are still on ---------------------------------------------------
   A view can declare `glows`: soft lights positioned in percent of the frame,
   like hotspots, and drawn over the art. `screen` blending means they ADD
   light to the photograph instead of laying a grey patch on it, so a dark
   picture keeps its blacks and only the lit thing brightens.

   Two periods that do not divide into each other, so the pair never settles
   into an obvious loop, and irregular keyframe stops so neither one is a
   metronome. */
.glow {
  position: absolute;
  pointer-events: none;          /* never in the way of the hotspot underneath */
  border-radius: 50%;
  mix-blend-mode: screen;
  will-change: opacity;
}

/* A screen refreshing: cool, small, and never quite steady. */
.glow-screen {
  background: radial-gradient(ellipse at center,
    rgba(150, 200, 255, 0.50) 0%,
    rgba(95, 145, 215, 0.24) 38%,
    rgba(40, 70, 120, 0) 72%);
  animation: glow-crt 6.3s linear infinite;
}
@keyframes glow-crt {
  0%, 100% { opacity: .82 }
  7%       { opacity: .95 }
  9%       { opacity: .71 }
  23%      { opacity: .88 }
  25%      { opacity: .68 }
  47%      { opacity: .97 }
  52%      { opacity: .79 }
  69%      { opacity: .87 }
  71%      { opacity: .64 }
  88%      { opacity: .92 }
}

/* A work lamp on a car battery: warm, wide, and slowly breathing as the
   charge sags and recovers. */
.glow-lamp {
  background: radial-gradient(ellipse at center,
    rgba(255, 196, 120, 0.34) 0%,
    rgba(215, 150, 80, 0.16) 42%,
    rgba(120, 80, 40, 0) 74%);
  animation: glow-lamp 11.7s ease-in-out infinite;
}
@keyframes glow-lamp {
  0%, 100% { opacity: .58; transform: scale(1) }
  38%      { opacity: .74; transform: scale(1.035) }
  61%      { opacity: .63; transform: scale(1.01) }
}

/* Someone who has asked for stillness gets the light, not the flicker. */
@media (prefers-reduced-motion: reduce) {
  .glow { animation: none !important; }
  .glow-screen { opacity: .84 }
  .glow-lamp { opacity: .64 }
}

/* The zone id is editable in place: renaming a zone is a normal thing to want
   and used to mean hand-editing content/nodes.json. It looks like the label it
   replaced until you focus it. */
input.zp-id {
  font: inherit; font-size: 13px; color: var(--ink);
  background: transparent; border: 1px solid transparent; border-radius: 3px;
  padding: 2px 5px; min-width: 8ch; width: 18ch; max-width: 100%;
}
input.zp-id:hover { border-color: var(--edge); }
input.zp-id:focus { background: var(--panel-2, #0f1613); border-color: var(--ink); outline: none; }

.challenge-toolbar { padding: 12px 0; }
.challenge-toolbar select { width: 100%; padding: 10px; color: var(--text); background: var(--panel); border: 1px solid var(--edge); }
.md-title small { display: block; opacity: .65; font-size: .75em; margin-top: 4px; }
.sidebar-tabs { flex-wrap: wrap; }

.archive-request { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; margin: 16px 0; }
.archive-request pre { grid-column: 1 / -1; white-space: pre-wrap; overflow-wrap: anywhere; }
.evidence-sources { display: grid; gap: 8px; margin: 16px 0; }
.evidence-sources .btn { white-space: normal; text-align: left; justify-content: flex-start; }
@media (max-width: 560px) { .archive-request { grid-template-columns: minmax(0, 1fr); } }
