/* ============================================================
 * 界面样式
 * ============================================================ */

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: calc(15px * var(--scale, 1));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  transition: background .25s ease, color .25s ease;
}

button, select, input { font-family: inherit; color: inherit; }

/* ---------------- 顶栏 ---------------- */
.topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.brand {
  font-size: calc(14px * var(--scale, 1));
  font-weight: 600;
  letter-spacing: .2px;
  white-space: nowrap;
}

.brand span { color: var(--accent); }

.topbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.sel {
  height: 34px;
  padding: 0 10px;
  border-radius: var(--radius);
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  font-size: calc(13px * var(--scale, 1));
  cursor: pointer;
  max-width: 220px;
  min-width: 0;
  text-overflow: ellipsis;
}

.sel:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

#libSelect { flex: 1 1 auto; }
#scopeSelect { flex: 0 0 auto; }

.tbtn {
  height: 34px;
  padding: 0 14px;
  border-radius: var(--radius);
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  font-size: calc(13px * var(--scale, 1));
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.tbtn .ic { width: 17px; height: 17px; flex: 0 0 auto; }
.tbtn:hover { background: var(--surface-3); }
.tbtn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.tbtn.primary:hover { background: var(--accent-2); }
.tbtn:active { transform: scale(.96); }

/* ---------------- 主题色板 ---------------- */
.swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(86px, 1fr));
  gap: 8px;
}

.sw {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 7px;
  border-radius: var(--radius);
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  cursor: pointer;
  text-align: center;
  transition: border-color .15s ease, background .15s ease, transform .12s ease;
}

.sw:hover { border-color: var(--accent); }
.sw:active { transform: scale(.96); }
.sw.on { border-color: var(--accent); background: var(--accent-soft); }

.sw-bars {
  display: flex;
  gap: 3px;
  height: 24px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.sw-bars i { flex: 1 1 0; }

/* 毛玻璃主题的色板：一条斜向渐变 + 上缘一道高光，像一小块玻璃 */
.sw-bars.gs {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .70),
              inset 0 0 0 1px rgba(255, 255, 255, .55);
}
.sw-bars.gs i { flex: 1 1 100%; }

/* 图片背景主题的色板：在渐变上再压一小片半透明「玻璃」，
   和纯色雾的九套一眼能分开 —— 表示背景是照片，不是色块 */
.sw-bars.ph { position: relative; }
.sw-bars.ph::after {
  content: "";
  position: absolute;
  left: 18%;
  right: 18%;
  bottom: 12%;
  height: 42%;
  border-radius: 4px;
  background: rgba(255, 255, 255, .40);
  border: 1px solid rgba(255, 255, 255, .70);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .18);
}

/* 呼吸主题的色板：渐变自己在极缓地鼓缩，和主题里的呼吸节奏对上，
   在设置面板里扫一眼就知道哪十套是会动的 */
.sw-bars.br i {
  transform-origin: 50% 50%;
  animation: swBreath 9s ease-in-out infinite;
}

@keyframes swBreath {
  0%, 100% { transform: scale(1);    filter: brightness(1); }
  50%      { transform: scale(1.16); filter: brightness(1.14); }
}

.sw-name {
  font-size: calc(11px * var(--scale, 1));
  color: var(--text-2);
  white-space: nowrap;
}

.sw.on .sw-name { color: var(--accent); font-weight: 500; }

/* ---------------- 舞台 ---------------- */
.stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  background:
    radial-gradient(circle at 12% 8%, var(--bg-deco) 0%, transparent 42%),
    radial-gradient(circle at 88% 92%, var(--bg-deco) 0%, transparent 42%);
}

.card {
  width: 100%;
  max-width: 860px;
  height: 100%;
  max-height: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  padding: 22px;
  overflow: hidden;
  position: relative;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 8px 24px -12px rgba(0, 0, 0, .10);
}

/* 图片区只负责「占多高」，不再负责「底色」。
   原来底色画在这一层：宽 420 × 高 340 的一块圆角色块，而配图都是正方
   （512×512 / 256×256），object-fit: contain 之后左右各剩一条 40px 的色带，
   看着就像一个空框；再加上图片自带 12px 圆角、外框 20px 圆角，
   四个角会各露出一点底色，"跟图片不匹配"就是这么来的。
   现在底色交给 .media-box（见下方 1:1 那一段），它正好裹住图片本身，
   有图时被完全盖住，只在 Emoji 兜底 / 图片没加载出来时才看得见。 */
.pic-area {
  /* 平时不缩：档位钉死，只在 step 0 → 1 缩一次。
     但要留 flex-shrink 兜底——遇到词根 + 长助记 + 三行例句那种内容，
     卡片高度塞不下，只能让图让一点；否则内容会把自评按钮顶出卡片，
     那就不是好不好看的问题，而是按钮点不到了。
     实测普通词不会触发这个兜底，只有极长内容才会。 */
  flex: 0 1 auto;
  width: min(100%, 420px);
  height: min(28vh, 210px);
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border-radius: var(--radius-lg);
  transition: opacity .3s ease, height .35s ease;
  overflow: hidden;
}

/* 图片只在「出题 → 揭晓」缩一次，之后不再变。
   原来按 0 / 1·2 / 3 / 4·5 分成四档，一轮播下来要缩三次，
   最后一次只差 15px（210 → 195），看着像一直在抽动，所以并成两档。 */
.card[data-step="0"] .pic-area { height: min(42vh, 340px); }
.card[data-step="1"] .pic-area,
.card[data-step="2"] .pic-area,
.card[data-step="3"] .pic-area,
.card[data-step="4"] .pic-area,
.card[data-step="5"] .pic-area { height: min(34vh, 260px); }

.pic-emoji { font-size: clamp(70px, 16vh, 150px); line-height: 1; }

/* 让 .pic-area 成为尺寸查询容器：下面靠 cqw / cqh 取它自己的宽高。
   .pic-area 的宽高都是显式给的（宽 min(100%,420px)、高按步骤给），
   加 size 容器不会反过来影响布局。 */
.card .pic-area {
  container-type: size;
}

/* 主卡片的图片：底色当相框，1:1 正好裹住图片，所以有图时看不见框。
   圆角与裁剪都交给这一层，图片自己不再单独设圆角 —— 两层圆角对不齐就是上一版
   "四角露底色"的元凶。

   相框边长取「容器宽、容器高」里更小的那个，宽高一起定死，永远是个正方形。
   ------------------------------------------------------------
   坑：以前写的是 height:100% + width:auto + aspect-ratio:1/1 + max-width:100%。
   高度被 100% 钉死，宽度只有被 max-width 压这一条路，两者不能同时满足时
   aspect-ratio 直接失效：
     视口 320×1000 → pic-area 272×300 → 相框算成 272×300 的长方形，
     里面 512×512 的方图 contain 之后只剩 272×272，上下各空 14px 底色，
     就是"框比图片大、上下超出图片"。
   实测 350px 宽以下必现（250×300 → 上下各空 25px）。
   现在用 min(100cqw, 100cqh)，任何视口下都必然是正方形，框 = 图，
   不会再露出底色。容器查询不支持时退回老写法（只有窄屏才会轻微变形）。 */
.card .pic-area .media-box {
  height: 100%;
  width: auto;
  aspect-ratio: 1 / 1;
  max-width: 100%;
  background: var(--pic-bg);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

@supports (width: 1cqw) {
  .card .pic-area .media-box {
    width: min(100cqw, 100cqh);
    height: min(100cqw, 100cqh);
  }
}

.pic-area img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 0;
}

.card .pic-area .media-box > img {
  width: 100%;
  height: 100%;
}

/* 兜底文字放正方形框里，长单词要能换行，不然会被裁掉 */
.card .pic-area .media-box > .pic-text {
  padding: 0 14px;
  text-align: center;
  overflow-wrap: anywhere;
}
.pic-color {
  width: clamp(90px, 22vh, 190px);
  height: clamp(90px, 22vh, 190px);
  border-radius: 50%;
  border: 1px solid var(--border-2);
}
.pic-num {
  font-family: var(--font-en);
  font-size: clamp(60px, 14vh, 130px);
  font-weight: 700;
  color: var(--accent);
  letter-spacing: -2px;
}
.pic-text {
  font-family: var(--font-en);
  font-size: clamp(26px, 6vh, 54px);
  font-weight: 600;
  color: var(--text-3);
}

/* ---- 有真实图片时：图片优先，加载失败自动露出 Emoji 兜底 ----
   .media-box 同时包住 <img> 和兜底内容，靠 .failed 切换，
   所以「图还没传」不会出现破图，只是看起来跟以前一样。 */
.media-box {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-box > img { display: block; }
.media-box > .pic-emoji,
.media-box > .pic-color,
.media-box > .pic-num,
.media-box > .pic-text { display: none; }

.media-box.failed > img { display: none; }
.media-box.failed > .pic-emoji,
.media-box.failed > .pic-color,
.media-box.failed > .pic-num,
.media-box.failed > .pic-text {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 还没拿到图片地址（新词 + 共享图按需解析）：先显示兜底内容，
   图下载完成后再淡入，避免出现空白方块。 */
.media-box.lazy > .pic-emoji,
.media-box.lazy > .pic-color,
.media-box.lazy > .pic-num,
.media-box.lazy > .pic-text {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* .plain：这个词压根没有配图（也没开共享图源），只有兜底内容。
   以前这种情况直接不包 .media-box，底色由 .pic-area 提供；
   现在底色挂在 .media-box 上，所以这里也要包一层并让兜底直接显示。 */
.media-box.plain > .pic-emoji,
.media-box.plain > .pic-color,
.media-box.plain > .pic-num,
.media-box.plain > .pic-text {
  display: flex;
  align-items: center;
  justify-content: center;
}

.media-box.loading > img { display: none; }
.media-box.lazy > img { animation: mediaFade .18s ease-out; }
@keyframes mediaFade { from { opacity: 0; } to { opacity: 1; } }

/* 生词本 / 跳转列表里的小缩略图，尺寸和原来的 Emoji 一致 */
.witem .thumb .media-box { width: 100%; height: 100%; }
.witem .thumb img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 6px;
}
.witem .thumb .pic-emoji { font-size: 19px; line-height: 1; }
.witem .thumb .pic-num { font-size: 13px; letter-spacing: 0; }
.witem .thumb .pic-color { width: 20px; height: 20px; }
.witem .thumb .pic-text { font-size: 11px; }

.answer-wrap {
  position: relative;
  width: 100%;
  /* 不再撑满剩余空间：紧跟在图片下面，文字才不会随图片变小而越飘越远 */
  flex: 0 0 auto;
  min-height: 96px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

/* 出题时（step 0）答案还没揭晓，但单词、音节、中文、释义、例句只是
   opacity: 0，位置照样占着，会把图片挤小——实测 1100×800 下，
   规范里写的 336px 被压到 265px，和揭晓后的 260px 几乎一样，
   「出题时图最大、揭晓时缩一次」就废了。
   这里只留出提示文字要的高度，把剩下的还回给图片。
   题干要显示中文（cn-on-question）时不能压，否则中文会被裁掉。 */
body:not(.cn-on-question) .card[data-step="0"] .answer-wrap {
  min-height: 64px;
  height: 64px;
  overflow: hidden;
}

.prompt {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  font-size: calc(15px * var(--scale, 1));
  letter-spacing: 1px;
  transition: opacity .3s ease;
  pointer-events: none;
}

.card.show-word .prompt { opacity: 0; }

/* 音节拆分：拼写在上、音标在下，一列一列对齐 */
.syl-line {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 4px;
}

.syl-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}

.syl-col b {
  font-family: var(--font-en);
  font-size: calc(20px * var(--scale, 1));
  font-weight: 500;
  color: var(--text-2);
  line-height: 1.2;
}

.syl-col i {
  font-style: normal;
  font-family: var(--font-en);
  font-size: calc(13px * var(--scale, 1));
  color: var(--text-3);
  line-height: 1.2;
}

.syl-line .syl-sep {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-3);
  margin-top: 10px;
  flex: 0 0 auto;
}

.answer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  width: 100%;
  text-align: center;
}

/* 逐级揭示：每一部分什么时候出现，由 card 上的 show-* class 决定。
   隐藏时用 opacity 而不是 display，这样卡片高度稳定、揭晓时不会跳动。 */
.answer > * { opacity: 0; transition: opacity .32s ease; }

.card.show-word .word,
.card.show-word .phonetic { opacity: 1; }
.card.show-syl .syl-line { opacity: 1; }
.card.show-cn .meaning { opacity: 1; }
.card.show-mn .details { opacity: 1; }
.card.show-eg .example { opacity: 1; }

.word {
  font-family: var(--font-en);
  font-size: calc(44px * var(--scale, 1));
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: .3px;
}

.phonetic {
  font-family: var(--font-en);
  font-size: calc(16px * var(--scale, 1));
  color: var(--text-3);
}

.meaning {
  font-size: calc(19px * var(--scale, 1));
  color: var(--text-2);
  transition: opacity .32s ease, font-size .3s ease;
}

.meaning .pos { color: var(--text-3); font-size: calc(13px * var(--scale, 1)); margin-right: 6px; }

.example {
  width: 100%;
  max-width: 560px;
  margin-top: 4px;
  padding: 10px 14px;
  border-radius: var(--radius);
  background: var(--surface-2);
  border: 1px solid var(--border);
  text-align: center;
  font-size: calc(13px * var(--scale, 1));
  line-height: 1.65;
}

.example .en { font-family: var(--font-en); color: var(--text); display: block; }
.example .cn { color: var(--text-3); display: block; margin-top: 3px; font-size: calc(12px * var(--scale, 1)); }

/* 例句里把目标单词标出来 */
.example em {
  font-style: normal;
  font-weight: 600;
  color: var(--accent);
}

/* ---------------- 词根词缀 / 助记 ---------------- */
.details {
  width: 100%;
  max-width: 620px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.detail {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  width: 100%;
  text-align: center;
}

.d-tag {
  flex: 0 0 auto;
  font-size: calc(11px * var(--scale, 1));
  color: var(--text-3);
  letter-spacing: 1px;
}

.d-body {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 0;
}

.d-body.mn {
  display: block;
  max-width: 560px;
  font-size: calc(12px * var(--scale, 1));
  line-height: 1.65;
  color: var(--text-2);
  text-align: center;
}

.morph {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 2px 9px;
  border-radius: 7px;
  background: var(--surface-2);
  border: 1px solid var(--border);
}

.morph b {
  font-family: var(--font-en);
  font-size: calc(13px * var(--scale, 1));
  font-weight: 600;
}

.morph i {
  font-style: normal;
  font-size: calc(11px * var(--scale, 1));
  color: var(--text-3);
}

.morph.pre { background: var(--accent-soft); border-color: transparent; }
.morph.pre b { color: var(--accent); }
.morph.root { background: var(--warn-soft); border-color: transparent; }
.morph.root b { color: var(--warn); }
.morph.suf { background: var(--ok-soft); border-color: transparent; }
.morph.suf b { color: var(--ok); }

.m-op { font-size: calc(12px * var(--scale, 1)); color: var(--text-3); }

/* 隐藏开关 */
body.hide-pic .pic-area { display: none; }

/* 关掉图片后，整张卡片就是给文字用的：
   · 出题那一拍已经没有东西可看，播放器会直接跳过（见 player.js stepOK），
     所以第一屏就是单词，不再是「一句提问 + 一大片空白」；
   · 答案区接住图片让出来的空间，内容从一个固定高度起排 —— 不做垂直居中，
     因为内容每步变多时居中会让整块上下跳，第八轮就是栽在这上面；
   · 文字整体放大一档，长单词该换行就换行。 */
body.hide-pic .card .answer-wrap {
  /* flex-shrink 必须是 0：内容比卡片高时（字 1.6x + 长例句），
     收缩会让文字从自评行底下穿过去；不收缩则整块往下排，多出来的高度交给卡片滚动 */
  flex: 1 0 auto;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  min-height: 0;
}
/* 上下各留一块「可伸可缩」的空白：空间够时两块均分，文字正好垂直居中；
   字号 1.6x、例句又长时，两块先缩到 0，内容从顶部开始，不会被裁掉头顶。
   为什么可以居中：答案区里的各层都是 opacity 过渡、不是 display，
   走过的步骤会一直占着高度，所以同一个词内高度是恒定的，居中不会上下跳。 */
body.hide-pic .card .answer-wrap::before,
body.hide-pic .card .answer-wrap::after {
  content: "";
  flex: 1 1 0;
  min-height: 0;
}
body.hide-pic .card .answer { gap: 12px; }
/* 单词不放大 —— 试过 50px，extraordinarily 在 390 宽下宽 348px，
   比内容区 326px 还宽，会顶到卡片边。保持 44px，另加断词兜底。 */
body.hide-pic .card .word { max-width: 100%; overflow-wrap: break-word; }
body.hide-pic .card .phonetic {
  font-size: calc(18px * var(--scale, 1));
  color: var(--text-2);
}
body.hide-pic .card .meaning { font-size: calc(21px * var(--scale, 1)); }

/* 没有图片可让位，第 0 步不再压成 64px（出题中文模式仍会用到这一步） */
body.hide-pic .card[data-step="0"] .answer-wrap {
  height: auto;
  min-height: 0;
  overflow: visible;
}

/* 极端情况兜底：字号 1.6x + 超长例句 + 词根 + 助记同时开，手机上一屏放不下。
   有图片时还能靠图片缩到 0 让位，没有图片就没有可让的东西了，
   所以把卡片改成可滚动 —— 宁可要滚动，也不能把例句裁掉。 */
body.hide-pic .card {
  overflow-x: hidden;
  overflow-y: auto;
  scrollbar-width: thin;
}

body.hide-syl .syl-line { display: none; }
body.hide-cn .meaning { display: none; }
body.hide-phonetic .phonetic { display: none; }
body.hide-example .example { display: none; }
body.hide-rate .rate-row { display: none; }

/* 出题时显示中文：第 0 步就把中文释义放出来，英文单词仍然遮住。
   若「显示中文」总开关本身是关的，则整套规则失效，退回默认节奏 */
body.cn-on-question:not(.hide-cn) .card .meaning { opacity: 1; }
body.cn-on-question:not(.hide-cn) .card .prompt { opacity: 0; }

/* 第 0 步时中文就是主角，放大到与英文单词同一视觉量级，先不显示词性 */
body.cn-on-question:not(.hide-cn) .card[data-step="0"] .meaning {
  font-size: calc(38px * var(--scale, 1));
  font-weight: 500;
  color: var(--text);
  line-height: 1.2;
}

body.cn-on-question:not(.hide-cn) .card[data-step="0"] .meaning .pos { display: none; }

/* ---------------- 自评 ---------------- */
/* 自评按钮钉在卡片底部，图片钉在卡片顶部。
   做法：卡片本身顶部对齐（.card 就是 flex-start，不随评分按钮显隐切换），
   答案区不再用 flex:1 撑满，而是紧跟图片、内容贴顶；剩下的空白交给
   rate-row 的 margin-top:auto 吃掉。

   老写法是「答案区 flex:1 吃掉中间空间、内容在其中垂直居中」，
   后果是图片越小、文字离图越远——手机 390 宽讲例句那一步被拉到 76px，
   看着像中间空了一块。而且评分一关，卡片退回居中，图片顶部还会往下掉 57px。 */

.rate-row {
  display: flex;
  gap: 10px;
  flex-wrap: nowrap;
  justify-content: center;
  width: 100%;
  max-width: 560px;
  flex: 0 0 auto;
  /* 卡片顶部对齐后，靠它把自评按钮压到下沿，中间的空白由这里吃掉 */
  margin-top: auto;
}

.rate-btn {
  flex: 1 1 0;
  min-width: 0;
  white-space: nowrap;
  height: 38px;
  padding: 0 14px;
  border-radius: var(--radius);
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  font-size: calc(14px * var(--scale, 1));
  cursor: pointer;
  transition: transform .12s ease, background .15s ease, border-color .15s ease;
}

/* 宽屏才有余量给固定最小宽度，窄屏一律平分 */
@media (min-width: 721px) {
  .rate-btn { flex: 0 0 auto; min-width: 104px; padding: 0 20px; }
}

.rate-btn:hover { background: var(--surface-3); }
.rate-btn:active { transform: scale(.96); }
.rate-btn[data-rate="2"]:hover { border-color: var(--ok); color: var(--ok); background: var(--ok-soft); }
.rate-btn[data-rate="1"]:hover { border-color: var(--warn); color: var(--warn); background: var(--warn-soft); }
.rate-btn[data-rate="0"]:hover { border-color: var(--bad); color: var(--bad); background: var(--bad-soft); }

/* 换词后鼠标或手指还停在原地时，:hover 会一直「粘」在刚才点的那个按钮上，
   看上去像这个词已经评过。切词瞬间由 clearRateMark() 加上 .quiet，
   把悬停配色压回默认；用户真的再动一次鼠标 / 摸一次屏幕就恢复。
   特异性要压过上面的 [data-rate]:hover，所以这里带上 .rate-row.quiet 前缀。 */
.rate-row.quiet .rate-btn:hover { background: var(--surface-2); }
.rate-row.quiet .rate-btn[data-rate]:hover {
  background: var(--surface-2);
  border-color: var(--border-2);
  color: inherit;
}
.rate-btn .key {
  font-family: var(--font-en);
  font-size: calc(11px * var(--scale, 1));
  color: var(--text-3);
  margin-left: 6px;
}

/* ---------------- 播放器 ---------------- */
.player {
  flex: 0 0 auto;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom, 0);
}

/* 计数 / 步骤条 / 收藏 同一行 */
.steps-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px 4px;
}

.steps-row .steps { flex: 1 1 auto; min-width: 0; }

/* 主控键：只有图标 */
.controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 8px 18px 12px;
}

.cbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 44px;
  min-width: 64px;
  padding: 0 18px;
  border-radius: 999px;
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  color: var(--text-2);
  font-size: calc(14px * var(--scale, 1));
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s ease, transform .12s ease, color .15s ease;
}

.cbtn svg { width: 18px; height: 18px; }
.cbtn-main svg { width: 20px; height: 20px; }

.cbtn:hover { background: var(--surface-3); }
.cbtn:active { transform: scale(.96); }

.cbtn-main {
  height: 52px;
  min-width: 132px;
  justify-content: center;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-size: calc(15px * var(--scale, 1));
}

.cbtn-main:hover { background: var(--accent-2); }

.counter {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 36px;
  padding: 0 13px;
  border-radius: 999px;
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  font-family: var(--font-en);
  font-size: calc(14px * var(--scale, 1));
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}

.counter:hover { background: var(--surface-3); border-color: var(--accent); color: var(--text); }
.counter::after { content: "▾"; font-size: 10px; color: var(--text-3); }

.fav-btn {
  min-width: 0;
  width: 36px;
  height: 36px;
  padding: 0;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
}

.fav-btn.on { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); }

/* ---------------- 抽屉 ---------------- */
.drawer {
  position: fixed;
  top: 0;
  right: 0;
  height: 100%;
  width: min(400px, 92vw);
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: -12px 0 40px rgba(0, 0, 0, .18);
  transform: translateX(102%);
  transition: transform .26s cubic-bezier(.32, .72, 0, 1);
  display: flex;
  flex-direction: column;
  z-index: 40;
  pointer-events: none;
}

.drawer.open { transform: translateX(0); pointer-events: auto; }

.drawer-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.drawer-head h2 { margin: 0; font-size: calc(15px * var(--scale, 1)); font-weight: 600; }
.drawer-head .close { margin-left: auto; }

.drawer-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 16px 18px 40px;
}

.mask {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .32);
  opacity: 0;
  pointer-events: none;
  transition: opacity .26s ease;
  z-index: 35;
}

.mask.open { opacity: 1; pointer-events: auto; }

/* 设置项 */
.group { margin-bottom: 22px; }

.group > h3 {
  margin: 0 0 10px;
  font-size: calc(12px * var(--scale, 1));
  font-weight: 600;
  letter-spacing: .6px;
  color: var(--text-3);
  text-transform: uppercase;
}

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}

.row:last-child { border-bottom: none; }
.row .label { font-size: calc(14px * var(--scale, 1)); }
.row .hint { display: block; font-size: calc(11px * var(--scale, 1)); color: var(--text-3); margin-top: 2px; }
.row .grow { flex: 1 1 auto; min-width: 0; }

/* 主题色板：38 套塞不进这一行的右侧。
   不改的话 .swatches 会被压成 min-content（实测只有 86px 宽），
   整列竖着排 38 行、2600px 长，翻都要翻半天。
   让这一行换行、色板独占整宽，就能排成 4 列。 */
.row[data-k="theme"] { flex-wrap: wrap; }
.row[data-k="theme"] .swatches { flex: 1 0 100%; }

.tone-row {
  display: flex;
  gap: 6px;
  margin-top: 7px;
  flex-wrap: wrap;
}
.row .val {
  font-family: var(--font-en);
  font-size: calc(13px * var(--scale, 1));
  color: var(--text-2);
  min-width: 46px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* 开关 */
.switch {
  flex: 0 0 auto;
  width: 46px;
  height: 26px;
  border-radius: 999px;
  background: var(--surface-3);
  border: 1px solid var(--border-2);
  position: relative;
  cursor: pointer;
  transition: background .18s ease, border-color .18s ease;
}

.switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .22);
  transition: transform .18s ease;
}

.switch.on { background: var(--accent); border-color: var(--accent); }
.switch.on::after { transform: translateX(20px); }

/* 滑杆 */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 130px;
  height: 22px;
  background: transparent;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: var(--surface-3);
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin-top: -6px;
  border-radius: 50%;
  background: var(--accent);
  border: none;
}

/* 文本输入 */
input.txt {
  width: 150px;
  height: 32px;
  padding: 0 10px;
  border-radius: var(--radius);
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  font-size: calc(13px * var(--scale, 1));
  font-family: var(--font-en);
  flex: 0 0 auto;
}

input.txt:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* 分段控件 */
.seg {
  display: inline-flex;
  border: 1px solid var(--border-2);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-2);
}

.seg button {
  border: none;
  background: transparent;
  padding: 6px 12px;
  font-size: calc(13px * var(--scale, 1));
  cursor: pointer;
  white-space: nowrap;
}

.seg button + button { border-left: 1px solid var(--border-2); }
.seg button.on { background: var(--accent); color: var(--on-accent); }

/* 统计卡 */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 22px;
}

.stat-card {
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 12px 14px;
}

.stat-card .k { font-size: calc(12px * var(--scale, 1)); color: var(--text-3); }
.stat-card .v {
  font-family: var(--font-en);
  font-size: calc(24px * var(--scale, 1));
  font-weight: 600;
  margin-top: 4px;
  line-height: 1.2;
}

.stat-card .v small { font-size: calc(12px * var(--scale, 1)); color: var(--text-3); font-weight: 400; }

/* 词条列表 */
.wlist { display: flex; flex-direction: column; }

.witem {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 4px;
  border-bottom: 1px solid var(--border);
}

.witem:last-child { border-bottom: none; }

.witem .thumb {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  background: var(--pic-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 19px;
  overflow: hidden;
}

.witem .thumb .dot { width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--border-2); }
.witem .thumb .num { font-family: var(--font-en); font-size: 13px; font-weight: 600; color: var(--accent); }

.witem .en { font-family: var(--font-en); font-size: calc(14px * var(--scale, 1)); font-weight: 600; }
.witem .cn { font-size: calc(12px * var(--scale, 1)); color: var(--text-3); }
.witem .meta { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* 跳转列表 */
.witem.jitem {
  cursor: pointer;
  border-radius: 10px;
  padding: 8px 10px;
  border-bottom: none;
}

.witem.jitem:hover { background: var(--surface-2); }
.witem.jitem.now { background: var(--accent-soft); }

.witem .idx {
  flex: 0 0 auto;
  width: 24px;
  text-align: right;
  font-family: var(--font-en);
  font-size: calc(11px * var(--scale, 1));
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

.witem .jtext { flex: 1 1 auto; min-width: 0; }
.witem .jtext .en { font-family: var(--font-en); font-size: calc(14px * var(--scale, 1)); font-weight: 600; }
.witem .jtext .cn { font-size: calc(12px * var(--scale, 1)); color: var(--text-3); }

.tag {
  font-size: calc(11px * var(--scale, 1));
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--text-2);
  white-space: nowrap;
}

.tag.bad { background: var(--bad-soft); color: var(--bad); }
.tag.warn { background: var(--warn-soft); color: var(--warn); }
.tag.ok { background: var(--ok-soft); color: var(--ok); }

.empty {
  text-align: center;
  color: var(--text-3);
  font-size: calc(13px * var(--scale, 1));
  padding: 40px 10px;
  line-height: 1.8;
}

.mini {
  height: 28px;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  font-size: calc(12px * var(--scale, 1));
  cursor: pointer;
}

.mini:hover { background: var(--surface-3); }

/* 提示条
   【配色】底用 --text、字用 --bg，是刻意取的一对反色：
     深色主题 --text 亮、--bg 暗 → 亮底暗字；
     浅色主题 --text 暗、--bg 亮 → 暗底亮字。
     永远和页面相反，所以在哪套主题上都醒目。
   以前字色写的是 --surface，那是个「玻璃面填充色」（rgba 白 .10 这类），
   当文字色用等于把字写成了 10% 透明 —— 深色玻璃主题下实测对比度 1.01，
   白底白字，完全看不见。别改回去。 */
.toast {
  position: fixed;
  left: 50%;
  bottom: 96px;
  transform: translate(-50%, 14px);
  background: var(--text);
  color: var(--bg);
  padding: 10px 20px;
  border-radius: 999px;
  font-size: calc(13px * var(--scale, 1));
  line-height: 1.5;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease, transform .2s ease;
  z-index: 60;
  /* 长句子要能换行，不能撑出屏幕（手机 390 下「已隐藏上下栏 · 连点卡片 4 下可恢复」
     原来 nowrap 会横向溢出）。短句仍然单行，胶囊形状不变。 */
  white-space: normal;
  text-align: center;
  width: max-content;
  max-width: calc(100vw - 32px);
  /* 浮在彩色 / 照片背景上时给一圈边界，否则白胶囊边缘会糊进背景 */
  box-shadow: 0 10px 30px -8px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .18);
}

.toast.show { opacity: .96; transform: translate(-50%, 0); }

/* 大屏模式 */
body.big .topbar { padding: 6px 14px; }
body.big .brand { display: none; }
body.big .card { max-width: 1180px; gap: 22px; }
body.big .pic-area { width: min(100%, 640px); height: min(56vh, 520px); }
body.big .card[data-step="0"] .pic-area { height: min(52vh, 480px); }
body.big .card[data-step="1"] .pic-area,
body.big .card[data-step="2"] .pic-area,
body.big .card[data-step="3"] .pic-area,
body.big .card[data-step="4"] .pic-area,
body.big .card[data-step="5"] .pic-area { height: min(44vh, 400px); }
body.big .pic-emoji { font-size: clamp(110px, 24vh, 240px); }
body.big .pic-color { width: clamp(140px, 30vh, 260px); height: clamp(140px, 30vh, 260px); }
body.big .pic-num { font-size: clamp(90px, 20vh, 190px); }
body.big .word { font-size: calc(72px * var(--scale, 1)); }
body.big .phonetic { font-size: calc(22px * var(--scale, 1)); }
body.big .meaning { font-size: calc(26px * var(--scale, 1)); }
body.big .rate-row { transform: scale(1.15); }

/* 开了词根/助记块的词内容更长。
   以前这里会把 step 3/4/5 的图再收一档（min(22vh, 170px)），
   但那等于「有释义的词会缩第二次」，和「只缩一次」冲突，所以去掉：
   多出来的内容由答案区自己往下长，卡片底部空间不够时再单独处理。 */

/* ---------------- 步骤条（同时也是进度条） ---------------- */
.steps {
  min-width: 0;
}

.steps-items {
  position: relative;
  display: flex;
  justify-content: center;
  width: 100%;
}

/* 连接线：从第一个圆点中心连到最后一个圆点中心。
   内缩量由 JS 按「当前显示的圆点数」算好写进 --track-inset，
   关掉某些步骤后圆点会重新均分，线也跟着重算。 */
.steps-track {
  position: absolute;
  top: 5px;
  left: var(--track-inset, 8.333%);
  right: var(--track-inset, 8.333%);
  transition: left .26s ease, right .26s ease;
  height: 2px;
  background: var(--border);
  border-radius: 1px;
}

.steps-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  border-radius: 1px;
  transition: width .16s linear;
}

.step {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  position: relative;
  color: var(--text-3);
  font-size: calc(11px * var(--scale, 1));
  transition: color .2s ease, flex-grow .26s ease, opacity .2s ease;
}

.step .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--surface-3);
  border: 1px solid var(--border-2);
  position: relative;
  z-index: 1;
  transition: background .2s ease, border-color .2s ease, box-shadow .2s ease;
}

.step span { white-space: nowrap; }

.step.done .dot { background: var(--accent); border-color: var(--accent); }

.step.now { color: var(--text); }
.step.now .dot {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

.step.off { opacity: .32; cursor: default; }
.step:not(.off):hover .dot { border-color: var(--accent); }
.step:not(.off):hover { color: var(--text-2); }

/* ============================================================
 * 账号与同步
 * ============================================================ */

.acc-lead {
  font-size: calc(13px * var(--scale, 1));
  color: var(--text-2);
  line-height: 1.75;
  margin: 0 0 16px;
}

.acc-tabs {
  display: flex;
  width: max-content;
  border: 1px solid var(--border-2);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 16px;
}
.acc-tabs button {
  border: 0;
  background: transparent;
  padding: 8px 20px;
  font-size: calc(13px * var(--scale, 1));
  color: var(--text-2);
  cursor: pointer;
}
.acc-tabs button + button { border-left: 1px solid var(--border-2); }
.acc-tabs button.on { background: var(--accent); color: var(--on-accent); }

.acc-field { display: block; margin-bottom: 12px; }
.acc-field > span {
  display: block;
  font-size: calc(12px * var(--scale, 1));
  color: var(--text-3);
  margin-bottom: 5px;
}
.acc-field input {
  width: 100%;
  height: 40px;
  padding: 0 12px;
  border-radius: var(--radius);
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  color: var(--text);
  font-size: calc(14px * var(--scale, 1));
  font-family: inherit;
}
.acc-field input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.acc-submit {
  width: 100%;
  height: 42px;
  margin-top: 4px;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-size: calc(14px * var(--scale, 1));
  font-weight: 600;
  cursor: pointer;
}
.acc-submit:disabled { opacity: .55; cursor: default; }

.acc-msg {
  min-height: 18px;
  margin-top: 12px;
  font-size: calc(12.5px * var(--scale, 1));
  line-height: 1.6;
  color: #d02b2b;
}
.acc-msg.good { color: #12934f; }

.acc-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  margin-bottom: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}
.acc-avatar {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 19px;
  font-weight: 600;
}
.acc-name { font-size: calc(15px * var(--scale, 1)); font-weight: 600; }
.acc-sub {
  font-family: var(--font-en);
  font-size: calc(12px * var(--scale, 1));
  color: var(--text-3);
}

.acc-state {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 16px;
  font-size: calc(12px * var(--scale, 1));
  color: var(--text-3);
}
.acc-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-3);
}
.acc-dot.ok { background: #22c55e; }
.acc-dot.bad { background: #ef4444; }
.acc-dot.busy { background: var(--accent); }

.acc-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.acc-btn {
  flex: 1 1 140px;
  height: 40px;
  border-radius: var(--radius);
  border: 1px solid var(--border-2);
  background: var(--surface-2);
  color: var(--text);
  font-size: calc(13px * var(--scale, 1));
  cursor: pointer;
}
.acc-btn:hover { background: var(--surface-3); }
.acc-btn.danger { color: #d02b2b; }
.acc-btn:disabled { opacity: .5; cursor: default; }

.acc-sep {
  height: 1px;
  background: var(--border);
  margin: 20px 0;
  border: 0;
}

/* ============================================================
 * 启动失败提示（接口不通 / 词库为空）
 * ============================================================ */
.fatal {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg);
}
.fatal-box {
  max-width: 520px;
  padding: 28px 26px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 12px 40px -18px rgba(0, 0, 0, .25);
  text-align: center;
}
.fatal-box h2 {
  margin: 0 0 12px;
  font-size: calc(17px * var(--scale, 1));
  font-weight: 600;
}
.fatal-box p {
  margin: 0 0 20px;
  font-size: calc(13.5px * var(--scale, 1));
  line-height: 1.8;
  color: var(--text-2);
}
.fatal-box code {
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--surface-3);
  font-size: .92em;
}
.fatal-box button {
  height: 40px;
  padding: 0 24px;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-size: calc(14px * var(--scale, 1));
  cursor: pointer;
}

/* ============================================================
 * 移动端适配（手机为主）
 * ============================================================ */
@media (max-width: 720px) {
  body { -webkit-tap-highlight-color: transparent; }
  button, select { touch-action: manipulation; -webkit-user-select: none; user-select: none; }

  .topbar { padding: 8px 9px; gap: 5px; flex-wrap: nowrap; }
  .brand { display: none; }
  .topbar-right { flex-wrap: nowrap; gap: 5px; }
  .sel { height: 38px; font-size: calc(12px * var(--scale, 1)); }
  #scopeSelect { max-width: 86px; }
  .tbtn { padding: 0; min-width: 36px; height: 38px; justify-content: center; }
  .tbtn .txt { display: none; }
  .tbtn .ic { width: 19px; height: 19px; }

  /* 顶栏一共 6 个控件，必须允许被压缩：
     .topbar-right 默认 min-width:auto 会按内容撑开，把顶栏顶出屏幕。
     这里让它和词库下拉都能缩，剩下的宽度不够时优先压词库名。 */
  .topbar-right { flex: 1 1 auto; min-width: 0; }
  .sel { padding: 0 8px; }
  #libSelect { flex: 1 1 60px; min-width: 0; }
  #scopeSelect { flex: 0 0 auto; max-width: 84px; }

  .stage { padding: 10px; }
  .card { padding: 14px; gap: 10px; }

  /* 计数 + 步骤条 + 收藏 挤在同一行 */
  .steps-row { gap: 8px; padding: 9px 10px 3px; }
  .steps { min-width: 0; }
  .step { font-size: calc(10.5px * var(--scale, 1)); gap: 5px; }
  .steps-track { top: 5px; }

  /* 主控键：只有图标，平分一行 */
  .controls { gap: 10px; padding: 8px 10px 10px; }
  .counter { height: 32px; font-size: calc(12px * var(--scale, 1)); flex: 0 0 auto; padding: 0 10px; }
  .cbtn { flex: 1 1 0; max-width: 120px; padding: 0; height: 46px; }
  .cbtn-main { flex: 1.4 1 0; max-width: 180px; }
  .cbtn svg { width: 20px; height: 20px; }
  .cbtn-main svg { width: 22px; height: 22px; }
  /* 放在 .cbtn 之后，压过它的 flex:1，保持正方形 */
  .fav-btn { flex: 0 0 auto; width: 32px; height: 32px; min-width: 0; padding: 0; font-size: 15px; }

  .rate-row { gap: 8px; }
  .rate-btn { flex: 1 1 0; min-width: 0; height: 44px; padding: 0 8px; }
  .rate-btn .key { display: none; }

  .pic-area { height: min(34vh, 250px); }
  .card[data-step="0"] .pic-area { height: min(40vh, 300px); }
  .card[data-step="1"] .pic-area,
  .card[data-step="2"] .pic-area,
  .card[data-step="3"] .pic-area,
  .card[data-step="4"] .pic-area,
  .card[data-step="5"] .pic-area { height: min(28vh, 210px); }

  .word { font-size: calc(34px * var(--scale, 1)); }
  .syl-line { gap: 8px; }
  .syl-col b { font-size: calc(18px * var(--scale, 1)); }
  .syl-col i { font-size: calc(12px * var(--scale, 1)); }
  .details { gap: 7px; }
  .example { padding: 9px 12px; }

  /* 抽屉在手机上接近全屏 */
  .drawer { width: min(430px, 100vw); }
  .drawer-body { padding: 14px 14px calc(28px + env(safe-area-inset-bottom, 0)); }
  .swatches { grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); }
}

/* 超窄屏（iPhone SE 等） */
@media (max-width: 360px) {
  .steps-row { gap: 6px; padding: 8px 8px 2px; }
  .step { font-size: 9.5px; }
  .step span { letter-spacing: -0.3px; }
  .counter { font-size: 11px; padding: 0 8px; gap: 3px; }
  .fav-btn { width: 30px; height: 30px; font-size: 14px; }
  .cbtn { font-size: calc(12px * var(--scale, 1)); }
}

/* ============================================================
 * 毛玻璃主题的渲染层
 * ------------------------------------------------------------
 * 只在 body 带 .glass 类时生效，由 js/ui.js 按主题名前缀判断。
 *
 * 参考了公开的 Liquid Glass / Glassmorphism 生产实践，取其中
 * 代价小、效果明显的六条（折射位移滤镜太吃 GPU，没有采用）：
 *   1. 玻璃背后必须有东西可模糊 → 铺五团色雾当渐变网格，
 *      纯色底上 backdrop-filter 是看不出效果的
 *   2. 四个边不能一样亮 → 光来自左上，上/左亮、下/右暗，
 *      这是「真玻璃」和「半透明方块」最大的区别
 *   3. 顶边 1px 高光 + 底边 1px 暗边 = 玻璃的厚度
 *   4. saturate 拉到 1.5 以上，模糊会把颜色洗成灰
 *   5. 叠一层极淡颗粒，压住大半径模糊在渐变色上产生的色带
 *   6. 不嵌套 backdrop-filter：一张卡只糊一层，里面不再套
 * ============================================================ */

body.glass .stage {
  position: relative;
  background: transparent;
}

/* 渐变网格：五团色雾，错开位置和大小，让背景有明暗起伏。
   注意是铺在 body 的负层级上、整屏铺满，不是只铺在 .stage 里 ——
   顶栏、播放器、抽屉都在 .stage 外面，色雾只放在舞台里的话，
   它们背后就是一块纯色，backdrop-filter 等于白开。
   同屏只此一层，静止不动，不参与动画，开销可以忽略。 */
body.glass::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(34% 30% at 14% 10%, var(--blob-1) 0%, transparent 66%),
    radial-gradient(30% 28% at 88% 16%, var(--blob-2) 0%, transparent 68%),
    radial-gradient(42% 34% at 78% 92%, var(--blob-3) 0%, transparent 66%),
    radial-gradient(26% 24% at 18% 84%, var(--blob-4) 0%, transparent 70%),
    radial-gradient(22% 20% at 52% 46%, var(--blob-5) 0%, transparent 74%);
  filter: blur(10px);
  opacity: var(--mesh-opacity, .58);
  pointer-events: none;
}

/* 颗粒噪点：一层静态贴图，不参与动画，几乎不耗性能。
   盖在最上层（z-index 5，低于遮罩与抽屉），让卡片、图片、文字
   统一在同一种质感里。 */
body.glass::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  opacity: var(--glass-grain, .045);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* ============================================================
 * 会呼吸的色雾（glass-breath-* 十套专用）
 * ------------------------------------------------------------
 * 只动 transform 与 opacity：这两样能交给合成器，不触发重绘。
 * 动 --blob-* 或 background 的话，每帧都要把整屏渐变重新栅格化一次，
 * 桌面也许扛得住，手机一开就烫。
 *
 * 三条约束：
 *   1. 周期 22s 起，最慢 34s。呼吸本来就是慢的，8s 以下是眨眼不是呼吸。
 *   2. 每套配一条自己的 --mesh —— 色团数量、位置、方向都不同。
 *      只换配色的话，十套会长得一模一样。
 *   3. 每套一套自己的 keyframes：缩放 / 潮汐 / 漂移 / 明暗 / 环流 /
 *      心跳 / 摆荡 / 光晕 / 涟漪 / 斜波，动作形态不重复。
 *
 * --breath-name 缺省是 none，其余十八套玻璃主题完全不参与动画。
 * ============================================================ */

[data-theme^="glass-breath"] body.glass::before {
  background: var(--mesh);
  transform-origin: 50% 50%;
  will-change: transform, opacity;
  animation:
    var(--breath-name, none)
    var(--breath-dur, 26s)
    var(--breath-ease, ease-in-out)
    var(--breath-delay, 0s)
    infinite;
}

/* 1. 缩放呼吸：整层慢慢鼓一圈再收回，最标准的一种 */
@keyframes breathScale {
  0%, 100% { transform: scale(1);     opacity: calc(var(--mesh-opacity, .65) * .58); }
  50%      { transform: scale(1.11);  opacity: var(--mesh-opacity, .65); }
}

/* 2. 潮汐：上下缓慢推移，像水面涨落 */
@keyframes breathTide {
  0%, 100% { transform: translate3d(0, -4.2%, 0) scale(1.10); opacity: calc(var(--mesh-opacity, .65) * .70); }
  50%      { transform: translate3d(0, 4.2%, 0) scale(1.10);  opacity: var(--mesh-opacity, .65); }
}

/* 3. 漂移：横向游移，同时极轻微放大 */
@keyframes breathDrift {
  0%, 100% { transform: translate3d(-4.6%, 0, 0) scale(1.10);      opacity: calc(var(--mesh-opacity, .65) * .68); }
  50%      { transform: translate3d(4.6%, -2%, 0) scale(1.13);     opacity: var(--mesh-opacity, .65); }
}

/* 4. 明暗呼吸：几何完全不动，只有浓淡起伏，最含蓄的一种 */
@keyframes breathLum {
  0%, 100% { opacity: calc(var(--mesh-opacity, .65) * .55); }
  50%      { opacity: var(--mesh-opacity, .65); }
}

/* 5. 环流：绕一个小四边形的环，像深处的光在慢慢转 */
@keyframes breathOrbit {
  0%, 100% { transform: translate3d(-3.4%, -2.4%, 0) scale(1.10); }
  25%      { transform: translate3d(2.8%, -3.6%, 0) scale(1.13); }
  50%      { transform: translate3d(3.6%, 2.8%, 0) scale(1.10); }
  75%      { transform: translate3d(-2.4%, 3.6%, 0) scale(1.13); }
}

/* 6. 心跳：一次循环两下，第二下更重；间隔很长，只当「余烬一明」看 */
@keyframes breathBeat {
  0%   { transform: scale(1);      opacity: calc(var(--mesh-opacity, .65) * .62); }
  11%  { transform: scale(1.075);  opacity: var(--mesh-opacity, .65); }
  20%  { transform: scale(1.01); }
  31%  { transform: scale(1.095);  opacity: var(--mesh-opacity, .65); }
  48%  { transform: scale(1); }
  100% { transform: scale(1);      opacity: calc(var(--mesh-opacity, .65) * .62); }
}

/* 7. 摆荡：极慢的轻微旋转，玻璃后面像有块布在晃 */
@keyframes breathSway {
  0%, 100% { transform: rotate(-2.2deg) scale(1.13); opacity: calc(var(--mesh-opacity, .65) * .62); }
  50%      { transform: rotate(2.2deg) scale(1.13);  opacity: var(--mesh-opacity, .65); }
}

/* 8. 光晕：从中间软软地撑开，亮到顶再慢慢缩回去 */
@keyframes breathHalo {
  0%   { transform: scale(.94);  opacity: calc(var(--mesh-opacity, .65) * .32); }
  42%  { transform: scale(1.20); opacity: var(--mesh-opacity, .65); }
  58%  { transform: scale(1.17); opacity: var(--mesh-opacity, .65); }
  100% { transform: scale(.94);  opacity: calc(var(--mesh-opacity, .65) * .32); }
}

/* 9. 涟漪：撑开、略停、再回流，节奏是三段的，不像单纯缩放那么机械 */
@keyframes breathRipple {
  0%, 100% { transform: scale(1);    opacity: calc(var(--mesh-opacity, .65) * .48); }
  30%      { transform: scale(1.15); opacity: var(--mesh-opacity, .65); }
  56%      { transform: scale(1.07); opacity: calc(var(--mesh-opacity, .65) * .78); }
}

/* 10. 斜波：沿对角线来回推，缩放在两个方向上不同步 */
@keyframes breathWave {
  0%, 100% { transform: translate3d(-4%, 3.4%, 0) scale(1.10);  opacity: calc(var(--mesh-opacity, .65) * .66); }
  50%      { transform: translate3d(4%, -3.4%, 0) scale(1.14);  opacity: var(--mesh-opacity, .65); }
}

/* 「流光呼吸」专用：玻璃面上一条极淡的光带缓慢扫过。
   为什么不用白色高光（screen 混合）：这套是浅色玻璃，卡面本来就接近纯白，
   白色压上去等于什么都没加，实测像素差为 0。改成极淡的冷色「阴影带」
   用 multiply 混，白卡上才有可见的推移。
   只做在 .card 上（它本身 overflow:hidden，不用额外裁剪），
   用 translate3d 推整条渐变，不动渐变本身，代价和一层贴图位移相当。 */
[data-theme="glass-breath-sheen"] body.glass .card::before {
  content: "";
  position: absolute;
  inset: -30% -70%;
  background: linear-gradient(104deg, transparent 43%, rgba(92, 132, 196, .30) 50%, transparent 57%);
  transform: translate3d(-58%, 0, 0);
  opacity: .85;
  mix-blend-mode: multiply;
  pointer-events: none;
  animation: sheenSweep 18s cubic-bezier(.42, 0, .58, 1) infinite;
}

/* 绝对定位的 ::before 默认会盖在未定位的兄弟内容之上，
   光带就会从图片和文字上游过去，实测图片区像素被压暗。
   给卡片的直接子元素各加一层定位，光带就只落在卡面上。 */
[data-theme="glass-breath-sheen"] body.glass .card > * {
  position: relative;
}

@keyframes sheenSweep {
  0%, 62%   { transform: translate3d(-58%, 0, 0); }
  92%, 100% { transform: translate3d(58%, 0, 0); }
}

/* 系统开了「减少动态效果」：呼吸全部停住，色雾退回静止的满强度。
   不是把色雾关掉 —— 关掉玻璃就白糊了。 */
@media (prefers-reduced-motion: reduce) {
  [data-theme^="glass-breath"] body.glass::before {
    animation: none;
    opacity: var(--mesh-opacity, .65);
    will-change: auto;
  }
  [data-theme^="glass-breath"] body.glass .card::before {
    animation: none;
    opacity: 0;
  }
}

/* 主要容器变玻璃面（border 保持 1px，只改四个边的颜色） */
body.glass .topbar,
body.glass .card,
body.glass .player,
body.glass .drawer {
  background-color: var(--surface);
  background-image: linear-gradient(165deg, var(--glass-sheen) 0%, transparent 46%);
  border: 1px solid transparent;
  border-top-color: var(--glass-hi);
  border-left-color: var(--glass-hi2);
  border-right-color: var(--glass-lo);
  border-bottom-color: var(--glass-lo);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)) brightness(var(--glass-bright));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)) brightness(var(--glass-bright));
  box-shadow:
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo),
    var(--glass-shadow);
}

/* 图片框贴在卡面上，不再单独模糊一层：
   嵌套 backdrop-filter 是二次采样，代价接近平方级，
   而这里底下只有卡面本身，糊了也看不出区别。 */
body.glass .card .pic-area .media-box {
  background: var(--pic-bg);
  box-shadow: inset 0 0 0 1px var(--glass-hi2);
}

/* 小控件只加一道顶部高光，不各自再模糊 */
body.glass .counter,
body.glass .cbtn,
body.glass .tbtn,
body.glass .sel,
body.glass .rate-btn,
body.glass .example,
body.glass .morph,
body.glass .sw,
body.glass .row {
  box-shadow: inset 0 1px 0 var(--glass-hi2);
}

/* 实心按钮本来就不透，别留模糊 */
body.glass .btn:hover,
body.glass .tbtn.primary,
body.glass .cbtn-main {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* 玻璃主题下遮罩更暗一点，衬托玻璃面 */
body.glass .mask { background: rgba(0, 0, 0, .45); }

/* ============================================================
 * 图片背景层（glass-photo-* 三套专用）
 * ------------------------------------------------------------
 * 为什么用真实元素而不是伪元素：
 *   1. body.glass::before 已经被色雾占了，::after 被颗粒占了；
 *   2. 更关键的是 background-image 无法过渡，换词时会「啪」地硬切。
 *      这里铺两层 <img> 轮换，新图 onload 之后淡入、旧图淡出，
 *      连着切词也不会闪黑。
 * 层级：.photo-layer(-2) → body.glass::before 色雾(-1) → 内容。
 * 图片模式下色雾被主题压到 .16~.22，只留一点主题色倾向。
 * ============================================================ */

.photo-layer {
  position: fixed;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity .5s ease;
}

/* 当前词真有配图才让这一层露出来。
   没配图的词（Emoji 兜底）保持 opacity 0，直接露出底色与色雾，
   不会留一块突兀的黑底或破图。 */
body.has-photo .photo-layer { opacity: 1; }

.photo-layer img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .55s ease;
  /* 放大一点点：大半径模糊会把边缘「吸」进来，不放大四周会透出底色 */
  transform: scale(var(--photo-zoom, 1.12));
  filter:
    blur(var(--photo-blur, 30px))
    saturate(var(--photo-sat, 1))
    brightness(var(--photo-bright, 1))
    contrast(var(--photo-contrast, 1));
  /* 模糊半径大，标成待合成，避免每帧重算 */
  will-change: opacity;
}

.photo-layer img.on { opacity: 1; }

/* 叠加层：暗夜主题是四周压暗的暗角，柔光是自上而下的奶白雾，
   浓彩是把整张图往紫红方向重新调色的渐变。三套各给一条，
   值写在 css/themes.css 的 --photo-veil 里。 */
.photo-layer .veil {
  position: absolute;
  inset: 0;
  background: var(--photo-veil, transparent);
  pointer-events: none;
}

/* 手机上把模糊半径降下来：390px 宽的屏上 blur(14px) 和 blur(24px)
   肉眼几乎看不出差别，GPU 开销却差一倍。只改 body 上的变量，
   写在主题里的值不用动，卡片自动继承新值。
   图片背景同理：整屏大图 blur(44px) 在手机上很吃 GPU，
   降到 26px 观感几乎不变，缩放也收一点少采一点像素。 */
@media (max-width: 720px) {
  body.glass { --glass-blur: 14px; }
  body.photo {
    --photo-blur: 26px;
    --photo-zoom: 1.10;
  }
}

/* 不支持 backdrop-filter 的浏览器（老 Chrome / 老 Firefox）：
   退回接近实色的底，至少文字还能看。图片背景同理——
   面板变成实色之后，背景图就只剩边缘露出来，会很难看，
   所以干脆把图片层也关掉，退回主题底色。 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  body.glass .topbar,
  body.glass .card,
  body.glass .player,
  body.glass .drawer { background-color: var(--bg-deco); }
  body.photo .photo-layer { display: none; }
}

/* 系统开了「降低透明度」（iOS 辅助功能）：玻璃会直接糊掉，强制实色 */
@media (prefers-reduced-transparency: reduce) {
  body.glass .topbar,
  body.glass .card,
  body.glass .player,
  body.glass .drawer {
    background-color: var(--bg-deco);
    background-image: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  body.glass::after { display: none; }
  /* 图片也一并撤掉：实色面板配整屏照片会显得脏 */
  body.photo .photo-layer { display: none; }
}

/* ============================================================
 * 学习海报（分享用）
 * ============================================================ */
.poster-layer {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 18px;
  background: rgba(0, 0, 0, .72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.poster-layer.open { display: flex; }

.poster-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-height: 100%;
}

#posterCanvas {
  max-width: 100%;
  max-height: calc(100vh - 190px);
  max-height: calc(100dvh - 190px);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
  background: var(--surface);
}

.poster-act { display: flex; gap: 10px; }

.poster-tip {
  margin: 0;
  font-size: calc(11.5px * var(--scale, 1));
  color: rgba(255, 255, 255, .62);
  text-align: center;
  max-width: 460px;
  line-height: 1.6;
}

/* 统计抽屉顶部的分享入口 */
.share-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  height: 50px;
  margin-bottom: 14px;
  border: none;
  border-radius: var(--radius);
  background: linear-gradient(120deg, var(--accent) 0%, var(--accent-2) 100%);
  color: var(--on-accent);
  font-size: calc(14.5px * var(--scale, 1));
  font-weight: 600;
  letter-spacing: .5px;
  cursor: pointer;
  box-shadow: 0 6px 18px -6px var(--accent);
  transition: transform .12s ease, filter .15s ease;
}

.share-btn:hover { filter: brightness(1.06); }
.share-btn:active { transform: scale(.98); }
.share-btn svg { width: 19px; height: 19px; }

@media (max-width: 720px) {
  #posterCanvas { max-height: calc(100vh - 210px); }
  .poster-layer { padding: 12px; }
}

/* 换词停顿期间，当前步骤的圆点轻轻呼吸一下，表示「还在播，只是停一会儿」 */
@keyframes gapBreath {
  0%, 100% { box-shadow: 0 0 0 4px var(--accent-soft); }
  50%      { box-shadow: 0 0 0 8px var(--accent-soft); }
}

body.gap .step.now .dot {
  animation: gapBreath 1.3s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  body.gap .step.now .dot { animation: none; }
}

/* 这个词没有内容的步骤：平滑收起，剩下的圆点自动重新均分。
   用 flex-grow 收 0 + 透明度渐隐，而不是 display:none，
   这样换词时圆点是滑过去的，不会硬跳。 */
.step.hidden {
  flex-grow: 0;
  flex-basis: 0;
  width: 0;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
}

/* ============================================================
 * 沉浸模式：连点卡片 4 次，顶部栏与底部控制栏一起滑出屏幕
 * ------------------------------------------------------------
 * 高度用 CSS 变量给（由 js/app.js 测量后写入），这样 height 能在
 * 「具体像素 ↔ 0」之间平滑过渡；height:auto 是没法做过渡的。
 * 上下栏高度归零后，中间的 .stage 是 flex:1，会自动把空间吃掉，
 * 卡片跟着变大，不是单纯留白。
 * ============================================================ */
.topbar {
  height: var(--topbar-h, auto);
  overflow: hidden;
  transition: height .34s cubic-bezier(.4, 0, .2, 1),
              padding .34s cubic-bezier(.4, 0, .2, 1),
              opacity .24s ease,
              transform .34s cubic-bezier(.4, 0, .2, 1),
              border-width .24s ease;
}

.player {
  height: var(--player-h, auto);
  overflow: hidden;
  transition: height .34s cubic-bezier(.4, 0, .2, 1),
              padding .34s cubic-bezier(.4, 0, .2, 1),
              opacity .24s ease,
              transform .34s cubic-bezier(.4, 0, .2, 1),
              border-width .24s ease;
}

body.immersive .topbar {
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0;        /* 只把颜色改透明还会留 1px，必须连宽度一起收 */
  opacity: 0;
  transform: translateY(-10px);
}

body.immersive .player {
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0;
  opacity: 0;
  transform: translateY(10px);
}

/* 隐藏后卡片占满整个屏幕，圆角收掉、阴影去掉，像真正的全屏。
   底部要补回 safe-area，否则 iPhone 的 Home 指示条会压住自评按钮
   （原来这段安全区是靠 .player 的 padding-bottom 让出来的）。 */
body.immersive .stage { padding: 0 0 env(safe-area-inset-bottom, 0); }
body.immersive .card {
  border-radius: 0;
  border-color: transparent;
  box-shadow: none;
  /* 录屏发短视频时，平台会在顶部叠标题、底部叠文案和互动按钮。
     顶部留 12vh、底部留 4vh，内容就不会被压住（数值由 --imm-top/--imm-bottom 控制，
     以后想微调改这两个变量即可，不用动结构）。 */
  padding-top: max(14px, var(--imm-top, 12vh));
  padding-bottom: max(14px, var(--imm-bottom, 4vh));
}

/* 连点卡片时别让浏览器把文字选中、也别闪蓝色高亮 */
body.immersive .card,
body:not(.immersive) .card {
  -webkit-tap-highlight-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .topbar, .player { transition: none; }
}

/* 沉浸模式下卡片变高了，图片也跟着放大一档，别让中间空一大片。
   每个步骤各写一条，保持「越往后图片越小」的递进关系不变。 */
body.immersive .card[data-step="0"] .pic-area { height: min(46vh, 380px); }
body.immersive .card[data-step="1"] .pic-area,
body.immersive .card[data-step="2"] .pic-area,
body.immersive .card[data-step="3"] .pic-area,
body.immersive .card[data-step="4"] .pic-area,
body.immersive .card[data-step="5"] .pic-area { height: min(34vh, 250px); }

/* ============================================================
 * 倒计时提示
 * ------------------------------------------------------------
 * 只在「提问语音播完 → 揭晓答案」之间出现，盖住图片框那一块，
 * 位置由 js/countdown.js 实测 #picArea 后写进内联样式，不写死高度
 * （图片在 step0 是 42vh 一档，在后面的步骤是 34vh，写死一定错位）。
 *
 * 为什么用深色蒙版 + 白字：
 *   38 套主题里浅色占多数，如果跟着 --text 走，浅色主题上是深字压在
 *   彩色配图上，对比度完全不够；深色蒙版 + 白字在任何主题、任何配图下
 *   都是同一套观感，也像一束光打在图上，正是「马上要揭晓」该有的样子。
 *
 * 环和滴答音是同一个节拍：每拍走一圈（1s），看和听能对上。
 * ============================================================ */
.cd-layer {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  z-index: 6;
  pointer-events: none;
  overflow: hidden;
  border-radius: var(--radius-lg);
  opacity: 0;
  visibility: hidden;
  transition: opacity .2s ease, visibility .2s ease;
}

.cd-layer.on { opacity: 1; visibility: visible; }
.cd-layer.done { opacity: 0; }

/* 蒙版单独一层：叠在图片上，压暗底图，把注意力收到数字上 */
.cd-layer::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(8, 12, 20, .46);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.cd-box {
  position: relative;
  width: clamp(92px, 25vmin, 128px);
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
}

.cd-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.cd-track {
  fill: none;
  stroke: rgba(255, 255, 255, .18);
  stroke-width: 4;
}

/* 周长 2π×45 ≈ 282.74。
   环走的是「整轮」而不是「每拍」：从满圈一路走空，3 拍下来正好走完 ——
   每拍重置会看成"没走满就跳回去"，整轮连续才像真正的倒计时表。
   时长由 js 写进 --cd-dur（= 总拍数秒），以后改成 5 拍不用动这里。 */
.cd-prog {
  fill: none;
  stroke: rgba(255, 255, 255, .92);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-dasharray: 282.74;
  stroke-dashoffset: 0;
  filter: drop-shadow(0 0 6px rgba(255, 255, 255, .55));
}

.cd-layer.run .cd-prog {
  animation-name: cdDrain;
  animation-duration: var(--cd-dur, 3s);
  animation-timing-function: linear;
  animation-fill-mode: both;
}

.cd-num {
  position: relative;
  font-family: var(--font-en);
  font-size: clamp(44px, 10.5vmin, 66px);
  font-weight: 900;
  line-height: 1;
  color: #fff;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 22px rgba(255, 255, 255, .6), 0 6px 20px rgba(0, 0, 0, .5);
}

.cd-label {
  position: relative;
  font-size: 12px;
  letter-spacing: .22em;
  /* letter-spacing 会在末字之后也补一份间距，整行看起来右偏，缩回来 */
  text-indent: .22em;
  color: rgba(255, 255, 255, .78);
  /* 这行小字经常正好压在画面里最亮的地方（天花板、水面），
     只靠 78% 的白撑不住，加一层暗影压住底图 */
  text-shadow: 0 1px 6px rgba(0, 0, 0, .65);
}

/* 每一拍重启一次：数字弹入 */
.cd-layer.tick .cd-num { animation: cdPop .5s cubic-bezier(.2, 1.45, .4, 1) both; }

@keyframes cdPop {
  0%   { transform: scale(1.6); opacity: 0; filter: blur(6px); }
  55%  { opacity: 1; filter: blur(0); }
  100% { transform: scale(1); opacity: 1; filter: blur(0); }
}

/* 整轮走空：满 → 空 */
@keyframes cdDrain {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: 282.74; }
}

/* 无图场景（关掉图片、或「出题时显示中文」）：
   不能整块压暗 —— 题面本身就是让人思考的东西，压暗了等于没给题。
   改成只给数字垫一个深色圆盘，环和数字照样醒目，题面一个字都不挡。 */
.cd-layer.plain::before { display: none; }

.cd-layer.plain .cd-box::before {
  content: "";
  position: absolute;
  inset: -7%;
  border-radius: 50%;
  background: rgba(8, 12, 20, .66);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  box-shadow: 0 14px 38px rgba(0, 0, 0, .28);
}

.cd-layer.plain .cd-label {
  color: var(--text-2);
  text-shadow: none;
}

/* 小屏上不要把图占满，留一点点边，别让蒙版顶到圆角上 */
@media (max-width: 560px) {
  .cd-box { width: clamp(84px, 24vmin, 108px); }
  .cd-label { font-size: 11px; }
}

/* ============================================================
 * 设置面板：被依赖项压住的行
 * ------------------------------------------------------------
 * 两种情况会用到：
 *   ① 倒计时提示关掉时，「倒计时尾音」没有意义；
 *   ② 倒计时提示打开时，「看图思考」的秒数不再生效（由提问语音决定）。
 * 灰掉而不是藏起来：用户看得见它还在、也知道为什么暂时不能用，
 * 比直接消失更好找。pointer-events 一起关掉，拖了也不会有反应。
 * ============================================================ */
.row.dim {
  opacity: .4;
  pointer-events: none;
}
