/* ==========================================================================
   components.css — 可复用组件
   --------------------------------------------------------------------------
   先定义组件，再拼页面。这样 8 个屏只是"组件拼装"，
   不会再出现"这一屏的按钮长得不一样"的问题。
   ========================================================================== */

/* ==========================================================================
   招牌动作（全站唯一招式，重复出现才形成风格）
   ========================================================================== */

/* 1. 斜切：给按钮、标签用 */
.skew {
  clip-path: polygon(
    var(--skew) 0,
    100% 0,
    calc(100% - var(--skew)) 100%,
    0 100%
  );
}

/* 2. 速度线：漫画感的速度条纹，用于标题下方 */
.speedlines {
  width: 100%;
  height: 10px;
  background: repeating-linear-gradient(
    90deg,
    var(--c-accent) 0 var(--speedline),
    transparent var(--speedline) calc(var(--speedline) * 2.4)
  );
  -webkit-mask-image: linear-gradient(90deg, var(--c-mask) 0%, transparent 100%);
  mask-image: linear-gradient(90deg, var(--c-mask) 0%, transparent 100%);
}

/* 3. 球网分隔：区块之间的分隔线（排球成为"结构"而不是贴纸） */
.net-divider {
  --net: rgba(20, 17, 15, 0.18);
  height: var(--s-6);
  margin-bottom: var(--s-6);
  border-top: 4px solid currentColor;
  border-bottom: 2px solid currentColor;
  background:
    repeating-linear-gradient(0deg, transparent 0 10px, var(--net) 10px 12px),
    repeating-linear-gradient(90deg, transparent 0 10px, var(--net) 10px 12px);
  opacity: 0.5;
}

.shell--dark .net-divider { --net: rgba(255, 246, 236, 0.2); }

/* ==========================================================================
   章节标签（SectionShell）
   ========================================================================== */

.chapter {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-4);
}

.chapter__no {
  font-family: var(--font-mono);
  font-size: var(--fs-deco);
  letter-spacing: var(--ls-deco);
  color: var(--c-accent);
}

/* ==========================================================================
   按钮（MangaButton）
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-1);
  min-height: var(--tap);
  padding: 0 var(--s-4);
  border: 0;
  border-radius: var(--r-sm);
  background: var(--c-accent);
  color: var(--c-ink);
  font-weight: 900;
  font-size: var(--fs-h3);
  letter-spacing: 0.08em;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease);
}

.btn:hover { transform: translateY(-2px); }

.btn:active {
  transform: translateY(0);
  background: var(--c-accent-dark);
}

.btn--sm {
  min-height: 40px;
  padding: 0 var(--s-3);
  font-size: var(--fs-body);
}

.btn--ghost {
  background: transparent;
  color: currentColor;
  box-shadow: inset 0 0 0 var(--bw) var(--c-second);
}

.btn--ghost:active { background: rgba(242, 101, 34, 0.16); }

.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* ==========================================================================
   比分牌（ScoreBoard）— 兼作游戏进度显示
   ========================================================================== */

.scoreboard {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-3);
  border: var(--bw) solid var(--c-accent);
  border-radius: var(--r-sm);
  background: rgba(242, 101, 34, 0.08);
}

.scoreboard__team {
  font-size: var(--fs-deco);
  letter-spacing: var(--ls-deco);
  opacity: 0.8;
}

.scoreboard__num {
  font-family: var(--font-mono);
  font-size: var(--fs-h2);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--c-accent);
}

.scoreboard__sep { color: var(--c-accent); opacity: 0.6; }

/* ==========================================================================
   拟声词 / 提示气泡（SpeechBubble）
   ========================================================================== */

.bubble {
  position: relative;
  display: inline-block;
  max-width: 30ch;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md);
  background: var(--c-accent);
  color: var(--c-ink);
  font-size: var(--fs-small);
  font-weight: 700;
}

.bubble::after {
  content: '';
  position: absolute;
  left: var(--s-4);
  top: -8px;
  width: 16px;
  height: 8px;
  background: inherit;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.bubble--bad { background: var(--c-second); color: var(--c-paper); }

/* 答错时抖一下：动效只用于表达"出错了"，而不是装饰 */
.shake { animation: shake var(--dur) var(--ease); }

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  75% { transform: translateX(6px); }
}

/* ==========================================================================
   表单字段（部員登録用）
   ========================================================================== */

.field { display: block; margin-bottom: var(--s-3); }

.field__label {
  display: block;
  margin-bottom: var(--s-1);
  font-size: var(--fs-deco);
  letter-spacing: var(--ls-deco);
  color: var(--c-accent);
}

.field__input {
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--s-2);
  border: 0;
  border-bottom: var(--bw) solid currentColor;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font-size: var(--fs-h3);
}

.field__input::placeholder { color: currentColor; opacity: 0.4; }

/* ==========================================================================
   拍立得卡片（PolaroidCard）
   ========================================================================== */

.polaroid {
  margin: 0;
  transform: rotate(var(--rot, 0deg));
  transition: transform var(--dur) var(--ease);
}

.polaroid:hover,
.polaroid:focus-within { transform: rotate(0deg) translateY(-4px); }

.polaroid__frame {
  position: relative; /* 钉子（.polaroid__pin）的定位参照 */
  padding: var(--s-1) var(--s-1) var(--s-3);
  border-radius: 2px;
  background: var(--c-frame);
  box-shadow: var(--shadow-card);
}

.polaroid__img {
  position: relative;
  aspect-ratio: var(--ratio, 4 / 5);
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--c-ink-soft);
}

/* ★ 一律不裁剪：容器的高度由每张照片真实的比例决定（--ratio 由 photos.js 提供），
   这里再用 contain 兜一层 —— 万一比例有偏差，宁愿留一丝白边，也绝不裁掉画面内容。 */
.polaroid__img img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* 照片占位：网点底 + 编号。位置看得见，不会出现破图图标 */
.polaroid__slot {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at 1px 1px, rgba(255, 246, 236, 0.26) 1px, transparent 0)
      0 0 / 8px 8px,
    var(--c-ink-soft);
  color: rgba(255, 246, 236, 0.78);
  font-family: var(--font-mono);
  font-size: var(--fs-deco);
  letter-spacing: 0.1em;
}

/* 配文在相纸**外面**（直接压在 section 背景上），而照片墙和彩蛋这两屏都是暗背景，
   所以用浅色字 + 一圈深色描边：壁纸亮的地方（人脸）也照样看得清。
   如果哪天照片墙换回浅底，把 color 和 text-shadow 这两行删掉就会自动继承深字。 */
.polaroid__caption {
  margin-top: var(--s-1);
  font-size: var(--fs-small);
  line-height: 1.6;
  text-align: center;
  color: var(--c-paper);
  text-shadow:
    0 1px 3px rgba(20, 17, 15, 0.8),
    0 0 10px rgba(20, 17, 15, 0.55);
}

.polaroid--locked .polaroid__slot {
  background:
    radial-gradient(circle at 1px 1px, rgba(20, 17, 15, 0.18) 1px, transparent 0)
      0 0 / 8px 8px,
    var(--c-locked);
  color: rgba(20, 17, 15, 0.55);
}

.polaroid--locked .polaroid__frame { box-shadow: none; }

/* 钉子：钉在照片的上沿。
   top: var(--s-1) 正好等于相纸的内边距，也就是照片的上边缘；
   translate(-50%, -50%) 把钉子的中心摆在那条边上
   → 一半压住照片、一半露在照片外，像用钉子把照片钉住。 */
.polaroid__pin {
  position: absolute;
  top: var(--s-1);
  left: 50%;
  z-index: 2; /* 画在照片之上，才有"钉住"的观感 */
  /* 单颗钉子可以按张缩放：--pin-scale 由 gallery.js 从 photos.js 的 pinScale 写入，
     省略就是 1（跟全局一样大）。缩小后"一半在内一半在外"的位置关系不变。 */
  width: calc(var(--pin-size) * var(--pin-scale, 1));
  transform: translate(-50%, -50%);
  pointer-events: none; /* 不抢照片的点击 */
}

.polaroid__pin img {
  display: block;
  width: 100%;
  height: auto;
}

/* 卡片按钮化：整张卡可点 */
.polaroid__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: zoom-in;
}

/* 锁定的彩蛋卡不可点 */
.polaroid--locked .polaroid__btn { cursor: not-allowed; }

/* ==========================================================================
   灯箱（Lightbox）— 优先用原生 <dialog>，自带焦点陷阱与 Esc
   ========================================================================== */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgba(20, 17, 15, 0.94);
  color: var(--c-paper);
}

.lightbox::backdrop { background: rgba(20, 17, 15, 0.6); }

.lightbox__stage {
  display: grid;
  place-items: center;
  height: 100%;
  padding: var(--s-8) var(--s-3) var(--s-6);
}

.lightbox__stage img {
  max-width: 100%;
  max-height: 72svh;
  width: auto;
  object-fit: contain;
  box-shadow: var(--shadow-lift);
}

.lightbox__ph {
  width: min(80vw, 420px);
  aspect-ratio: 4 / 5;
  display: grid;
  place-items: center;
  background: var(--c-ink-soft);
  color: rgba(255, 246, 236, 0.78);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.1em;
}

.lightbox__close,
.lightbox__nav {
  position: absolute;
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-pill);
  background: rgba(255, 246, 236, 0.14);
  color: var(--c-paper);
  font-size: 20px;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}

.lightbox__close:hover,
.lightbox__nav:hover { background: rgba(255, 246, 236, 0.26); }

.lightbox__close { top: var(--s-2); right: var(--s-2); }

.lightbox__nav { top: 50%; transform: translateY(-50%); }

.lightbox__nav--prev { left: var(--s-1); }

.lightbox__nav--next { right: var(--s-1); }

.lightbox__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--s-4) + env(safe-area-inset-bottom, 0px));
  padding-inline: var(--s-3);
  text-align: center;
  font-size: var(--fs-small);
}

.lightbox__counter {
  display: block;
  margin-top: var(--s-1);
  font-family: var(--font-mono);
  font-size: var(--fs-deco);
  opacity: 0.7;
}

/* ==========================================================================
   蛋糕场景（CandleCake）—— 蛋糕 + 小人的"绝不遮挡"布局
   --------------------------------------------------------------------------
   为什么不用"蛋糕角上绝对定位"：
   手机上蛋糕盒子两侧只剩 56~66px，任何看得清的小人（≥80px）都必然压住蛋糕。
   所以改成"预留舞台"：场景顶部留出 42% 的空白当台子，小人就站在那里面中；
   蛋糕从这条留白之后才开始排布，两者的盒子永远不会相交。
   小人仍用 position:absolute + 更低的 z-index，作第二道保险。
   ========================================================================== */

.cake-scene {
  position: relative;
  width: min(100%, 420px);
  margin-inline: auto;
  /* 顶部留出 42% 的空白当"小人的舞台"（百分比按宽度算，任何屏幕都刚好放得下）。
     小人高度 ≈ 34% × (529/480) ≈ 37.5% 宽，所以 42% 一定装得下、且不会碰到蛋糕。 */
  padding-top: 42%;
}

.cheer {
  position: absolute;
  top: 0; /* 舞台顶部 */
  left: 50%; /* 蛋糕（也就是蜡烛）在场景正中 → 小人正对蜡烛 */
  z-index: 1; /* 永远低于蛋糕（蛋糕是 2） */
  width: 34%;
  opacity: 0;
  transform: translate(-50%, 8px); /* 淡入时从下往上浮一点点 */
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
  pointer-events: none; /* 纯装饰，不抢点击 */
}

.cheer img { width: 100%; height: auto; }

/* 吹灭蜡烛后由 JS 加上这个 class → 平滑淡入到 --cheer-opacity */
.cheer.is-in {
  opacity: var(--cheer-opacity);
  transform: translate(-50%, 0);
}

.cake {
  position: relative;
  z-index: 2; /* 兜底：任何情况下都压在小人之上 */
  display: block;
  width: 100%;
  max-width: 240px;
  aspect-ratio: 1 / 0.9;
  margin-inline: auto;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.cake__body {
  position: absolute;
  inset: auto 0 0 0;
  height: 60%;
  border-radius: var(--r-sm) var(--r-sm) var(--r-md) var(--r-md);
  background: linear-gradient(
    var(--c-accent) 0 42%,
    var(--c-paper) 42% 100%
  );
  box-shadow: var(--shadow-card);
}

.cake__candle {
  position: absolute;
  left: 50%;
  bottom: 58%;
  width: 10px;
  height: 24%;
  transform: translateX(-50%);
  background: repeating-linear-gradient(
    45deg,
    var(--c-paper) 0 6px,
    var(--c-accent) 6px 12px
  );
}

.cake__flame {
  position: absolute;
  left: 50%;
  top: -30px;
  width: 16px;
  height: 26px;
  transform: translateX(-50%);
  border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  background: radial-gradient(
    circle at 50% 70%,
    var(--c-flame-hot),
    var(--c-flame-warm) 62%,
    transparent 70%
  );
  animation: flicker 1.4s var(--ease) infinite alternate;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}

@keyframes flicker {
  from { transform: translateX(-50%) scale(1) rotate(-3deg); }
  to { transform: translateX(-50%) scale(1.1) rotate(3deg); }
}

.cake.is-out .cake__flame {
  opacity: 0;
  transform: translateX(-50%) scale(0.2);
}

.cake__tip {
  margin-top: var(--s-4);
  text-align: center;
  font-size: var(--fs-small);
  opacity: 0.75;
}

/* ==========================================================================
   球衣号码水印（JerseyNumber）
   ========================================================================== */

.jersey-num {
  position: absolute;
  inset: auto 0 -4% 0;
  z-index: 0;
  text-align: center;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-num);
  line-height: 0.8;
  color: currentColor;
  opacity: 0.08;
  pointer-events: none;
  user-select: none;
}

/* ==========================================================================
   音效开关
   ========================================================================== */

.toggle {
  position: fixed;
  right: var(--s-2);
  bottom: calc(var(--s-2) + env(safe-area-inset-bottom, 0px));
  z-index: var(--z-sticky);
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  border: var(--bw) solid var(--c-accent);
  border-radius: var(--r-pill);
  background: rgba(20, 17, 15, 0.72);
  color: var(--c-accent);
  font-size: 20px;
  cursor: pointer;
  backdrop-filter: blur(4px);
}

.toggle[aria-pressed='false'] { opacity: 0.5; }

/* ==========================================================================
   游戏卡片（GameCard）
   ========================================================================== */

.game {
  padding: var(--s-3);
  border-radius: var(--r-md);
  background: var(--c-ink-soft);
}

.game + .game { margin-top: var(--s-4); }

.game__title {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  font-size: var(--fs-h3);
  font-weight: 700;
}

.game__no { font-family: var(--font-mono); color: var(--c-accent); }

.game__desc {
  margin-top: var(--s-1);
  font-size: var(--fs-small);
  color: rgba(255, 246, 236, 0.72);
}

.game__canvas {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  margin-top: var(--s-2);
  border-radius: var(--r-sm);
  background: var(--c-canvas);
  touch-action: none;              /* 手机上拖动不漏成滚动 */
}

.game__hud {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-2);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  color: rgba(255, 246, 236, 0.8);
}

.game__hud b { color: var(--c-accent); }

.game__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-2);
}

.game__state {
  font-size: var(--fs-small);
  color: rgba(255, 246, 236, 0.7);
}

/* 怪人速攻：判定条 */
.quick { margin-top: var(--s-2); }

.quick__bar {
  position: relative;
  height: 34px;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: rgba(255, 246, 236, 0.12);
  touch-action: manipulation;
}

.quick__zone {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}

.quick__zone--good { width: 24%; background: rgba(242, 101, 34, 0.3); }

.quick__zone--perfect { width: 8%; background: var(--c-accent); }

.quick__cursor {
  position: absolute;
  top: -4px;
  bottom: -4px;
  left: calc(var(--x, 0) * 100%);
  width: 3px;
  background: var(--c-paper);
  box-shadow: 0 0 8px rgba(255, 246, 236, 0.9);
}

.quick__round {
  margin-top: var(--s-1);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
}

/* ==========================================================================
   特效层（fx.js 的 canvas）
   --------------------------------------------------------------------------
   铺满视口、只负责画、不接收指针事件 —— 所以永远不会挡住蛋糕或按钮。
   层级低于灯箱，高于页面内容。
   ========================================================================== */

.fx-layer {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  pointer-events: none;
}

/* ==========================================================================
   名言飘屏（吹灭蜡烛后，一句一句从右往左慢慢飘）
   ========================================================================== */

/* 名言从蛋糕"后面"飘过，所以正文要压在它上面 */
.shell--quotes > .container {
  position: relative;
  z-index: 1;
}

.quotes {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden; /* 裁掉屏幕外的部分：不会撑出横向滚动条 */
  pointer-events: none; /* 纯装饰，不抢点击 */
}

.quotes__line {
  position: absolute;
  left: 0;
  margin: 0;
  white-space: nowrap; /* 一句一行，不折行 */
  font-size: var(--fs-body);
  color: var(--c-ink);
  opacity: 0.5;
  will-change: transform;
  animation-name: quote-drift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

/* 从屏幕右边缘外 → 完全飘出左侧。
   100vw 是屏幕宽（≥ 容器宽），多出来的一点只是让下一句稍微晚一点进场。 */
@keyframes quote-drift {
  from { transform: translateX(100vw); }
  to { transform: translateX(-100%); }
}

/* 减少动效偏好：不飘，改成静态列出来（内容照样看得到） */
.quotes--static {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--s-2);
  padding: var(--s-4) var(--s-3);
}

.quotes--static .quotes__line {
  position: static;
  transform: none;
  animation: none;
  max-width: 40ch;
  white-space: normal;
  text-align: center;
  font-size: var(--fs-small);
  opacity: 0.45;
}
