/* ==========================================================================
   app.css — 界面组件（全部基于 tokens）
   命名：BEM 近似（block__element--modifier），便于后续拆成组件库
   ========================================================================== */

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

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-3);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

#gl {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: var(--z-canvas);
  background: var(--paper);
}

.ui {
  position: fixed;
  inset: 0;
  z-index: var(--z-hud);
  pointer-events: none;
}

.ui > * {
  pointer-events: auto;
}

button {
  font-family: inherit;
  color: inherit;
}

/* ───────────────────────────── 基础排版 ───────────────────────────── */

.display {
  font-family: var(--font-display);
  letter-spacing: var(--tracking);
  line-height: 0.92;
  text-transform: uppercase;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.muted {
  color: var(--ink-3);
}

.dim {
  color: var(--ink-4);
}

.hr {
  height: var(--line);
  background: var(--ink);
  border: 0;
  margin: var(--s3) 0;
}

/* ───────────────────────────── 按钮 ───────────────────────────── */

.btn {
  --btn-bg: var(--paper);
  --btn-fg: var(--ink);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: 12px 20px;
  min-height: 46px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: var(--line) solid var(--ink);
  border-radius: 0;
  box-shadow: var(--shadow);
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  cursor: pointer;
  transition: transform var(--fast) var(--ease), box-shadow var(--fast) var(--ease),
    background var(--fast) var(--ease);
  user-select: none;
}

.btn:hover:not(:disabled) {
  transform: translate(-2px, -2px);
  box-shadow: 6px 6px 0 var(--ink);
}

.btn:active:not(:disabled) {
  transform: translate(2px, 2px);
  box-shadow: 1px 1px 0 var(--ink);
}

.btn:focus-visible {
  outline: var(--line-thick) solid var(--warn);
  outline-offset: 3px;
}

.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  box-shadow: 2px 2px 0 var(--ink);
}

.btn--primary {
  --btn-bg: var(--ink);
  --btn-fg: var(--paper);
  box-shadow: var(--shadow);
}

.btn--primary:hover:not(:disabled) {
  box-shadow: 6px 6px 0 var(--ink-3);
}

.btn--danger {
  --btn-bg: var(--warn);
  --btn-fg: var(--paper);
}

.btn--ghost {
  background: transparent;
  box-shadow: none;
  border-color: var(--ink-3);
  color: var(--ink-2);
  min-height: 38px;
  font-size: 14px;
  padding: 8px 14px;
}

.btn--ghost:hover:not(:disabled) {
  background: var(--paper-2);
  box-shadow: var(--shadow-sm);
  transform: translate(-1px, -1px);
}

.btn--sm {
  min-height: 32px;
  padding: 6px 12px;
  font-size: 13px;
  box-shadow: var(--shadow-sm);
}

.btn--wide {
  width: 100%;
}

.btn__key {
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 1px 5px;
  border: 1px solid currentColor;
  opacity: 0.6;
}

/* ───────────────────────────── 面板 / 卡片 ───────────────────────────── */

.panel {
  background: var(--paper);
  border: var(--line) solid var(--ink);
  box-shadow: var(--shadow);
  padding: var(--s4);
}

.panel--flat {
  box-shadow: none;
}

.panel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding-bottom: var(--s2);
  margin-bottom: var(--s3);
  border-bottom: var(--line) solid var(--ink);
}

.panel__title {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  margin: 0;
}

.card {
  background: var(--paper);
  border: var(--line) solid var(--ink);
  padding: var(--s3);
  transition: transform var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}

.card--hover:hover {
  transform: translate(-2px, -2px);
  box-shadow: var(--shadow);
}

.card--selected {
  background: var(--ink);
  color: var(--paper);
}

.card--locked {
  opacity: 0.42;
  filter: grayscale(1);
  cursor: not-allowed;
}

/* ───────────────────────────── 徽标 / 标签 ───────────────────────────── */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border: 1.5px solid var(--ink);
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
}

.chip--solid {
  background: var(--ink);
  color: var(--paper);
}

.chip--warn {
  border-color: var(--warn);
  color: var(--warn);
}

.chip--gold {
  border-color: var(--gold);
  color: #8a6a12;
}

.chip--cyan {
  border-color: var(--cyan);
  color: #0b6a5c;
}

.stars {
  font-size: 12px;
  letter-spacing: 1px;
}

/* ───────────────────────────── 进度条 ───────────────────────────── */

.bar {
  position: relative;
  height: 12px;
  background: var(--paper-2);
  border: var(--line) solid var(--ink);
  overflow: hidden;
}

.bar > i {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--ink);
  transition: width var(--mid) var(--ease);
}

.bar--thin {
  height: 7px;
  border-width: 1.5px;
}

.bar--warn > i {
  background: var(--warn);
}

.bar--cyan > i {
  background: var(--cyan);
}

.bar--striped > i {
  background-image: repeating-linear-gradient(
    -45deg,
    var(--ink) 0 4px,
    var(--ink-3) 4px 8px
  );
}

/* ───────────────────────────── 头像 ───────────────────────────── */

.avatar {
  position: relative;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  border: var(--line) solid var(--ink);
  background: var(--paper-2);
  overflow: hidden;
  display: grid;
  place-items: center;
}

.avatar svg {
  width: 100%;
  height: 100%;
  display: block;
}

.avatar__ring {
  position: absolute;
  inset: -4px;
  border: 2px solid var(--cyan);
  opacity: 0;
  transform: scale(0.86);
  transition: opacity 80ms linear, transform 80ms linear;
  pointer-events: none;
}

.avatar__ring--on {
  opacity: 1;
  transform: scale(1);
}

.avatar--sm {
  width: 32px;
  height: 32px;
}

.avatar--lg {
  width: 72px;
  height: 72px;
}

/* ───────────────────────────── 启动画面 ───────────────────────────── */

.boot {
  position: fixed;
  inset: 0;
  z-index: var(--z-boot);
  display: grid;
  place-items: center;
  background: var(--paper);
  transition: opacity 420ms var(--ease);
}

.boot::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--ink-4) 0.9px, transparent 1px);
  background-size: 5px 5px;
  opacity: 0.5;
}

.boot--out {
  opacity: 0;
  pointer-events: none;
}

.boot__inner {
  position: relative;
  text-align: center;
  padding: var(--s6);
}

.boot__mark {
  font-size: 48px;
  line-height: 1;
  animation: chainSwing 2.4s var(--ease) infinite;
  display: inline-block;
}

@keyframes chainSwing {
  0%, 100% { transform: rotate(-6deg); }
  50% { transform: rotate(6deg); }
}

.boot__title {
  font-family: var(--font-display);
  font-size: var(--fs-1);
  letter-spacing: 0.1em;
  margin: var(--s3) 0 0;
  text-transform: uppercase;
}

.boot__sub {
  font-family: var(--font-mono);
  font-size: var(--fs-5);
  letter-spacing: 0.5em;
  color: var(--ink-3);
  margin: var(--s1) 0 var(--s5);
}

.boot__bar {
  width: min(320px, 70vw);
  height: 8px;
  border: var(--line) solid var(--ink);
  margin: 0 auto;
  overflow: hidden;
  background: var(--paper-2);
}

.boot__bar > i {
  display: block;
  height: 100%;
  width: 8%;
  background: var(--ink);
  animation: bootFill 1.6s var(--ease) infinite;
}

@keyframes bootFill {
  0% { width: 6%; }
  60% { width: 78%; }
  100% { width: 96%; }
}

.boot__note {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  color: var(--ink-3);
  margin-top: var(--s3);
  text-transform: uppercase;
}

/* ───────────────────────────── 屏幕容器 ───────────────────────────── */

.screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  animation: screenIn 260ms var(--ease);
}

.screen::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--ink-4) 0.9px, transparent 1px);
  background-size: 5px 5px;
  opacity: 0.45;
  pointer-events: none;
}

.screen--clear {
  background: transparent;
  pointer-events: none;
}

.screen--clear::before {
  display: none;
}

@keyframes screenIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.screen__body {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--s5) var(--s6);
}

.screen__footer {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) var(--s6);
  border-top: var(--line) solid var(--ink);
  background: var(--paper);
}

/* 顶栏 */
.topbar {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3) var(--s6);
  border-bottom: var(--line-thick) solid var(--ink);
  background: var(--paper);
}

.topbar__brand {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: var(--tracking);
  text-transform: uppercase;
}

.topbar__mark {
  font-size: 20px;
}

.topbar__spacer {
  flex: 1;
}

/* ───────────────────────────── 主菜单 ───────────────────────────── */

.menu {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(280px, 0.85fr);
  gap: var(--s6);
  align-items: start;
  max-width: 1180px;
  margin: 0 auto;
  width: 100%;
}

.menu__hero {
  padding-top: var(--s4);
}

.menu__title {
  font-family: var(--font-display);
  font-size: clamp(52px, 9vw, 116px);
  line-height: 0.84;
  letter-spacing: 0.02em;
  margin: 0;
  text-transform: uppercase;
  color: var(--ink);
  text-shadow: 4px 4px 0 var(--paper-3);
}

.menu__title span {
  display: block;
}

.menu__sub {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-family: var(--font-mono);
  font-size: var(--fs-5);
  letter-spacing: 0.34em;
  color: var(--ink-3);
  margin: var(--s3) 0 var(--s5);
  text-transform: uppercase;
}

.menu__rule {
  flex: 1;
  height: var(--line);
  background: var(--ink);
}

.menu__actions {
  display: grid;
  gap: var(--s3);
  max-width: 360px;
}

.menu__quote {
  margin-top: var(--s6);
  padding-left: var(--s4);
  border-left: var(--line-thick) solid var(--ink);
  font-size: var(--fs-4);
  color: var(--ink-2);
  max-width: 460px;
  line-height: 1.6;
}

.menu__quote b {
  font-family: var(--font-display);
  letter-spacing: var(--tracking);
}

.menu__side {
  display: grid;
  gap: var(--s3);
}

.spec {
  display: grid;
  grid-template-columns: 92px 1fr;
  gap: var(--s2) var(--s3);
  font-size: var(--fs-5);
}

.spec dt {
  font-family: var(--font-mono);
  color: var(--ink-4);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-size: var(--fs-6);
}

.spec dd {
  margin: 0;
  color: var(--ink-2);
}

/* ───────────────────────────── 大厅 ───────────────────────────── */

.lobby {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--s4);
  align-items: start;
  max-width: 1320px;
  margin: 0 auto;
  width: 100%;
}

.levels {
  display: grid;
  gap: var(--s4);
}

.chapter {
  border: var(--line) solid var(--ink);
  background: var(--paper);
}

.chapter__head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  border-bottom: var(--line) solid var(--ink);
  background: var(--paper-2);
}

.chapter__id {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: var(--tracking);
}

.chapter__name {
  font-family: var(--font-display);
  font-size: 18px;
}

.chapter__fn {
  font-size: var(--fs-6);
  color: var(--ink-3);
  margin-left: auto;
}

.levels__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0;
}

.level {
  position: relative;
  display: grid;
  gap: 2px;
  padding: var(--s2) var(--s3);
  border-right: var(--line) solid var(--ink);
  border-bottom: var(--line) solid var(--ink);
  background: var(--paper);
  text-align: left;
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}

.level:hover {
  background: var(--paper-3);
}

.level--selected {
  background: var(--ink);
  color: var(--paper);
}

.level--locked {
  opacity: 0.35;
  cursor: not-allowed;
}

/* 锁的原因写出来（「先通过 X」）：只灰掉不说理由，玩家会以为是 bug */
.level__lock {
  font-size: var(--fs-6);
  font-family: var(--font-mono);
  letter-spacing: 0.06em;
  opacity: 0.9;
}

.level__id {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: 0.12em;
  opacity: 0.6;
}

.level__name {
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.03em;
}

.level__meta {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: var(--fs-6);
  font-family: var(--font-mono);
  opacity: 0.7;
}

.party {
  display: grid;
  gap: var(--s2);
}

.party-card {
  display: grid;
  grid-template-columns: 44px 1fr auto;
  gap: var(--s2);
  align-items: center;
  padding: var(--s2);
  border: var(--line) solid var(--ink);
  background: var(--paper);
}

.party-card--empty {
  border-style: dashed;
  border-color: var(--ink-4);
  color: var(--ink-4);
  justify-content: center;
  display: flex;
  align-items: center;
  min-height: 60px;
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
}

.party-card__name {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.03em;
  display: flex;
  align-items: center;
  gap: 6px;
}

.party-card__meta {
  font-size: var(--fs-6);
  color: var(--ink-3);
  font-family: var(--font-mono);
}

.roomcode {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

.roomcode__value {
  font-family: var(--font-display);
  font-size: 34px;
  letter-spacing: 0.22em;
  padding: 2px 8px 2px 14px;
  border: var(--line-thick) solid var(--ink);
  background: var(--paper-2);
  line-height: 1.1;
}

.input {
  width: 100%;
  padding: 10px 12px;
  background: var(--paper);
  border: var(--line) solid var(--ink);
  font-family: var(--font-mono);
  font-size: 16px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
}

.input:focus {
  outline: var(--line-thick) solid var(--warn);
  outline-offset: 1px;
}

.input--plain {
  text-transform: none;
  letter-spacing: 0;
  font-family: var(--font-body);
  font-size: 14px;
}

/* ───────────────────────────── 装备 / 角色 ───────────────────────────── */

.loadout {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s2);
}

.slot {
  border: var(--line) solid var(--ink);
  padding: var(--s2);
  background: var(--paper);
  cursor: pointer;
  text-align: left;
  display: grid;
  gap: 2px;
  transition: background var(--fast) var(--ease);
}

.slot:hover {
  background: var(--paper-3);
}

.slot__label {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  color: var(--ink-4);
  letter-spacing: var(--tracking-wide);
}

.slot__name {
  font-family: var(--font-display);
  font-size: 15px;
}

.chars {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  gap: var(--s2);
}

.char {
  border: var(--line) solid var(--ink);
  background: var(--paper);
  padding: var(--s2);
  cursor: pointer;
  display: grid;
  justify-items: center;
  gap: 4px;
  text-align: center;
  transition: transform var(--fast) var(--ease), background var(--fast) var(--ease);
}

.char:hover {
  transform: translateY(-2px);
  background: var(--paper-3);
}

.char--selected {
  background: var(--ink);
  color: var(--paper);
}

.char--selected .char__role {
  color: var(--paper-3);
}

.char__name {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.04em;
}

.char__role {
  font-size: var(--fs-6);
  color: var(--ink-3);
  font-family: var(--font-mono);
}

.form-toggle {
  display: flex;
  border: var(--line) solid var(--ink);
}

.form-toggle button {
  flex: 1;
  padding: 6px 10px;
  background: var(--paper);
  border: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  cursor: pointer;
}

.form-toggle button.on {
  background: var(--ink);
  color: var(--paper);
}

/* ───────────────────────────── HUD ───────────────────────────── */

.hud {
  position: absolute;
  inset: 0;
  pointer-events: none;
  font-family: var(--font-mono);
  color: var(--ink);
  text-shadow: 0 0 6px var(--paper), 0 0 2px var(--paper);
}

.hud__tl {
  position: absolute;
  top: var(--s4);
  left: var(--s4);
  display: grid;
  gap: var(--s2);
  max-width: 340px;
}

.hud__tr {
  position: absolute;
  top: var(--s4);
  right: var(--s4);
  display: grid;
  gap: 6px;
  width: 236px;
}

.hud__bl {
  position: absolute;
  bottom: var(--s4);
  left: var(--s4);
  display: grid;
  gap: 8px;
  width: 240px;
}

.hud__br {
  position: absolute;
  bottom: var(--s4);
  right: var(--s4);
  text-align: right;
  font-size: var(--fs-6);
  letter-spacing: 0.1em;
  color: var(--ink-3);
  display: grid;
  gap: 3px;
}

.hud__tc {
  position: absolute;
  top: var(--s4);
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
}

.hud-objective {
  background: var(--paper);
  border: var(--line) solid var(--ink);
  padding: 6px 10px;
  display: grid;
  gap: 2px;
  box-shadow: var(--shadow-sm);
}

.hud-objective__label {
  font-size: 9px;
  letter-spacing: 0.24em;
  color: var(--ink-4);
  text-transform: uppercase;
}

.hud-objective__text {
  font-family: var(--font-display);
  font-size: 17px;
  letter-spacing: 0.02em;
}

/**
 * 副目标 / 隐藏目标的计数行。
 *
 * 比正文小两档、颜色降一级：它是「还有别的事」的提示，不是第二个目标。
 * 两者同字号会让玩家以为三条都得做完才能过关（其实只有主目标挡通关）。
 */
.hud-objective__sub {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  color: var(--ink-4);
  margin-top: 2px;
}

/**
 * 本关的动词读数（R15-3）。
 *
 * 和「副目标计数」长得像但**不是一回事**：副目标是清单（成没成），
 * 读数是仪表（现在怎么样）。所以这里给它一个动词方块（黑底白字），
 * 视觉上像一枚按钮而不是一行注释 —— 它对应一个**动作**。
 *
 * `--verb-k` 是 0..1 的进度（洞厅完成度 / 完好度 / 余气），
 * 用底部一条 2px 的线表达。没有进度的模式（M1/M2/M5 是绝对量）就不画线。
 */
.hud-verb {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 3px;
  padding-bottom: 2px;
  background-image: linear-gradient(var(--ink), var(--ink));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: calc(var(--verb-k, 0) * 100%) 2px;
}

.hud-verb__key {
  flex: 0 0 auto;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-display);
  font-size: 10px;
  line-height: 1;
  padding: 2px 4px;
}

.hud-verb__text {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  color: var(--ink-3);
  white-space: nowrap;
}

/* 读数越界（偏出基准线 / 文物撞坏 / 气不够）：只换文字色，不整块反白 ——
   它不是错误弹窗，是「这个数现在不好看」。 */
.hud-verb.is-warn .hud-verb__key {
  background: var(--danger, #c0392b);
}

.hud-verb.is-warn .hud-verb__text {
  color: var(--danger, #c0392b);
}

/**
 * 灯灭。
 *
 * 画面会明显变暗，如果 HUD 上没有任何提示，玩家的第一反应是「游戏坏了」。
 * 反白（黑底白字）而不是变红：灯灭是玩家自己按的，不是错误。
 */
.hud-lamp.is-off {
  background: var(--ink);
  color: var(--paper);
}

.hud-timer {
  font-family: var(--font-display);
  font-size: 34px;
  letter-spacing: 0.08em;
  padding: 2px 12px;
  border: var(--line) solid var(--ink);
  background: var(--paper);
  box-shadow: var(--shadow-sm);
}

.hud-timer--low {
  color: var(--warn);
  animation: pulse 0.9s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

.hud-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-6);
  letter-spacing: 0.08em;
}

.hud-row__label {
  width: 42px;
  color: var(--ink-3);
  text-transform: uppercase;
  font-size: 9px;
  letter-spacing: 0.16em;
}

.hud-party {
  display: grid;
  gap: 4px;
}

.hud-party .party-card {
  padding: 4px 6px;
  grid-template-columns: 28px 1fr auto;
  gap: 6px;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(2px);
}

.hud-party .party-card__name {
  font-size: 12px;
}

.hud-chain {
  height: 10px;
  border: 1.5px solid var(--ink);
  background: var(--paper-2);
  position: relative;
  overflow: hidden;
}

.hud-chain > i {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--ink);
  transition: width 90ms linear;
}

.hud-chain--tight > i {
  background: var(--warn);
}

/* 磨损过半：链子还没断，但已经短了一截。用虚线描边而不是换颜色 ——
   颜色在黑白线稿里已经全部被「张力」占用了，再加一档会读不出来。 */
.hud-chain--worn {
  outline: 1px dashed var(--warn);
  outline-offset: 2px;
}

/* 过载预告（张力第三态的入口）：绷满 1.5 秒会踉跄，最后半秒读数开始跳。
   必须是**运动**而不是换个颜色 —— 颜色已经被「紧/绷/断」占完了，
   而「快摔了」这件事本质是一个倒计时。强度由 --warn-k（0..1）驱动：
   越接近踉跄跳得越快，这是玩家唯一能提前看见的提前量。 */
.hud-chain--overload {
  outline: 1.5px solid var(--warn);
  outline-offset: 2px;
  animation: hud-chain-overload calc(0.5s - 0.34s * var(--warn-k, 1)) steps(2, end) infinite;
}

@keyframes hud-chain-overload {
  0% {
    outline-color: var(--warn);
  }
  100% {
    outline-color: transparent;
  }
}

/* 无障碍：减少动态时不做闪烁，改成常亮描边（信息不能丢，只是不闪） */
@media (prefers-reduced-motion: reduce) {
  .hud-chain--overload {
    animation: none;
    outline-width: 2.5px;
  }
}

/* 链长档位（大厅）：这一局的难度旋钮，不是玩家的偏好设置。
   显示米数而不是只显示「紧/松」—— 名字是形容，米数才是承诺，
   所以它跟着关卡和人数实时变（写死一个数会在 5 人局里说谎）。 */
.scale {
  margin-top: 8px;
  padding: 7px 9px;
  border: 1px dashed var(--ink-4);
  background: var(--paper-2);
}

.scale__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}

.scale__label {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.scale__value {
  font-size: var(--fs-6);
}

.scale__opts {
  display: flex;
  gap: 4px;
  margin-top: 6px;
}

.scale__opt {
  flex: 1;
  padding: 3px 0;
  font-family: inherit;
  font-size: var(--fs-6);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink-4);
  cursor: pointer;
}

.scale__opt.on {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* 客人看到的按钮：变灰但仍然可点 —— 点一下会告诉他「这是房主决定的」。
   disable 掉的按钮点不动，也就没法解释为什么。 */
.scale__opt.locked {
  opacity: 0.55;
  cursor: not-allowed;
}

.scale__note {
  margin: 6px 0 0;
  font-size: var(--fs-6);
  line-height: 1.5;
  color: var(--ink-3);
}

.crosshair {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 14px;
  transform: translate(-50%, -50%);
  opacity: 0.55;
  pointer-events: none;
}

.crosshair::before,
.crosshair::after {
  content: "";
  position: absolute;
  background: var(--ink);
}

.crosshair::before {
  left: 50%;
  top: 0;
  width: 1.5px;
  height: 100%;
  transform: translateX(-50%);
}

.crosshair::after {
  top: 50%;
  left: 0;
  height: 1.5px;
  width: 100%;
  transform: translateY(-50%);
}

.crosshair--dig {
  opacity: 1;
  transform: translate(-50%, -50%) rotate(45deg) scale(1.25);
}

/* 提示（toast） */
.toasts {
  position: absolute;
  top: 96px;
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  gap: 6px;
  justify-items: center;
  pointer-events: none;
  z-index: var(--z-toast);
}

/* 灾难期间让位：横幅占住顶部中央，通知带整体下移到横幅下方。
   两者都在顶部居中，硬叠的结果必然是「一个盖住另一个」—— 而这两种消息
   都重要（横幅是状态与倒计时，toast 是事件），谁被盖都是错。
   刻意不做过渡：横幅本身是瞬时出现的，让通知带跟着瞬移更一致，
   也免得「刚加 class 那一帧还在动画中」这种测量歧义。 */
.hud.has-disaster .toasts {
  top: 214px;
}

.toast {
  background: var(--paper);
  border: var(--line) solid var(--ink);
  box-shadow: var(--shadow-sm);
  padding: 6px 14px;
  font-size: 13px;
  letter-spacing: 0.04em;
  animation: toastIn 220ms var(--ease);
  max-width: 70vw;
}

.toast--danger {
  border-color: var(--warn);
  color: var(--warn);
  font-family: var(--font-display);
  font-size: 17px;
  letter-spacing: 0.08em;
}

.toast--loot {
  border-color: var(--gold);
}

/* 预警（不是事故）：链子开始磨、补丁快用完这一类。
   比 danger 轻一档，但必须**看得出来**是提醒而不是闲聊 ——
   否则「拎起来走」会跟「捡到一块矿石」长得一模一样。 */
.toast--warn {
  border-color: var(--warn);
  border-style: dashed;
}

.toast--goal {
  border-color: var(--cyan);
}

@keyframes toastIn {
  from { opacity: 0; transform: translateY(-8px) scale(0.96); }
  to { opacity: 1; transform: none; }
}

/* 地图 / 图纸 */
.map-overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(20, 19, 15, 0.55);
  z-index: var(--z-modal);
  /* HUD 根节点整体 pointer-events:none（点击要能穿过 HUD 打到画布上）。
     图纸是例外：它盖住整个屏幕，这时候点击本来就该落在纸上。 */
  pointer-events: auto;
}

.map-sheet {
  position: relative;
  background: var(--paper);
  border: var(--line-thick) solid var(--ink);
  box-shadow: 12px 12px 0 rgba(20, 19, 15, 0.85);
  padding: var(--s4);
  max-width: min(880px, 92vw);
  max-height: 88vh;
  overflow: auto;
}

/* 两层画布：印刷底图在下、铅笔标注在上。
   边框放在容器上而不是画布上 —— 画布自己有边框的话，
   绝对定位的标注层会按 border-box 对齐，两层的坐标就差一个边框宽度。 */
.map-stack {
  position: relative;
  line-height: 0;
  border: var(--line) solid var(--ink-3);
}

.map-stack canvas {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: auto;
}

.map-stack canvas.map-live {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.map-sheet__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s3);
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  color: var(--ink-3);
  letter-spacing: 0.1em;
}

/* ───────────────────────────── 结算 ───────────────────────────── */

.results {
  max-width: 880px;
  margin: 0 auto;
  width: 100%;
  display: grid;
  gap: var(--s4);
}

.results__head {
  text-align: center;
}

.results__title {
  font-family: var(--font-display);
  font-size: clamp(40px, 7vw, 76px);
  line-height: 0.9;
  letter-spacing: 0.02em;
  margin: 0;
}

.results__title--fail {
  color: var(--warn);
}

.results__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--s3);
}

/**
 * 目标清单（加载屏 / 结算页）。
 *
 * 三列的视觉权重刻意不同：
 *   主目标最重（它挡通关），副目标次之，隐藏目标最轻 —— 未达成时是一行虚线框，
 *   只告诉你「这儿还有个东西」，名字是 `？？？`。
 * 已达成的一律加删除线 + 降饱和：清单的作用是「还剩什么」，
 * 做完的事不该继续抢注意力。
 */
.obj-list {
  display: grid;
  gap: 4px;
  border: var(--line) solid var(--ink);
  background: var(--paper);
  padding: var(--s2) var(--s3);
  box-shadow: var(--shadow-sm);
}

.obj-row {
  display: grid;
  grid-template-columns: 16px 18px 1fr auto;
  align-items: baseline;
  gap: var(--s2);
  font-size: var(--fs-4);
}

.obj-row__mark {
  font-family: var(--font-mono);
  text-align: center;
}

.obj-row__kind {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  color: var(--ink-4);
}

.obj-row--primary .obj-row__text {
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: var(--tracking);
}

.obj-row--secondary .obj-row__text,
.obj-row--hidden .obj-row__text {
  color: var(--ink-3);
}

/* 未达成的隐藏目标：虚线 + 更淡，只留「有东西」这件事 */
.obj-row.is-secret {
  border-bottom: 1px dashed var(--ink-5);
}

.obj-row.is-secret .obj-row__text {
  color: var(--ink-4);
  letter-spacing: var(--tracking-wide);
}

.obj-row.is-done .obj-row__text {
  color: var(--ink-4);
  text-decoration: line-through;
}

.obj-row__prog {
  font-size: var(--fs-6);
  color: var(--ink-4);
}

.stat-box {
  border: var(--line) solid var(--ink);
  padding: var(--s3);
  background: var(--paper);
}

.stat-box__label {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: 0.16em;
  color: var(--ink-4);
  text-transform: uppercase;
}

.stat-box__value {
  font-family: var(--font-display);
  font-size: 30px;
  line-height: 1.1;
}

.social {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s2);
}

.social__cell {
  border: var(--line) solid var(--ink);
  padding: var(--s2) var(--s3);
  background: var(--paper-2);
}

.social__cell b {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.04em;
  display: block;
}

.social__cell span {
  font-size: var(--fs-6);
  color: var(--ink-3);
  font-family: var(--font-mono);
}

/* ───────────────────────────── 图鉴 ───────────────────────────── */

.codex {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: var(--s4);
  max-width: 1180px;
  margin: 0 auto;
  width: 100%;
}

.tabs {
  display: grid;
  gap: 2px;
  align-content: start;
}

.tab {
  text-align: left;
  padding: 10px 12px;
  border: var(--line) solid var(--ink);
  background: var(--paper);
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.tab--on {
  background: var(--ink);
  color: var(--paper);
}

.entries {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--s3);
}

.entry {
  border: var(--line) solid var(--ink);
  padding: var(--s3);
  background: var(--paper);
  display: grid;
  gap: 4px;
  align-content: start;
}

.entry__name {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: 0.03em;
}

.entry__meta {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  color: var(--ink-4);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.entry__quote {
  font-size: 12px;
  color: var(--ink-2);
  border-left: 2px solid var(--ink-3);
  padding-left: 8px;
  margin-top: 4px;
}

/* ───────────────────────────── 遗留物图鉴 ───────────────────────────── */

.relic-bar {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: var(--s2);
}

.relics {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--s2);
}

.relic {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px var(--s2);
  border: var(--line) solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  text-align: left;
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.03em;
  transition: background 120ms ease, color 120ms ease;
}

.relic:hover,
.relic--on {
  background: var(--ink);
  color: var(--paper);
}

/* 未收集：虚线框 —— 不用低透明度，扫一眼就知道「这里还有格子」 */
.relic--locked {
  border-style: dashed;
  border-color: var(--ink-3);
  color: var(--ink-4);
}

.relic__mark {
  font-size: 11px;
  opacity: 0.7;
}

.relic__name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.relic__n {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  opacity: 0.7;
}

.relic-detail {
  border: var(--line) solid var(--ink);
  border-left-width: 6px;
  padding: var(--s3);
  margin-bottom: var(--s4);
  background: var(--paper);
  min-height: 72px;
}

.relic-detail__head {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
}

.relic-detail__empty {
  color: var(--ink-3);
  font-size: 12px;
}

/* ───────────────────────────── 装配台（营地商店）─────────────────────────────
   两栏：左边「挑装备」，右边「这套装备算出来是什么」。
   右边必须实时变 —— 玩家改一件装备就能立刻看到孔径/节奏/射程怎么动，
   这是「数值型能力改手感」这条设计原则唯一能被玩家看见的地方。 */

.board {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 268px;
  gap: var(--s4);
  max-height: 62vh;
  overflow: auto;
}

.board__main {
  display: grid;
  gap: var(--s3);
  align-content: start;
}

.board__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  border-bottom: var(--line) solid var(--ink);
  padding-bottom: 6px;
}

.board__points {
  font-family: var(--font-mono);
  font-size: var(--fs-5);
  letter-spacing: var(--tracking-wide);
}

.board__points b {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: normal;
}

.board__empty {
  border: var(--line) dashed var(--ink-3);
  padding: var(--s3);
  font-size: 13px;
  color: var(--ink-3);
}

.board__group {
  display: grid;
  gap: 6px;
}

.board__group-head {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
}

.board__group-name {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.board__items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: 6px;
}

.item-card {
  border: var(--line) solid var(--ink);
  padding: 8px 9px;
  display: grid;
  gap: 4px;
  align-content: start;
  background: var(--paper);
}

/* 已装备：实心左边条，扫一眼就知道当前带的是什么 */
.item-card.is-on {
  border-left-width: 7px;
  background: var(--paper-2);
}

/* 未拥有：虚线 + 淡一档，但**不降透明度到看不清** —— 玩家要能读到价格和词条 */
.item-card.is-locked {
  border-style: dashed;
  border-color: var(--ink-3);
}

.item-card__head {
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.item-card__name {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.02em;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.item-card__ab {
  font-size: 11px;
  color: var(--ink-2);
  line-height: 1.35;
}

.item-card__sub {
  color: var(--ink-3);
}

.item-card__foot {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 2px;
  flex-wrap: wrap;
}

.item-card .entry__quote {
  font-size: 11px;
  margin-top: 0;
}

.chip--lv {
  border-color: var(--ink-3);
  color: var(--ink-2);
}

/* 右栏 */
.board__preview {
  display: grid;
  gap: 4px;
  align-content: start;
  border-left: var(--line) solid var(--ink-3);
  padding-left: var(--s3);
}

.board__sub {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-3);
  margin: var(--s3) 0 2px;
}

.board__sub:first-child {
  margin-top: 0;
}

.board__abs {
  display: grid;
  gap: 6px;
}

.ability-row {
  border-left: 2px solid var(--ink);
  padding-left: 7px;
}

.ability-row__head {
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.ability-row__name {
  font-family: var(--font-display);
  font-size: 14px;
}

.ability-row__text {
  font-size: 11px;
  color: var(--ink-2);
}

/* 「取最高级」的解释：只在真的撞车时出现 */
.ability-row__why {
  font-size: 10px;
  color: var(--ink-3);
  font-family: var(--font-mono);
  margin-top: 2px;
  line-height: 1.4;
}

.stat-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s2);
  font-size: 11px;
  padding: 2px 0;
  border-bottom: 1px dotted var(--ink-4);
}

.stat-row__k {
  color: var(--ink-3);
}

.stat-row__v {
  color: var(--ink);
}

/* ───────────────────────────── 设置 ───────────────────────────── */
.settings {
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
  display: grid;
  gap: var(--s3);
}

.field {
  display: grid;
  grid-template-columns: 1fr 220px;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--paper-3);
}

.field__label {
  font-size: 14px;
}

.field__hint {
  font-size: var(--fs-6);
  color: var(--ink-4);
  font-family: var(--font-mono);
}

.field input[type="range"] {
  width: 100%;
  accent-color: var(--ink);
}

.field select {
  padding: 6px 8px;
  border: var(--line) solid var(--ink);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: 13px;
}

.seg {
  display: flex;
  border: var(--line) solid var(--ink);
}

.seg button {
  flex: 1;
  padding: 6px;
  background: var(--paper);
  border: 0;
  border-right: 1px solid var(--ink);
  font-family: var(--font-mono);
  font-size: 11px;
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.seg button:last-child {
  border-right: 0;
}

.seg button.on {
  background: var(--ink);
  color: var(--paper);
}

/* ───────────────────────────── 加载 ───────────────────────────── */

.loading {
  display: grid;
  place-items: center;
  background: var(--paper);
}

.loading__inner {
  width: min(560px, 86vw);
  display: grid;
  gap: var(--s3);
}

.loading__chapter {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: 0.3em;
  color: var(--ink-4);
  text-transform: uppercase;
}

.loading__title {
  font-family: var(--font-display);
  font-size: clamp(36px, 6vw, 64px);
  line-height: 0.92;
  margin: 0;
  letter-spacing: 0.02em;
}

.loading__lines {
  display: grid;
  gap: 4px;
  font-size: 14px;
  color: var(--ink-2);
  min-height: 84px;
  border-left: var(--line-thick) solid var(--ink);
  padding-left: var(--s3);
}

.loading__lines p {
  margin: 0;
  opacity: 0;
  animation: lineIn 420ms var(--ease) forwards;
}

@keyframes lineIn {
  to { opacity: 1; }
}

.loading__bar {
  height: 14px;
  border: var(--line) solid var(--ink);
  background: var(--paper-2);
  position: relative;
  overflow: hidden;
}

.loading__bar > i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: var(--ink);
  transition: width 160ms linear;
}

.loading__meta {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  color: var(--ink-3);
  letter-spacing: 0.1em;
}

/* ───────────────────────────── 对话框 ───────────────────────────── */

.modal {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(20, 19, 15, 0.5);
  z-index: var(--z-modal);
  padding: var(--s4);
}

.modal__box {
  background: var(--paper);
  border: var(--line-thick) solid var(--ink);
  box-shadow: var(--shadow-lg);
  padding: var(--s5);
  max-width: 520px;
  width: 100%;
  display: grid;
  gap: var(--s3);
}

/* 宽版：装配台是「列表 + 预览」两栏，520px 挤不下 */
.modal__box--wide {
  max-width: 880px;
}

.modal__title {
  font-family: var(--font-display);
  font-size: 28px;
  margin: 0;
  letter-spacing: 0.03em;
}

.modal__actions {
  display: flex;
  gap: var(--s2);
  justify-content: flex-end;
}

/* 暂停 */
.pause-menu {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(244, 241, 233, 0.92);
  z-index: var(--z-modal);
}

.pause-menu__inner {
  display: grid;
  gap: var(--s3);
  width: min(360px, 86vw);
  text-align: center;
}

/* 聊天 */
.chat {
  position: absolute;
  bottom: 120px;
  left: 50%;
  transform: translateX(-50%);
  width: min(520px, 80vw);
  display: grid;
  gap: 4px;
}

.chat__line {
  font-size: 12px;
  background: var(--paper);
  border: 1px solid var(--ink-3);
  padding: 2px 8px;
  width: fit-content;
  max-width: 100%;
}

.chat__input {
  width: 100%;
  padding: 8px 10px;
  border: var(--line) solid var(--ink);
  background: var(--paper);
  font-family: var(--font-body);
  font-size: 14px;
}

/* ───────────────────────────── 响应式 ───────────────────────────── */

@media (max-width: 900px) {
  .menu,
  .lobby,
  .codex {
    grid-template-columns: 1fr;
  }

  .screen__body {
    padding: var(--s4);
  }

  .topbar {
    padding: var(--s2) var(--s4);
  }

  .hud__tr {
    width: 160px;
  }

  .hud__bl {
    width: 170px;
  }
}

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

/* ==========================================================================
   调试面板（F3）
   刻意做得「像工程图纸上的批注」：纸底、墨框、等宽字，不抢画面但一眼可读。
   z-index 高于一切 UI —— 出问题时它必须能盖在暂停菜单和结算页上面。
   ========================================================================== */
.dbg {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 9998;
  width: min(520px, 46vw);
  max-height: 62vh;
  display: flex;
  flex-direction: column;
  background: rgba(244, 241, 233, 0.94);
  border: var(--line) solid var(--ink);
  box-shadow: var(--shadow);
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink);
  user-select: none;
}

.dbg__head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 3px 6px;
  background: var(--ink);
  color: var(--paper);
  cursor: move;
  letter-spacing: var(--tracking-wide);
  font-size: 10px;
}

.dbg__hint {
  opacity: 0.55;
  letter-spacing: 0;
}

.dbg__spacer {
  flex: 1;
}

.dbg__btn {
  font: inherit;
  font-size: 10px;
  padding: 1px 5px;
  background: transparent;
  color: var(--paper);
  border: 1px solid rgba(244, 241, 233, 0.45);
  cursor: pointer;
}

.dbg__btn:hover {
  background: var(--paper);
  color: var(--ink);
}

.dbg__rows {
  padding: 4px 6px;
  border-bottom: 1px dashed var(--ink-4);
}

.dbg__row {
  display: flex;
  gap: var(--s2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dbg__row.is-bad {
  color: var(--warn);
  font-weight: 700;
}

.dbg__k {
  flex: 0 0 34px;
  color: var(--ink-4);
}

.dbg__v {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dbg__sep {
  padding: 3px 6px 1px;
  color: var(--ink-4);
  font-size: 10px;
}

.dbg__log {
  flex: 1;
  overflow: hidden;
  padding: 0 6px 4px;
}

.dbg__line {
  display: flex;
  gap: 4px;
  white-space: nowrap;
  overflow: hidden;
}

.dbg__line.is-bad {
  background: rgba(232, 80, 60, 0.09);
}

.dbg__t {
  flex: 0 0 58px;
  color: var(--ink-4);
}

.dbg__lv {
  flex: 0 0 40px;
}

.dbg__cat {
  flex: 0 0 58px;
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
}

.dbg__msg {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dbg__foot {
  padding: 2px 6px;
  border-top: 1px solid var(--ink-4);
  color: var(--ink-3);
  font-size: 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 链子断掉时的顶部警示条：不需要解释，玩家看一眼就知道要回去找人 */
.hud-warn {
  position: absolute;
  left: 50%;
  top: 74px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 5px 12px;
  background: var(--warn);
  color: var(--paper);
  border: var(--line) solid var(--ink);
  box-shadow: var(--shadow-sm);
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  animation: warn-pulse 1.1s steps(2) infinite;
}

.hud-warn__arrow {
  display: inline-block;
  font-size: 16px;
  line-height: 1;
  transition: transform 120ms linear;
}

@keyframes warn-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.62;
  }
}

/* ───────────── 灾难横幅（断链警示之下）─────────────
   预告阶段是**玩法信息**：进度条告诉你还剩几秒，名字告诉你是什么，
   最后一行告诉你带对装备能怎么办。三段式而不是一句话，是因为
   「还剩多久」和「该做什么」是两种不同的决策信息。 */
.hud-disaster {
  position: absolute;
  left: 50%;
  top: 118px;
  transform: translateX(-50%);
  width: 268px;
  padding: 7px 12px 8px;
  background: var(--paper);
  border: var(--line) solid var(--ink);
  box-shadow: var(--shadow-sm);
  display: grid;
  gap: 5px;
  /* 横幅和 toast 都在顶部居中，会重叠。横幅是持续状态、toast 是临时消息，
     所以横幅必须永远在上面 —— 否则「灾难生效」那一刻的通知条会把倒计时盖掉。 */
  z-index: calc(var(--z-toast) + 1);
}

.hud-disaster__head {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
}

.hud-disaster__tag {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  color: var(--paper);
  background: var(--warn);
  padding: 1px 5px;
}

.hud-disaster__name {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: var(--tracking-wide);
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hud-disaster__left {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  color: var(--ink-3);
}

.hud-disaster__bar {
  height: 4px;
  background: var(--ink-5);
  border: 1px solid var(--ink-3);
  overflow: hidden;
}

.hud-disaster__bar > i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--ink);
  transition: width 90ms linear;
}

.hud-disaster__counter {
  font-size: 11px;
  color: var(--ink-2);
  border-left: 2px solid var(--warn);
  padding-left: 7px;
}

/* 现场话术：预告是征兆（「上面在掉东西。」），生效是事实（「塌了！」） */
.hud-disaster__line {
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink-2);
}

/* 生效中：反色 + 慢脉冲，一眼看出「已经不是预警了」 */
.hud-disaster.is-active {
  background: var(--ink);
  color: var(--paper);
  animation: warn-pulse 1.4s steps(2) infinite;
}

.hud-disaster.is-active .hud-disaster__name {
  color: var(--paper);
}

.hud-disaster.is-active .hud-disaster__left {
  color: var(--ink-5);
}

.hud-disaster.is-active .hud-disaster__bar {
  border-color: var(--paper);
  background: var(--ink-3);
}

.hud-disaster.is-active .hud-disaster__bar > i {
  background: var(--paper);
}

.hud-disaster.is-active .hud-disaster__counter {
  color: var(--ink-5);
}

.hud-disaster.is-active .hud-disaster__line {
  color: var(--ink-5);
}

/* 氛围类灾难（只有预警，没有物理效果）：虚线框，视觉上「轻一档」 */
.hud-disaster.is-light {
  border-style: dashed;
}

/* ───────────── 主动能力条（听岩 / 修补）─────────────
   放在底部居中：不抢左下（链/气/力）与右下（工具/网络）的位置，
   但一眼能看见按键。设计上沿用「纸底墨框硬投影」的漫画贴纸语言。 */
.hud__bc {
  position: absolute;
  bottom: calc(var(--s4) + var(--s5));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
}

.hud-abil-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.hud-abil {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px 5px 7px;
  background: var(--paper);
  border: var(--line) solid var(--ink);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-5);
  letter-spacing: 0.08em;
  color: var(--ink);
  transition: opacity 0.12s, transform 0.08s;
}

.hud-abil kbd {
  display: inline-block;
  min-width: 16px;
  padding: 1px 4px;
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  font-size: var(--fs-6);
  text-align: center;
  line-height: 1.3;
}

.hud-abil__name {
  font-weight: 700;
}

.hud-abil__n {
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}

/* 长按 / 计时进度条：贴在贴纸底边的一条实心墨线。
   为什么必须有：剪链和打钉都是「按住」型动作，没有进度反馈玩家不知道自己
   按够了没有，会一直按着不放，然后以为按键坏了。 */
.hud-abil__fill {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  width: 0;
  background: var(--ink);
  pointer-events: none;
  transition: width 0.06s linear;
}

/* 靠近绞盘 / 正在被拉：这条能力现在是「环境给你的」，不是装备给的 */
.hud-abil.is-hot {
  background: var(--cyan);
  color: var(--ink);
  border-color: var(--ink);
}
.hud-abil.is-hot kbd {
  background: var(--ink);
  color: var(--cyan);
}
.hud-abil.is-hot .hud-abil__n {
  color: var(--ink);
}

/* 正在长按：轻微下沉，像真的被按住了 */
.hud-abil.is-holding {
  transform: translateY(1px);
}

/* 剪链进行中：反色 + 秒数，和灾难横幅同一套语言（正在发生的事实） */
.hud-abil.is-active {
  background: var(--warn);
  color: var(--paper);
  border-color: var(--warn);
}
.hud-abil.is-active kbd {
  background: var(--paper);
  color: var(--warn);
}
.hud-abil.is-active .hud-abil__fill {
  background: var(--paper);
}

/* 装备没带这个能力 → 灰掉但保留占位，玩家才知道「这里本该有个东西」 */
.hud-abil.is-off {
  opacity: 0.32;
  box-shadow: none;
}

/* 断链时修补变成「现在就该按」的那一个 */
.hud-abil.is-urgent {
  background: var(--warn);
  color: var(--paper);
  border-color: var(--warn);
  animation: abil-urgent 0.7s steps(2) infinite;
}
.hud-abil.is-urgent .hud-abil__n {
  color: var(--paper);
}

@keyframes abil-urgent {
  50% { transform: translate(0, -2px); }
}

/* ── P1-2：快脱 / 爆破 / 噪音 ──
   两条能力贴纸沿用 .hud-abil 的全部形状，只补「什么时候它是亮的」：

   快脱（Q）—— 只有真的有东西挂着你的时候才是亮的（is-hot）。
   平时它就在那儿，压暗着。这是刻意的：玩家得先知道「有 Q 这个键」，
   才会在被绞盘拽着走的那一刻想起它。藏起来就永远学不会。

   爆破（左键）—— 引信跑起来时整条变红并开始报秒。这一秒玩家只想知道
   「还有多久」，所以名字直接被倒计时替换掉。 */
.hud-abil--unhook.is-hot {
  background: var(--warn);
  color: var(--paper);
}
.hud-abil--unhook.is-hot kbd {
  background: var(--paper);
  color: var(--warn);
}

.hud-abil--charge .hud-abil__name {
  font-variant-numeric: tabular-nums;
}

/* 钉子打满了：告诉玩家「再多就得先拔一根」，而不是让他按下去没反应 */
.hud-abil--pin.is-full .hud-abil__n {
  color: var(--warn);
}

/* 噪音条：条本身是墨色，到「震」的时候整条转成警示色。
   不做渐变 —— 这套画风里所有渐变都会变成灰噪点。 */
.bar--noise > i {
  background: var(--ink-2);
}

.hud-row__label--noise {
  color: var(--ink-2);
}

.hud-row__label--noise.is-loud {
  color: var(--warn);
  font-weight: 700;
}

/* 声呐脉冲：一圈方框从贴纸边缘扩散出去，只活 0.5 秒。
   刻意不用圆形 —— 全站圆角为 0，圆环会显得像另一个设计系统的组件。 */
.hud-abil__pulse {
  position: absolute;
  inset: 0;
  border: 2px solid var(--cyan);
  opacity: 0;
  pointer-events: none;
}

.hud-abil.is-pulse .hud-abil__pulse {
  animation: abil-ping 0.5s ease-out 1;
}

@keyframes abil-ping {
  0% { opacity: 0.95; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.9); }
}

/* 听岩读数：一行等宽小字，浮在能力条上方 */
.hud-abil__read {
  max-width: 46ch;
  padding: 4px 10px;
  background: var(--paper);
  border: 1px solid var(--ink-3);
  font-size: var(--fs-5);
  letter-spacing: 0.06em;
  color: var(--ink-2);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.18s, transform 0.18s;
}

.hud-abil__read.is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 720px) {
  .hud__bc {
    bottom: 104px;
  }
  .hud-abil__read {
    max-width: 30ch;
  }
}

/* ───────────── 情境台词（角色在说话）─────────────
   左下、链/气/力 之上。刻意做得像一张贴在屏幕上的便条：
   名字用身份色（和角色身上的色带一致），台词是墨色正文字。
   和 toast 的区别必须一眼看得出来 —— toast 是系统播报，这是人在说话。 */
.hud-bark {
  position: absolute;
  left: var(--s4);
  bottom: calc(var(--s4) + 88px);
  max-width: 30ch;
  padding: 7px 11px;
  background: var(--paper);
  border: var(--line) solid var(--ink);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-4);
  line-height: 1.5;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
  pointer-events: none;
}

.hud-bark.is-in {
  opacity: 1;
  transform: translateY(0);
}

.hud-bark__who {
  display: block;
  font-size: var(--fs-6);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  margin-bottom: 2px;
}

.hud-bark__text {
  color: var(--ink-2);
}

@media (max-width: 720px) {
  .hud-bark {
    max-width: 22ch;
    bottom: calc(var(--s4) + 76px);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   角色进化（P0-5）—— 形态卡 / 进度 / 营地徽标
   ───────────────────────────────────────────────────────────────────
   视觉规则：卡片是「一张盖章的档案」。
   - 左（小白）用纸底 + 灰字，像一张还没填的表格；
   - 右（专家）用墨底反白，像已经盖章生效；
   - 中间一个粗箭头，不画过渡动画 —— 进化是**瞬间且不可逆**的，
     渐变会把它演成「可以来回切」。
   ═══════════════════════════════════════════════════════════════════ */

.avatar--md {
  width: 48px;
  height: 48px;
}

.formcard {
  border: var(--line-thick) solid var(--ink);
  background: var(--paper);
  box-shadow: var(--shadow);
  padding: var(--s4);
  display: grid;
  gap: var(--s3);
}

.formcard__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.formcard__body {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  gap: var(--s3);
}

.formcard__side {
  display: grid;
  justify-items: center;
  gap: var(--s1);
  padding: var(--s3) var(--s2);
  border: var(--line) solid var(--ink);
  text-align: center;
}

.formcard__side--from {
  background: var(--paper-2);
  color: var(--ink-3);
}

.formcard__side--to {
  background: var(--ink);
  color: var(--paper);
}

.formcard__side--to .formcard__desc {
  color: var(--paper-3);
}

.formcard__avatar {
  display: grid;
  place-items: center;
}

.formcard__side--from .formcard__avatar {
  filter: grayscale(1) opacity(0.72);
}

.formcard__name {
  font-family: var(--font-display);
  font-size: var(--fs-2);
  letter-spacing: var(--tracking);
  line-height: 1.05;
}

.formcard__desc {
  font-size: var(--fs-5);
  line-height: 1.5;
  color: var(--ink-3);
}

.formcard__arrow {
  align-self: center;
  font-family: var(--font-display);
  font-size: 30px;
  line-height: 1;
  color: var(--ink);
  padding: 0 var(--s1);
}

.formcard__note {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking);
  color: var(--warn);
  border-left: var(--line-thick) solid var(--warn);
  padding-left: var(--s2);
  line-height: 1.6;
}

.formcard__line {
  font-size: var(--fs-4);
  color: var(--ink-2);
  font-style: italic;
}

/* 进化进度：结算页与营地共用同一套标记 */
.evolve-progress {
  border: var(--line) solid var(--ink);
  background: var(--paper-2);
  padding: var(--s3);
  display: grid;
  gap: var(--s2);
}

.evolve-progress--done {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--paper);
}

.evolve-progress__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s2);
  flex-wrap: wrap;
}

.evolve-progress__who,
.evolve-progress__hint {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking);
  color: var(--ink-3);
}

.evolve-progress__hint {
  color: var(--ink-2);
  font-size: var(--fs-5);
}

.evolve-progress__checks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.evolve-check {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking);
  color: var(--ink-4);
  border: 1px solid var(--ink-4);
  padding: 1px 6px;
  white-space: nowrap;
}

.evolve-check.is-done {
  color: var(--ink);
  border-color: var(--ink);
  background: var(--ink-5);
}

/* 营地的角色卡徽标 */
.char__badge {
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--paper);
  background: var(--ink);
  padding: 0 4px;
}

.char--selected .char__badge {
  color: var(--ink);
  background: var(--paper);
}

/* 形态开关现在是「状态显示」，置灰要看得出来但不能像坏掉 */
.form-toggle button:disabled {
  cursor: default;
  opacity: 0.42;
}

.form-toggle button:disabled.on {
  opacity: 1;
}

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

  .formcard__arrow {
    transform: rotate(90deg);
    justify-self: center;
  }
}

/* ==========================================================================
   进度 = 建房权限（大厅）
   ========================================================================== */

.levels__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s2) var(--s3);
  padding: var(--s2) var(--s3);
  border: var(--line) solid var(--ink);
  background: var(--paper-2);
}

.levels__progress {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.levels__count {
  font-family: var(--font-display);
  font-size: 24px;
  letter-spacing: var(--tracking);
}

.levels__label {
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  opacity: 0.7;
}

/* 规则说明：只给数字不给理由，玩家会以为关卡是随机灰的 */
.levels__rule {
  margin-left: auto;
  font-size: var(--fs-6);
  color: var(--ink-3);
  line-height: 1.5;
  text-align: right;
}

.room-authority {
  display: grid;
  gap: 2px;
  margin-top: var(--s3);
  padding-top: var(--s2);
  border-top: var(--line) solid var(--ink);
}

.room-authority__label {
  font-family: var(--font-mono);
  font-size: var(--fs-6);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  opacity: 0.7;
}

.room-authority__value {
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.04em;
}

.room-authority__note {
  font-size: var(--fs-6);
  color: var(--ink-3);
  line-height: 1.5;
}

/* 「这一关还没解锁」：看得见、点得动（点一下会告诉你差什么），但不像能开 */
.btn--locked {
  background: var(--paper-3);
  color: var(--ink-3);
  border-color: var(--ink-3);
  opacity: 0.9;
}

.chip--cheat {
  border-color: var(--warn);
  color: var(--warn);
}

/* ==========================================================================
   调试控制台（CheatSheet）
   刻意做成「墨底纸字」的反相终端：它不该看起来像游戏界面的一部分
   ========================================================================== */

.cheat {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 46vh;
  min-height: 240px;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  color: var(--paper);
  border-top: 3px solid var(--paper);
  box-shadow: 0 -18px 40px rgba(20, 19, 15, 0.35);
  font-family: var(--font-mono);
  /* 高于 HUD / toast，低于启动层 */
  z-index: calc(var(--z-boot) - 1);
}

.cheat__head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 6px 12px;
  border-bottom: 1px solid rgba(244, 241, 233, 0.25);
}

.cheat__title {
  font-size: var(--fs-6);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.cheat__hint {
  margin-left: auto;
  font-size: 11px;
  opacity: 0.55;
}

.cheat__close {
  width: 22px;
  height: 22px;
  line-height: 1;
  background: none;
  border: 1px solid rgba(244, 241, 233, 0.35);
  color: inherit;
  cursor: pointer;
}

.cheat__close:hover {
  background: rgba(244, 241, 233, 0.12);
}

.cheat__out {
  flex: 1;
  overflow-y: auto;
  padding: 8px 12px;
  font-size: 12.5px;
  line-height: 1.65;
}

.cheat__line {
  white-space: pre-wrap;
  word-break: break-word;
}

/* 回显压暗：一眼能分出「我打的」和「它答的」 */
.cheat__line--echo {
  color: rgba(244, 241, 233, 0.5);
}

.cheat__line--ok {
  color: #a8e6bd;
}

.cheat__line--err {
  color: #ff9d8a;
}

.cheat__line--info {
  color: rgba(244, 241, 233, 0.62);
}

.cheat__line--gap {
  height: 6px;
}

.cheat__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-top: 1px solid rgba(244, 241, 233, 0.25);
}

.cheat__prompt {
  opacity: 0.6;
}

.cheat__field {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  outline: none;
  color: var(--paper);
  font: inherit;
  font-size: 13px;
}

.cheat__field::placeholder {
  color: rgba(244, 241, 233, 0.3);
}

@media (max-width: 720px) {
  .levels__rule {
    margin-left: 0;
    text-align: left;
  }

  .cheat {
    height: 58vh;
  }
}
