/*
 * emoji-charades 的皮肤与本作专有元素。
 *
 * 两条纪律：
 *   ① 组件库的 `.gs-*` 选择器**一个都不覆盖**——那会把 44×44 下限、安全区、
 *      焦点态这些可用性一起改坏。换皮只改 `--gs-*` 变量。
 *   ② 本作自己的元素一律 `.ec-` 前缀，和组件库不共享任何类名。
 *
 * **不写任何桌面版式。** 视口比手机宽时由 `shared/game-shell/ui/ui.css` 末尾的
 * 「手机画布锁定」把 `#app` 收成一块 540×960 的手机画布居中摆着、两侧留黑边。
 * 所以这份文件里没有一条不带 `max-height` 的 `@media (min-width: …)`；
 * 唯一的分档是手机横屏那一档 `(orientation: landscape) and (max-height: 560px)`，
 * 560 这个数来自 `shared/phone-canvas.json` 的 `shortViewportMaxHeightPx`（唯一真值）。
 *
 * **分区几何不在这里。** 六个分区的矩形是 S2 冻结的（`art/SCREEN_SPEC.json` 的 `zones`，
 * 经 `game.config.json` 机械同步），由 `render.mjs` 按当前朝向写成百分比的 inline style。
 * 写进 CSS 就会出现第二份几何，而第二份一定会和第一份漂开。
 */

:root {
  /* ── 调色板：art/ART_PLAN.json 的 style_tokens.palette ── */
  --ec-ink: #0a0714;
  --ec-night: #150c2b;
  --ec-panel: #1c1140;
  --ec-panel-hi: #2e1c60;
  --ec-cyan: #2bf3ff;
  --ec-cyan-deep: #0c829c;
  --ec-magenta: #ff3fa4;
  --ec-magenta-deep: #92155c;
  --ec-amber: #ffc24b;
  --ec-lime: #8bff5a;
  --ec-violet: #a86aff;
  --ec-hot: #fff6ff;
  --ec-dim: #483a6e;
  --ec-ash: #766a96;

  /* ── 皮肤钩子：三套外观只换这两个，一个判定参数都不改（§5.3） ── */
  --ec-skin-tint: var(--ec-violet);
  --ec-skin-warm: var(--ec-magenta);

  /* ── 覆盖组件库的令牌 ── */
  --gs-ink: #f4ecff;
  --gs-ink-soft: rgba(244, 236, 255, 0.74);
  --gs-ink-dim: rgba(244, 236, 255, 0.46);
  --gs-ink-on-light: var(--ec-ink);

  --gs-surface: rgba(28, 17, 64, 0.84);
  --gs-surface-raised: rgba(46, 28, 96, 0.92);
  --gs-surface-sunken: rgba(10, 7, 20, 0.62);
  --gs-hairline: rgba(43, 243, 255, 0.2);

  --gs-accent: var(--ec-cyan);
  --gs-accent-2: var(--ec-magenta);
  --gs-gold: var(--ec-amber);
  --gs-danger: var(--ec-magenta);

  /* 吹制玻璃 + 多层柔光，不是一圈等宽外发光（style_tokens.render_style） */
  --gs-glow-accent: 0 0 0 1px rgba(43, 243, 255, 0.55), 0 0 10px rgba(43, 243, 255, 0.4),
    0 0 30px rgba(43, 243, 255, 0.24);
  --gs-glow-gold: 0 0 10px rgba(255, 194, 75, 0.55), 0 0 34px rgba(255, 194, 75, 0.3);
  --gs-shadow-card: 0 16px 38px rgba(10, 7, 20, 0.62);
  --gs-shadow-button: 0 5px 0 rgba(10, 7, 20, 0.5), 0 12px 22px rgba(10, 7, 20, 0.5);
  --gs-shadow-press: 0 2px 0 rgba(10, 7, 20, 0.5), 0 6px 12px rgba(10, 7, 20, 0.5);

  /* 方形一律 18% 圆角（style_tokens.shape_language） */
  --gs-radius-sm: 8px;
  --gs-radius-md: 16px;
  --gs-radius-lg: 22px;

  --gs-cta: linear-gradient(135deg, var(--ec-cyan), var(--ec-violet) 55%, var(--ec-magenta));
  --gs-bg-focus: 50% 62%;

  /* 主光来自右上（招牌自身），青色补光来自左下的灯带（style_tokens.lighting） */
  --ec-light-main: 118deg;
  --ec-tap-min: 44px;
}

html,
body {
  margin: 0;
  background: var(--ec-ink);
  color: var(--gs-ink);
  overscroll-behavior: none;
}

body {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", sans-serif;
  -webkit-text-size-adjust: 100%;
}

/* ═════════════ 舞台与分区 ═════════════ */

/*
 * 手机画布的根容器。**`position: fixed; inset: 0` 是共用库那套「手机画布锁定」的前提**：
 * 锁定规则只给 `#app` 写 `width` / `height` / `margin`，高度的来源得由游戏自己钉住。
 * 少了这一条，`#app` 就是一个普通的块级元素，而它下面的 `.ec-stage` 写的是 `height: 100%`
 * ——百分比高度在 auto 高度的父级上解析成 0，于是整块画布量出来是 375×0，
 * 分区按百分比算全部塌到 0，按钮被挤到视口上方（实测 y = -63）。
 */
#app {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: var(--ec-ink);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

/*
 * `#stage` 是**方向判定的量具**：`render.mjs` 对它取 `getBoundingClientRect()` 比宽高。
 * 所以它必须撑满安全区之后的可视区，而且宽高都不能是 0——否则量出来的方向恒为横。
 */
.ec-stage {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  padding: var(--gs-safe-top) var(--gs-safe-right) var(--gs-safe-bottom) var(--gs-safe-left);
  box-sizing: border-box;
  isolation: isolate;
}

/* 分区容器。矩形由 render.mjs 按 zones 写成 inline 的 left/top/width/height 百分比。 */
.ec-zone {
  position: absolute;
  display: flex;
  box-sizing: border-box;
  pointer-events: none; /* 分区本身不吃事件，只有里面的按钮吃 */
}

.ec-zone > * {
  pointer-events: auto;
}

/* 分区里那些明确是读数的东西，连子元素一起不吃事件（§2.2） */
.ec-zone[data-readonly='true'] > * {
  pointer-events: none;
}

.ec-zone[data-zone='hud'] {
  align-items: center;
  gap: var(--gs-space-2);
  padding-inline: var(--gs-space-2);
}

.ec-zone[data-zone='guest'] {
  align-items: stretch;
  gap: var(--gs-space-2);
  padding-inline: var(--gs-space-2);
}

.ec-zone[data-zone='readout'] {
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  padding-inline: var(--gs-space-5);
}

.ec-zone[data-zone='bubble'] {
  align-items: center;
  justify-content: center;
  padding-inline: var(--gs-space-4);
}

/*
 * 两块贴着画布最下沿的分区都留出**底部安全距**（§10.1.1 / P42：最下沿可点元素到
 * 视口底 ≥16px，或 `env(safe-area-inset-bottom)` 更大时取它）。
 * 分区矩形是 S2 冻结的、一路顶到 1.0，所以留白只能从分区**内侧**收——
 * 不留的话灯板最后一行与送客按钮就贴在屏幕最下沿，刘海机上直接被横条压住。
 */
.ec-zone[data-zone='board'] {
  align-items: center;
  justify-content: center;
  padding-bottom: max(16px, var(--gs-safe-bottom, 0px));
}

.ec-zone[data-zone='side'] {
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding-top: 2px;
  padding-bottom: max(16px, var(--gs-safe-bottom, 0px));
}

/* 装饰层：只画霓虹粒子、招牌电流、冲击波。不承载任何控件与判定（§10.2）。 */
.ec-fx {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  pointer-events: none;
}

/* 屏幕与浮层必须真的铺满画布。没有这层，绝对定位的 zones 会相对舞台散落，
   暂停页也只是一段普通文档流，视觉上会退化成「通用深色面板」。 */
.ec-screenhost,
.ec-overlayhost,
.ec-screen,
.ec-overlay {
  position: absolute;
  inset: 0;
}

.ec-screenhost { z-index: 2; }
.ec-overlayhost { z-index: 40; pointer-events: none; }

.ec-overlay {
  pointer-events: auto;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  padding: calc(var(--gs-safe-top) + 14px) calc(var(--gs-safe-right) + 14px)
    calc(var(--gs-safe-bottom) + 14px) calc(var(--gs-safe-left) + 14px);
  background: rgba(10, 7, 20, 0.76);
  backdrop-filter: blur(10px) brightness(0.68);
}

.ec-zone[data-spec-zone='head'] {
  align-items: center;
  justify-content: center;
  gap: var(--gs-space-3);
  padding: var(--gs-space-2) var(--gs-space-4);
}

.ec-zone[data-spec-zone='body'],
.ec-zone[data-spec-zone='detail'] {
  flex-direction: column;
  align-items: stretch;
  gap: var(--gs-space-3);
  padding: var(--gs-space-3) var(--gs-space-4);
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
}

.ec-zone[data-spec-zone='body']::-webkit-scrollbar,
.ec-zone[data-spec-zone='detail']::-webkit-scrollbar { display: none; }

.ec-zone[data-spec-zone='hero'],
.ec-zone[data-spec-zone='progress'] {
  align-items: center;
  justify-content: center;
  padding: var(--gs-space-3) var(--gs-space-4);
}

.ec-zone[data-spec-zone='play'] {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gs-tap-gap);
  /* 底部留白至少 16px（§10.1.1 / P42）。刘海机上取 `env(safe-area-inset-bottom)` 那一档更大的值。
     这条 `padding` 简写排在 `[data-zone='board']` 之后、特指度相同，所以留白的真值在这里。 */
  padding: var(--gs-space-2) var(--gs-space-4) max(16px, calc(var(--gs-safe-bottom) + var(--gs-space-2)));
}

/*
 * 灯板那一块 play 区另算一档留白。
 * 竖屏 360×640 上它只有 300×307，而 §10.1.1 冻结的灯板是 `88×3 + 12×2 = 288`：
 * 上面那条留白（8 + 16）一加，288 的灯板就装不进 283 的内容盒，居中之后
 * 最后一行反而更贴近屏幕底（实测底留白 14 < 16）。这里把上边与左右收到最小，
 * 底部那 16px 一分不让——它才是 P42 真正判的那个数。
 */
.ec-screen[data-screen='gameplay'] .ec-zone[data-spec-zone='play'],
.ec-screen[data-screen='coach'] .ec-zone[data-spec-zone='play'] {
  padding: 2px 0 max(16px, var(--gs-safe-bottom, 0px));
}

/* ═════════════ 加载、标题与选择 ═════════════ */

.ec-brand {
  position: relative;
  width: min(92%, 440px);
  height: 100%;
  min-height: 74px;
  display: grid;
  place-items: center;
  isolation: isolate;
}

.ec-brand__mark {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0.28;
  filter: brightness(0.72) saturate(1.25) drop-shadow(0 0 18px rgba(43, 243, 255, 0.45));
}

.ec-brand__title,
.ec-screen__title {
  margin: 0;
  color: var(--ec-hot);
  text-align: center;
  font-weight: 900;
  line-height: 1.02;
  text-wrap: balance;
  text-shadow: 0 0 7px var(--ec-cyan), 0 0 20px var(--ec-magenta), 0 3px 0 var(--ec-ink);
}

.ec-brand__title { font-size: clamp(28px, 8vw, 52px); }
.ec-screen__title { font-size: clamp(22px, 6vw, 34px); }

.ec-boot {
  width: min(86%, 390px);
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  gap: 8px;
  padding: 13px 16px;
  border: 1px solid rgba(168, 106, 255, 0.72);
  border-radius: var(--gs-radius-pill);
  background: linear-gradient(180deg, rgba(46, 28, 96, 0.94), rgba(10, 7, 20, 0.94));
  box-shadow: var(--gs-shadow-card), 0 0 24px rgba(255, 63, 164, 0.28);
}

.ec-boot__bulb {
  display: block;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #171127;
  border: 1px solid var(--ec-dim);
  box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.75);
}

.ec-boot__bulb[data-lit='true'] {
  background: var(--ec-amber);
  border-color: var(--ec-hot);
  box-shadow: 0 0 7px var(--ec-amber), 0 0 16px rgba(255, 194, 75, 0.7);
}

.ec-stats {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gs-tap-gap);
}

.ec-stat {
  min-width: 0;
  min-height: 62px;
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  grid-template-rows: 1fr 1fr;
  align-items: center;
  gap: 0 8px;
  box-sizing: border-box;
  padding: 8px 10px;
  border: 1px solid rgba(43, 243, 255, 0.3);
  border-radius: var(--gs-radius-md);
  background: linear-gradient(155deg, rgba(46, 28, 96, 0.92), rgba(10, 7, 20, 0.88));
  box-shadow: var(--gs-shadow-card), inset 0 1px rgba(255, 255, 255, 0.12);
}

.ec-stat > img { width: 30px; height: 30px; grid-row: 1 / 3; object-fit: contain; }
.ec-stat__value { color: var(--ec-amber); font-size: 22px; line-height: 1; font-variant-numeric: tabular-nums; }
.ec-stat__label { color: var(--gs-ink-soft); font-size: 11px; line-height: 1.15; overflow-wrap: anywhere; }

.ec-navwrap,
.ec-actions {
  width: min(100%, 480px);
  display: flex;
  flex-direction: column;
  gap: var(--gs-tap-gap);
}

/* 标题屏的 body 区现在放两样东西（成绩行 + 通往选择屏的入口），得竖着排、居中。
   只作用在标题屏，不动选择 / 图鉴 / 帮助三屏共用的 body 区。 */
.ec-screen[data-screen='title'] .ec-zone[data-spec-zone='body'] {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gs-space-4);
}

.ec-navwrap--select { flex: 0 0 auto; width: min(92%, 440px); }

/* 标题稿的主行动是一整块霓虹招牌。文案仍是选择入口自己的 sel.mode，
   但形态必须和走查稿的巨型 CTA 同级，不能缩成导航里的一枚小圆钮。 */
.ec-title-cta {
  min-height: 72px;
  border: 2px solid var(--ec-hot);
  border-radius: 28px;
  font-size: clamp(18px, 5vw, 28px);
  font-weight: 900;
  letter-spacing: 0.02em;
  box-shadow: 0 0 0 3px rgba(43, 243, 255, 0.28), 0 0 28px rgba(43, 243, 255, 0.72),
    0 8px 0 rgba(10, 7, 20, 0.7);
}

.ec-actions__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gs-tap-gap); }
.ec-group__title { margin: 0 0 8px; color: var(--ec-amber); font-size: 15px; letter-spacing: 0.02em; }
.ec-lock { width: 16px; height: 16px; margin-inline-start: 6px; }

/* 选择屏是四排玩具卡，不是四排文字胶囊。只给本作附加类换皮，不覆盖组件库选择器。 */
.ec-select-group {
  padding: 10px;
  background: linear-gradient(155deg, rgba(28, 17, 64, 0.96), rgba(10, 7, 20, 0.9));
}

.ec-select-option {
  min-height: 74px;
  display: grid;
  grid-template-rows: 36px auto;
  place-items: center;
  gap: 4px;
  padding: 7px 5px;
  border-radius: 18%;
  font-size: 11px;
  line-height: 1.12;
  overflow-wrap: anywhere;
}

.ec-select-option__art {
  width: 36px;
  height: 36px;
  object-fit: contain;
  filter: drop-shadow(0 0 7px rgba(43, 243, 255, 0.65));
  pointer-events: none;
}

.ec-select-option[aria-pressed='true'] {
  outline: 2px solid var(--ec-cyan);
  outline-offset: -2px;
  box-shadow: inset 0 0 16px rgba(43, 243, 255, 0.26), 0 0 18px rgba(43, 243, 255, 0.5);
}

.ec-select-option[data-locked='true'] {
  filter: grayscale(0.9) brightness(0.58);
  background: repeating-linear-gradient(135deg, rgba(10, 7, 20, 0.9) 0 8px, rgba(46, 28, 96, 0.75) 8px 16px);
}

/* ═════════════ 结算、解锁与分享 ═════════════ */

/*
 * 总分落地那一下的「跟随」（§10.5 结算那一行）：**砸落 + 一圈金光**，不是逐位滚动。
 * 滚动会让这一屏最要紧的那个数在头 0.7 秒里写着读不出意义的中间值；
 * 砸落把强调放在同一个时刻上，而数字从第一帧起就是对的。
 * 动的是本作自己的包装元素，不覆盖组件库的 `.gs-hero*` 选择器。
 */
.ec-hero-wrap[data-slam='true'] {
  animation: ec-hero-slam 420ms var(--gs-ease-out);
}

@keyframes ec-hero-slam {
  0% { transform: scale(1.14); filter: brightness(1.5); }
  55% { transform: scale(0.985); }
  100% { transform: scale(1); filter: brightness(1); }
}

:root[data-reduced-motion='on'] .ec-hero-wrap[data-slam='true'] { animation: none; }

.ec-hero-wrap {
  width: min(92%, 430px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-radius: var(--gs-radius-lg);
  background: rgba(28, 17, 64, 0.9);
  border: 1px solid rgba(255, 194, 75, 0.48);
  box-shadow: var(--gs-shadow-card), var(--gs-glow-gold);
}

.ec-stars { display: flex; gap: 10px; align-items: center; justify-content: center; }
.ec-star-pip { width: 42px; height: 42px; object-fit: contain; }
.ec-star-pip[data-lit='false'] { filter: grayscale(1) brightness(0.55); }

.ec-lines { display: grid; grid-template-columns: 1fr; gap: 5px; }
.ec-line {
  min-height: 38px;
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 4px 12px;
  border-radius: var(--gs-radius-md);
  color: var(--gs-ink-soft);
  background: rgba(28, 17, 64, 0.84);
  border: 1px solid rgba(168, 106, 255, 0.25);
}
.ec-line__icon { width: 24px; height: 24px; object-fit: contain; filter: drop-shadow(0 0 5px rgba(43, 243, 255, 0.5)); }
.ec-line__label { min-width: 0; font-size: 12px; overflow-wrap: anywhere; }
.ec-line__value { color: var(--ec-hot); font-size: 17px; font-variant-numeric: tabular-nums; }
.ec-trackwrap,
.ec-barhost { width: 100%; min-width: 0; }
.ec-trackwrap [data-fresh='true'] { box-shadow: 0 0 0 2px var(--ec-amber), var(--gs-glow-gold); }
.ec-barhost { display: flex; flex-direction: column; gap: 6px; }

.ec-cardpreview {
  width: 100%;
  min-height: 240px;
  display: grid;
  place-items: center;
  border-radius: var(--gs-radius-lg);
  background: rgba(10, 7, 20, 0.6);
  border: 1px solid rgba(43, 243, 255, 0.35);
  box-shadow: var(--gs-shadow-card), var(--gs-glow-accent);
  overflow: hidden;
}
.ec-cardpreview__canvas { display: block; width: 100%; height: auto; }
.ec-share-status { min-height: 24px; text-align: center; color: var(--ec-lime); font-weight: 800; }

/* ═════════════ 图鉴与帮助 ═════════════ */

.ec-gallery {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 6px;
}

.ec-gallery__cell {
  appearance: none;
  position: relative;
  min-width: 44px;
  min-height: 44px;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  padding: 3px;
  border: 1px solid rgba(43, 243, 255, 0.34);
  border-radius: 18%;
  background: linear-gradient(155deg, var(--ec-panel-hi), var(--ec-panel));
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.14), 0 3px 0 rgba(10, 7, 20, 0.56);
  color: var(--gs-ink);
  touch-action: manipulation;
}

.ec-gallery__cell[data-seen='true'] { box-shadow: var(--gs-glow-accent), 0 3px 0 rgba(10, 7, 20, 0.56); }
.ec-gallery__cell[data-selected='true'] { outline: 2px solid var(--ec-amber); outline-offset: 1px; }
.ec-gallery__art { width: 100%; height: 100%; object-fit: contain; border-radius: 14%; }
.ec-gallery__lock { width: 20px; height: 20px; opacity: 0.72; }

.ec-drawer {
  width: 100%;
  min-height: 92px;
  display: grid;
  grid-template-columns: minmax(90px, 0.9fr) minmax(0, 1.6fr);
  grid-template-rows: auto auto;
  align-items: center;
  gap: 3px 12px;
  padding: 9px 12px;
  box-sizing: border-box;
  border-radius: var(--gs-radius-lg);
  border: 1px solid rgba(255, 63, 164, 0.45);
  background: rgba(28, 17, 64, 0.93);
  box-shadow: var(--gs-shadow-card), 0 0 18px rgba(255, 63, 164, 0.24);
}
.ec-drawer__label { color: var(--gs-ink-soft); font-size: 10px; }
.ec-drawer__name { color: var(--ec-cyan); font-size: 18px; overflow-wrap: anywhere; }
.ec-drawer__icons { grid-column: 2; grid-row: 1 / 3; display: flex; justify-content: flex-end; gap: 7px; min-width: 0; }
.ec-drawer__icon { position: relative; width: 42px; height: 52px; display: grid; place-items: center; border-radius: 10px; background: var(--gs-surface-sunken); }
.ec-drawer__img { width: 34px; height: 34px; object-fit: contain; }
.ec-drawer__s { position: absolute; right: 3px; bottom: 1px; color: var(--ec-amber); font-weight: 900; font-size: 11px; }

.ec-longtext,
.ec-tip,
.ec-faq__a { white-space: pre-line; overflow-wrap: anywhere; }
.ec-longtext { margin: 0; color: var(--gs-ink-soft); font-size: 13px; line-height: 1.45; }
.ec-section { margin: 6px 0 0; color: var(--ec-amber); font-size: 20px; text-shadow: var(--gs-glow-gold); }
.ec-tips { margin: 0; padding-inline-start: 24px; display: flex; flex-direction: column; gap: 8px; }
.ec-tip { padding-inline-start: 3px; color: var(--gs-ink-soft); font-size: 13px; line-height: 1.4; }
.ec-faq { display: flex; flex-direction: column; gap: 8px; }
.ec-faq__item { border: 1px solid rgba(43, 243, 255, 0.3); border-radius: var(--gs-radius-md); overflow: hidden; background: rgba(10, 7, 20, 0.54); }
.ec-faq__q { appearance: none; width: 100%; min-height: 44px; padding: 9px 12px; border: 0; color: var(--ec-cyan); background: transparent; font: 700 13px/1.3 inherit; text-align: start; touch-action: manipulation; }
.ec-faq__q::after { content: '+'; float: inline-end; color: var(--ec-amber); }
.ec-faq__q[aria-expanded='true']::after { content: '\2212'; }
.ec-faq__a { margin: 0; padding: 0 12px 12px; color: var(--gs-ink-soft); font-size: 12px; line-height: 1.4; }

/* ═════════════ 传手机 ═════════════ */

.ec-pass-compose {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.ec-pass-compose__card { width: 82px; height: 82px; flex: 0 0 auto; padding: 4px; border-radius: 18%; background: var(--gs-surface-raised); box-shadow: var(--gs-glow-accent); }
.ec-pass-compose__art { width: 100%; height: 100%; object-fit: contain; border-radius: 14%; }
.ec-pass-compose .ec-board { --ec-brick: 76px; --ec-brick-gap: 10px; }

.ec-curtain {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% 38%, rgba(43, 243, 255, 0.26), transparent 23%),
    repeating-linear-gradient(94deg, #250c42 0 8%, #5a1672 9% 15%, #230a3e 17% 25%);
  box-shadow: inset 0 0 56px #09040f, inset 0 -20px 50px rgba(255, 63, 164, 0.22);
}
.ec-curtain::before { content: ''; position: absolute; inset: 12% 21% 22%; border-radius: 50%; border: 4px solid var(--ec-cyan); box-shadow: 0 0 22px var(--ec-cyan), inset 0 0 22px var(--ec-cyan); }
.ec-curtain::after { content: ''; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(255,255,255,.07), transparent 18% 82%, rgba(255,255,255,.07)); }

.ec-passboard { width: min(100%, 330px); display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: auto; }
.ec-passboard__cell { aspect-ratio: 1; display: grid; place-items: center; border-radius: 18%; background: rgba(28, 17, 64, 0.9); border: 1px solid rgba(43, 243, 255, 0.38); box-shadow: inset 0 1px rgba(255,255,255,.12); }
.ec-passboard__cell[data-empty='true'] { background: rgba(10, 7, 20, 0.7); border-color: rgba(72, 58, 110, 0.46); }
.ec-passboard__art { width: 76%; height: 76%; object-fit: contain; }

.ec-cardgrid { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gs-tap-gap); }
.ec-card { appearance: none; min-height: 132px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; padding: 7px; border: 1px solid rgba(168, 106, 255, 0.54); border-radius: var(--gs-radius-lg); background: linear-gradient(155deg, var(--ec-panel-hi), var(--ec-panel)); color: var(--gs-ink); box-shadow: var(--gs-shadow-button); touch-action: manipulation; }
.ec-card:active { transform: translateY(3px); box-shadow: var(--gs-shadow-press); }
.ec-card__art { width: min(82px, 72%); aspect-ratio: 1; object-fit: contain; }
.ec-card__name { font-weight: 800; font-size: 13px; line-height: 1.15; overflow-wrap: anywhere; }

/* ═════════════ 暂停、设置与错误 ═════════════ */

.ec-panel {
  width: min(100%, 430px);
  max-height: 100%;
  box-sizing: border-box;
  padding: 18px;
  border: 1px solid rgba(43, 243, 255, 0.7);
  border-radius: 28px;
  background: linear-gradient(165deg, rgba(46, 28, 96, 0.98), rgba(10, 7, 20, 0.98));
  box-shadow: 0 0 0 2px rgba(255, 63, 164, 0.22), 0 0 34px rgba(43, 243, 255, 0.34), var(--gs-shadow-card);
}
.ec-row { appearance: none; width: 100%; min-height: 52px; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 7px 10px; box-sizing: border-box; color: var(--gs-ink); background: rgba(10, 7, 20, 0.46); border: 1px solid rgba(168, 106, 255, 0.3); border-radius: var(--gs-radius-md); font: 700 14px/1.2 inherit; touch-action: manipulation; }
.ec-row__label { min-width: 0; overflow-wrap: anywhere; }
.ec-row__value { color: var(--ec-cyan); font-weight: 900; }
.ec-row > img { width: 38px; height: 24px; object-fit: contain; }
.ec-hint { margin: 0; padding: 6px 10px; color: var(--ec-amber); font-size: 12px; line-height: 1.35; text-align: center; }

.ec-error__art { width: min(72%, 300px); height: min(70%, 330px); margin: auto; object-fit: contain; filter: grayscale(1) brightness(0.42) drop-shadow(0 0 16px rgba(43, 243, 255, 0.35)); }
.ec-error__text { margin: 0; color: var(--gs-ink-soft); text-align: center; font-size: 16px; line-height: 1.4; }

/* ui.mjs 本身或配置加载失败时也要有完整画面；这一层不依赖 renderer。 */
.ec-stage[data-screen='error'] {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 18px;
  align-items: center;
  padding: max(24px, var(--gs-safe-top)) max(20px, var(--gs-safe-right))
    max(24px, var(--gs-safe-bottom)) max(20px, var(--gs-safe-left));
  background:
    linear-gradient(rgba(10, 7, 20, 0.32), rgba(10, 7, 20, 0.56)),
    url('./assets/bg-street-sky@1x.webp') center / cover no-repeat,
    var(--ec-ink);
}

.ec-boot-error__body {
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
}

.ec-boot-error__play {
  width: min(100%, 430px);
  padding-bottom: max(16px, var(--gs-safe-bottom));
}

/* screen-07 的 hero-number：只在一气呵成揭晓帧占 HUD 中央，其他状态完全不占位。 */
.ec-hud-score { margin-inline-start: auto; color: var(--ec-amber); font-size: 24px; line-height: 1; text-shadow: var(--gs-glow-gold); font-variant-numeric: tabular-nums; }

/* ═════════════ 灯板与九枚砖（主操作面） ═════════════ */

.ec-board {
  display: grid;
  grid-template-columns: repeat(3, var(--ec-brick, 88px));
  grid-template-rows: repeat(3, var(--ec-brick, 88px));
  gap: var(--ec-brick-gap, 12px);
  position: relative;
  z-index: 2;
  /*
   * `touch-action: none`，不是 `manipulation`。
   * `manipulation` 仍然把「手指按下后横向移动」留给浏览器当成可能的滚动手势：
   * 浏览器于是先观望，判定成平移之后**发一次 pointercancel 并且不再派 pointermove**。
   * 而 §6.1 的 press-drift 判的正是「按下之后手指漂出去会怎样」——事件不来，
   * 捕获归属就无从谈起。灯板这一块永远不滚动，把手势全要过来才对。
   */
  touch-action: none;
}

/*
 * 一枚砖。`border-box` + 视觉方块 = 命中方块，**不做隐形扩区**：
 * 扩了就会在 12 px 的缝上误熄一枚，而熄灯不可复原（§6.1 boundary-press）。
 */
.ec-brick {
  appearance: none;
  -webkit-tap-highlight-color: transparent;
  box-sizing: border-box;
  width: var(--ec-brick, 88px);
  height: var(--ec-brick, 88px);
  margin: 0;
  padding: 6px;
  border: 0;
  border-radius: 18%;
  display: grid;
  /* `minmax(0, …)` 不是装饰：`1fr` 展开是 `minmax(auto, 1fr)`，而图标行的 auto 下限
     取的是 `<img>` 的固有高度——于是 88 px 的砖里塞进 93 px 的内容，底下那排点数被削掉 5 px。 */
  grid-template-rows: minmax(0, 1fr) auto;
  place-items: center;
  gap: 2px;
  cursor: pointer;
  /* 同 `.ec-board`：按住再漂的手势必须整条留在页面里（§6.1 press-drift）。 */
  touch-action: none;
  background:
    radial-gradient(120% 90% at 78% 18%, rgba(255, 246, 255, 0.22), transparent 62%),
    linear-gradient(168deg, var(--ec-panel-hi), var(--ec-panel) 62%, #130a30);
  box-shadow:
    inset 0 1px 0 rgba(255, 246, 255, 0.24),
    inset 0 -3px 6px rgba(10, 7, 20, 0.6),
    0 0 0 1px rgba(43, 243, 255, 0.34),
    0 0 12px rgba(43, 243, 255, 0.24),
    0 4px 0 rgba(10, 7, 20, 0.55);
  /*
   * **`transform` 的过渡只挂在「熄灭」那一个状态上**（见下面的 `[data-state='dim']`）。
   * 挂在这里的话，`transform` 是双向补间的：新客人入场、砖从 dim 退回 lit 时，
   * 那一枚砖会用 120ms 从 `scale(0.88)` 长回 1——而它的 `getBoundingClientRect()`
   * 在这 120ms 里一直在变。剧本在「九枚全亮」那一帧量砖的矩形算间隙，
   * 量到的是缩了 12% 的盒子，两枚同排砖的 y 因此差了 5px，
   * 间隙算出来是 −77.44px（= −88 × 0.88）。熄灭要有塌陷动画，**复位不需要**。
   */
  transition: box-shadow var(--gs-fast) var(--gs-ease-out), filter var(--gs-normal) var(--gs-ease-out);
}

.ec-brick__icon {
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: contain;
  filter: drop-shadow(0 0 6px rgba(43, 243, 255, 0.45));
  pointer-events: none;
}

/* 按下瞬间下压 4%，霓虹描边闪一次白（§10.5 `dim` 的「预备」列） */
.ec-brick[data-pressed='true'] {
  transform: scale(0.96);
  box-shadow:
    inset 0 1px 0 rgba(255, 246, 255, 0.4),
    0 0 0 2px var(--ec-hot),
    0 0 20px rgba(255, 246, 255, 0.55),
    0 1px 0 rgba(10, 7, 20, 0.55);
}

/* 熄灭：向内塌陷 12%、转暗、图标去色。**仍占位、仍是 DOM 元素**，灯板永不重排。 */
.ec-brick[data-state='dim'] {
  cursor: default;
  /* 「向内塌陷 12%」只在**进入**熄灭态时补间：这条规则一失效，transform 就直接复位。 */
  transition: transform var(--gs-fast) var(--gs-ease-out),
    box-shadow var(--gs-fast) var(--gs-ease-out), filter var(--gs-normal) var(--gs-ease-out);
  transform: scale(0.88);
  background: linear-gradient(168deg, #1a1236, #0e0822);
  box-shadow: inset 0 2px 8px rgba(10, 7, 20, 0.85), 0 0 0 1px rgba(72, 58, 110, 0.5);
}

.ec-brick[data-state='dim'] .ec-brick__icon {
  filter: grayscale(1) brightness(0.42);
}

.ec-brick[data-state='dim'] .ec-dots {
  opacity: 0.3;
}

/* 电力为 0：亮砖转入拒绝态（还能按，按下去给三重反馈，绝不静默吞掉） */
.ec-brick[data-refuse='true'] {
  box-shadow: inset 0 1px 0 rgba(255, 246, 255, 0.18), 0 0 0 1px rgba(255, 63, 164, 0.5),
    0 4px 0 rgba(10, 7, 20, 0.55);
}

.ec-brick[data-shake='true'] {
  animation: ec-shake-x 220ms var(--gs-ease-out);
}

@keyframes ec-shake-x {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  50% { transform: translateX(6px); }
  80% { transform: translateX(-3px); }
}

.ec-brick:focus-visible {
  outline: 3px solid var(--ec-amber);
  outline-offset: 2px;
}

/* 死局：灯板整体压暗 20%（不是浮层，不打断计时） */
.ec-board[data-dead='true'] .ec-brick {
  filter: brightness(0.8);
}

/* 引导：被指的那一枚保持满亮，其余压到 45% 但**仍可见** */
.ec-board[data-coach='true'] .ec-brick[data-hint='false'] {
  filter: brightness(0.45);
}

/*
 * screen-04 的 `spotlight` 区：挖空高亮只圈住这一拍要点的那枚砖。
 *
 * **`pointer-events` 必须写在这条选择器上，不能指望组件库的默认值。**
 * 上面那条 `.ec-zone > * { pointer-events: auto }` 与 `.gs-spotlight`
 * 特指度相同（都是 0,1,0），而 theme.css 排在 ui.css 之后——同分后来者胜，
 * 组件自带的 `pointer-events: none` 当场被翻掉。走查因此逐档报
 * 「中心点被 div.gs-spotlight 盖住」：教学圈住一枚砖，玩家却点不动它。
 */
.ec-zone > .ec-spotlight {
  position: absolute;
  z-index: 29;
  pointer-events: none;
  border-radius: calc(var(--gs-radius-md) + 4px);
  box-shadow: 0 0 0 3px var(--ec-amber), 0 0 18px rgba(255, 194, 75, 0.6),
    0 0 0 9999px rgba(6, 3, 18, 0.55);
  animation: ec-spot-pulse 1200ms var(--gs-ease-out) infinite;
}

@keyframes ec-spot-pulse {
  0%, 100% { box-shadow: 0 0 0 3px var(--ec-amber), 0 0 18px rgba(255, 194, 75, 0.6), 0 0 0 9999px rgba(6, 3, 18, 0.55); }
  50% { box-shadow: 0 0 0 5px var(--ec-amber), 0 0 28px rgba(255, 194, 75, 0.9), 0 0 0 9999px rgba(6, 3, 18, 0.55); }
}

:root[data-reduced-motion='on'] .ec-zone > .ec-spotlight { animation: none; }

/* ═════════════ 砖上的点数：青点实心圆 = s，品红点空心方 = n ═════════════ */

/*
 * 形状区分，不只颜色（§10.5「不只靠颜色」）。**用 CSS 画而不出图**——
 * 24 枚图标 × 4 档青点 × 4 档品红点 = 384 种组合，出图会直接撑爆首包（§10.3）。
 */
.ec-dots {
  display: flex;
  gap: 5px;
  align-items: center;
  height: 9px;
  pointer-events: none;
}

.ec-dots__group {
  display: flex;
  gap: 2px;
}

.ec-dot {
  width: 7px;
  height: 7px;
  box-sizing: border-box;
}

.ec-dot[data-kind='s'] {
  border-radius: 50%;
  background: var(--ec-cyan);
  box-shadow: 0 0 5px rgba(43, 243, 255, 0.85);
}

.ec-dot[data-kind='n'] {
  border-radius: 1px;
  background: transparent;
  border: 1.5px solid var(--ec-magenta);
  box-shadow: 0 0 5px rgba(255, 63, 164, 0.6);
}

/* ═════════════ 两条读数 + 斜纹带 + 空心星刻线 ═════════════ */

.ec-meters {
  position: relative;
  display: flex;
  flex-direction: column;
  /* 读数分区是画布高的 10%：360×640 上只有 64px，而标签 + 两条读数 + 电池条
     按 4px 的缝排下来是 66px——多出来的 2px 正好把电池条削掉一条边。 */
  gap: 3px;
  width: 100%;
}

.ec-meter {
  position: relative;
  height: 16px;
  border-radius: 8px;
  background: var(--gs-surface-sunken);
  box-shadow: inset 0 1px 3px rgba(10, 7, 20, 0.8);
  overflow: hidden;
}

/* 1 点一格的细刻度（§2.2「两条读数以 1 点为一格画细刻度」） */
.ec-meter::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    90deg,
    rgba(255, 246, 255, 0.16) 0 1px,
    transparent 1px var(--ec-tick, 11px)
  );
  pointer-events: none;
}

.ec-meter__fill {
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform var(--gs-normal) var(--gs-ease-out);
}

.ec-meter[data-role='meter-resonance'] .ec-meter__fill {
  background: linear-gradient(90deg, var(--ec-cyan-deep), var(--ec-cyan));
  box-shadow: 0 0 12px rgba(43, 243, 255, 0.6);
}

.ec-meter[data-role='meter-noise'] .ec-meter__fill {
  background: linear-gradient(90deg, var(--ec-magenta-deep), var(--ec-magenta));
  box-shadow: 0 0 12px rgba(255, 63, 164, 0.55);
}

.ec-meter[data-role='meter-power'] {
  height: 10px;
  border-radius: 5px;
}

.ec-meter[data-role='meter-power'] .ec-meter__fill {
  background: linear-gradient(90deg, #8a5c10, var(--ec-amber));
}

.ec-meter[data-low='true'] .ec-meter__fill {
  animation: ec-pulse 1s steps(2, end) infinite;
}

.ec-meter[data-flash='true'] {
  animation: ec-flash-red 320ms steps(2, end) 2;
}

@keyframes ec-pulse {
  50% { filter: brightness(1.7); }
}

@keyframes ec-flash-red {
  50% { box-shadow: inset 0 0 0 2px var(--ec-magenta), 0 0 14px var(--ec-magenta); }
}

/* 搏动：两条末端在读数变化的同一帧各搏动一次（§10.5 `dim` 的「跟随」列） */
.ec-meter[data-beat='true'] .ec-meter__fill {
  animation: ec-beat 180ms var(--gs-ease-out);
}

@keyframes ec-beat {
  40% { filter: brightness(1.8) saturate(1.3); }
}

/*
 * 斜纹不确定带。**视觉区域大于命中区域，而命中区域是零**——它是读数不是控件（§2.2）。
 * 画法上刻意不像进度条：45° 斜纹 + 两端向内的箭头，只会变窄不会走到 0。
 */
/*
 * 位移与长度都走 `transform`（合成层），不动 `width` / `left`——R7：0.6 秒收拢环对帧率
 * 最敏感，掉 10 帧就是掉掉窗口的 1/6。元素铺满整条轨道，由 `render.mjs` 写
 * `translateX(%) scaleX(r)`；`transform-origin: left` 让 scaleX 从品红端点向右长。
 */
.ec-band {
  position: absolute;
  left: 0;
  width: 100%;
  bottom: 20px; /* 贴着品红条的顶向上生长 */
  height: var(--ec-band-h, 12px);
  border-radius: 3px;
  transform-origin: left center;
  transform: scaleX(0);
  background: repeating-linear-gradient(
    45deg,
    rgba(255, 194, 75, 0.9) 0 4px,
    rgba(10, 7, 20, 0.88) 4px 8px
  );
  background-size: 8px 8px;
  box-shadow: 0 0 10px rgba(255, 194, 75, 0.42);
  transition: height var(--gs-normal) var(--gs-ease-out), transform var(--gs-normal) var(--gs-ease-out);
  pointer-events: none; /* 整体不可点 */
}

/* 两端向内的箭头：告诉玩家它是一段区间，不是一条在填的进度 */
.ec-band::before,
.ec-band::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 0;
  height: 0;
  border-block: 4px solid transparent;
}

.ec-band::before {
  left: 1px;
  transform: translateY(-50%);
  border-left: 5px solid var(--ec-amber);
}

.ec-band::after {
  right: 1px;
  transform: translateY(-50%);
  border-right: 5px solid var(--ec-amber);
}

/* 收敛到真值：整条闪白，压成 2px 实线（不是归零） */
.ec-band[data-settled='true'] {
  height: 2px;
  background: var(--ec-amber);
}

.ec-band[data-flash='true'] {
  animation: ec-band-flash 220ms var(--gs-ease-out);
}

@keyframes ec-band-flash {
  40% { background: var(--ec-hot); box-shadow: 0 0 22px var(--ec-hot); }
}

/* §7.3 P53：同局连续 2 次被抢答后，下一位客人的带子上多一个向下的收敛目标箭头 */
.ec-band__hint {
  position: absolute;
  top: -9px;
  left: 0;
  width: 100%;
  height: 0;
  transform-origin: left center;
  border-inline: 5px solid transparent;
  border-top: 7px solid var(--ec-lime);
  pointer-events: none;
}

/*
 * 空心星刻线：恒在 N + Fmax + 1，随品红端点移动。
 * **不是第四条进度条**——没有填充量、不会归零，只决定 breath 是否成立。
 */
/*
 * 刻线**不能像斜纹带那样整体 `translateX(%)`**：斜纹带同时带 `scaleX`，
 * 位移加缩放之后右沿仍落在轨道内；刻线没有缩放，一条 100% 宽的元素往右推 100%，
 * 右沿就跑到轨道外整整一个轨道宽——读数分区因此被撑宽 198px（走查 `zone-overflow`）。
 * 所以这里让元素自己**不动**，只把两个伪元素按 `--ec-star-x` 沿轨道挪。
 * 百分比相对 `.ec-star` 的宽度（= 整条轨道），算法和原来一样，但没有任何东西越界。
 */
.ec-star {
  position: absolute;
  left: 0;
  bottom: 16px;
  width: 100%;
  height: 14px;
  pointer-events: none;
}

.ec-star::before,
.ec-star::after {
  left: var(--ec-star-x, 0%);
  margin-left: -7px;
  transition: left var(--gs-normal) var(--gs-ease-out);
}

.ec-star::before {
  content: '';
  position: absolute;
  top: 0;
  width: 14px;
  height: 14px;
  clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
  background: var(--ec-hot);
  opacity: 0.95;
}

.ec-star::after {
  content: '';
  position: absolute;
  top: 13px;
  width: 1px;
  height: 9px;
  margin-left: 6.5px;
  background: var(--ec-hot);
}

.ec-readout-label {
  display: flex;
  justify-content: space-between;
  line-height: 1.1;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--gs-ink-soft);
  text-transform: uppercase;
}

/* ═════════════ HUD：名声与连击是两个独立元素，类名不相交（P21） ═════════════ */

.ec-hud-fame {
  display: flex;
  align-items: center;
  gap: 3px;
}

.ec-hud-fame__bulb {
  width: 18px;
  height: 18px;
  object-fit: contain;
}

.ec-hud-fame__bulb[data-lit='false'] {
  filter: grayscale(1) brightness(0.5);
  clip-path: polygon(0 0, 100% 0, 100% 42%, 62% 58%, 100% 74%, 100% 100%, 0 100%, 0 62%, 34% 48%, 0 34%);
}

.ec-hud-streak {
  display: flex;
  align-items: center;
  gap: 4px;
  font-variant-numeric: tabular-nums;
}

.ec-hud-streak__value {
  font-size: 17px;
  font-weight: 800;
  color: var(--ec-amber);
  text-shadow: var(--gs-glow-gold);
}

.ec-hud-guests {
  font-size: 12px;
  color: var(--gs-ink-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ec-hud-surge {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  text-align: center;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.22em;
  color: var(--ec-hot);
  text-shadow: 0 0 12px var(--ec-magenta);
}

/* screen-08 的首次掉名声提示属于 HUD 分区，不能借住 bubble 分区。 */
.ec-hud-hint {
  position: absolute;
  inset-inline: 27%;
  bottom: 2px;
  max-height: calc(100% - 4px);
  overflow: hidden;
  padding: 3px 7px;
  border: 1px solid rgba(255, 63, 164, 0.72);
  border-radius: var(--gs-radius-pill);
  color: var(--gs-ink);
  background: rgba(10, 7, 20, 0.92);
  box-shadow: 0 0 12px rgba(255, 63, 164, 0.4);
  font-size: 9px;
  font-weight: 700;
  line-height: 1.1;
  text-align: center;
  pointer-events: none;
}

/* ═════════════ 客人、耐心环、收拢环、目标卡、队列 ═════════════ */

/* 客人立绘是这一行里**唯一可压缩的一项**：目标卡与队列各有自己的下限，
   横屏 568×320 上三项并排比分区宽 32px，压缩位只能落在这里。 */
.ec-guest {
  position: relative;
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.ec-guest__art {
  height: 100%;
  max-width: 100%;
  object-fit: contain;
  filter: drop-shadow(-6px 6px 10px rgba(10, 7, 20, 0.7));
}

.ec-guest[data-impatient='true'] .ec-guest__art {
  animation: ec-jitter 1s steps(4, end) infinite;
}

@keyframes ec-jitter {
  0%, 84%, 100% { transform: translate(0, 0); }
  88% { transform: translate(-1.5px, 1px); }
  94% { transform: translate(1.5px, -1px); }
}

.ec-rings {
  position: absolute;
  top: -6px;
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  place-items: center;
}

.ec-rings > * {
  grid-area: 1 / 1;
}

/* 收拢环：向内收拢的实心扇形，不是外圈刻度——它归零是「结果揭晓」，不是「我输了」 */
.ec-window-ring {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: conic-gradient(var(--ec-hot) calc(var(--ec-window, 1) * 360deg), transparent 0);
  mask: radial-gradient(circle, transparent 38%, #000 40%);
  opacity: 0.9;
}

.ec-window-ring[data-flash='true'] {
  box-shadow: 0 0 0 3px var(--ec-hot), 0 0 20px var(--ec-hot);
}

/* reducedMotion 下改成一条从右向左走的直线，**时长一帧不变**（仍然 0.6 秒） */
:root[data-reduced-motion='on'] .ec-window-ring {
  border-radius: 2px;
  width: 56px;
  height: 6px;
  background: linear-gradient(270deg, var(--ec-hot) calc(var(--ec-window, 1) * 100%), transparent 0);
  mask: none;
}

.ec-target {
  flex: 0 0 auto;
  align-self: center;
  aspect-ratio: 1;
  /* 目标卡是正方形，它的边长由**分区高度**封顶——不封的话内容按图自己的比例撑开，
     矮一点的分区（横屏 151px）就被顶出去 4px，走查报 `zone-overflow`。 */
  max-height: 100%;
  max-width: 100%;
  border-radius: 18%;
  padding: 4px;
  box-sizing: border-box;
  background: linear-gradient(160deg, var(--ec-panel-hi), var(--ec-panel));
  box-shadow: var(--gs-glow-accent), var(--gs-shadow-card);
}

.ec-target__art {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 14%;
}

.ec-target[data-shake='true'] {
  animation: ec-shake-x 300ms var(--gs-ease-out);
}

/* 队列剪影：后面三位客人的星级，不可点 */
.ec-queue {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
}

.ec-queue__slot {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 14px;
}

.ec-queue__silhouette {
  width: 12px;
  height: 12px;
  border-radius: 50% 50% 40% 40%;
  background: var(--ec-dim);
  box-shadow: inset 0 0 0 1px rgba(43, 243, 255, 0.28);
}

.ec-queue__pip {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ec-amber);
}

/* 概念插画未就绪时帘子保持闭合；ui.mjs 同时冻结逻辑时钟，等待不会消耗耐心。 */
.ec-stage-curtain {
  position: absolute;
  inset: 0;
  z-index: 8;
  border-radius: var(--gs-radius-lg);
  background: repeating-linear-gradient(96deg, #220b3d 0 12%, #5b1772 14% 22%, #1b082f 24% 34%);
  box-shadow: inset 0 0 34px #09040f, 0 0 16px rgba(255, 63, 164, 0.3);
}

/* 想法泡：猜中时浮出目标卡，被抢答时浮出别的概念卡，完全没看懂时是问号 */
.ec-bubble {
  display: flex;
  align-items: center;
  gap: var(--gs-space-2);
  max-width: 100%;
  padding: 6px 10px;
  border-radius: var(--gs-radius-lg);
  background: var(--gs-surface-raised);
  box-shadow: var(--gs-shadow-card), 0 0 0 1px var(--gs-hairline);
}

.ec-bubble__art {
  width: 40px;
  height: 40px;
  object-fit: contain;
  border-radius: 20%;
}

.ec-bubble__glyph {
  font-size: 26px;
  line-height: 1;
}

.ec-bubble__lines {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ec-bubble__line {
  font-size: 12px;
  font-weight: 700;
  color: var(--ec-magenta);
}

.ec-bubble__line[data-tone='good'] { color: var(--ec-cyan); }
.ec-bubble__line[data-tone='breath'] { color: var(--ec-amber); text-shadow: var(--gs-glow-gold); }

/* 死局提示：灯板**正上方**的结果泡位，不压在任何一行砖上（§10.5 / screen-09） */
.ec-hint-strip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--gs-radius-pill);
  font-size: 12px;
  font-weight: 700;
  text-align: center;
  color: var(--ec-ink);
  background: repeating-linear-gradient(45deg, var(--ec-amber) 0 8px, #2a1c04 8px 16px);
  box-shadow: 0 0 16px rgba(255, 194, 75, 0.4);
}

.ec-hint-strip[data-tone='fame'] {
  color: var(--gs-ink);
  background: var(--gs-surface-raised);
  box-shadow: 0 0 0 1px var(--ec-magenta), 0 0 16px rgba(255, 63, 164, 0.35);
}

/*
 * 送客与暂停：§10.1.1 的几何表把这两枚定死在 **48×48**（P42 也按 ≥48 判），
 * 比共用库圆形图标按钮的 `--gs-tap-min`（44）再大一档。改的是本作自己挂上去的
 * `.ec-round-48`，不覆盖 `.gs-icon-btn`——那会把所有游戏的圆钮一起改掉。
 */
.ec-round-48 {
  width: 48px;
  height: 48px;
  min-width: 48px;
  min-height: 48px;
  /* 送客按钮要验「按下后漂出按钮矩形 → 取消这次送客」，所以它也得拿到整串
     pointermove，不能让浏览器把手势当成滚动收走（§6.1 dismiss 的 cancel-on-leave）。 */
  touch-action: none;
}

/* 回到标题：三张子屏底部那一行的第一枚，只有图标、没有文字（见 render.mjs::buildClose）。 */
.ec-back {
  flex: 0 0 auto;
}

.ec-navwrap--row {
  flex-direction: row;
  align-items: center;
  gap: var(--gs-space-3);
}

/* 导航自己写的是 `width: 100%`，作为这一行的 flex 子项它会按剩余空间收缩，
   所以这里不需要（也不许）去覆盖 `.gs-nav`。 */
.ec-navwrap--row > :last-child {
  flex: 1 1 auto;
  min-width: 0;
}

/* 送客按钮：死局时外圈亮一圈提示环——那才是玩家该去的地方 */
.ec-wave-off[data-hint='dead-board'] {
  animation: ec-ring-pulse 900ms var(--gs-ease-out) infinite;
}

@keyframes ec-ring-pulse {
  0%, 100% { box-shadow: 0 0 0 2px var(--ec-amber), 0 0 10px rgba(255, 194, 75, 0.5); }
  50% { box-shadow: 0 0 0 4px var(--ec-amber), 0 0 22px rgba(255, 194, 75, 0.85); }
}

/* 举手窗口 / 结算演出期间：aria-disabled 而不是真 disabled——
   真 disabled 收不到指针事件，那 6 ms 震 + 横摇 4px 就发生不了，那正是静默吞输入（A120） */
.ec-wave-off[aria-disabled='true'] {
  opacity: 0.45;
}

.ec-wave-off[data-shake='true'] {
  animation: ec-shake-small 200ms var(--gs-ease-out);
}

@keyframes ec-shake-small {
  0%, 100% { transform: translateX(0); }
  30% { transform: translateX(-4px); }
  70% { transform: translateX(4px); }
}

/* ═════════════ 手机横屏那一档 ═════════════ */

/*
 * 560 = shared/phone-canvas.json 的 shortViewportMaxHeightPx（唯一真值）。
 * 这一档**只服务手机横过来**，不是桌面版式：分区矩形由 render.mjs 换成 landscape 那一套
 * （方向按 `#stage` 的矩形判，不读视口），这里只跟着缩一档字号与读数高度。
 */
@media (orientation: landscape) and (max-height: 560px) {
  /* 读数分区在 568×320 上只有 64px，而三条标签在 211px 宽里折成两行（20px）——
     两条读数与电池条因此各再收一档，整块 61px 装进 64px。 */
  .ec-meter { height: 12px; }
  .ec-meter[data-role='meter-power'] { height: 8px; }
  .ec-band { bottom: 15px; height: var(--ec-band-h, 9px); }
  .ec-star { bottom: 11px; }
  .ec-zone[data-zone='readout'] { padding-inline: var(--gs-space-2); }
  .ec-zone[data-zone='guest'] { gap: 4px; padding-inline: 4px; }
  .ec-zone[data-zone='hud'] { flex-wrap: wrap; }
  .ec-hud-streak__value { font-size: 15px; }
  .ec-readout-label { font-size: 9px; }
  /* 横屏的 head 区只有 0.14 画布高（932×430 上 60px）：
     字标的 74px 下限与 43px 的大标题都得收进去，不然整块被裁掉 22px。 */
  .ec-brand { min-height: 0; }
  .ec-brand__title { font-size: clamp(22px, 4vw, 34px); }
  .ec-zone[data-spec-zone='body'],
  .ec-zone[data-spec-zone='detail'] { padding: var(--gs-space-2); gap: var(--gs-space-2); }
  .ec-zone[data-spec-zone='play'] { padding: var(--gs-space-2); }
  /*
   * 灯板那一块 play 区在最小横屏档（568×320）只有 279×288：
   * 横向留白一收，砖才守得住 §10.1.2 的 84×84 下限；
   * 其余四屏（标题 / 选择 / 图鉴 / 帮助）的 play 区照旧留白，按钮不贴画布边。
   * 砖边长按 play 区实高算，上限 96（§10.1.2 的 844×390 档），间距 10。
   */
  .ec-screen[data-screen='gameplay'] .ec-zone[data-spec-zone='play'],
  .ec-screen[data-screen='coach'] .ec-zone[data-spec-zone='play'] { padding: 4px 0; }
  .ec-board { --ec-brick: min(96px, calc((90dvh - 32px) / 3)); --ec-brick-gap: 10px; }
  /* 横屏的 side 区是 0.25 画布高（568×320 上 80px）：两枚 48 的钮只能并排，不能叠。 */
  .ec-zone[data-zone='side'] { flex-direction: row; justify-content: space-evenly; align-items: center; }
  .ec-stats { grid-template-columns: 1fr; }
  .ec-stat { min-height: 48px; }
  .ec-gallery { grid-template-columns: repeat(6, minmax(44px, 1fr)); }
  .ec-drawer { min-height: 76px; }
  .ec-pass-compose { flex-direction: row; }
  .ec-pass-compose .ec-board { --ec-brick: 68px; --ec-brick-gap: 8px; }
  .ec-cardgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ec-card { min-height: 96px; }
  .ec-card__art { width: min(54px, 62%); }
  .ec-panel { max-width: 680px; padding: 12px; }
  .ec-lines { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ═════════════ 矮竖屏那一档（站内嵌入框 342×403） ═════════════ */

/*
 * 视口是**竖**的，却只有 403px 高：画布锁定不生效（高 ≤ 560 那一档按「手机横过来」
 * 处理，而这里方向是竖的，横屏样式也不匹配），于是竖屏分区原样塞进半屏高度——
 * 六个分区每个都只剩原来的一半，读数、教学气泡、灯板逐个顶破自己的分区。
 *
 * 这一档**只收尺寸**：分区矩形、分区顺序、热区归属一律照旧，不是第二套版式。
 * 触控下限守住 44×44 不收（灯板按分区实算，下面那条 `min()` 的两个除法就是它）。
 * 560 与横屏那一档同一个数（`shared/phone-canvas.json` 的 `shortViewportMaxHeightPx`）。
 */
@media (orientation: portrait) and (max-height: 560px) {
  /* 灯板按 play 分区（83.3vw × 48dvh）实算，封顶仍是 §10.1.1 的 88px。
     342×403 上算出来 ≈ 56px，仍高于平台基线的 44。 */
  .ec-board {
    --ec-brick-gap: 8px;
    --ec-brick: min(88px, calc((83.3vw - 16px) / 3), calc((48dvh - 34px) / 3));
  }

  /* HUD：0.06 × 403 ≈ 24px，连击的 17px 大字装不下。 */
  .ec-zone[data-zone='hud'] { padding-inline: 4px; }
  .ec-hud-streak__value { font-size: 12px; }
  .ec-hud-fame__bulb { width: 14px; height: 14px; }
  .ec-hud-guests { font-size: 10px; }
  /* 一气呵成那一帧的金色大数字也在这条分区里，24px 一出现就顶破 24px 的分区。 */
  .ec-hud-score { font-size: 15px; }

  /* 读数：0.1 × 403 ≈ 40px，要装下标签 + 两条读数 + 电池条。 */
  .ec-zone[data-zone='readout'] { padding-inline: var(--gs-space-2); }
  .ec-meters { gap: 2px; }
  .ec-meter { height: 10px; }
  .ec-meter[data-role='meter-power'] { height: 6px; }
  .ec-readout-label { font-size: 8px; letter-spacing: 0; line-height: 1; }
  .ec-band { bottom: 12px; height: var(--ec-band-h, 8px); }
  .ec-star { bottom: 9px; height: 10px; }
  .ec-star::before { width: 10px; height: 10px; }
  .ec-star::after { top: 9px; height: 6px; margin-left: 4.5px; }

  /* 教学气泡待在 bubble 区（0.147 × 403 ≈ 59px）里：字号与内边距各收一档。
     改的是气泡自己能读到的**令牌**，不覆盖 `.gs-coach` 选择器。 */
  .ec-zone[data-zone='bubble'] {
    --gs-text-sm: 11px;
    --gs-space-1: 2px;
    --gs-space-2: 2px;
    --gs-space-3: 4px;
    --gs-space-4: 6px;
  }

  /* 标题屏：字标与四枚圆盘导航各收一档，导航整排留在视口里。 */
  .ec-brand { min-height: 0; }
  .ec-brand__title { font-size: clamp(20px, 6vw, 30px); }
  /* 令牌写在 play 分区上而不是 `.ec-navwrap` 上：三张子屏的导航直接挂在分区里，
     挂在包装元素上就只有标题屏收得到。 */
  .ec-zone[data-spec-zone='play'] {
    --gs-text-xs: 9px;
    --gs-space-1: 2px;
    --gs-space-2: 2px;
    --gs-space-4: 8px;
  }

  /* 其余密集屏：卡片、图鉴、结算明细各收一档，都是尺寸不是版式。 */
  .ec-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ec-stat { min-height: 44px; }
  .ec-gallery { grid-template-columns: repeat(6, minmax(44px, 1fr)); }
  .ec-cardgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ec-card { min-height: 88px; }
  .ec-panel { padding: 10px; }
  .ec-pass-compose .ec-board { --ec-brick: 56px; --ec-brick-gap: 6px; }
}
