/*
 * 小猫搬家 · 主题与分区层。
 *
 * 两条纪律：
 * ① **只覆盖组件库的变量，不覆盖 `.gs-*` 选择器。** 覆盖选择器会把组件的可用性
 *    （44×44 触控下限、安全区、焦点圈定）一起改坏，而那些东西坏了在手机上看不出来。
 *    本作自己的东西一律用 `km-` 前缀，和组件库井水不犯河水。
 * ② **只做手机端。** 全文没有一条不带 `max-height` 的 `@media (min-width: …)`——
 *    那种写法是在给桌面另写一套版式，而桌面跑的是共用库锁出来的一块手机画布
 *    （`shared/game-shell/ui/ui.css` 末尾「手机画布锁定」）。手机横屏是手机的一个朝向，
 *    写成 `(orientation: landscape) and (max-height: 560px)` 这一档；
 *    560 来自 `shared/phone-canvas.json`，全仓同一个数。
 *
 * 分区（`.km-zone`）的 0~1 坐标是**扣掉安全区之后**的比例，逐条来自
 * `game.config.json.zones`，而那份又逐格镜像 S2 冻结的 `art/SCREEN_SPEC.json`。
 * 位置由 JS 写成内联 style，这里只管它们长什么样。
 *
 * 配色逐条取自 `art/ART_PLAN.json` 的 `style_tokens.palette`，并遵守它的
 * `palette_rules`：**金色只给「这一箱还是干净的」，红色只给「坏事」，
 * 青绿只给「待封」**。任何按钮、任何标题都不许用金色——玩家一眼扫到金色
 * 就该知道那是干净的箱子，而不是「这里可以点」。
 */

:root {
  /* ---- 组件库令牌：换成本作的霓虹玩具街机配色 ---- */
  --gs-ink: #F6F8FF;
  --gs-ink-soft: rgba(216, 224, 255, 0.82);
  --gs-ink-dim: rgba(154, 163, 216, 0.66);
  --gs-ink-on-light: #2A1B3D;

  --gs-surface: rgba(11, 16, 48, 0.80);
  --gs-surface-raised: rgba(42, 33, 112, 0.92);
  --gs-surface-sunken: rgba(7, 10, 28, 0.70);
  --gs-hairline: rgba(56, 232, 255, 0.26);

  --gs-accent: #38E8FF;
  --gs-accent-2: #FF5FD2;
  --gs-gold: #FFE066;
  --gs-danger: #FF4D6A;

  --gs-radius-sm: 10px;
  --gs-radius-md: 16px;
  --gs-radius-lg: 24px;

  --gs-glow-accent: 0 0 0 1px rgba(56, 232, 255, 0.5), 0 0 22px rgba(56, 232, 255, 0.3);
  --gs-glow-gold: 0 0 22px rgba(255, 224, 102, 0.5);
  --gs-shadow-card: 0 16px 38px rgba(7, 10, 28, 0.6);
  --gs-shadow-button: 0 5px 0 rgba(7, 10, 28, 0.55), 0 10px 20px rgba(7, 10, 28, 0.45);
  --gs-shadow-press: 0 2px 0 rgba(7, 10, 28, 0.55), 0 5px 10px rgba(7, 10, 28, 0.45);

  /* 主按钮是**瓦楞纸**，不是霓虹：设计稿里 Play / Play again / Start 都是纸板牌子。
     纸板是本作面积最大的暖色材质，按钮用它，玩家一眼知道「这是能按的东西」。 */
  --gs-cta: linear-gradient(180deg, #FFD9A8 0%, #E7B173 52%, #B8814A 100%);

  /* ---- 本作自己的令牌（`art/ART_PLAN.json::style_tokens.palette`）---- */
  --km-night-deep: #0B1030;
  --km-night-mid: #16205A;
  --km-room-violet: #2A2170;
  --km-cyan: #38E8FF;
  --km-magenta: #FF5FD2;
  --km-card-core: #E7B173;
  --km-card-shade: #B8814A;
  --km-card-light: #FFD9A8;
  --km-tape: #FFB03A;
  --km-gold-rim: #FFE066;
  --km-perfect: #FFD34D;
  --km-hollow: #FF4D6A;
  --km-lid: #7CFFDA;
  --km-ink: #070A1C;
  --km-panel-frost: rgba(255, 241, 220, 0.10);
  --km-panel-dark: rgba(11, 16, 48, 0.78);

  --km-pad: clamp(8px, 2.6vw, 15px);
  /* 分区的**纵向**内边距单独一条：分区高度是 SCREEN_SPEC 冻结的比例，
     矮分区（结算页的 reason 只有画布高的 9%）里，横向那点留白照搬到纵向就是溢出。
     跟 `vh` 不跟 `vw`：手机横屏时视口很宽而很矮，按宽度算的留白会把矮分区撑爆。 */
  --km-pad-y: clamp(3px, 0.9vh, 10px);
  --km-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: #05070f;
  color: var(--gs-ink);
  font-family: var(--km-font);
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
}

#app {
  position: fixed;
  inset: 0;
  overflow: hidden;
  /* 背景图是 deferred 的：没到之前这层渐变就是房间。**绝不留白。** */
  background:
    radial-gradient(120% 80% at 50% 0%, var(--km-room-violet) 0%, var(--km-night-mid) 42%, var(--km-night-deep) 100%);
}

/* 安全区扣完之后剩下的那块可视矩形。分区的 0~1 全部相对它。 */
#km-safe {
  position: absolute;
  top: var(--gs-safe-top);
  right: var(--gs-safe-right);
  /* §6.1 最后一行：底部再留 16px，纸箱不贴屏幕最下沿。 */
  bottom: calc(var(--gs-safe-bottom) + 16px);
  left: var(--gs-safe-left);
  overflow: hidden;
}

#km-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* 画布只负责画；所有点击都落在它上面那层的分区节点上（走查靠 DOM 指纹）。 */
  pointer-events: none;
}

#km-layer {
  position: absolute;
  inset: 0;
}

.km-zone {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.km-zone[data-scroll="true"] {
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* ============================ HUD（顶栏 / 横屏是右侧竖条） ============================ */

.km-hud {
  display: flex;
  align-items: center;
  gap: var(--gs-tap-gap);
  padding: var(--km-pad-y) var(--km-pad);
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  /* 顶栏里的东西必须**能缩**。撑破一格的后果不是"有点挤"：
     暂停按钮会被顶到分区外面去，走查当场报 `control-unclickable`
     ——玩家的理解是「按钮点不动」，也就是「这游戏坏了」。 */
  min-width: 0;
  overflow: hidden;
}

.km-hud > * { min-width: 0; }
.km-hud > .gs-icon-btn { flex: none; }

.km-hud--landscape {
  flex-direction: column;
  justify-content: flex-start;
  gap: var(--km-pad-y);
}

.km-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: var(--gs-tap-min);
  box-sizing: border-box;
  overflow: hidden;
  padding: 4px 10px;
  border-radius: var(--gs-radius-md);
  background: var(--km-panel-dark);
  border: 1px solid var(--gs-hairline);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: var(--gs-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.km-chip__icon {
  width: 20px;
  height: 20px;
  flex: none;
  filter: var(--gs-icon-tint-ink);
}

.km-chip__value {
  font-size: var(--gs-text-lg);
  font-weight: 800;
  line-height: 1.05;
}

.km-chip__label {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gs-ink-dim);
}

.km-chip--score { flex: 1 1 auto; min-width: 0; }
.km-chip--score .km-chip__value { color: var(--gs-ink); }

/* 连封数递增时放大一次并换成金色（§10.5 `streak`）。
   金色在这里是**允许的第三处**吗？不是——`palette_rules` 只给金色三个位置。
   所以连封用青色，不用金色：金色一出现就必须意味着「那一箱还是干净的」。 */
.km-chip--chain .km-chip__value { color: var(--km-cyan); }
.km-chip--chain[data-bump="true"] .km-chip__value { animation: km-bump 220ms var(--gs-ease-out, ease-out); }

@keyframes km-bump {
  0% { transform: scale(1); }
  40% { transform: scale(1.35); }
  100% { transform: scale(1); }
}

/* 胶带：**画 3 枚，消耗一枚就把那一枚换成碎裂态**，不是把图标数量减少
   （数量变少玩家会忘记总共有几枚，§10.1 第 4 屏）。 */
.km-tape {
  display: flex;
  align-items: center;
  gap: 4px;
}

.km-tape__roll {
  width: 26px;
  height: 26px;
  flex: none;
  object-fit: contain;
}

.km-tape__roll[data-spent="true"] { opacity: 0.85; filter: saturate(0.35) brightness(0.8); }

.km-pause-btn { flex: none; }

/* ============================ 传送带分区（画布画的，这里只留占位与提示） ============================ */

.km-belt {
  width: 100%;
  height: 100%;
  position: relative;
  pointer-events: none;
}

/* ============================ 游玩分区：按压区 + 两个纸箱 ============================ */

/* 按压区 = 整个 `play` 分区（§6.1：下半屏任意位置左滑 / 右滑都算）。
   `touch-action: none` 是必须的：不写它，浏览器会把横向滑动当成页面滚动手势
   吞掉 pointermove，玩家会觉得「我明明划了」。 */
.km-press {
  position: relative;
  width: 100%;
  height: 100%;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.km-box {
  position: absolute;
  box-sizing: border-box;
  border: 0;
  padding: 0;
  background: transparent;
  /* 箱框、格子、家具全部画在画布上；这一层只提供**可点区与读屏名称**。
     所以它是透明的，但绝不是假控件——点它真的会改这一拍的方向。 */
  cursor: pointer;
  touch-action: none;
  border-radius: var(--gs-radius-md);
}

.km-box:focus-visible {
  outline: 3px solid var(--gs-accent);
  outline-offset: 2px;
}

/* 箱体金边：**亮 / 灭两态，不是一条会走到 0 的条**（§6.5 第 3b 项）。
   它没有 `data-indicator`、没有 `data-value`——被做成条就会被误读成「灭了我要输了」，
   而它只影响分，不影响命。 */
.km-rim {
  position: absolute;
  pointer-events: none;
  border-radius: var(--gs-radius-md);
  /* `border-box` 不是顺手加的：金边是 3px 的**实边框**，而它的宽高是分区的百分比。
     不算进盒子里的话，两条边把它撑高 6px，`play` 分区当场溢出 6px——
     走查的 `zone-overflow` 逐档报的就是这一条。 */
  box-sizing: border-box;
  border: 3px solid transparent;
  transition: box-shadow 160ms ease-out, border-color 160ms ease-out;
}

.km-rim[data-state="lit"] {
  border-color: var(--km-gold-rim);
  box-shadow: var(--gs-glow-gold), inset 0 0 14px rgba(255, 224, 102, 0.35);
}

.km-rim[data-state="dim"] {
  border-color: rgba(154, 163, 216, 0.28);
  box-shadow: none;
}

/* 填充条：**往上涨**的条，视觉上必须与那三枚会碎裂的胶带图标明显区分，
   否则会被读成「涨满就输」（§6.5 第 3 项）。所以它是暖色 + 上涨 + 3/4 处一道刻度线。
   48 上不挂任何奖励，顶端**不做**任何「再冲一点」的暗示。 */
.km-fill {
  position: absolute;
  box-sizing: border-box;
  height: 26px;
  border-radius: var(--gs-radius-pill);
  background: linear-gradient(180deg, rgba(7, 10, 28, 0.92), rgba(7, 10, 28, 0.78));
  border: 1px solid var(--gs-hairline);
  overflow: hidden;
  pointer-events: none;
}

.km-fill__bar {
  position: absolute;
  inset: 2px auto 2px 2px;
  border-radius: var(--gs-radius-pill);
  background: linear-gradient(180deg, var(--km-card-light), var(--km-card-core) 60%, var(--km-card-shade));
  transition: width 180ms ease-out;
}

/* 3/4 处的刻度线 = 待封阈值 36/48。位置由 JS 从 `logic.constants()` 算出来写成
   `--km-fill-mark`，**不写死 75%**：阈值改了这条线要跟着走。 */
.km-fill__mark {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--km-fill-mark, 75%);
  width: 2px;
  background: var(--km-lid);
  opacity: 0.9;
}

.km-fill__text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--gs-text-sm);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--gs-ink);
  text-shadow: 0 1px 2px rgba(7, 10, 28, 0.9);
}

.km-fill[data-ready="true"] .km-fill__bar {
  background: linear-gradient(180deg, #BFFFE9, var(--km-lid) 60%, #34C9A4);
}

/* ============================ 提示分区（小猫坐在这里，画布画） ============================ */

.km-hint {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ============================ 教学横条（**内嵌，不是模态**） ============================ */

/* GAME_PLAN §10.1 第 3 屏：「画在 play 屏之上的非模态提示层，不拦截输入」。
   它没有遮罩、不圈焦点、不进弹窗栈，而且**钉在 `hint` 分区里**——
   结构上就压不到棋盘。上一版把三步教学做成屏幕底部一条固定提示，
   正好压住玩家要操作的那一行格子。 */
.km-coach {
  position: absolute;
  left: var(--km-pad);
  right: var(--km-pad);
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-radius: var(--gs-radius-md);
  background: var(--km-panel-dark);
  border: 1px solid var(--gs-hairline);
  pointer-events: none;
  /* 四件事一起防溢出：把边框算进盒子、**把宽度交回给 left/right**、
     允许里面的东西缩、真缩不动就裁在自己身上，
     **绝不把 `hint` / `belt` 分区撑破**——被撑破的那几像素在手机上就是被切掉的一条。
     `width: auto` 那一行是必须的：共用库的 `.gs-strip` 自带 `width: 100%`，
     配上 `left: pad` 之后盒子会正好右溢出一个 `pad`（实测 375 上 10px、430 上 11px、
     锁定画布上 15px——每一档都恰好等于 `--km-pad`）。 */
  box-sizing: border-box;
  width: auto;
  /* 高度同理：`.gs-strip` 自带 `height: 100%`，在横屏那一档它会把整条 `belt` 分区
     填满再被自己的边框顶出 2px。教学条本来就该是**一条**，不是一整块，所以让它抱内容。 */
  height: auto;
  max-width: calc(100% - 2 * var(--km-pad));
  max-height: 100%;
  min-width: 0;
  margin: 0;
  overflow: hidden;
}

.km-coach > * { min-width: 0; }

.km-coach__dots {
  display: flex;
  gap: 5px;
  flex: none;
}

.km-coach__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(154, 163, 216, 0.4);
}

.km-coach__dot[data-done="true"] { background: rgba(56, 232, 255, 0.55); }
.km-coach__dot[data-active="true"] { background: var(--km-cyan); box-shadow: 0 0 8px var(--km-cyan); }

.km-coach__figure {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  overflow: hidden;
}

/* 教学条里有两类图，**必须分开写**（2026-09-01 走查）：
   `.km-coach__pic` 是真实美术图（床垫、纸箱、小猫），**一条 filter 都不许上**——
   `--gs-icon-tint-*` 那条链是给线条图标设计的，落到真图上会把整张图压成白剪影；
   `.km-coach__glyph` 是线条图标（`icon-*.svg` 全是 `stroke="currentColor"`），
   **必须上色**，不然在深色底上就是一个黑箭头。
   两条各写各的尺寸也是有意的：真图小于 30px 就认不出是什么东西了。 */
/* 选择器写成两个类是**必须的**，不是啰嗦：共用库那条 `.gs-strip__figure img`
   （`height: min(100%, 46px); width: auto`）是"一个类 + 一个标签"，
   优先级比单写 `.km-coach__pic` 高——单写的话 flex 生效、宽高被它盖掉，
   图会按 `img` 的固有尺寸铺开（实测 96px 的箭头把整条撑到 100px 高，
   再被 `overflow: hidden` 从底下裁掉两行字）。 */
.km-coach__figure .km-coach__pic {
  flex: 0 1 auto;
  width: 34px;
  height: 34px;
  max-height: 100%;
  min-width: 0;
  object-fit: contain;
}

.km-coach__figure .km-coach__glyph {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  max-height: 100%;
  object-fit: contain;
  filter: var(--gs-icon-tint-ink);
  opacity: 0.85;
}

/* 文字位：一行标题 + 最多两行正文。**它比图更该活下来**——
   窄画布上先让图缩、再让图整块退场，句子留到最后（见下面的窄档规则）。 */
.km-coach .gs-strip__copy {
  flex: 1 1 auto;
  min-width: 0;
  gap: 0;
}

.km-coach .gs-strip__title {
  font-size: var(--gs-text-sm);
  font-weight: 800;
  color: var(--gs-ink);
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.km-coach .gs-strip__body {
  font-size: 11px;
  line-height: 1.2;
  color: var(--gs-ink-soft);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* 共用库的 `.gs-strip__figure` 是 `height: 100%`——那是给"整条就是一张图"的
   教学条设计的。这里图只是配角，让它抱内容，否则它会把整条撑到分区那么高，
   再被 `.km-coach` 的 `overflow: hidden` 从底下裁掉几行字。 */
.km-coach .gs-strip__figure {
  height: auto;
  flex: 0 1 auto;
}

.km-coach__figure {
  height: auto;
}

.km-coach__counter {
  font-size: var(--gs-text-sm);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--gs-ink-soft);
  flex: none;
}

/* ============================ 加载屏 ============================ */

.km-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  text-align: center;
}

.km-brand__logo {
  max-width: min(78%, 320px);
  max-height: 46%;
  min-height: 0;
  object-fit: contain;
}

.km-title-word {
  margin: 0;
  font-size: var(--gs-text-xl);
  font-weight: 900;
  letter-spacing: 0.01em;
  color: var(--km-card-light);
  text-shadow: 0 2px 0 var(--km-card-shade), 0 0 22px rgba(56, 232, 255, 0.35);
}

.km-tagline {
  margin: 0;
  font-size: var(--gs-text-sm);
  color: var(--gs-ink-soft);
}

.km-progress {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  height: 100%;
  text-align: center;
}

.km-progress__percent {
  font-size: var(--gs-text-lg);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: var(--km-tape);
}

/* 24 格的加载条：**任何一帧都必须有非零矩形**（`art/SCREEN_SPEC.json` screen-01）。
   它不进 §6.5 的登记表——那张表只管游玩中的读数，而这一条是往上涨的、只在加载期存在。 */
.km-cells {
  display: grid;
  grid-template-columns: repeat(24, 1fr);
  gap: 2px;
  height: 18px;
}

.km-cells__cell {
  border-radius: 3px;
  background: rgba(255, 241, 220, 0.12);
}

.km-cells__cell[data-on="true"] {
  background: linear-gradient(180deg, #FFD9A8, var(--km-tape));
  box-shadow: 0 0 8px rgba(255, 176, 58, 0.7);
}

.km-status {
  margin: 0;
  font-size: var(--gs-text-sm);
  color: var(--gs-ink-soft);
}

/* ============================ 标题屏 ============================ */

.km-best {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: center;
  padding: 6px 14px;
  border-radius: var(--gs-radius-pill);
  background: var(--km-panel-dark);
  border: 1px solid var(--gs-hairline);
}

.km-best__value {
  font-size: var(--gs-text-lg);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  /* 这是玩家**自己**的最好成绩，不是名次。旁边不许出现任何跨玩家比较。 */
  color: var(--km-tape);
}

.km-actions {
  display: flex;
  flex-direction: column;
  gap: var(--gs-tap-gap);
  height: 100%;
  justify-content: flex-end;
  padding-bottom: 2px;
  /* `height: 100%` 加内边距，盒模型不写死就是**比分区高 2px**——
     内容盒已经等于分区高，2px 的 `padding-bottom` 全都长在分区外面。
     走查在 342×403 上报的「分区『actions』的内容比它高 2px」就是这 2px，
     和里面装了多少东西无关：把内容全删光它照样多 2px。 */
  box-sizing: border-box;
}

.km-grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gs-tap-gap);
}

.km-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gs-tap-gap);
}

.km-icon-row {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--gs-space-4);
  /* 窄画布上"帮助"那一颗带了字，三颗挤不下就换行，**不要横向溢出**——
     溢出的那几像素在手机上就是被切掉的一条。 */
  flex-wrap: wrap;
}

/* 帮助入口：带字的胶囊，和右边两颗圆图标并排。
   为什么不是三颗一样的圆钮，见 `ui.mjs` 里这一行的注释。 */
.km-help-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: auto;
  min-height: var(--gs-tap-min);
  padding: 0 16px;
  white-space: nowrap;
}

.km-help-btn__icon {
  width: 18px;
  height: 18px;
  flex: none;
}

.km-meta-row {
  display: flex;
  align-items: center;
  gap: var(--gs-tap-gap);
}

.km-meta-card {
  flex: 1 1 0;
  min-width: 0;
  padding: 6px 10px;
  border-radius: var(--gs-radius-md);
  background: var(--km-panel-dark);
  border: 1px solid var(--gs-hairline);
  font-size: var(--gs-text-sm);
}

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

.km-dots__dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid rgba(255, 224, 102, 0.55);
}

.km-dots__dot[data-done="true"] { background: var(--km-tape); }

/* ============================ 结算屏 ============================ */

.km-reason {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 100%;
  text-align: center;
}

.km-reason__line {
  margin: 0;
  font-size: var(--gs-text-lg);
  font-weight: 800;
}

.km-score {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
}

/* 输局的总分用**冷白**，不用金色——回报色只给赢的东西
   （`art/ART_PLAN.json::style_tokens.palette_rules`）。破了最好成绩才换金。 */
.km-score__value {
  font-size: clamp(34px, 11vw, 60px);
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--gs-ink);
  text-shadow: 0 0 24px rgba(56, 232, 255, 0.35);
}

.km-score__value[data-best="true"] {
  color: var(--km-perfect);
  text-shadow: var(--gs-glow-gold);
}

.km-score__compare {
  font-size: var(--gs-text-sm);
  color: var(--gs-ink-soft);
  font-variant-numeric: tabular-nums;
}

.km-lastbox {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.km-lastbox__canvas {
  flex: 1 1 auto;
  min-height: 0;
  display: block;
}

/* 圈红的洞：**一个洞一个节点**，数量必须等于那一箱真实的洞数
   （§4.1「把那一两个洞圈红」；画一个意思意思等于把再玩一局的理由扔掉）。 */
.km-hole {
  position: absolute;
  border: 2px solid var(--km-hollow);
  border-radius: 50%;
  box-shadow: 0 0 10px rgba(255, 77, 106, 0.7);
  background: repeating-linear-gradient(
    45deg,
    rgba(255, 77, 106, 0.22) 0 3px,
    rgba(255, 77, 106, 0) 3px 6px
  );
  pointer-events: none;
}

.km-missed {
  margin: 0;
  font-size: var(--gs-text-sm);
  text-align: center;
  color: var(--gs-ink-soft);
}

.km-missed b { color: var(--km-perfect); font-variant-numeric: tabular-nums; }

.km-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px var(--gs-tap-gap);
  align-content: center;
  height: 100%;
}

.km-stat {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 2px 8px;
  border-radius: var(--gs-radius-sm);
  background: rgba(11, 16, 48, 0.55);
  border: 1px solid var(--gs-hairline);
}

.km-stat__label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--gs-text-xs);
  color: var(--gs-ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.km-stat__value {
  font-size: var(--gs-text-sm);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.km-stat__icon { width: 16px; height: 16px; filter: var(--gs-icon-tint-accent); }

/* ============================ 浮层面板 ============================ */

.km-panel {
  position: absolute;
  border-radius: var(--gs-radius-lg);
  background: linear-gradient(180deg, rgba(42, 33, 112, 0.95), rgba(11, 16, 48, 0.96));
  border: 1px solid rgba(56, 232, 255, 0.45);
  box-shadow: var(--gs-shadow-card), 0 0 34px rgba(56, 232, 255, 0.22);
  pointer-events: none;
}

.km-scrim {
  position: absolute;
  inset: 0;
  background: rgba(7, 10, 28, 0.62);
}

.km-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gs-tap-gap);
  height: 100%;
}

.km-panel-title {
  margin: 0;
  font-size: var(--gs-text-lg);
  font-weight: 900;
}

.km-error-icon {
  width: 112px;
  height: 72px;
  flex: none;
  display: block;
}

.km-panel-body {
  display: flex;
  flex-direction: column;
  gap: var(--gs-tap-gap);
  padding-right: 2px;
}

.km-panel-actions {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--gs-tap-gap);
  height: 100%;
  min-height: 0;
  overflow: hidden;
}

.km-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--gs-tap-min);
  padding: 4px 10px;
  border-radius: var(--gs-radius-md);
  background: rgba(11, 16, 48, 0.5);
  border: 1px solid var(--gs-hairline);
}

.km-row__label { flex: 1 1 auto; min-width: 0; font-size: var(--gs-text-md); }
.km-row__icon { width: 22px; height: 22px; flex: none; filter: var(--gs-icon-tint-accent); }

.km-language-option[aria-pressed="true"] {
  border-color: var(--gs-accent);
  box-shadow: var(--gs-glow-accent);
}

.km-toggle {
  flex: none;
  min-width: 68px;
  min-height: var(--gs-tap-min);
  border-radius: var(--gs-radius-pill);
  border: 1px solid var(--gs-hairline);
  background: rgba(7, 10, 28, 0.7);
  color: var(--gs-ink-dim);
  font-weight: 800;
  font-size: var(--gs-text-sm);
  cursor: pointer;
}

.km-toggle[aria-pressed="true"] {
  background: linear-gradient(180deg, #7CFFDA, #2FC79E);
  color: var(--km-ink);
  border-color: transparent;
}

.km-note {
  margin: 0;
  font-size: var(--gs-text-xs);
  color: var(--gs-ink-dim);
  line-height: 1.5;
}

.km-section { display: flex; flex-direction: column; gap: 2px; }
.km-section__title { margin: 0; font-size: var(--gs-text-md); font-weight: 800; color: var(--gs-accent); }
.km-section__body { margin: 0; font-size: var(--gs-text-sm); line-height: 1.55; color: var(--gs-ink-soft); }

/* 「新手最容易犯的一个错」——本作唯一一处直接教玩家怎么变强的文字，不许被砍。 */
.km-highlight {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--gs-radius-md);
  border: 1px solid rgba(255, 77, 106, 0.55);
  background: rgba(255, 77, 106, 0.10);
  font-size: var(--gs-text-sm);
  line-height: 1.5;
}

.km-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gs-tap-gap);
}

.km-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-height: var(--gs-tap-min);
  padding: 6px 4px;
  border-radius: var(--gs-radius-md);
  border: 1px solid var(--gs-hairline);
  background: rgba(11, 16, 48, 0.55);
  color: var(--gs-ink);
  font: inherit;
  cursor: pointer;
  text-align: center;
}

.km-tile[aria-pressed="true"] { border-color: var(--gs-accent); box-shadow: var(--gs-glow-accent); }

/* 锁定项：**去饱和 + 锁图形 + 斜纹覆层**，不靠单一明度区分；标题照样读得到，
   真实进度照样写出来（19 / 150）。玩家要知道有什么可追。 */
.km-tile[aria-disabled="true"] {
  filter: saturate(0.25);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.05) 0 6px,
    rgba(255, 255, 255, 0) 6px 12px
  );
}

.km-tile__art { width: 100%; aspect-ratio: 1 / 1; object-fit: contain; }
.km-tile__name { font-size: var(--gs-text-xs); font-weight: 800; }
.km-tile__note { font-size: 10px; color: var(--gs-ink-dim); font-variant-numeric: tabular-nums; }
.km-tile__lock { position: absolute; top: 4px; right: 4px; width: 18px; height: 18px; filter: var(--gs-icon-tint-ink); }
.km-tile { position: relative; }

.km-share-card {
  width: 100%;
  height: auto;
  border-radius: var(--gs-radius-md);
  border: 1px solid var(--gs-hairline);
  display: block;
}

/* ============================ 手机横屏：分区重排，不是把竖屏拉宽 ============================ */

/*
 * 唯一那一档：`(orientation: landscape) and (max-height: 560px)`。
 * 560 来自 `shared/phone-canvas.json` 的 `shortViewportMaxHeightPx`——那个高度以下
 * 视为「手机横过来」，共用库不锁画布，走满屏横屏分区。
 * **不带 `max-height` 的 `@media (min-width: …)` 全文一条都没有**：那种写法是在给桌面
 * 另写一套版式，而桌面跑的是一块 540 宽的手机画布（`check_screens.py::check_mobile_only` 会拦）。
 */
@media (orientation: landscape) and (max-height: 560px) {
  .km-hud { flex-direction: column; align-items: stretch; justify-content: flex-start; }
  .km-chip { min-height: 34px; padding: 2px 8px; }
  .km-chip__value { font-size: var(--gs-text-md); }
  .km-tape { justify-content: center; }
  .km-actions { justify-content: center; }
  .km-stats { grid-template-columns: minmax(0, 1fr); }
  /* 横屏的 `panel-actions` 只有画布高的 22%，三个竖排按钮放不下——改成**一行**。
     `nowrap` 是关键：折行就是 9px 的溢出，而溢出的那一条恰好是主按钮的下沿。 */
  .km-panel-actions { flex-direction: row; flex-wrap: nowrap; align-items: center; }
  .km-panel-actions > * { flex: 1 1 0; min-width: 0; }
  .km-tiles { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .km-coach { bottom: auto; top: 2px; }
}

/* ============================ 减少动态 ============================ */

/*
 * 系统开关是**默认值**，玩家在暂停面板里的开关可以覆盖它（§10.5）。
 * 玩家那一侧由 JS 在 `#app` 上挂 `data-reduced-motion="true"`，两条规则等价。
 * 两种情况都**只去掉动，不改任何判定**——C18 会验：开关前后同种子同输入结局逐拍相同。
 */
@media (prefers-reduced-motion: reduce) {
  .km-fill__bar { transition: none; }
  .km-chip--chain[data-bump="true"] .km-chip__value { animation: none; }
  .km-rim { transition: none; }
}

#app[data-reduced-motion="true"] .km-fill__bar,
#app[data-reduced-motion="true"] .km-rim { transition: none; }

#app[data-reduced-motion="true"] .km-chip--chain[data-bump="true"] .km-chip__value { animation: none; }

/* 高对比纹理：新产生的空洞除红点外还带**斜纹填充**，不靠单一色相区分（§10.5 无障碍）。 */
#app[data-high-contrast="true"] .km-fill__bar {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(7, 10, 28, 0.35) 0 4px,
    rgba(7, 10, 28, 0) 4px 8px
  );
}

/* ============================ 矮画布档（放在最后是有意的） ============================

   这一档必须写在**所有基础规则之后**：媒体查询不加特异性，
   同名同权重的规则谁在后面谁赢。第一版把它写在 `.km-coach` 附近（文件中段），
   于是后面那条 `.km-brand { flex-direction: column }` 又把它盖了回去——
   媒体查询里的 `row` 一行都没生效，小猫照旧被压成 9px 的一条边。
   **写在中间的媒体查询是一颗定时炸弹**：今天生效，明天有人在下面补一条同名规则就失效，
   而且不报任何错。 */

/* 站点上玩家真正拿到的那块画布很矮（342×403，比 0.85），教学条能用的高度
   只有四十来像素——图 + 标题 + 两行正文一定装不下，硬装的结果是
   `overflow: hidden` 把句子从底下裁掉，**字比图先死**，那正好是反的。
   所以矮档按重要性倒着退场：先退图，再退标题，**句子留到最后**。
   句子是这一条存在的理由（2026-09-01：它原来只活在 aria-label 里）。 */
@media (max-height: 560px) {
  .km-coach__figure { display: none; }
  .km-coach .gs-strip__title { display: none; }
  .km-coach .gs-strip__body { font-size: 11.5px; -webkit-line-clamp: 2; line-clamp: 2; }

  /* ---- 标题屏：矮画布上整列比自己的分区高一倍 ----
     站点在 375×667 的手机上交给游戏的框是 342×403（`shared/site-stage.mjs`）。
     `actions` 分区只有 0.34 × 403 = 137px，而里面是主按钮 + 四颗次按钮 +
     两张信息卡 + 一排图标，加起来将近 300px。`.km-actions` 是
     `justify-content: flex-end`，装不下就**整列往上溢**——正好压在 `showcase`
     那两只纸箱上。纸箱是画在画布上的，压不动它，于是屏幕上就是
     "按钮后面戳出来两个箱子"。同一档里 `.km-brand` 的
     `overflow: hidden` 又把小猫主视觉从上下两头裁掉，只剩箱子顶一条边。

     两件事一起收：这一档把每一行都收窄一点（**触控靶 44px 一分不让**），
     同时 `render.mjs` 在这一档不画 `showcase`——它是装饰，
     而被它挡住的是玩家唯一的入口。装饰让路，不是入口让路。 */
  /* 品牌块在这一档**改成横排**。竖排要 30(标志) + 24(字标) + 30(最好成绩) + 间距
     ≈ 90px，而 `brand` 分区只有 0.22 × 403 = 88.7px——差的那一点点全被
     `.km-brand` 的 `overflow: hidden` 从上下两头咬掉：小猫只剩箱子顶一条边，
     「BEST 0」那颗药丸半个身子藏到主按钮后面。
     矮画布缺的是**高**，不缺宽（342px 摆得下三样）。所以换个方向排，
     而不是把三样里的哪一样删掉——它们各有各的活：标志是品牌，
     字标是这游戏叫什么，最好成绩是玩家上次留下的东西。 */
  .km-brand {
    flex-direction: row;
    justify-content: center;
    align-items: center;
    gap: 10px;
  }
  .km-brand__logo { max-height: 88%; max-width: 26%; }
  .km-title-word { font-size: var(--gs-text-lg); }

  /* 这一列在 342×403 上要装 292px 的东西进 255px 的格子。下面每一条都只做一件事：
     **把行与行之间的空、以及文字换行带来的第二行**收回来，
     `min-height: 44px` 的触控靶一分不动——那是这一屏能不能点准的底线。
     相邻可点元素之间仍保留平台要求的 8px；额外高度从非交互的信息卡内边距和
     进度条拿回。实测整列不超过 actions 内容盒，且每颗按钮仍是 44px。 */
  .km-actions { gap: var(--gs-tap-gap); }
  .km-actions .gs-btn { min-height: var(--gs-tap-min); }
  /* 次按钮在这一档缩一号字：不缩的话「Today's Move」「Achievements」会折成两行，
     一颗按钮就从 44px 长到 64px，四颗里有两颗折就是 40px 的白吃。 */
  .km-grid-2 { gap: var(--gs-tap-gap); }
  .km-grid-2 .gs-btn { font-size: var(--gs-text-sm); padding-inline: 8px; }

  .km-meta-row { gap: 6px; }
  .km-meta-card { padding: 3px 9px; font-size: 12px; line-height: 1.25; }
  /* 两张信息卡里的进度条在这一档再收 8px。它们是**读数，不是触控靶**——
     进度条与卡片内边距合计让出 6px，正好抵消两组按钮间距从 6px 恢复到 8px
     增加的高度；触控靶与相邻触控靶的 44px / 8px 两条下限都不让。 */
  .km-meta-card .km-fill { height: 18px; }
  .km-icon-row { gap: var(--gs-space-3); }
}
