/* 俄罗斯方块 —— 布局与界面
   三套布局：桌面（侧栏）/ 手机竖屏（按键在下）/ 手机横屏（按键分到左右拇指）。 */

html, body{ height: 100%; overflow: hidden; overscroll-behavior: none; }
/* 屏幕锁住：双击不放大（iOS 不认 viewport 里的 user-scalable=no，靠这条关掉双击缩放）。
   双指缩放和空白处拖动在 tetris.js 的 lockPage() 里挡 */
html, body{ touch-action: manipulation; }
/* clip 而不是只靠 hidden：hidden 的容器手指滚不动，但 focus()、点击之类会把它程序性地
   滚下去。实测 375×812 上 body 内容比屏幕高 78px，点一下「开始」顶栏就被顶出屏幕。
   clip 不产生滚动容器，没有东西可滚。不支持 clip 的老浏览器退回上一行的 hidden。 */
html, body{ overflow: clip; }
/* hidden 属性一律要赢。好几个元素 HTML 里带着 hidden，却被各自的 display 规则盖掉了：
   标准版的「?」、「未入段」、「玩法说明」、结算页的称号 —— 都本该只在疯狂版出现，
   点开是疯狂版的规则。之前只修了「?」那一个（.tbtn[hidden]），这里一次兜住全部。 */
[hidden]{ display: none !important; }
/* 顶上那条 2px 彩线浮在最上面、不占位置。原来它在 .game（正好一屏高）上面单独占一行，
   整页比屏幕高 2px，游戏最底部被裁掉 2px。只在这里覆盖，shared.css 别的游戏还在用 */
.topline{ position: fixed; top: 0; left: 0; right: 0; z-index: 30; pointer-events: none; }
body{
  /* iOS 上手指长按不要弹出选择/放大镜 */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.game{
  height: 100dvh;
  /* 超出整屏的东西直接裁掉、也不计入可滚动区域。盘面背后 FEVER 的斜条纹层
     （.shaker::before，inset -40%）在标准版里透明度是 0，但照样占位置，
     往下伸出 78px，是 body 能被滚动的来源 */
  overflow: clip;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 1000px;
  margin: 0 auto;
  /* 刘海、灵动岛、底部横条都躲开 */
  padding:
    env(safe-area-inset-top) 
    max(10px, env(safe-area-inset-right))
    max(34px, calc(env(safe-area-inset-bottom) + 30px))
    max(10px, env(safe-area-inset-left));
}

/* ── 顶栏 ── */
.bar{
  grid-area: bar;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 2px 2px;
}
.bar h1{
  font-size: 16px;
  font-weight: 600;
  letter-spacing: .04em;
  margin: 0;
  white-space: nowrap;
}
.bar h1 em{
  font-family: var(--mono);
  font-style: normal;
  font-size: 10px;
  letter-spacing: .14em;
  color: var(--n-cyan);
  display: block;
  margin-top: 1px;
  opacity: .8;
}
.bar .sp{ flex: 1; }

.tbtn{
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .06em;
  color: var(--ink-soft);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 7px 13px;
  white-space: nowrap;
  cursor: pointer;
  transition: color .16s, border-color .16s, background .16s;
  -webkit-tap-highlight-color: transparent;
}
.tbtn:hover{ color: var(--ink); border-color: var(--line-2); }
.tbtn.off{ color: var(--ink-dim); }

/* ── 主区 ── */
.stage{
  flex: 1;
  min-height: 0;
  display: grid;
  gap: 8px;
  grid-template-areas:
    "bar   bar"
    "board side"
    "pads  pads";
  grid-template-columns: minmax(0, 1fr) 64px;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 6px;
}

.side{
  grid-area: side;
  display: flex;
  flex-direction: column;
  justify-content: center;   /* 不贴顶，跟棋盘一起居中 */
  gap: 8px;
  min-height: 0;
}

.slot{
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  padding: 6px 8px 7px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.slot-label{
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .18em;
  color: var(--ink-dim);
  text-align: center;
}
.slot canvas{ display: block; width: 100%; }
.slot.next canvas{ height: 72px; }     /* 上排一个大的，下排两个小的 */
.slot.hold canvas{ height: 30px; }

.slot.hold{ cursor: pointer; -webkit-tap-highlight-color: transparent; transition: border-color .14s, background .14s; }
.slot.hold:hover, .slot.hold:focus-visible{ border-color: var(--line-2); background: rgba(120,160,255,.07); outline: none; }
.slot.hold:active{ background: rgba(34,211,238,.13); border-color: var(--n-cyan); }
/* 触屏走 touchstart 加 .active，:active 在 touch 上不一定跟手 */
.slot.hold.active{ background: rgba(34,211,238,.13); border-color: var(--n-cyan); }

/* NEXT 槽在疯狂版里可以点（花热度换牌）。热度够了才亮边，
   不够的时候看起来就是个普通展示框 —— 免得点了没反应还扣不掉。 */
.slot.next{ -webkit-tap-highlight-color: transparent; transition: border-color .16s, background .16s; }
.slot.next.ready{
  border-color: rgba(255,193,71,.5);
  background: linear-gradient(150deg, rgba(255,193,71,.10), rgba(255,193,71,.02)), var(--panel);
  cursor: pointer;
}
.slot.next.ready .slot-label::after{ content: '　换牌'; color: #ffd166; letter-spacing: 0; }
.slot.next.active{ background: rgba(255,193,71,.16); border-color: rgba(255,193,71,.8); }

/* 统计 */
/* ── 状态格 ── */
.fxbox{ padding: 5px 6px 6px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.fxbox:hover, .fxbox:focus-visible{ border-color: rgba(255,255,255,.28); outline: none; }
/* 高度是 min 不是固定值 —— 带「接受 / 启动 / 缓期」按钮的行里那颗按钮
   自己就 30px，钉死 22px 会让它撑出去压住下一行。
   原来只给梭哈单独开了 height:auto，后加的 ZONE 和缓期就踩了这个坑。 */
.fxbox .fxs{ min-height: 22px; margin-top: 3px; }
/* 空槽平时照样占位（三槽常驻是为了防止事件起落时整列上下跳），
   但出现按钮那一行时例外：按钮自己就 26px，不让空槽塌陷的话整个框会
   向上顶出侧栏 20px —— 「状态」标题钻到顶栏按钮后面、底下「段位」被挤出屏幕。
   梭哈十秒才来一次，这点高度变化远比布局炸掉可接受。 */
.fxbox.acting .fxs:empty{ min-height: 0; margin-top: 0; }
/* 还差最后几个像素，从内边距里省 —— 不动按钮尺寸，它是要用手指点的 */
.fxbox.acting{ padding-top: 2px; padding-bottom: 2px; }
.fxbox .fxs:first-of-type{ margin-top: 2px; }
.fxbox .fxl{ display: flex; align-items: baseline; justify-content: space-between; gap: 3px; }
.fxbox b{ font-family: var(--mono); white-space: nowrap; }
.fxbox .fxn{ font-size: 9.5px; }
.fxbox .fxv{ font-size: 10px; }
.fxbox .fxbar{ display: block; height: 3px; border-radius: 2px;
               background: rgba(255,255,255,.10); overflow: hidden; margin-top: 2px; }
/* 条只动 transform，走合成器，不吃 paint */
.fxbox .fxbar i{ display: block; height: 100%; transform-origin: left; border-radius: 2px;
                 will-change: transform; }
.fxbox .bad  b{ color: #ffb4a6 } .fxbox .bad  .fxbar i{ background: #f43f5e }
.fxbox .good b{ color: #b8f5e4 } .fxbox .good .fxbar i{ background: #5fe0c8 }
.fxbox .fev  b{ color: #7fe3ff } .fxbox .fev  .fxbar i{ background: #7fe3ff }
.fxbox .burn b{ color: #ffb86b } .fxbox .burn .fxbar i{ background: #ff8a3c }
.fxbox .betrow b{ color: #ffd166 } .fxbox .betrow .fxbar i{ background: #ffb703 }
.fxbox .betoffer b{ color: #ffd166 }
.fxbox .betoffer .fxbar i{ background: #ffb703 }
.fxbox .fxgo{
  display: block; width: 100%;
  margin: 3px 0 2px;
  font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
  color: #241503;
  background: linear-gradient(180deg, #ffd980, #ffb020);
  border: 0; border-radius: 7px;
  /* 侧栏只有 64px 宽，高度就得给够 —— 这个键十秒就没，点不中等于白给 */
  min-height: 26px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fxbox .fxgo:active{ transform: translateY(1px); }
/* 盘面上那一下提示：跟梭哈结算共用飘字样式，只是换个颜色钩子 */
.pop.bet.offer b{ color: #ffd166; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 22px rgba(255,193,71,.6); }
.pop.bet.offer i{ color: #ffb703; }
.fxbox .heat .fxn{ color: var(--ink-soft) } .fxbox .heat .fxv{ color: #ff8a5c }
.fxbox .rush .fxn{ color: var(--ink-soft) } .fxbox .rush .fxv{ color: #ffd166 }
/* 320 宽放不下：右栏内容 520px 而盘面只有 420px */
@media (max-width: 340px){ .fxbox{ display: none; } }

.stats{
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  align-content: start;
}
.stat{
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  padding: 5px 6px 6px;
  text-align: center;
  min-width: 0;
}
.stat span{
  display: block;
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: .14em;
  color: var(--ink-dim);
}
.stat b{
  display: block;
  font-family: var(--mono);
  /* --stat-fs 是这一档布局的基准字号，--fit 是 JS 按字数给的缩放。
     两个分开，断点改基准、JS 只管缩放，互不覆盖。 */
  --stat-fs: 15px;
  font-size: calc(var(--stat-fs) * var(--fit, 1));
  /* 行高钉死在基准字号上，不跟 --fit 走：「万」「亿」是中日韩字形，行框比
     纯数字高 4px，分数跨过 1 万那一刻整个侧栏会跳一下；字号一缩又会再跳一次。 */
  line-height: calc(var(--stat-fs) * 1.2);
  white-space: nowrap;
  font-weight: 500;
  color: var(--ink);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stat.hi b{ color: var(--n-cyan); }
/* 这局分数超过了开局前的历史最高：BEST 格换成金色、标签写「已破纪录」，数字仍是旧纪录 */
.stat.beat{ border-color: rgba(255,209,102,.55); }
.stat.beat span, .stat.beat b{ color: #ffd166; }
#levelBox.flash{ animation: lvl .6s ease; }
@keyframes lvl{
  0%,100%{ box-shadow: none; border-color: var(--line); }
  40%{ box-shadow: 0 0 22px rgba(34,211,238,.55); border-color: var(--n-cyan); }
}

/* 棋盘 */
.board-wrap{
  grid-area: board;
  position: relative;      /* 棋盘绝对定位在里面 */
  align-self: stretch;
  min-width: 0;
  min-height: 0;
}
/* 贴着 canvas 的盒子。必须脱离文档流——否则它的高度由 canvas 撑，
   而 canvas 又按容器高度算，两边互相定义，尺寸就再也长不回去了。 */
.board-box{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  line-height: 0;
}
/* 边框的颜色/粗细/辉光由 JS 按等级写进这几个变量，见 syncEdge() */
#board{
  display: block;
  border-radius: 10px;
  background: rgba(6, 10, 20, .72);
  --bd-w: 1px;
  --bd-ring: rgba(120,160,255,.16);
  --bd-glow: rgba(34,211,238,.09);
  --bd-blur: 38px;
  box-shadow:
    0 0 0 var(--bd-w) var(--bd-ring),
    0 0 var(--bd-blur) var(--bd-glow),
    0 18px 50px rgba(0,0,0,.5);
  transition: box-shadow .55s ease;
}

/* 满级光环。只动 opacity，合成器就能搞定，不会每帧重绘那圈大模糊。 */
.board-aura{
  position: absolute;
  inset: 0;
  border-radius: 10px;
  pointer-events: none;
  opacity: 0;
  /* 提前提升成合成层：不然第一次亮起来那一帧要临时建层，实测会掉一帧 */
  will-change: opacity;
  --au-ring: rgba(255,62,200,.78);
  --au-glow: rgba(255,62,200,.32);
  --au-blur: 72px;
  box-shadow:
    0 0 0 2px var(--au-ring),
    0 0 var(--au-blur) var(--au-glow);
  transition: opacity .5s ease;
}
.board-box.maxed .board-aura{ animation: auraPulse 2.2s ease-in-out infinite; }
@keyframes auraPulse{ 0%, 100%{ opacity: .28 } 50%{ opacity: 1 } }
@media (prefers-reduced-motion: reduce){
  .board-box.maxed .board-aura{ animation: none; opacity: .62; }
  #board{ transition: none; }
}

#board.flash{ animation: boardFlash .34s ease-out; }
#board.flash-big{ animation: boardFlashBig .55s ease-out; }
@keyframes boardFlash{
  0%   { box-shadow: 0 0 0 var(--bd-w) var(--bd-ring), 0 0 var(--bd-blur) var(--bd-glow), 0 18px 50px rgba(0,0,0,.5); }
  22%  { box-shadow: 0 0 0 calc(var(--bd-w) + 1.5px) rgba(190,240,255,.55), 0 0 54px var(--bd-glow), 0 18px 50px rgba(0,0,0,.5); }
  100% { box-shadow: 0 0 0 var(--bd-w) var(--bd-ring), 0 0 var(--bd-blur) var(--bd-glow), 0 18px 50px rgba(0,0,0,.5); }
}
@keyframes boardFlashBig{
  0%   { box-shadow: 0 0 0 var(--bd-w) var(--bd-ring), 0 0 var(--bd-blur) var(--bd-glow), 0 18px 50px rgba(0,0,0,.5); }
  14%  { box-shadow: 0 0 0 3px rgba(255,255,255,.8), 0 0 90px var(--bd-glow), 0 0 140px rgba(168,85,247,.4), 0 18px 50px rgba(0,0,0,.5); }
  45%  { box-shadow: 0 0 0 calc(var(--bd-w) + 1px) rgba(190,240,255,.5), 0 0 70px var(--bd-glow), 0 18px 50px rgba(0,0,0,.5); }
  100% { box-shadow: 0 0 0 var(--bd-w) var(--bd-ring), 0 0 var(--bd-blur) var(--bd-glow), 0 18px 50px rgba(0,0,0,.5); }
}

/* ── 遮罩层 ── */
.overlay{
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
  border-radius: 10px;
  background: rgba(8, 11, 20, .93);
  text-align: center;
  padding: 14px;
  line-height: 1.6;
}
.overlay.show{ display: grid; }
/* 卡片高过视口就自己滚。横屏 844×390 下，统计最满的那张结算卡有 411px 高，
   而窗口只有 390px —— 原来卡片不可滚，「再来一局」就永远够不到了。 */
.overlay .card{ max-width: 290px; max-height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.overlay h2{ font-size: 21px; margin: 0 0 6px; letter-spacing: .04em; }
.overlay h2 .neon{ color: var(--n-cyan); text-shadow: 0 0 18px rgba(34,211,238,.6); }
.overlay p{ font-size: 12.5px; line-height: 1.75; color: var(--ink-soft); margin: 0 0 14px; }
.overlay kbd{
  font-family: var(--mono);
  font-size: 10.5px;
  background: rgba(120,160,255,.10);
  border: 1px solid var(--line-2);
  border-radius: 5px;
  padding: 1px 5px;
  color: var(--ink);
  white-space: nowrap;
}
.pbtn{
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: .1em;
  color: #04121a;
  background: linear-gradient(180deg, #67e8f9, #22d3ee);
  border: 0;
  border-radius: 999px;
  padding: 11px 30px;
  cursor: pointer;
  box-shadow: 0 0 26px rgba(34,211,238,.42);
  transition: transform .12s, box-shadow .16s;
  -webkit-tap-highlight-color: transparent;
}
#resumeSaveBtn{ display: none; margin-bottom: 9px; }
#resumeSaveBtn b{ font-variant-numeric: tabular-nums; }
.overlay.has-save[data-mode="start"] #resumeSaveBtn{ display: inline-block; }
.overlay.has-save[data-mode="start"] #startBtn{
  background: none;
  color: var(--ink-soft);
  border: 1px solid var(--line-2);
  box-shadow: none;
}
.pbtn:hover{ transform: translateY(-1px); box-shadow: 0 0 34px rgba(34,211,238,.6); }
.pbtn:active{ transform: translateY(1px); }

.final{ display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin: 0 0 14px; }
.final div{ background: rgba(120,160,255,.06); border: 1px solid var(--line); border-radius: var(--r-s); padding: 6px 4px 7px; }
.final span{ display: block; font-family: var(--mono); font-size: 8.5px; letter-spacing: .14em; color: var(--ink-dim); }
.final b{ display: block; font-family: var(--mono); font-size: 15px; color: var(--ink); margin-top: 2px; font-variant-numeric: tabular-nums; }

/* 灰线预告条。贴在棋盘底沿，最后三秒填满。
   只动子元素的 transform，父层只切一次 opacity。 */
.gwarn{
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 5px;
  border-radius: 0 0 10px 10px;
  overflow: hidden;
  background: rgba(147,164,196,.12);
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s;
}
.gwarn.on{ opacity: 1; }
.gwarn i{
  display: block; height: 100%;
  transform: scaleX(var(--p, 0));
  transform-origin: left center;
  background: linear-gradient(90deg, rgba(147,164,196,.7), #ff5c6e);
  will-change: transform;
}
/* 快满的时候闪一下，余光里也看得见 */
.gwarn.on i{ animation: gwarnPulse .5s ease-in-out infinite alternate; }
@keyframes gwarnPulse{ from{ opacity: .75 } to{ opacity: 1 } }
@media (prefers-reduced-motion: reduce){ .gwarn.on i{ animation: none; } }

/* 危险区里消行热度翻倍，所以那条带子不能只是「危险」的红，
   得让人看出「这里也很值钱」 */
body.crazy .danger{ background: linear-gradient(180deg, rgba(255,160,50,.36), rgba(244,63,94,0)); }

/* ── 称号 ── */
/* 开始页的称号牌 */
.rankchip{
  display: inline-flex; align-items: baseline; gap: 8px;
  margin: 0 0 12px; padding: 7px 16px;
  border-radius: 999px;
  background: rgba(255,193,71,.10);
  border: 1px solid rgba(255,193,71,.42);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.rankchip span{ font-family: var(--mono); font-size: 14px; letter-spacing: .14em; color: #ffd166; }
.rankchip em{ font-style: normal; font-size: 10.5px; color: var(--ink-dim); letter-spacing: .1em; }
.todayline{ font-size: 11px; color: var(--ink-dim); margin: -6px 0 12px; }
.todayline b{ font-family: var(--mono); color: var(--ink-soft); font-weight: 500; }

/* 右侧栏常驻那一格 */
.stat.rankbox{ cursor: pointer; -webkit-tap-highlight-color: transparent; transition: border-color .16s, background .16s; }
.stat.rankbox b{ color: #ffd166; }
/* 生涯那格换个色，扫一眼就知道是另一条梯子 */
.stat.rankbox.career b{ color: #8fd4ff; }
.stat.rankbox.career:hover, .stat.rankbox.career:focus-visible{ border-color: rgba(143,212,255,.5); }
.stat.rankbox.career:active{ background: rgba(143,212,255,.13); }
.stat.rankbox:hover, .stat.rankbox:focus-visible{ border-color: rgba(255,193,71,.5); outline: none; }
.stat.rankbox:active{ background: rgba(255,193,71,.13); }

/* 结算页的两条梯子：最强（金）+ 段位（蓝），各自独立判升 */
.overrank{ display: flex; align-items: baseline; justify-content: center; gap: 18px; margin: 0 0 12px; }
.overrank i{ display: flex; align-items: baseline; gap: 7px; font-style: normal; }
.overrank b{
  font-family: var(--mono); font-size: 22px; letter-spacing: .16em; color: #ffd166;
  text-shadow: 0 0 20px rgba(255,193,71,.5);
}
.overrank .career b{ color: #8fd4ff; text-shadow: 0 0 20px rgba(143,212,255,.5); }
.overrank span{ font-size: 10.5px; color: var(--ink-dim); letter-spacing: .1em; }
/* 只有真升了的那一条亮起来并弹一下，另一条保持安静 */
.overrank i.up span{ color: #ffd166; }
.overrank i.career.up span{ color: #8fd4ff; }
.overrank i.up b{ animation: rankPop .7s cubic-bezier(.2,.8,.3,1); }
/* 320 宽下两条一起亮会折行 —— 段位那条可能是「荣耀王者」四个字，
   比原来的两字宽一倍。收字距、收间隙、收字号。 */
/* 断点取 380 不是 340：结算卡在 360 宽下比 390 宽下更窄，
   340 的断点盖不住它，实测 360 会折行而 340 反而不折。 */
@media (max-width: 380px){
  .overrank{ gap: 7px; }
  .overrank i{ gap: 4px; }
  .overrank b{ font-size: 16px; letter-spacing: .02em; }
  .overrank span{ font-size: 9.5px; letter-spacing: .04em; }
}
@keyframes rankPop{
  0%{ transform: scale(.6); opacity: 0 }
  45%{ transform: scale(1.18); opacity: 1 }
  100%{ transform: scale(1) }
}

/* 面板里的梯子 */
/* 两条梯子加起来 17 行，压紧一点才塞得进 76dvh 的抽屉；塞不下的窄屏
   靠下面的吸底按钮保证「好了」永远够得到。 */
.ranks{ display: grid; gap: 2px; margin-bottom: 4px; }
.rk{
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  padding: 3px 10px; border-radius: 7px;
  background: rgba(120,160,255,.04); border: 1px solid transparent;
  font-size: 11px; color: var(--ink-dim);
}
.rk i{ font-style: normal; font-family: var(--mono); letter-spacing: .12em; }
.rk b{ font-family: var(--mono); font-weight: 500; font-size: 10.5px; opacity: .75; }
.rk.got{
  color: #ffd166;
  background: rgba(255,193,71,.10);
  border-color: rgba(255,193,71,.34);
}
.rank-foot{ font-size: 10.5px; color: var(--ink-dim); text-align: center; margin: 6px 0 2px; }
/* 抽屉本身是滚动的，按钮吸在底部 —— 不然 17 行要滚到头才点得到「好了」 */
.helplink{
  display: block; margin: 0 auto 12px; padding: 4px 12px;
  background: none; border: 1px solid var(--line); border-radius: 999px;
  color: var(--ink-dim); font-size: 11.5px; letter-spacing: .06em; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.helplink:hover, .helplink:focus-visible{ color: var(--ink-soft); border-color: rgba(255,255,255,.3); outline: none; }

/* ── 玩法说明 ── */
.helpbody{ text-align: left; }
.helpbody h4{
  margin: 14px 0 6px; font-size: 11px; letter-spacing: .16em;
  color: var(--ink-dim); font-weight: 600; font-family: var(--mono);
}
.helpbody h4:first-child{ margin-top: 2px; }
.helprow{ display: flex; align-items: flex-start; gap: 8px; margin: 0 0 7px; }
.helprow canvas{ flex: 0 0 auto; width: 26px; height: 26px; margin-top: 1px; }
.helprow .dot{ flex: 0 0 auto; width: 26px; text-align: center; font-size: 13px; line-height: 26px; }
.helprow .tx{ flex: 1 1 auto; min-width: 0; }
.helprow b{ display: block; font-size: 12px; color: var(--ink); }
.helprow b em{ font-style: normal; color: var(--ink-dim); font-family: var(--mono); font-size: 10.5px; margin-left: 5px; }
.helprow span{ display: block; font-size: 11px; line-height: 1.5; color: var(--ink-soft); }
#helpSheet .sheet-done, #rankSheet .sheet-done, #drSheet .sheet-done, #hintSheet .sheet-done, #nesSheet .sheet-done, #vsSheet .sheet-done,
#prSheet .sheet-done, #styleSheet .sheet-done{
  position: sticky; bottom: 0; z-index: 1;
  display: block; width: 100%;
  box-shadow: 0 -12px 18px rgba(11,16,28,.96), 0 0 26px rgba(34,211,238,.42);
}

/* 狂欢局：整局都有效，所以提示得是常驻的。
   金边不能再用 outline —— 镜像事件也写 .shaker 的 outline，两条特异度相同、
   它在后面，狂欢局里一发镜像那 8 秒金边就被盖没了。改走 ::after
   （::before 被 FEVER 占着），呼吸只动 opacity，不动 box-shadow，不吃 paint。 */
body.rushrun .shaker::after{
  content: '';
  position: absolute; inset: -6px;
  border: 2.5px solid rgba(255,190,70,.8);
  border-radius: 15px;
  /* 外扩暖光 + 往盘面里渗一层。box-shadow 只在这里画一次，
     动画只动 opacity / transform，都是合成器的活，不会每帧重绘。 */
  box-shadow: 0 0 24px rgba(255,140,40,.45), inset 0 0 30px rgba(255,120,30,.16);
  pointer-events: none;
  will-change: opacity, transform;
  animation: rushBreath 1.4s ease-in-out infinite alternate;
}
@keyframes rushBreath{
  from{ opacity: .58; transform: scale(.996) }
  to  { opacity: 1;   transform: scale(1.007) }
}
/* 狂欢局里分数格的标签改成金色的 SCORE ×1.2 —— 低头看分就确认了，不占新格子 */
body.rushrun .stat.hi span{ color: #ffd166; }

/* 狂欢局报幕：压三秒，期间不落方块。只动 opacity/transform。 */
.rushintro{
  position: absolute;
  left: 50%; top: 38%;
  transform: translate(-50%, -50%) scale(.88);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 16px 26px; border-radius: 16px;
  background: rgba(24,16,4,.82);
  border: 1px solid rgba(255,193,71,.6);
  box-shadow: 0 0 40px rgba(255,193,71,.25);
  pointer-events: none;
  opacity: 0;
  transition: opacity .22s, transform .22s;
  will-change: opacity, transform;
  z-index: 6;
  /* 必须显式写 —— 这个框挂在 .shaker 里面，而 .shaker 是 line-height: 0
     （给 canvas 去基线间隙用的）。继承下来两行都是零高度，标题和副标题
     会原地摞成一团。 */
  line-height: 1.35;
}
.rushintro.on{ opacity: 1; transform: translate(-50%, -50%) scale(1); }
/* 两行都要 nowrap：框是绝对定位在盘面里的，盘面窄到 210px 时
   「狂欢局」三个字带 .24em 字距会被挤成三行 */
.rushintro b{
  font-family: var(--mono); font-size: 26px; letter-spacing: .24em; white-space: nowrap;
  color: #ffd166; text-shadow: 0 0 24px rgba(255,193,71,.55);
}
.rushintro span{ font-size: 11.5px; color: var(--ink-dim); letter-spacing: .12em; white-space: nowrap; }
@media (max-width: 380px){
  .rushintro{ padding: 13px 18px; }
  .rushintro b{ font-size: 21px; letter-spacing: .16em; }
  .rushintro span{ font-size: 10.5px; letter-spacing: .06em; }
}
@media (prefers-reduced-motion: reduce){
  body.rushrun .shaker::after{ animation: none; opacity: .85; transform: none; }
}

@media (prefers-reduced-motion: reduce){ .overrank.up b{ animation: none; } }

/* 死因说人话，摆在标题下面 */
.died{ font-size: 11px; color: var(--ink-dim); margin: -2px 0 12px; letter-spacing: .04em; }

/* 死亡慢镜：结算页弹出来之前，盘面褪色定住 700ms。
   只动 filter 和 opacity，合成器层面的事，不会引起重绘。 */
body.dying #board{ filter: grayscale(.72) brightness(.62); transition: filter .6s ease; }
body.dying .board-aura,
body.dying .danger,
body.dying .streak,
body.dying .evwarn{ opacity: 0 !important; transition: opacity .35s ease; }
@media (prefers-reduced-motion: reduce){
  body.dying #board{ transition: none; }
}

/* 这局的流水账。所有机制都在跑，但之前没有一个地方把它们汇总给玩家看。 */
.runlog{ margin: 0 0 13px; text-align: left; }
.runtitle{ display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.runtitle b{
  font-family: var(--mono); font-size: 17px; letter-spacing: .12em;
  color: #ffd166; text-shadow: 0 0 16px rgba(255,193,71,.45);
}
.runtitle span{ font-size: 10.5px; color: var(--ink-dim); }
.runstats{
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px 14px;
  font-size: 10.5px; color: var(--ink-soft); line-height: 1.65;
}
.runstats span{ display: flex; justify-content: space-between; gap: 6px; min-width: 0; }
.runstats span.w{ grid-column: 1 / -1; }     /* 变异块那行长，占满一排 */
.runstats i{ font-style: normal; color: var(--ink-dim); flex: none; }
.runstats b{
  font-family: var(--mono); color: var(--ink); font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* 局后复盘（标准版） */
.review{ margin: 14px 0 0; text-align: left; }
.rv-lb{ display: block; margin: 9px 0 4px; font-size: 10.5px; letter-spacing: .08em; color: var(--ink-dim); }
.rv-tips{ list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.rv-tips li{ padding: 7px 9px; border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.03);
  font-size: 11px; line-height: 1.5; color: var(--ink-soft); }
.rv-tips b{ display: block; color: #ffd166; font-weight: 600; margin-bottom: 1px; }

.overlay [data-when]{ display: none; }
.overlay[data-mode="start"] [data-when~="start"],
.overlay[data-mode="pause"] [data-when~="pause"],
.overlay[data-mode="over"]  [data-when~="over"]{ display: block; }

/* 消行提示 */
.toast{
  position: absolute;
  top: 14%;
  left: 50%;
  transform: translate(-50%, 0) scale(.9);
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: .14em;
  color: #fff;
  text-shadow: 0 0 20px rgba(34,211,238,.9), 0 0 6px rgba(255,255,255,.7);
  pointer-events: none;
  opacity: 0;
  /* 原来是 white-space: nowrap 且**没有宽度上限** —— 任何长一点的文案都会
     横着溢出棋盘跑到画面外。短的本来就不换行，所以放开换行不影响它们。

     width: max-content 不能省。绝对定位 + left:50% 时，shrink-to-fit 的可用宽度
     只有容器右半边，于是 max-width:92% 根本碰不到 —— 实际上限是 50%，
     修行那种带描述的长句会被挤成四行窄条。给了 width 之后盒子先取
     max-content，再由 max-width 收到 92%，translateX(-50%) 照旧居中。 */
  width: max-content;
  max-width: 92%;
  text-align: center;
  line-height: 1.35;
}
/* 时长必须跟着 showToast 传进来的走。写死 1.1s 的时候，传多长的 ms 都没用 ——
   JS 那个定时器只负责摘 class，而动画早就 forwards 到 opacity:0 了，
   «提示挂 3.2 秒» 是句空话。实测 1.5 秒时 opacity 已经是 0。 */
.toast.show{ animation: toast var(--toast-ms, 1.1s) cubic-bezier(.2,.8,.3,1) forwards; }
@keyframes toast{
  0%  { opacity: 0; transform: translate(-50%, 10px) scale(.86); }
  18% { opacity: 1; transform: translate(-50%, -4px) scale(1.06); }
  70% { opacity: 1; transform: translate(-50%, -8px) scale(1); }
  100%{ opacity: 0; transform: translate(-50%, -26px) scale(.98); }
}

/* ── 按键区 ── */
.pads{
  grid-area: pads;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 9px;
  align-items: start;
}
/* 两组各两个键：左边走位（左移 右移），右边动作（落底 变换）。
   软降不在这一排 —— 它在右栏最底下、变换正上方，见 .side .k-soft。
   （中间有一周软降挤在这一排里，五个键一排，左右移被压窄、软降老按错，退回来了。） */
.pad-g{
  display: grid;
  gap: 9px;
  grid-template-columns: 1fr 1fr;
}
.k{
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 58px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .06em;
  color: var(--ink-soft);
  border-radius: 18px;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  /* 磨砂玻璃的观感，但不用 backdrop-filter——那东西在手机上一直吃 GPU，
     而按键底下本来就是纯色，模糊出来跟这层渐变看不出差别。 */
  background:
    linear-gradient(150deg, rgba(255,255,255,.115), rgba(255,255,255,.028)),
    rgba(22, 30, 48, .82);
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 6px 18px rgba(0,0,0,.30);
  transition: transform .08s, background .12s, border-color .12s;
}
.k svg{ width: 24px; height: 24px; display: block; color: var(--ink); }
/* 落底和其他三个等高、上下对齐，只收窄宽度：占自己那格的 72%（390 宽上约 62px；64% 用户嫌小了点，原来 74px），
   最窄 48px 守住 44pt 的可点下限。两边空出来的就是死区 —— 落底不接「按偏了也算」，
   按在空白里会就近交给右移 / 变换，不会变成落底（用户：落底按钮小一点，好容易按错）。
   高度压矮那版看着一排不齐，所以不动高度。 */
.k-drop{ justify-self: center; width: max(48px, 72%); margin: 0; }

.k-soft{ min-height: 46px; }
/* 竖屏：软降钉在右栏最底下，正好在「变换」正上方。右栏原本是上下居中的，
   两个 auto 外边距把剩余空间均分：其余内容仍居中，软降贴底。 */
.side > :first-child{ margin-top: auto; }
/* 和变换之间多留 8px（网格本身只隔 6px）—— 这个键挪到这儿就是因为老按错，
   拇指往上够变换的时候不能擦到它 */
.side .k-soft{ margin-top: auto; margin-bottom: 8px; }
/* HOLD 排在右栏最后（BEST / 每行下面、软降上面）。原来紧跟 NEXT 在最上面，拇指够不着（用户：HOLD 太靠上）。
   只改显示顺序不改 DOM：横屏 / 桌面里 .side 是 display:contents、每块有自己的 grid-area，order 在那边不起作用 */
.side .slot.hold{ order: 1; }
.side .k-soft{ order: 2; }
.k em{ font-style: normal; opacity: .8; }
.k.active, .k:active{
  transform: translateY(1px);
  background: linear-gradient(150deg, rgba(34,211,238,.26), rgba(34,211,238,.08));
  border-color: rgba(120,235,255,.42);
  color: var(--ink);
}
/* 配色：落底是唯一不可撤销的操作，颜色上也和其他三个区分开 */
.k-drop{
  background:
    linear-gradient(150deg, rgba(34,211,238,.28), rgba(168,85,247,.14)),
    rgba(18, 34, 52, .86);
  border-color: rgba(120,235,255,.34);
}
.k-drop em{ color: #a5f2ff; opacity: 1; }
.k-drop svg{ color: #a5f2ff; }
.k-drop.active, .k-drop:active{
  background: linear-gradient(150deg, rgba(34,211,238,.42), rgba(168,85,247,.22));
}

/* ── 样式切换面板 ── */
/* 贴底的抽屉，故意不挡住棋盘——点一下就能看到方块跟着变 */
.sheet{
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  align-items: end;
  justify-items: center;
  background: rgba(6, 9, 17, .35);
}
.sheet[hidden]{ display: none; }
.sheet-box{
  width: min(480px, 100%);
  max-height: 76dvh;
  overflow: auto;
  background: rgba(11, 16, 28, .96);
  border: 1px solid var(--line-2);
  border-bottom: 0;
  border-radius: 22px 22px 0 0;
  padding: 18px 18px calc(16px + env(safe-area-inset-bottom));
  box-shadow: 0 -18px 50px rgba(0,0,0,.6);
  animation: sheetUp .22s cubic-bezier(.2,.8,.3,1);
}
@keyframes sheetUp{ from{ transform: translateY(18px); opacity: .6; } to{ transform: none; opacity: 1; } }
.sheet-top{ display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.sheet-box h3{ margin: 0; font-size: 16px; letter-spacing: .03em; flex: 1; }
.sheet-lb{
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .16em;
  color: var(--ink-dim);
  margin: 0 0 7px;
}
.seg{ display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 13px; }
.seg-4{ grid-template-columns: repeat(4, 1fr); }
.seg-btn{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 9px 6px 8px;
  background: rgba(255,255,255,.035);
  border: 1px solid var(--line);
  border-radius: 13px;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--ink-soft);
  -webkit-tap-highlight-color: transparent;
  transition: border-color .14s, background .14s, color .14s;
}
.seg-btn canvas{ width: 100%; height: 26px; display: block; }
.seg-4 .seg-btn{ padding: 7px 4px 7px; font-size: 10px; gap: 4px; }
.seg-4 .seg-btn canvas{ height: 34px; }
.seg-btn.on{
  border-color: var(--n-cyan);
  background: rgba(34,211,238,.12);
  color: var(--ink);
  box-shadow: 0 0 18px rgba(34,211,238,.16);
}
.row-btn{
  width: auto;
  padding: 7px 13px;
  margin: 0;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-soft);
  background: rgba(255,255,255,.035);
  border: 1px solid var(--line);
  border-radius: 13px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.row-btn.off{ color: var(--ink-dim); }
.sheet-done{ width: 100%; margin-top: 4px; }

.tbtn-ic{ display: inline-flex; align-items: center; gap: 6px; }
.tbtn-ic i{ font-style: normal; }

.tbtn-sq{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  width: 32px;
  height: 30px;
}
.tbtn-sq svg{ width: 17px; height: 17px; }
/* hidden 属性的兜底挪到了文件开头的全局 [hidden] 规则 */

/* 顶栏模式切换：普通 | 练习 */
.modesw{
  flex: none; display: inline-flex; gap: 2px; padding: 2px;
  border-radius: 10px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.13);
}
.modesw button{
  font: inherit; font-size: 12px; line-height: 1;
  padding: 6px 8px; border: 0; border-radius: 8px;
  background: none; color: var(--ink-soft); cursor: pointer;
}
.modesw button.on{
  background: linear-gradient(180deg, rgba(127,227,255,.22), rgba(127,227,255,.08));
  color: #7fe3ff;
}
body.practising .modesw{ border-color: rgba(127,227,255,.5); }
.tbtn-sq .cross{ display: none; }
.tbtn-sq.muted{ color: var(--ink-dim); }
.tbtn-sq.muted .wave{ display: none; }
.tbtn-sq.muted .cross{ display: block; }

/* ════════ 手机横屏：按键分到左右两侧，靠下贴近拇指 ════════ */
@media (orientation: landscape) and (max-height: 600px){
  .game{ gap: 6px; }
  /* 顶栏横过来不占高度，改成右列的一组小按钮 */
  .bar{
    flex-wrap: wrap;
    gap: 5px;
    justify-content: center;
    align-self: center;
    padding: 0;
  }
  .bar .back, .bar h1, .bar .sp{ display: none; }
  .tbtn{ font-size: 10px; padding: 5px 9px; }

  .stage{
    gap: 6px 10px;
    grid-template-areas:
      "hold  board next"
      "stats board bar"
      "padl  board padr";
    grid-template-columns: minmax(140px, 1fr) minmax(170px, 250px) minmax(140px, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr);
    align-items: start;
  }
  .board-wrap{ align-self: stretch; }   /* 别跟着 align-items:start 缩掉 */
  .slot{ align-self: start; }           /* HOLD / NEXT 只要内容那么高 */
  .pads{ gap: 0; }
  /* 让左右两组按键成为 stage 的直接网格项 */
  .side{ display: contents; }          /* 横屏把三块摊回各自的格子 */
  .slot.next{ grid-area: next; }
  .slot.hold{ grid-area: hold; }
  .stats{ grid-area: stats; }
  .pads{ display: contents; }
  .pad-l{ grid-area: padl; align-self: end; }
  .pad-r{ grid-area: padr; align-self: end; }
  /* 横屏两组按键区是 stage 的直接网格项（grid-area 要写在网格元素上）。
     中间有一周竖屏把 .pad-g 改成过 display:contents，横屏两组直接变 0 尺寸，
     这条显式写 display:grid 就是防那个。 */
  .pad-g{ display: grid; gap: 7px; grid-template-columns: 1fr 1fr; }
  /* 横屏里 .side 是 display:contents，软降成了 stage 的直接网格项，得自己找位置：
     和右下那组（落底 变换）共用一格，贴顶靠右 —— 就在变换上面 */
  .side .k-soft{ grid-area: padr; align-self: start; justify-self: end; width: 112px; margin: 0; min-height: 44px; }
  .k{ min-height: 50px; }
  .k .ico{ font-size: 19px; }
  .k-drop{ justify-self: center; width: max(48px, 72%); margin: 0; }

  .stats{ grid-template-columns: 1fr 1fr; gap: 5px; align-content: start; }
  .stat{ padding: 3px 5px 4px; text-align: left; }
  .stat span{ font-size: 7.5px; }
  .stat b{ --stat-fs: 13px; }

  .slot{ padding: 5px 6px 6px; }
  .slot.hold canvas{ width: 100%; height: 40px; }
  .slot.next canvas{ width: 100%; height: 96px; }
  .slot-label{ font-size: 8px; }

  .overlay .card{ max-width: 340px; }
  .overlay h2{ font-size: 18px; }
  .overlay p{ font-size: 11.5px; margin-bottom: 10px; }
  .final{ grid-template-columns: repeat(4, 1fr); margin-bottom: 10px; }
  .pbtn{ padding: 9px 26px; font-size: 12px; }
}

/* ════════ 手机竖屏细调 ════════ */
@media (orientation: portrait), (min-height: 601px){
  @media (max-width: 859px){
    .bar{ gap: 6px; padding: 8px 2px 2px; }
    .bar h1{ display: none; }        /* 按钮多了，手机上标题让位 */
    /* 全屏键去掉之后腾出位置，文字可以留着 —— 光图标看不出「样式」是干嘛的 */
    .tbtn-ic{ padding: 5px 10px 5px 8px; gap: 5px; }
    .tbtn-ic svg{ width: 14px; height: 14px; }
    .back{ font-size: 11px; padding: 5px 9px 5px 7px; white-space: nowrap; }
    .tbtn{ font-size: 10.5px; padding: 6px 9px; }

    .stat{ padding: 3px 3px 4px; }
    .stat span{ font-size: 7.5px; letter-spacing: .06em; }
    .stat b{ --stat-fs: 14px; letter-spacing: -.02em; }

    .slot{ padding: 4px 4px 5px; }
    .slot-label{ font-size: 8px; letter-spacing: .1em; }
  }
}

/* 很窄的手机 */
@media (max-width: 380px){
  .game{ padding-left: max(10px, env(safe-area-inset-left)); padding-right: max(10px, env(safe-area-inset-right)); }
  .pads{ gap: 8px; }
  .pad{ gap: 5px; }
  .k{ min-height: 50px; }
  /* 原来写死 98px —— 第一版右栏更宽时定的；右栏缩到 64px 之后，375 宽的手机上
     NEXT 一直溢出框外 34px（SE 2/3、mini、X/11 Pro 都是 375） */
  .slot.next canvas{ width: 100%; }
  .stat b{ --stat-fs: 12.5px; }
  .tbtn{ font-size: 10px; padding: 5px 7px; }
  /* 多了玩法按钮之后，375 及以下顶栏排不下（实测内容 369px、条只有 365px）。
     收掉图标按钮的文字只留图标，一个省 30 多像素，够了。 */
  .tbtn-ic span{ display: none; }
  .tbtn-ic{ padding: 6px 8px; gap: 0; }
}

/* ════════ 桌面：侧栏竖排，按键收起 ════════ */
@media (min-width: 860px) and (min-height: 620px){
  .game{ gap: 12px; padding: 0 20px 18px; }
  .stage{
    grid-template-areas:
      "bar   bar   bar"
      "hold  board next"
      "stats board next";
    grid-template-columns: 158px minmax(0, 1fr) 150px;
    grid-template-rows: auto auto minmax(0, 1fr);
    gap: 16px;
    justify-content: center;
  }
  .slot{ padding: 10px 10px 12px; }
  .side{ display: contents; }
  .slot.next{ grid-area: next; align-self: start; }
  .slot.hold{ grid-area: hold; }
  .stats{ grid-area: stats; }
  .slot.hold canvas{ width: 100%; height: 74px; }
  .slot.next canvas{ width: 100%; height: 190px; }
  .slot-label{ font-size: 10px; }
  .stats{ grid-template-columns: 1fr; gap: 8px; align-content: start; }
  .stat{ padding: 8px 10px 9px; text-align: left; }
  .stat span{ font-size: 9.5px; }
  .stat b{ --stat-fs: 20px; }
  /* 桌面用键盘，按键区收起来（空格 = 落底） */
  .pads{ display: none; }
  .overlay h2{ font-size: 26px; }
  .overlay p{ font-size: 13px; }
}
@media (min-width: 860px) and (min-height: 620px) and (max-height: 760px){
  .slot.next canvas{ height: 160px; }
}

/* ════════ 游戏模式：壳收起来，但控制键得留着 ════════ */
/* 返回入口在沉浸模式下也必须留着。
   以前是靠全屏键退出沉浸模式再点返回，全屏键删掉之后它就是唯一的出口了。 */
body.immersive .bar h1{ display: none; }
body.immersive .bar .back{ padding: 5px 10px 5px 7px; font-size: 11px; }
body.immersive .bar{ justify-content: flex-end; gap: 6px; padding: 2px 0 0; }
body.immersive .game{ gap: 6px; padding-top: max(4px, env(safe-area-inset-top)); padding-bottom: max(34px, calc(env(safe-area-inset-bottom) + 30px)); }
body.immersive .stage{ gap: 7px; }
body.immersive .tbtn{ font-size: 10.5px; padding: 5px 10px; }
/* 桌面进游戏模式时也把侧栏压扁，棋盘优先 */
@media (min-width: 860px) and (min-height: 620px){
  body.immersive .stage{ gap: 14px; }
}

/* 顶栏按钮：图标 + 文字，统一一档 */
.tbtn-ic{ display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px 6px 10px; }
.tbtn-ic svg{ width: 15px; height: 15px; display: block; flex: none; }
.tbtn-ic span{ line-height: 1; }
.tbtn:active{ transform: translateY(1px); }

/* HOLD 是个可点的槽，让它看起来像按钮 */
.slot.hold{ cursor: pointer; transition: border-color .16s, background .16s; }
.slot.hold:hover, .slot.hold:focus-visible{
  border-color: rgba(34,211,238,.38);
  background: linear-gradient(150deg, rgba(34,211,238,.10), rgba(34,211,238,.02)), var(--panel);
}
.slot.hold:active{ transform: translateY(1px); }

/* ── 反馈层 ──
   全部脱离 #boardWrap 的布局流：那里挂着 ResizeObserver → layout()，
   高度一变就重算 CELL、整盘重绘，徽章一亮一灭棋盘会跳尺寸。 */

/* 震屏。挂在 canvas 外面的新内层，不碰 .board-box 的居中 transform，
   也不跟 #board 上的 flash/flash-big 抢 animation 属性。 */
.shaker{ position: relative; line-height: 0; will-change: transform; }
.shaker.shake{ animation: shake .22s ease-out; }
.shaker.shake-big{ animation: shakeBig .34s ease-out; }
@keyframes shake{
  0%,100%{ transform: translate(0,0) }
  25%{ transform: translate(-3px, 2px) }
  60%{ transform: translate(2px, -2px) }
}
@keyframes shakeBig{
  0%,100%{ transform: translate(0,0) }
  12%{ transform: translate(-6px, 4px) }
  34%{ transform: translate(5px, -4px) }
  58%{ transform: translate(-4px, -2px) }
  80%{ transform: translate(3px, 2px) }
}

/* 危险警戒带：盖在顶部三行，只动 opacity。
   不能用边框 —— 边框颜色已经被等级占了（高等级本来就是红的），新提示会看不见。 */
.danger{
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 22%;
  pointer-events: none;
  border-radius: 10px 10px 0 0;
  background: linear-gradient(180deg, rgba(244,63,94,.34), rgba(244,63,94,0));
  opacity: 0;
  will-change: opacity;
}
.danger.on{ animation: dangerPulse 1.1s ease-in-out infinite; }
@keyframes dangerPulse{ 0%,100%{ opacity: .25 } 50%{ opacity: .85 } }

/* 分数飘字。绝对定位在棋盘上方，DOM 层做 —— canvas 那边消行期间是满帧、
   但结算页和暂停态整个循环是停的，飘字必须不依赖帧。 */
.fx{ position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.pop{
  position: absolute;
  transform: translate(-50%, -50%);
  font-family: var(--mono);
  font-weight: 600;
  white-space: nowrap;
  text-align: center;
  line-height: 1.04;
  animation: popUp 1s cubic-bezier(.2,.7,.3,1) forwards;
  will-change: transform, opacity;
}
.pop b{ display: block; font-weight: 600; }
/* 道具/压实清出来的行。刻意做得比亲手打出来的素 —— 它不该抢 TETRIS 的风头 */
/* 梭哈结算。赢的金、输的红，都比普通飘字重一点 */
.pop.bet b{ font-size: 1.05em; }
/* 热流：橙金，比消行飘字小一号，它是附加不是主角 */
.pop.flow b{ color: #ffb703; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 18px rgba(255,183,3,.6); }
.pop.flow i{ color: #ffb703; }
.pop.bet.win b{ color: #ffd166; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 22px rgba(255,193,71,.65); }
.pop.bet.win i{ color: #ffd166; }
.pop.bet.lose b{ color: #ff8f7a; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 20px rgba(244,63,94,.6); }
.pop.bet.lose i{ color: #ff8f7a; }
.pop.tool b{ color: #9fb4d0; text-shadow: 0 0 10px rgba(159,180,208,.35); }
/* 全消：整局可能一次都打不出来，飘字要压得住场 */
.pop.perfect b{
  color: #fff;
  text-shadow: 0 2px 12px rgba(0,0,0,.7), 0 0 30px rgba(124,244,255,.9), 0 0 60px rgba(124,244,255,.5);
  letter-spacing: .22em;
}
.pop.perfect i{ color: #7cf4ff; letter-spacing: .3em; }
/* 破纪录 */
.pop.record b{ color: #ffd166; text-shadow: 0 2px 12px rgba(0,0,0,.7), 0 0 26px rgba(255,193,71,.8); }
.pop.record i{ color: #ffb703; letter-spacing: .24em; }
/* 冰裂：行凑满了却不消，这句话必须看得见 */
.pop.thaw b{ color: #bfe9ff; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 20px rgba(191,233,255,.7); }
/* 燃点：橙红，和 FEVER 的青、梭哈的金都分得开 */
.pop.burn b{ color: #ff9a4d; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 24px rgba(255,120,50,.85); }
.pop.burn i{ color: #ffb86b; letter-spacing: .2em; }
/* 过线：青绿，和破纪录的金、燃点的橙都分得开 */
.pop.goal b{ color: #8ef5c0; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 22px rgba(95,224,200,.8); }
.pop.goal i{ color: #5fe0c8; letter-spacing: .22em; }
/* 彩虹行命中 */
.pop.rain b{ color: #ff6bd6; text-shadow: 0 2px 12px rgba(0,0,0,.7), 0 0 26px rgba(255,107,214,.9); }
.pop.rain i{ color: #ffd166; letter-spacing: .2em; }
/* 结算页那一行 */
.overgoal{ margin: 2px 0 0; font-size: 12px; color: var(--ink-soft); letter-spacing: .04em; }
.overgoal.hit{ color: #8ef5c0; }
.pop.thaw i{ color: #8fd3f4; }

/* 破纪录时分数格本身高亮回弹 —— 那才是玩家盯着的地方 */
.stat.record{ animation: recordPop .7s cubic-bezier(.2,.8,.3,1); }
@keyframes recordPop{
  0%  { transform: scale(1);    box-shadow: 0 0 0 rgba(255,193,71,0); }
  28% { transform: scale(1.14); box-shadow: 0 0 22px rgba(255,193,71,.75); border-color: rgba(255,193,71,.9); }
  100%{ transform: scale(1);    box-shadow: 0 0 0 rgba(255,193,71,0); }
}
@media (prefers-reduced-motion: reduce){ .stat.record{ animation: none; } }
.pop.tool i{ color: #6f88a8; }
/* 类型名固定小号：它是说明，不该跟着分数一起长大抢戏 */
.pop i{
  display: block;
  margin-top: 2px;
  font-size: 9.5px;
  font-style: normal;
  font-weight: 500;
  letter-spacing: .14em;
  opacity: .92;
}
@keyframes popUp{
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.7) }
  18%  { opacity: 1; transform: translate(-50%, -62%) scale(1.12) }
  35%  { transform: translate(-50%, -66%) scale(1) }
  100% { opacity: 0; transform: translate(-50%, -128%) scale(.96) }
}

/* ── 飘字分级 ──
   颜色只说"这是哪种消除"：普通消除走冷色梯度，T-spin 系走品红，四行走金，
   全消走彩虹。字号和附加特效在 JS 那边给，三条轴各管各的，
   所以 TETRIS 和 T-SPIN TRIPLE 不会再长成一个样。
   渐变字统一 background-clip；不支持的浏览器退回 color，不会变透明。 */
.pop.t1{ color: #b8c6da; text-shadow: 0 2px 10px rgba(0,0,0,.6); }
.pop.t2{ color: #5fe0c8; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 14px rgba(95,224,200,.45); }
.pop.t3{ color: #7aa8ff; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 18px rgba(122,168,255,.55); }
.pop.tm{ color: #c9a6ff; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 14px rgba(201,166,255,.45); }
.pop.ts{ color: #ff6bd6; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 20px rgba(255,107,214,.6); }

.pop.t4, .pop.ts3, .pop.pc{
  filter: drop-shadow(0 2px 10px rgba(0,0,0,.7));
}
.pop.t4 b, .pop.ts3 b, .pop.pc b{
  background-clip: text; -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
.pop.t4{ color: #ffd166; }
.pop.t4 b{ background-image: linear-gradient(100deg, #fff3c4, #ffc247 42%, #ff9a3c); }
.pop.t4 i{ color: #ffd9a0; }

.pop.ts3{ color: #ff8ae0; }
.pop.ts3 b{ background-image: linear-gradient(100deg, #ffffff, #ff6bd6 55%, #a855f7); }
.pop.ts3 i{ color: #ffb3ea; }

.pop.pc{ color: #fff; }
.pop.pc b{ background-image: linear-gradient(100deg, #ff6b9d, #ffd166 30%, #5fe0c8 58%, #7aa8ff 82%, #c9a6ff); }
.pop.pc i{ color: #ffe9b8; }

/* B2B：给字镀一圈金边，和底色无关，任何类型都能叠 */
.pop.b2b i{ color: #ffe1a0; }
.pop.b2b b{ -webkit-text-stroke: .6px rgba(255,209,102,.75); }
.pop.b2b.t4 b, .pop.b2b.ts3 b, .pop.b2b.pc b{ -webkit-text-stroke: 0; }
.pop.b2b.t4, .pop.b2b.ts3, .pop.b2b.pc{
  filter: drop-shadow(0 2px 10px rgba(0,0,0,.7)) drop-shadow(0 0 7px rgba(255,209,102,.7));
}
/* 连击到 5 往橙红推 */
.pop.cmb i{ color: #ff9d5c; letter-spacing: .18em; }
/* 疯狂版 FEVER：只压色调，底下是哪种消除还认得出来。
   写成 .fx .pop.hot 是为了压过上面那些双类选择器，否则 B2B+FEVER 时它不生效。 */
.fx .pop.hot{
  filter: drop-shadow(0 2px 10px rgba(0,0,0,.7))
          drop-shadow(0 0 11px rgba(255,208,70,.7))
          saturate(1.5);
}

/* B2B / 连击 常驻徽章。绝对定位贴在棋盘左上角外沿 */
.streak{
  position: absolute;
  left: 0; top: 0;
  display: flex; flex-direction: column; gap: 4px;
  pointer-events: none;
  transform: translate(6px, 6px);
}
.badge{
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  padding: 3px 7px;
  border-radius: 999px;
  line-height: 1;
  opacity: 0;
  transform: scale(.8);
  transition: opacity .18s, transform .18s;
  will-change: opacity, transform;
}
.badge.on{ opacity: 1; transform: scale(1); }
.badge.b2b{ color: #ffd9f2; background: rgba(232,121,249,.22); border: 1px solid rgba(232,121,249,.5); }
.badge.cmb{ color: #bdf3ff; background: rgba(34,211,238,.18);  border: 1px solid rgba(34,211,238,.45); }

/* 热度徽章：疯狂版的主读数。分四档换色，档位由 JS 写 data-tier，
   不逐帧改样式 —— 一局只会变几次。 */
.badge.heat{ font-size: 11px; font-weight: 600; letter-spacing: .04em; }
.badge.heat[data-tier="1"]{ color: #bdf3ff; background: rgba(34,211,238,.16); border: 1px solid rgba(34,211,238,.42); }
.badge.heat[data-tier="2"]{ color: #d8ffd0; background: rgba(95,224,200,.20); border: 1px solid rgba(95,224,200,.55); }
.badge.heat[data-tier="3"]{ color: #ffe3a8; background: rgba(255,193,71,.22); border: 1px solid rgba(255,193,71,.62); }
.badge.heat[data-tier="4"]{
  color: #fff; background: rgba(255,92,92,.28); border: 1px solid rgba(255,120,80,.85);
  animation: heatBeat .7s ease-in-out infinite;
}
/* 第 5 档：从 ×22 起。顶档要让人知道自己已经不正常了 */
.badge.heat[data-tier="5"]{
  color: #fff; background: rgba(255,60,120,.34); border: 1px solid rgba(255,90,140,.95);
  animation: heatBeat .42s ease-in-out infinite, heatHue 1.6s linear infinite;
  text-shadow: 0 0 10px rgba(255,120,160,.9);
}
@keyframes heatBeat{ 0%,100%{ transform: scale(1) } 50%{ transform: scale(1.12) } }

/* ── 边框 = 热度计 ──
   全部只改 #board 已有的那四个 CSS 变量，以及 .board-aura 的 opacity/filter。
   变量一变走的是 #board 自己的 box-shadow transition（0.55s），
   而档位一局只跳几次，不是逐帧 —— 所以这里没有 box-shadow 动画。 */
/* 边框的粗细和颜色由 JS 的 syncEdge 直接写内联变量 —— 在这里写
   body[data-heat] #board{--bd-w:...} 是压不过内联样式的，别再试。
   这一层只负责动画，因为 animation 不在内联里。 */
body[data-heat="3"] .board-aura{ animation: auraPulse 1.5s ease-in-out infinite; }
body[data-heat="4"] .board-aura{ animation: auraPulse .8s ease-in-out infinite, heatHue 3.2s linear infinite; }
/* 第 5 档：光晕更亮、脉动更快、色相转得更凶 */
body[data-heat="5"] .board-aura{
  animation: auraPulse .5s ease-in-out infinite, heatHue 1.4s linear infinite;
  opacity: 1;
}
@keyframes heatHue{ from{ filter: hue-rotate(0deg) } to{ filter: hue-rotate(360deg) } }

/* ── 事件预告 ── */
.evwarn{
  position: absolute;
  left: 50%; top: 26%;
  transform: translate(-50%, -50%) scale(.85);
  font-family: var(--mono);
  font-size: 22px; font-weight: 600; letter-spacing: .22em;
  padding: 8px 18px; border-radius: 12px;
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s, transform .2s;
  will-change: opacity, transform;
}
.evwarn.on{ opacity: 1; transform: translate(-50%, -50%) scale(1); animation: evPulse .5s ease-in-out infinite alternate; }
.evwarn[data-bad="1"]{ color: #ffd9d0; background: rgba(244,63,94,.24); border: 1px solid rgba(244,63,94,.7); }
.evwarn[data-bad="0"]{ color: #d8ffd0; background: rgba(95,224,200,.22); border: 1px solid rgba(95,224,200,.7); }
@keyframes evPulse{ from{ transform: translate(-50%,-50%) scale(1) } to{ transform: translate(-50%,-50%) scale(1.09) } }

/* ── 事件覆盖层 ──
   只有一个元素、只动 transform，走合成器，不吃 paint。
   条纹本体做得比容器大一圈再平移，避免动 background-position（那会重绘）。 */
.evfx{
  position: absolute; inset: 0;
  overflow: hidden; border-radius: 12px;
  pointer-events: none;
  opacity: 0; transition: opacity .25s;
}
.evfx i{ position: absolute; display: block; will-change: transform; }
body[data-ev="wind"] .evfx, body[data-ev="slam"] .evfx{ opacity: 1; }

/* 狂风：横向流线，跟方块被吹偏的方向一致 */
body[data-ev="wind"] .evfx i{
  inset: -12% -110%;
  background: repeating-linear-gradient(90deg,
    rgba(150,215,255,.20) 0 2px, rgba(150,215,255,.07) 2px 5px, transparent 5px 48px);
  animation: windFlow 1.05s linear infinite;
}
@keyframes windFlow{ from{ transform: translateX(0) } to{ transform: translateX(48px) } }

/* 瞬落：竖向下坠流线 + 底部一道受力光 */
body[data-ev="slam"] .evfx i{
  inset: -110% -12%;
  background: repeating-linear-gradient(180deg,
    rgba(255,170,120,.13) 0 2px, rgba(255,170,120,.045) 2px 5px, transparent 5px 54px);
  animation: slamFall .5s linear infinite;
}
@keyframes slamFall{ from{ transform: translateY(0) } to{ transform: translateY(54px) } }
body[data-ev="slam"] .evfx{
  box-shadow: inset 0 -22px 24px -18px rgba(255,140,60,.38);
}

@media (prefers-reduced-motion: reduce){
  .evfx i{ animation: none !important; }
}

/* 暗幕：盘面压暗一点，配合 canvas 那边只画轮廓 */
body[data-ev="blackout"] #board{ filter: brightness(.82); }
/* 镜像：给个持续的提示，不然玩家只会以为是 bug */
body[data-ev="mirror"] .shaker{ outline: 2px dashed rgba(232,121,249,.55); outline-offset: 3px; }
/* 缓流：灰线停了。边框换成一圈静下来的青色，和镜像那圈虚线（紫）、
   狂欢局那圈金色都区分得开。有利事件要让人一眼看出「这次是好事」。 */
body[data-ev="calm"] .shaker{ outline: 2px solid rgba(95,224,200,.6); outline-offset: 3px; }
body[data-ev="calm"] #board{ filter: saturate(1.08); }
/* 燃点进行中：盘面光晕烧成橙红并急促脉动。
   不能用 .shaker::after —— 那个已经被狂欢局的金环占了，::before 被 FEVER
   背景流动占了，两个伪元素都没空位。改挂在 .board-aura 这个真实元素上，
   狂欢局 + 燃点同时发生时两者各用各的层，不打架。 */
body.burning .board-aura{
  /* 颜色由 syncEdge 写内联（CSS 压不过它），这里只管节奏和亮度 */
  animation: auraPulse .45s ease-in-out infinite !important;
  opacity: 1;
}
@media (prefers-reduced-motion: reduce){
  body.burning .board-aura{ animation: none !important; opacity: .8; }
}
/* 缓流：预警条停住并变青。它的价值就是「灰线停了」，表现就该做在那根条上 */
body[data-ev="calm"] .gwarn i{ background: #5fe0c8 !important; animation: none !important; }
body[data-ev="calm"] .gwarn{ box-shadow: 0 0 10px rgba(95,224,200,.5); }

/* ── 梭哈 ──
   压成一条横幅摆在顶上。第一版做成居中的大卡片，实测会把下落区整个盖住，
   而它偏偏只在打完大消除、正要接着打的时候出现 —— 最不该挡人的时机。 */
/* 梭哈弹出时盘面上的三秒提示。
   比旧横幅窄、贴得更靠上，而且三秒就走 —— 旧的那条 291×67 压在第 1.8~4 行
   停满十秒，正好盖住你还在挪方块、看形状的那一段。 */
.betflash{
  position: absolute;
  left: 50%;
  /* 不能贴到 5% —— 左上角那列徽章（热度 / B2B / COMBO）最高能长到盘面 13%，
     横幅会把它们整个盖住。下移到徽章之下。 */
  top: 16%;
  transform: translate(-50%, 0);
  width: min(90%, 272px);
  display: none;
  padding: 8px 12px;
  border-radius: 12px;
  text-align: center;
  background: linear-gradient(120deg, rgba(52,26,72,.94), rgba(24,13,34,.94));
  border: 1px solid rgba(255,193,71,.62);
  box-shadow: 0 0 24px rgba(255,160,50,.24), 0 8px 24px rgba(0,0,0,.5);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.betflash.on{ display: block; animation: betflashIn .2s cubic-bezier(.2,.8,.3,1) forwards; }
.betflash:active{ transform: translate(-50%, 1px); }
@keyframes betflashIn{
  from{ opacity: 0; transform: translate(-50%, -8px) scale(.96) }
  to  { opacity: 1; transform: translate(-50%, 0) scale(1) }
}
.betflash-txt{ font-size: 10px; line-height: 1.6; color: var(--ink-soft); }
.betflash-txt b{
  font-family: var(--mono); font-size: 13px; letter-spacing: .16em;
  color: #ffd166; margin-right: 6px;
}
.betflash-txt i{ font-style: normal; font-size: 9px; color: rgba(255,209,102,.78); }

/* 收手按钮：次要动作，不能和「继续」抢视觉 */
.pbtn.ghost{
  background: transparent;
  border: 1px solid rgba(255,255,255,.22);
  color: var(--ink-soft);
  font-size: 13px;
  margin-top: 8px;
}
.pbtn.ghost:active{ background: rgba(255,255,255,.06); }

/* 赌约进行中：边框常亮提醒「你还欠一次消行」 */
body.betting .shaker{ outline: 2px solid rgba(255,193,71,.7); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce){
  .betflash.on{ animation: none; opacity: 1; }
  .evwarn.on{ animation: none; }
  }

@media (prefers-reduced-motion: reduce){
  .shaker.shake, .shaker.shake-big{ animation: none; }
  .danger.on{ animation: none; opacity: .6; }
  .pop{ animation-duration: .01s; }
  .badge.heat[data-tier="4"]{ animation: none; }
  body[data-heat="3"] .board-aura,
  body[data-heat="4"] .board-aura,
  body[data-heat="5"] .board-aura{ animation: none; opacity: .55; filter: none; }
  .badge.heat[data-tier="5"]{ animation: none; }
}

/* 音乐 / 音效 三选一 */
.seg-3{ grid-template-columns: repeat(3, 1fr); }
.seg-3 .seg-btn{ padding: 8px 4px; font-size: 11.5px; gap: 0; }

/* ── FEVER ── */
/* 只动 opacity / transform 的东西放这层；盘面配色由 JS 换 activePal 处理 */
body.fever .board-aura{
  --au-ring: rgba(255,210,63,.9);
  --au-glow: rgba(255,140,0,.55);
  animation: auraPulse 0.55s ease-in-out infinite !important;
  opacity: 1;
}
body.fever .hud .cell.hi b{ color: #ffd23f; }
body.fever .topline{ animation: feverLine 1.1s linear infinite; }
@keyframes feverLine{ 0%{ filter: hue-rotate(0deg) } 100%{ filter: hue-rotate(360deg) } }

/* FEVER 横幅：只动 transform/opacity，走合成层 */
.feverbar{
  position: absolute; left: 0; right: 0; top: 38%;
  text-align: center;
  font-family: var(--mono);
  font-size: clamp(22px, 9vw, 44px);
  font-weight: 700;
  letter-spacing: .12em;
  color: #ffd23f;
  text-shadow: 0 0 24px rgba(255,160,0,.8);
  pointer-events: none;
  opacity: 0;
}
body.fever .feverbar{ animation: feverIn 1.2s ease-out forwards; }
@keyframes feverIn{
  0%   { opacity: 0; transform: scale(.6) }
  25%  { opacity: 1; transform: scale(1.1) }
  55%  { opacity: 1; transform: scale(1) }
  100% { opacity: 0; transform: scale(1.04) translateY(-14px) }
}
@media (prefers-reduced-motion: reduce){
  body.fever .board-aura{ animation: none !important; }
  body.fever .topline{ animation: none; }
  body.fever .feverbar{ animation: none; opacity: .9; }
}

/* ── 阶段 4：疯狂版特效层 ── */

/* FEVER 时背景流动。不能画进 canvas —— 格线烘在离屏 bgCv 里，
   让它动等于每帧重画两百个圆角矩形，正是当初优化掉的开销。
   这里用 #board 底下一层伪元素推平铺图，只动 transform。 */
.shaker::before{
  content: '';
  position: absolute; inset: -40% -10%;
  pointer-events: none;
  opacity: 0;
  background:
    repeating-linear-gradient(115deg, rgba(255,180,40,.10) 0 8px, transparent 8px 34px);
  will-change: transform, opacity;
}
body.fever .shaker::before{
  opacity: .5;                 /* 太浓会盖住方块，压到一半刚好是"有东西在动"的程度 */
  animation: feverFlow 1.6s linear infinite;
}
@keyframes feverFlow{ from{ transform: translate(0,0) } to{ transform: translate(0, 38px) } }

/* 连击 5+ 盘面边缘霓虹呼吸。复用光环层，只动 opacity。 */
body.hotcombo .board-aura{
  --au-ring: rgba(120,235,255,.85);
  --au-glow: rgba(34,211,238,.45);
  animation: auraPulse 0.85s ease-in-out infinite;
}
body.hotcombo .badge.cmb{ box-shadow: 0 0 14px rgba(34,211,238,.55); }
/* 连击 10+ / 15+：光晕一段比一段急，徽章一段比一段亮 */
body.hotcombo2 .board-aura{
  --au-ring: rgba(170,245,255,.95);
  --au-glow: rgba(34,211,238,.6);
  animation: auraPulse .6s ease-in-out infinite;
}
body.hotcombo2 .badge.cmb{ box-shadow: 0 0 20px rgba(34,211,238,.8); }
body.hotcombo3 .board-aura{
  --au-ring: rgba(255,255,255,.98);
  --au-glow: rgba(124,244,255,.75);
  animation: auraPulse .4s ease-in-out infinite, heatHue 2s linear infinite;
  opacity: 1;
}
body.hotcombo3 .badge.cmb{
  box-shadow: 0 0 26px rgba(124,244,255,.95);
  animation: heatBeat .5s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce){
  body.fever .shaker::before{ animation: none; opacity: .5; }
  body.hotcombo .board-aura,
  body.hotcombo2 .board-aura,
  body.hotcombo3 .board-aura{ animation: none; opacity: .7; }
  body.hotcombo3 .badge.cmb{ animation: none; }
}

/* 缓期：状态框里的那一行。用青色 —— 和「坏事」的红、「好事」的金都分得开，
   它既不是奖励也不是惩罚，是一笔交易。 */
.fxs.delay{ --fx: #5fe0c8; }
.fxs.delay .fxn{ color: #5fe0c8; }
.fxs.delay .fxgo{
  background: linear-gradient(180deg, rgba(95,224,200,.22), rgba(95,224,200,.1));
  border-color: rgba(95,224,200,.55);
  color: #8ff0de;
}

/* ZONE。紫色 —— 盘面上没有别的东西用紫，灰线是蓝灰、冰是浅蓝、
   燃点是金、缓期是青，不会认错。 */
.fxs.zone{ --fx: #b49bff; }
.fxs.zone .fxn, .fxs.zoneon .fxn{ color: #b49bff; }
.fxs.zoneon .fxgo{
  background: linear-gradient(180deg, rgba(180,155,255,.26), rgba(180,155,255,.12));
  border-color: rgba(180,155,255,.6);
  color: #d4c4ff;
}
.pop.zone b{ color: #d4c4ff; text-shadow: 0 0 14px rgba(180,155,255,.9); }

/* ZONE 进行中：盘面压一层紫，光晕换色。重力停了，这层底色是唯一的「时间不一样了」的信号 */
/* 边框/光晕的颜色不写在这里 —— --au-ring 是 syncEdge() 用内联样式写的，
   CSS 压不过它。ZONE 的染色折在 syncEdge 里（和燃点同一个坑）。 */
body.zoning .shaker{ filter: saturate(1.15) hue-rotate(-8deg); }
body.zoning .board-aura{
  box-shadow: 0 0 44px rgba(180,155,255,.55), inset 0 0 70px rgba(107,79,216,.3);
  animation: zonePulse 1.1s ease-in-out infinite alternate;
}
@keyframes zonePulse{ from{ opacity: .7 } to{ opacity: 1 } }
@media (prefers-reduced-motion: reduce){ body.zoning .board-aura{ animation: none; } }

/* 修行三选一。单行排版 —— 面板要尽量矮，盖住盘面越少越好：
   局内那次（每 50 行）弹出时你的堆就在盘面下半部分，盖住了等于
   选完回来丢了对局面的记忆。两行卡片时整个面板 393px、盖住 57~62%。 */
.ups{ display: flex; flex-direction: column; gap: 7px; margin: 7px 0 2px; }
/* 彩虹行指示：棋盘右框外侧的小三角，尖端朝左指那一行。
   用 border 画三角，这样它完全在 canvas 之外，一个格子都挡不到。 */
.rainmark{
  position: absolute;
  /* 整个三角都要在棋盘外侧，所以偏移量要把自身宽度算进去。
     只写 right:-3px 的话三角本体还压在盘内，会挡住最后一列。 */
  right: calc(-3px - var(--rm-w, 10px));
  width: 0; height: 0;
  border-top: var(--rm-h) solid transparent;
  border-bottom: var(--rm-h) solid transparent;
  border-right: var(--rm-w) solid #ff6bd6;
  filter: drop-shadow(0 0 6px rgba(255,107,214,.95));
  animation: rainPulse 1.5s ease-in-out infinite alternate;
  pointer-events: none;
  z-index: 3;
}
@keyframes rainPulse{ from{ opacity: .72 } to{ opacity: 1 } }
@media (prefers-reduced-motion: reduce){ .rainmark{ animation: none; } }

/* 盘面提示三个动作共用，按 data-act 换色 —— 和右侧状态栏那一行同色，
   这样「盘面上这块」和「侧栏那行」一眼能对上是同一件事。 */
.betflash[data-act="zone"]{ border-color: rgba(180,155,255,.62);
  box-shadow: 0 0 24px rgba(140,110,240,.26), 0 8px 24px rgba(0,0,0,.5); }
.betflash[data-act="zone"] .betflash-txt b{ color: #c9b6ff; }
.betflash[data-act="zone"] .betflash-txt i{ color: rgba(201,182,255,.8); }
.betflash[data-act="delay"]{ border-color: rgba(95,224,200,.62);
  box-shadow: 0 0 24px rgba(60,200,180,.24), 0 8px 24px rgba(0,0,0,.5); }
.betflash[data-act="delay"] .betflash-txt b{ color: #8ff0de; }
.betflash[data-act="delay"] .betflash-txt i{ color: rgba(143,240,222,.8); }

/* 梭哈横幅亮着时，toast 让到它下面。
   横幅占盘面 5%~31%，而 toast 默认 14% —— 正好压在横幅中间，
   两条提示叠在一起谁也读不清。 */
body.flashing .toast{ top: 38%; }

/* 讲解类横幅（ZONE 第一次）：紫色，而且不做成可点的样子 ——
   它不是号召你点，只是在说明刚才发生了什么。 */
.betflash.info{
  border-color: rgba(180,155,255,.62);
  box-shadow: 0 0 24px rgba(140,110,240,.26), 0 8px 24px rgba(0,0,0,.5);
  cursor: default;
}
.betflash.info .betflash-txt b{ color: #c9b6ff; }
.betflash.info .betflash-txt i{ color: rgba(201,182,255,.8); }

/* 矮屏（12 mini / 13 mini 这一档，视口高 629）：侧栏内容本身放得下
   （482 vs 493），溢出全来自缝隙 —— 四个块之间 3 道 8px + stats 内部 5 道 6px，
   把 482 顶到 506，于是上下各溢出 6~10px，「段位」压住按键区、「状态」钻进顶栏。
   13 Pro（664 高）是 517 vs 520 刚好塞得下，所以只收紧矮屏这一档。 */
/* 断点取 700：13 Pro（664 高）在最重的状态下 —— 连击徽章 + 梭哈的「接受」按钮
   把状态框撑高 —— 同样会溢出 4.4px，所以它也要收。14 Pro Max 那一档富余 40px，不动。 */
@media (max-height: 700px){
  .side{ gap: 5px; }
  .side .stats{ gap: 3px; }
}


/* ── 练习模式 ──
   对局时只有一条细栏（一行），其余都在 #prSheet 弹层里。
   细栏横跨两列、自成一行 —— 它总归要从盘面那一行（1fr）分走高度，
   所以只给一行：实测 iPhone 13 上格子从 26px 只降到 24px。 */
.prbar{ display: none; }
body.practising .prbar{
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 6px;
  min-height: 34px;
}
.pr-goal{
  flex: 1; min-width: 0;
  font-size: 13px; color: #7fe3ff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pr-s{
  flex: none; font: inherit; font-size: 12px; line-height: 1;
  padding: 8px 11px; border-radius: 9px; cursor: pointer;
  color: var(--ink);
  background: rgba(255,255,255,.07);
  border: 1px solid rgba(255,255,255,.14);
}
.pr-s:disabled{ opacity: .38; cursor: default; }
.pr-menu{ border-color: rgba(127,227,255,.55); color: #7fe3ff; }
/* 弹层里的内容 */
.pr-tip{ margin: 0 0 4px; font-size: 12.5px; line-height: 1.55; opacity: .78; }
/* 手感那排下面的一行：当前这档按住多久连发、每格多快 */
.hand-tip{ margin: -6px 0 12px; font-size: 11.5px; }
.pr-levels, .pr-pieces{ display: flex; flex-wrap: wrap; gap: 6px; }
.pr-levels button, .pr-pieces button, .pr-b{
  font: inherit; font-size: 12.5px; line-height: 1;
  padding: 8px 11px; border-radius: 9px; cursor: pointer;
  color: var(--ink);
  background: rgba(255,255,255,.07);
  border: 1px solid rgba(255,255,255,.14);
}
.pr-levels button.on{
  border-color: rgba(127,227,255,.65);
  background: linear-gradient(180deg, rgba(127,227,255,.16), rgba(127,227,255,.05));
  color: #7fe3ff;
}
.pr-pieces button{ min-width: 36px; font-weight: 600; font-family: var(--mono, monospace); }
.pr-row{ display: flex; gap: 6px; margin-top: 8px; }
#prSheet .sheet-lb{ margin-top: 10px; }
.pr-b{ flex: 1; min-width: 0; text-align: center; }
/* 「单招 / 组合」分组标签，独占一行 */
.pr-grp{ flex-basis: 100%; font-size: 11px; opacity: .55; letter-spacing: .08em; margin-top: 2px; }
.pr-grp:first-child{ margin-top: 0; }
/* 演示放着时把整条操作区压暗，告诉你现在别按 */
body.demoing .pads{ opacity: .4; pointer-events: none; }
.pr-b:disabled{ opacity: .38; cursor: default; }
/* 练习局不该让人以为在冲分 —— 把分数那格压暗 */
body.practising .side .stats .stat[data-k="score"]{ opacity: .5; }


/* 窄屏（iPhone SE 一类 320px）原来单独把落底的边距收到 3px 保住 44pt；
   现在落底宽度是 max(48px, 64%)，自带下限，这条不需要了 */

/* 有模式开关的顶栏（只在标准版）：手机宽度上「样式 / 暂停 / 重开」只留图标。
   实测 iPhone 13 带文字要 422px、顶栏只有 370px，会折行；收成图标之后余 30 多 px。
   用 :has 只管标准版 —— 疯狂版没有开关，顶栏照旧带文字。 */
@media (max-width: 480px){
  .bar:has(.modesw) .tbtn-ic span{ display: none; }
  .bar:has(.modesw) .tbtn-ic{ padding: 6px 8px; gap: 0; }
}
/* 窄屏顶栏：加了模式开关之后，320 宽排不下（按钮已经只剩图标了）。
   「合集」两个字收起来只留箭头，开关再紧一点。 */
@media (max-width: 360px){
  .bar:has(.modesw) .back span{ display: none; }
  /* 三格开关（普通 练习 提示）之后 320 宽又排不下了，再收一档 */
  .modesw button{ font-size: 11px; padding: 6px 6px; }
  .bar:has(.modesw){ gap: 4px; }
}

/* ── 堆叠提示模式 ──
   细栏和练习那条同一个位置、同一个高度（一行），盘面只让出这一行。 */
.hintbar{ display: none; }
body.hinting .hintbar{
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 8px;
  min-height: 34px;
}
.hint-why{
  flex: 1; min-width: 0;
  font-size: 13px; color: #5ee08a;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hint-score{ flex: none; font-size: 11.5px; opacity: .65; font-variant-numeric: tabular-nums; }
/* 没有 HOLD：框压暗，告诉你这个模式不用它 */
/* （原来这里把 HOLD 框压暗：那时提示模式不能暂存。现在能、而且常推荐暂存，不再压暗） */
body.hinting .modesw{ border-color: rgba(94,224,138,.5); }
body.hinting .modesw button.on{
  background: linear-gradient(180deg, rgba(94,224,138,.22), rgba(94,224,138,.08));
  color: #5ee08a;
}

/* ── 每日练习 ──
   细栏和练习/提示那条同一个位置、同一个高度。堆叠段借用提示模式，
   但推荐理由写在这条里，提示细栏收起来，免得两条一起挂、盘面少两行。 */
.drbar{ display: none; }
body.drilling .drbar{
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 6px;
  min-height: 34px;
}
body.drilling.hinting .hintbar{ display: none; }
body.drilling .pr-goal{ color: #ffd166; }
body.drilling.hinting .pr-goal{ color: #5ee08a; }
.dr-stat{ flex: none; font-size: 12px; opacity: .75; font-variant-numeric: tabular-nums; }
/* 冲刺幽灵：比你历史最好的那次快（绿）/ 慢（红）多少秒 */
.dr-ghost{ flex: none; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
.dr-ghost.up{ color: #5ee08a; }
.dr-ghost.dn{ color: #ff7a7a; }
.dr-entry{ width: 100%; margin: 0 0 8px; font-size: 14px; }
.dr-body{ display: flex; flex-direction: column; gap: 6px; }
.dr-st{
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px; border-radius: 10px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.10);
}
.dr-st b{ font-size: 13.5px; }
.dr-st span{ font-size: 12px; opacity: .78; }
.dr-st em{ font-style: normal; font-size: 11.5px; color: #5ee08a; }
.dr-st.now{ border-color: rgba(255,209,102,.6); }
.dr-st.done b{ color: #5ee08a; }
.dr-st.skip{ opacity: .45; }
.dr-body .sheet-lb{ margin-top: 6px; }
.dr-hist{ width: 100%; border-collapse: collapse; font-size: 11.5px; font-variant-numeric: tabular-nums; }
.dr-hist th, .dr-hist td{ padding: 4px 2px; text-align: center; border-bottom: 1px solid rgba(255,255,255,.08); }
.dr-hist th{ font-weight: 500; opacity: .6; }
#drSheet .pr-b:disabled{ opacity: .38; cursor: default; }
/* 开局卡片在 SE 上只有两百来像素宽：等宽字 + 字距的次按钮会折成两行，收紧左右留白、不折行 */
@media (max-width: 360px){
  .overlay [data-when="start"] .pbtn.ghost{ white-space: nowrap; padding-left: 14px; padding-right: 14px; letter-spacing: .04em; }
}

/* ── 提示模式：规则 ──
   细栏多了两个小按钮，理由那段让出位置、照旧单行省略。理由本身可点（开规则卡）。 */
.hint-why{ cursor: pointer; }
.hintbar .pr-s{ padding: 7px 8px; }
#hintHide.on{ border-color: rgba(94,224,138,.6); color: #5ee08a; }
.hint-rules{ border-color: rgba(94,224,138,.55); color: #5ee08a; }
.hr-body{ display: flex; flex-direction: column; gap: 6px; }
.hr-rule{
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px; border-radius: 10px;
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.10);
}
.hr-rule b{ font-size: 13.5px; color: #5ee08a; }
.hr-rule span{ font-size: 12px; opacity: .8; line-height: 1.5; }
.hr-rule em{ font-style: normal; font-size: 11.5px; opacity: .6; }
.hr-rule.worst{ border-color: rgba(255,209,102,.6); }
.hr-rule.worst em{ color: #ffd166; opacity: 1; }
.hr-body .sheet-lb{ margin-top: 6px; }
.hr-fits{ display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.hr-fit{
  display: flex; gap: 8px; align-items: flex-start;
  padding: 7px 9px; border-radius: 10px; background: rgba(255,255,255,.04);
  font-size: 12px; line-height: 1.6;
}
.hr-fit b{ font-size: 15px; font-family: var(--mono, monospace); min-width: 14px; }
.hr-fit i{ font-style: normal; font-family: var(--mono, monospace); letter-spacing: 1px; margin-right: 5px; color: #cfe8ff; }
.hr-switch{ width: 100%; margin-top: 10px; font-size: 12px; line-height: 1.4; padding: 9px 10px; }
@media (max-width: 360px){
  .hintbar .pr-s{ padding: 7px 6px; font-size: 11px; }
  .hint-score{ display: none; }
}
/* 台阶小图：几根柱子，▁ 低 ▄ 高一格 █ 高两格。原字符藏着（复制/读屏用） */
.stp{ display: inline-flex; align-items: flex-end; gap: 1.5px; vertical-align: -1px; margin: 0 2px; }
.stp b{ position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.stp s{ display: block; width: 7px; border-radius: 1.5px; background: currentColor; text-decoration: none; }
.stp s.h0{ height: 4px; } .stp s.h1{ height: 9px; } .stp s.h2{ height: 14px; }
.hr-fit i .stp s{ width: 8px; }

/* ── 对战 / 经典 NES 入口 ── 并排两个，SE 上也是一行 */
.mode-row{ display: flex; gap: 8px; justify-content: center; }
.mode-row .pbtn.ghost{ flex: 1; min-width: 0; padding-left: 10px; padding-right: 10px; white-space: nowrap; }

/* ── 对战：右栏小窗 ── 平时不占地方；对战时把四个数字格换成它 */
.vsbox{ display: none; }
body.vsing .side .stats{ display: none; }
/* 小窗固定 1:2（和盘面一样），不跟着右栏拉长 —— 第一版 flex 撑满，量出来 52×263，
   盘面只缩在底部一小块 */
body.vsing .vsbox{
  display: flex; flex-direction: column; align-items: stretch; gap: 3px;
  padding: 5px 4px; border-radius: 12px;
  background: rgba(255,255,255,.04); border: 1px solid rgba(255,84,112,.35);
  flex: none;
}
.vsbox .slot-label{ text-align: center; color: #ff8fa3; white-space: nowrap; }
#vsCv{ display: block; width: 100%; height: auto; aspect-ratio: 1 / 2; }
.vs-info{ font-size: 10px; text-align: center; opacity: .8; font-variant-numeric: tabular-nums; line-height: 1.35; white-space: pre-line; }

/* ── 经典 NES ── 没有暂存：HOLD 框压暗 */
body.nesing .slot.hold{ opacity: .25; }
.nes-result{ display: flex; flex-direction: column; align-items: center; gap: 4px; margin-bottom: 8px; }
.nes-score{ font-size: 30px; font-family: var(--mono, monospace); font-variant-numeric: tabular-nums; color: #67e8f9; }
.vs-head{ font-size: 26px; }
.vs-head.win{ color: #5ee08a; } .vs-head.lose{ color: #ff8fa3; }
.nes-best{ font-style: normal; font-size: 12px; color: #ffd166; }
.nes-stats{ display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 14px; font-size: 11.5px; opacity: .85; }
.nes-stats span{ display: flex; flex-direction: column; align-items: center; }
.nes-stats b{ font-size: 15px; font-variant-numeric: tabular-nums; }
.nes-tier{ margin: 4px 0 0; font-size: 12px; text-align: center; opacity: .85; }
.nes-tier b{ color: #ffd166; }
.nes-levels{ display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px; }
.vs-levels{ display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px; }
.nes-levels button, .vs-levels button{
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  font: inherit; padding: 6px 2px; border-radius: 9px; cursor: pointer; color: var(--ink);
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.14);
}
.nes-levels button b, .vs-levels button b{ font-size: 14px; }
.nes-levels button span, .vs-levels button span{ font-size: 9.5px; opacity: .65; white-space: nowrap; }
.nes-levels button.on, .vs-levels button.on{ border-color: rgba(103,232,249,.7); background: rgba(103,232,249,.12); }
#nesSheet .pr-row, #vsSheet .pr-row{ margin-top: 8px; }

/* ── 重开确认 ── 主按钮（重开当前玩法）在上，换玩法一格格排，关闭占满一行 */
.rs-main{ display: block; width: 100%; margin: 2px 0 10px; }
.rs-modes{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
#restartSheet .sheet-lb{ margin-top: 2px; }
.rs-close{ display: block; width: 100%; margin-top: 10px; }

/* ── 打击感（标准版）── 幅度 / 时长 / 颜色 / 亮度都由 JS 按打击力度写进变量 */
.shaker.hit{ animation: shakeHit var(--sh-ms, .22s) cubic-bezier(.2,.7,.3,1); }
@keyframes shakeHit{
  0%, 100%{ transform: translate(0, 0); }
  12%{ transform: translate(calc(var(--sh, 3px) * -1), calc(var(--sh, 3px) * .7)); }
  30%{ transform: translate(calc(var(--sh, 3px) * .8), calc(var(--sh, 3px) * -.6)); }
  52%{ transform: translate(calc(var(--sh, 3px) * -.5), calc(var(--sh, 3px) * -.3)); }
  76%{ transform: translate(calc(var(--sh, 3px) * .25), calc(var(--sh, 3px) * .2)); }
}
.board-hit{
  position: absolute; inset: 0; border-radius: 10px; pointer-events: none;
  opacity: 0; will-change: opacity;
  box-shadow: 0 0 0 2px var(--hit-c, #fff), 0 0 40px var(--hit-c, #fff);
}
.board-hit.on{ animation: boardHit .48s ease-out; }
@keyframes boardHit{ 0%{ opacity: 0; } 14%{ opacity: var(--hit-a, .6); } 100%{ opacity: 0; } }
/* 连击计数：越连越热 */
.pop.combo b{ color: #ffe3a3; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 18px rgba(255,190,90,.6); }
.pop.combo i{ color: #ffd166; letter-spacing: .3em; }
.pop.combo.c2 b{ color: #ffb36b; text-shadow: 0 2px 10px rgba(0,0,0,.6), 0 0 24px rgba(255,140,60,.8); }
.pop.combo.c3 b{ color: #ff7a6b; text-shadow: 0 2px 12px rgba(0,0,0,.7), 0 0 30px rgba(255,80,80,.9); }
.pop.combo.c3 i{ color: #ff8f7a; }
@media (prefers-reduced-motion: reduce){
  .shaker.hit{ animation: none; }
}

/* ── T 旋练习 ── 提示模式的变体：可以暂存（HOLD 框不压暗），没有「先自己找」 */
body.hinting.tspinning .slot.hold{ opacity: 1; }
body.tspinning #hintHide{ display: none; }
body.tspinning .hint-why{ color: #ff9ce4; }
/* 开局面板三个玩法按钮：一行排三个，窄屏也不折行 */
.mode-row{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.mode-row .pbtn.ghost{ padding-left: 4px; padding-right: 4px; font-size: 12px; letter-spacing: 0; }

/* ── 界面复查（子代理）修的几处 ── */
/* 钉在底边的主按钮下面那一截（面板的下内边距）原来能透出后面滚过去的文字，补一块同色底 */
#helpSheet .sheet-done::after, #rankSheet .sheet-done::after, #drSheet .sheet-done::after, #hintSheet .sheet-done::after,
#nesSheet .sheet-done::after, #vsSheet .sheet-done::after, #prSheet .sheet-done::after, #styleSheet .sheet-done::after{
  content: ''; position: absolute; left: -24px; right: -24px; top: 100%; height: 48px;
  background: rgba(11, 16, 28, .98); pointer-events: none;
}
/* 开局 / 结算 / 暂停卡片里的按钮：外发光会被卡片的滚动裁成一块硬边长方形，换成收在卡片内边距里的柔和投影 */
.overlay .card .pbtn{ box-shadow: 0 8px 18px -10px rgba(34, 211, 238, .75); }
.overlay .card .pbtn.ghost{ box-shadow: none; }
/* 窄屏开局：三个玩法按钮用短名（对战 / NES / T 旋），还是一行三个。
   排两行会让 SE 上整张卡片放不下，最下面那个被切掉一半 */
@media (max-width: 360px){
  .mode-row .long{ display: none; }
}
/* 模式标签：盘面左上角，不挡操作 */
.mode-tag{
  position: absolute; left: 6px; top: 6px; z-index: 3; pointer-events: none;
  font-size: 10.5px; line-height: 1; padding: 4px 7px; border-radius: 7px;
  color: #ffd166; background: rgba(6, 10, 20, .72); border: 1px solid rgba(255, 209, 102, .35);
  letter-spacing: .04em;
}
/* 得分飘字：垫一层深色底，压在亮的盘面上也读得清 */
.pop{ background: rgba(6, 10, 20, .55); padding: 3px 10px 4px; border-radius: 10px; }
/* 练习面板里的每日练习：次要按钮，一行放得下 */
.dr-entry{ width: 100%; margin: 0 0 8px; font-size: 13px; white-space: nowrap; }

/* 竖屏：练习 / 提示 / 每日练习那条细栏挪到最底下，和按键排拉开 16px —— 原来贴着「变换」下沿，
   按变换时老误点到「规则」「先自己找」。底部留白相应少 16px，盘面大小不变 */
@media (orientation: portrait){
  body:is(.practising, .hinting, .drilling) .game,
  body.immersive:is(.practising, .hinting, .drilling) .game{
    padding-bottom: max(18px, calc(env(safe-area-inset-bottom) + 14px));
  }
  body.practising .prbar, body.hinting .hintbar, body.drilling .drbar{ margin-top: 16px; }
}

/* ── 顶栏菜单 ── */
.menu-grid{ display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 4px 0 12px; }
.mi{
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 12px 12px 11px; border-radius: 14px; cursor: pointer; text-align: left;
  font: inherit; color: var(--ink);
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12);
  -webkit-tap-highlight-color: transparent;
}
.mi:active{ background: rgba(127,227,255,.10); }
.mi svg{ width: 20px; height: 20px; color: #7fe3ff; margin-bottom: 5px; }
.mi b{ font-size: 14px; font-weight: 600; }
.mi span{ font-size: 11px; color: var(--ink-dim); line-height: 1.4; }
.mi .cross{ display: none; }
.mi.muted svg{ color: var(--ink-dim); }
.mi.muted .wave{ display: none; }
.mi.muted .cross{ display: block; }

/* ── 历史记录 ── */
.hist-sum{ font-size: 12px; line-height: 1.7; color: var(--ink-soft); margin: 0 0 10px; }
.hist-sum b{ color: var(--ink); font-family: var(--mono); font-weight: 500; }
.hist-sum b.up{ color: #5ee08a; }
.hist-sum b.dn{ color: #ff8a8a; }
.hist-seg{ display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }
.hist-seg button{
  font: inherit; font-size: 12px; line-height: 1; padding: 8px 10px; border-radius: 9px; cursor: pointer;
  color: var(--ink-soft); background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12);
}
.hist-seg button.on{ color: #7fe3ff; border-color: rgba(127,227,255,.6); background: rgba(127,227,255,.10); }
.hist-range button{ font-size: 11px; padding: 6px 9px; }
.hist-tip{ min-height: 18px; margin: 2px 0 4px; font-size: 11.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.hist-chart{ margin: 0 0 6px; }
.hist-chart svg{ display: block; width: 100%; height: auto; touch-action: pan-y; cursor: crosshair; }   /* 在曲线上也能上下划着滚，点一下选局 */
.hist-chart .grid{ stroke: rgba(255,255,255,.08); stroke-width: 1; }
.hist-chart .ax{ fill: var(--ink-dim); font-size: 9.5px; font-family: var(--mono); }
.hist-chart .ln{ fill: none; stroke: #22d3ee; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.hist-chart .pt{ fill: #22d3ee; stroke: rgb(11,16,28); stroke-width: 2; }
.hist-chart .pt.best{ fill: #ffd166; }
.hist-chart .pt.on{ stroke: #fff; }
.hist-chart .xh{ stroke: rgba(255,255,255,.3); stroke-width: 1; stroke-dasharray: 3 3; }
.hist-chart .vl{ fill: var(--ink); font-size: 10px; font-family: var(--mono); }
.hist-chart .vl.b{ fill: #ffd166; }
.hist-empty{ margin: 18px 0; font-size: 12.5px; color: var(--ink-dim); text-align: center; }
.hist-list{ font-size: 11.5px; font-variant-numeric: tabular-nums; margin-bottom: 10px; }
.hl-row{ display: grid; grid-template-columns: 1.5fr 1.2fr .7fr .6fr .8fr; gap: 4px; padding: 5px 2px; border-bottom: 1px solid rgba(255,255,255,.06); color: var(--ink-soft); }
.hl-row span:not(:first-child){ text-align: right; }
.hl-row.hl-h{ color: var(--ink-dim); font-size: 10.5px; }
.hl-row.best{ color: #ffd166; }
#histSheet .sheet-done{ position: sticky; bottom: 0; z-index: 1; display: block; width: 100%; box-shadow: 0 -12px 18px rgba(11,16,28,.96), 0 0 26px rgba(34,211,238,.42); }
#histSheet .sheet-done::after{ content: ''; position: absolute; left: -24px; right: -24px; top: 100%; height: 48px; background: rgba(11, 16, 28, .98); pointer-events: none; }

/* 顶栏只剩暂停和菜单两个按钮，手机上也放得下字（原来五个按钮挤，只留图标） */
@media (max-width: 480px){
  .bar:has(.modesw) #pauseBtn span, .bar:has(.modesw) #menuBtn span{ display: inline; }
  .bar:has(.modesw) #pauseBtn, .bar:has(.modesw) #menuBtn{ padding: 6px 10px 6px 8px; gap: 4px; }
}

/* ── 中途开局 ── */
.mid-tiers{ display: flex; flex-direction: column; gap: 8px; margin: 6px 0 4px; }
.mid-t{
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left;
  padding: 10px 12px; border-radius: 12px; cursor: pointer; font: inherit; color: var(--ink);
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12);
}
.mid-t b{ font-size: 15px; }
.mid-t span{ font-size: 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.mid-t em{ font-style: normal; font-size: 11px; color: var(--ink-dim); }
.mid-t.on{ border-color: rgba(255,138,76,.7); background: rgba(255,138,76,.12); }
.mid-t.on b{ color: #ff8a4c; }
.mid-src{ font-size: 11.5px; }
/* 中途开局的入口统一用暖橙色：开局面板、重开面板、档位面板的「开始」、对局里左上角的模式标签。
   别的入口都是灰描边 / 青色，橙色单独一个，一眼找得到 */
.pbtn.ghost.mid-entry{ border-color: rgba(255,138,76,.65); color: #ff8a4c; }
.pbtn.ghost.mid-entry:active{ background: rgba(255,138,76,.14); }
.pr-b.mid-entry{ border-color: rgba(255,138,76,.55); color: #ff8a4c; background: rgba(255,138,76,.08); }
#midGo{ background: linear-gradient(180deg, #ffa877, #ff8a4c); box-shadow: 0 0 26px rgba(255,138,76,.42); }
#midSheet #midGo{ box-shadow: 0 -12px 18px rgba(11,16,28,.96), 0 0 26px rgba(255,138,76,.42); }
.mode-tag.mid{ color: #ff8a4c; border-color: rgba(255,138,76,.55); }
#midSheet .sheet-done{ position: sticky; bottom: 0; z-index: 1; display: block; width: 100%; box-shadow: 0 -12px 18px rgba(11,16,28,.96), 0 0 26px rgba(34,211,238,.42); }
.hl-row em{ font-style: normal; font-size: 10px; margin-left: 4px; padding: 1px 4px; border-radius: 4px; background: rgba(127,227,255,.12); color: #7fe3ff; }
.hl-row.mid{ color: var(--ink-dim); }

/* 弹出面板只许上下滚：钉底按钮后面那块遮挡底左右各伸出 24px，把面板撑宽了 6px，
   手指斜着一划整块面板就左右漂。横向裁掉，滚到头也不带动后面的页面 */
.sheet-box{ overflow-x: hidden; overscroll-behavior: contain; touch-action: pan-y; }

/* 右栏「每行」：和平均水平比，高了绿、低了红 */
.stat.pace.up b{ color: #5ee08a; }
.stat.pace.dn b{ color: #ff8a8a; }
.aid-row{ display: flex; gap: 8px; flex-wrap: wrap; margin: 2px 0 6px; }
.aid-tip{ font-size: 11.5px; margin-bottom: 12px; }

/* 配色：五组、三列，每组一行小标题；色卡一排七种块 */
#palOpts{ grid-template-columns: repeat(3, 1fr); gap: 6px; }
#palOpts .pal-grp{ grid-column: 1 / -1; font-size: 10.5px; color: var(--ink-dim); letter-spacing: .06em; margin: 6px 0 0; }
#palOpts .pal-grp:first-child{ margin-top: 0; }
#palOpts .seg-btn{ padding: 7px 4px 6px; gap: 4px; font-size: 10.5px; }
#palOpts .seg-btn canvas{ height: 14px; }

/* 复盘：损失最大的几手 */
.rv-w{ display: flex; gap: 10px; align-items: flex-start; padding: 7px 0; border-top: 1px solid rgba(255,255,255,.06); }
.rv-w canvas{ flex: none; border-radius: 4px; background: rgba(0,0,0,.25); }
.rv-w span{ font-size: 11px; line-height: 1.5; color: var(--ink-soft); }
.rv-w b{ display: block; color: #ffd166; font-weight: 600; }
.rv-w em{ display: block; font-style: normal; color: var(--ink-dim); font-size: 10.5px; }

/* 复盘里占一整行的那几条（分段每行、跟教练比、T 块去向…）允许折行：原来一行放不下就省略号，「200 后」那段被切掉 */
.review .runstats span.w{ flex-wrap: wrap; }
.review .runstats span.w b{ white-space: normal; text-align: right; overflow: visible; }
/* 局结束：提示细栏收起来（占位留着，盘面不跳） */
body.gameover .hintbar{ visibility: hidden; }

/* 结算卡：一行分数 + 一行「最高 · 行 · 级」，替掉原来四个大格（和右栏重复，把复盘挤到要滑） */
.final{ display: flex; flex-direction: column; align-items: center; gap: 2px; margin: 0 0 12px; }
.final .final-score{ font-family: var(--mono); font-size: 26px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.final .final-sub{ display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; font-size: 11.5px; color: var(--ink-dim); }
.final .final-sub b{ font-family: var(--mono); font-weight: 500; color: var(--ink-soft); }

/* 「先离开这一局？」 */
.leave-go{ border-color: rgba(127,227,255,.55); color: #7fe3ff; }

/* 顶栏按键的点按区（外观不变）：竖向往外各扩 8px。原来只在 361~480 宽的竖屏里扩，320 宽的小屏和横屏
   暂停 / 菜单 / 模式键只有 23~30px 高，全靠按准。横向不扩 —— 三个模式键之间只隔 1px，往外扩只会互相抢。
   下面 361~480 那条会盖过这里（那边竖向 7px、横向 2px，暂停键单独再宽一点）。 */
.bar:has(.modesw) :is(.back, .modesw button, .tbtn){ position: relative; }
.bar:has(.modesw) :is(.back, .modesw button, .tbtn)::after{ content: ''; position: absolute; inset: -8px 0; }

/* 顶栏好按：模式开关和按钮做高一点，点击区再往外扩到 44pt 上下；「合集」和模式开关拉开，
   原来「合集」57×25、紧挨着「普通」，一碰就离开页面。320 宽的小屏放不下，照旧用紧凑的 */
@media (min-width: 361px) and (max-width: 480px){
  .bar:has(.modesw){ gap: 6px; }
  .bar:has(.modesw) .back{ margin-right: 8px; padding-top: 8px; padding-bottom: 8px; }
  .bar:has(.modesw) .modesw button{ padding: 9px 9px; font-size: 12.5px; }
  .bar:has(.modesw) #pauseBtn, .bar:has(.modesw) #menuBtn{ padding: 8px 11px; }
  .bar:has(.modesw) #pauseBtn span{ display: none; }      /* 暂停只留图标（谁都认得），省出位置给其它按钮加大 */
  .bar:has(.modesw) :is(.back, .modesw button, .tbtn){ position: relative; }
  .bar:has(.modesw) :is(.back, .modesw button, .tbtn)::after{ content: ''; position: absolute; inset: -7px -2px; }
  /* 暂停键只有 39px 宽，往左右多扩一点凑到 48px；它右边离菜单 6px，往右只扩 3px，两边各留一点不抢 */
  .bar:has(.modesw) #pauseBtn::after{ inset: -7px -3px -7px -6px; }
}
/* 上面那几条被老的 .final span / .final b（四个大格时代的块级样式）盖了，这里拉回行内 */
.final .final-sub span{ display: inline; font-family: inherit; font-size: inherit; letter-spacing: 0; color: inherit; }
.final .final-sub b{ display: inline; font-size: 12px; margin: 0; }
.final .final-score{ margin: 0; }
/* 结算卡下面还有东西（第三条建议 / 损失最大的几手）：底边一道渐隐 + 「往下滑」，滑到底就收起 */
.overlay[data-mode="over"] .card.more::after{
  content: '往下滑还有'; position: sticky; bottom: -1px; display: block; margin-top: -34px; height: 34px;
  padding-top: 14px; font-size: 10.5px; color: var(--ink-dim); text-align: center; pointer-events: none;
  background: linear-gradient(rgba(8,11,20,0), rgba(8,11,20,.96) 55%);
}

/* 提示模式：细栏下面三行详细说明（为什么 / 别处 / 按法） */
.hintbar{ flex-wrap: wrap; }
.hint-more{ flex-basis: 100%; display: flex; flex-direction: column; gap: 1px; font-size: 11px; line-height: 1.4; color: var(--ink-soft); padding: 0 2px 2px; }
.hint-more span{ display: block; }
.hint-more span:last-child{ color: #9fe6b8; }
.wc-fits{ grid-template-columns: 1fr; }
/* 暂停：遮罩变半透明、「继续」缩到上面一条，盘面和提示都看得见（照着推荐想一想再接着打） */
.overlay[data-mode="pause"]{ background: rgba(8, 11, 20, .32); place-items: start center; }
.overlay[data-mode="pause"] .card{ padding: 8px 12px; border-radius: 14px; background: rgba(11, 16, 28, .88); margin-top: 8px; }
.overlay[data-mode="pause"] .card h2{ font-size: 15px; margin: 0 0 4px; }
.overlay[data-mode="pause"] .card p{ display: none; }
.overlay[data-mode="pause"] .card .pbtn{ padding: 8px 18px; font-size: 12px; margin: 2px 4px; }

/* 说明每条一行：折行会把盘面挤小一大截，宁可末尾省略（点「规则」看完整原则） */
.hint-more span{ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hint-more{ font-size: 10.5px; line-height: 1.35; }

/* 提示模式多了三行说明，盘面会矮一截（iPhone 17 实测 310 → 270）。说明一行不少、和按键排的 16px 间距也不动
   （贴着「变换」会误点），只收细栏自己：按钮矮一点、说明 10.5px、细栏底下只留安全区 —— 收回一档到 280 */
@media (orientation: portrait){
  body.hinting .hintbar{ min-height: 28px; }
  body.hinting .hintbar .pr-s{ padding: 5px 8px; }
  .hint-more{ font-size: 10.5px; line-height: 1.25; padding: 0; }
  body.hinting .game, body.immersive.hinting .game{ padding-bottom: max(8px, env(safe-area-inset-bottom)); }
}

/* 提示模式推荐暂存：HOLD 框亮绿边一闪一闪 + 盘面右上一条提示 */
.slot.hold.hint-hold{ border-color: #5ee08a; background: rgba(94,224,138,.12); animation: holdCue 1s ease-in-out infinite; }
@keyframes holdCue{ 0%, 100%{ box-shadow: 0 0 0 0 rgba(94,224,138,0); } 50%{ box-shadow: 0 0 14px 2px rgba(94,224,138,.55); } }
.hold-cue{
  position: absolute; top: 8px; right: 8px; z-index: 3; pointer-events: none;
  padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 600;
  color: #04120a; background: #5ee08a; box-shadow: 0 2px 10px rgba(0,0,0,.35);
}
@media (prefers-reduced-motion: reduce){ .slot.hold.hint-hold{ animation: none; } }
.hold-cue{ display: block; line-height: 1.5; height: auto; white-space: nowrap; letter-spacing: 0; font-family: inherit; }

/* 底部说明：标签粗体上色，「为什么」整行加粗最显眼；字号不放大（再大盘面又要缩一档） */
.hint-more span{ color: var(--ink); }
.hint-more b{ font-weight: 700; margin-right: 4px; }
.hint-more .hm-why{ font-weight: 600; }
.hint-more .hm-why b{ color: #5ee08a; }
.hint-more .hm-alt b{ color: #ffd166; }
.hint-more .hm-how b{ color: #7fe3ff; }
.hint-more span:last-child{ color: var(--ink); }

/* 游戏页面：长按不出「拷贝 / 选中 / 放大镜」，整页不能缩放、不能拖动。
   只给 body 设 user-select:none 的话，iOS 上按钮里的字、底部提示照样能长按选中 —— 一律给到每个元素。
   touch-action:none 让浏览器不做任何拖动 / 缩放（双击、双指、单指拖都不行）；
   只有真要滚的弹层、结算卡、历史曲线放开上下滑 */
/* 只给元素本身，别带 ::before / ::after：带上的话 WebKit 每帧重算样式变慢，计时一卡，轻点 60ms 被当成长按滑出好几格（实测） */
*{ -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
html, body{ touch-action: none; }
.sheet-box, .overlay .card, .hist-chart svg{ touch-action: pan-y; }
