/* =====================================================================
   TypeUp 3D 立体视觉语言 v3.0
   ---------------------------------------------------------------------
   这一层叠在 main.css 之上，只做一件事：把既有构件统一到一套守恒的
   立体物理里。main.css 的规则一条都不改 —— 出问题时把本文件的 <link>
   注释掉即可整层回退。

   五条守恒物理（照抄设计稿，不要在页面里自己发明）：
   ① 单一光源：全场景只有【左上 45°】。高光在左上、暗面在右下。
      做成 3D 却依然显得平，九成原因是这里不统一。
   ② 一个"块"四个来源：受光边 + 背光边 + 挤出厚度 + 落地阴影（.d3）。
   ③ 厚度 = 层级：.x1=3px / .x2=6px / .x3=10px / .x4=15px。
      有厚度就不需要分隔线；同一屏内厚度档不超过两档。
   ④ 材质按功能分三档：果冻（能量/奖励）/ 亚克力（面板/数字）/ 木（地面/桌面）。
      错配会让孩子读不出"什么能点"。
   ⑤ 抬角只给【可点】的卡片；文字永远不跟着斜。
   ===================================================================== */

/* ---------- 0. 光源与体积变量 ---------- */
:root {
  /* 受光边 / 背光边 —— 光源左上 45°，改这两个值等于改光源方向，别改 */
  --hi: rgba(255, 255, 255, 0.95);
  --sh2: rgba(16, 28, 54, 0.10);
  /* 环境遮蔽（接触阴影）与落地阴影 */
  --occl: rgba(16, 28, 54, 0.16);
  --drop: rgba(16, 28, 54, 0.30);
  /* 侧面（挤出厚度）的默认色：浅色主题下一律取冷灰，绝不取高饱和 */
  --side: #CBD5E4;
  --side-soft: #DCE4F0;
  /* 木材质（地面 / 桌面 / 地图） */
  --wood: #D5A96E;
  --wood-side: #8E6231;
  /* 果冻材质（能量 / 奖励 / 宝箱） */
  --jelly: #FFB020;
  --jelly-side: #D98A00;
  /* 抬角上限（度）。超过 6° 文字会开始难读，孩子会歪头 */
  --tilt-max: 6;
}

/* 深色与高对比主题下，侧面色必须跟着变暗，否则"块"会发光 */
.theme-dark, .theme-contrast {
  --hi: rgba(255, 255, 255, 0.16);
  --sh2: rgba(0, 0, 0, 0.55);
  --occl: rgba(0, 0, 0, 0.55);
  --drop: rgba(0, 0, 0, 0.65);
  --side: #12162A;
  --side-soft: #1E2440;
  --wood: #4A3A28;
  --wood-side: #241A10;
}

/* ---------- 1. 立体原子 .d3 ----------
   四个来源缺一个，立体感就塌。换颜色只改 --side / --drop，形状与光照不变。 */
.d3 {
  --ex: 6px;
  box-shadow:
    inset 0 1.8px 0 var(--hi),
    inset 0 -2.6px 0 var(--sh2),
    0 var(--ex) 0 var(--side),
    0 calc(var(--ex) + 2px) 3px -2px var(--occl),
    0 calc(var(--ex) + 10px) 18px -8px var(--drop);
  transition: transform .1s cubic-bezier(.2, .8, .3, 1), box-shadow .1s cubic-bezier(.2, .8, .3, 1);
}

/* 按下去 = 整块往下走、厚度收到 0。这是"能按"的唯一判据 */
.d3.press { cursor: pointer; }
.d3.press:active,
.d3.press.down {
  transform: translateY(var(--ex));
  box-shadow:
    inset 0 1.8px 0 var(--hi),
    inset 0 -1px 0 var(--sh2),
    0 0 0 var(--side),
    0 2px 4px -2px var(--occl),
    0 4px 8px -5px var(--drop);
}
.d3.press:hover:not(:active):not(.down) { filter: brightness(1.035); }

/* ---------- 2. 厚度三档（层级） ---------- */
.x1 { --ex: 3px; }
.x2 { --ex: 6px; }
.x3 { --ex: 10px; }
.x4 { --ex: 15px; }

/* ---------- 3. 材质三档（按功能分配，不许混用） ---------- */
/* 果冻：能量币、奖励、宝箱。半透明 + 内部辉光 */
.mat-jelly {
  --side: var(--jelly-side);
  background: linear-gradient(180deg, #FFD98A 0%, var(--jelly) 62%, #F09A00 100%) !important;
  border-color: rgba(255, 255, 255, .5) !important;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .85),
    inset 0 -3px 0 rgba(120, 60, 0, .28),
    0 var(--ex, 6px) 0 var(--jelly-side),
    0 calc(var(--ex, 6px) + 12px) 18px -8px rgba(160, 90, 0, .55) !important;
}
/* 亚克力：面板、数字、说明。磨砂冷白，绝不高饱和 */
.mat-acryl {
  --side: var(--side-soft);
  background: linear-gradient(180deg, #FFFFFF 0%, #F4F7FC 100%);
}
/* 木：地图地面、桌面、摊位。世界的地面 */
.mat-wood {
  --side: var(--wood-side);
  background: linear-gradient(180deg, #E6C08C 0%, var(--wood) 42%, #C08F51 100%) !important;
}

/* ---------- 4. 既有构件统一套用 ---------- */
/* 顶栏：不是"块"，只给一条接触阴影，避免导航条浮起来抢焦点 */
.navbar {
  box-shadow: 0 1px 0 var(--border), 0 8px 20px -14px rgba(16, 28, 54, .5) !important;
}

/* 导航按钮：x1 厚度，选中态用主色侧面 */
.nav-btn {
  border: 0;
  border-radius: 12px;
  box-shadow:
    inset 0 1.4px 0 var(--hi),
    inset 0 -2px 0 var(--sh2),
    0 3px 0 transparent;
  transition: transform .1s cubic-bezier(.2, .8, .3, 1), box-shadow .1s, background .15s;
}
.nav-btn.active,
.nav-btn[data-active="1"] {
  box-shadow:
    inset 0 1.4px 0 rgba(255, 255, 255, .55),
    inset 0 -2px 0 var(--sh2),
    0 3px 0 var(--accent-light);
}
.nav-btn:active { transform: translateY(3px); box-shadow: inset 0 1.4px 0 var(--hi); }

/* 卡片家族：x2 厚度（面板层） */
.stat-card, .data-card, .chart-container, .setting-item,
.adv-energy-card, .daily-banner, .result-card, .modal-card {
  box-shadow:
    inset 0 1.8px 0 var(--hi),
    inset 0 -2.6px 0 var(--sh2),
    0 6px 0 var(--side),
    0 8px 3px -2px var(--occl),
    0 16px 18px -8px var(--drop) !important;
}

/* 弹窗 / 结果卡 / 通关层：x3 厚度（最高层） */
.modal-card, .result-card, .adv-overlay-card {
  box-shadow:
    inset 0 2.2px 0 var(--hi),
    inset 0 -3px 0 var(--sh2),
    0 10px 0 var(--side),
    0 13px 5px -3px var(--occl),
    0 30px 40px -14px var(--drop) !important;
}

/* 按钮家族：x1 厚度，可点感立刻出来 */
.btn-action, .login-btn, .adv-big-btn, .adv-continue-btn, .daily-start-btn,
.ku-btn, .login-try, .bubble-key, .block-key, .ak-key {
  box-shadow:
    inset 0 1.6px 0 var(--hi),
    inset 0 -2.4px 0 var(--sh2),
    0 4px 0 var(--side),
    0 6px 3px -2px var(--occl),
    0 12px 14px -9px var(--drop) !important;
  transition: transform .09s cubic-bezier(.2, .8, .3, 1), box-shadow .09s, filter .12s;
}
.btn-action:active, .login-btn:active, .adv-big-btn:active, .adv-continue-btn:active,
.daily-start-btn:active, .ku-btn:active, .bubble-key:active, .block-key:active, .ak-key:active {
  transform: translateY(4px);
  box-shadow: inset 0 1.6px 0 var(--hi), 0 0 0 var(--side), 0 2px 3px -2px var(--occl) !important;
}
/* 主色按钮的侧面要跟主色走，否则"块"和面不同色，看起来像浮错了 */
.login-btn, .btn-action.primary, .adv-continue-btn, .daily-start-btn {
  --side: var(--accent-light);
}

/* 次级/幽灵按钮不占厚度：它们不该看起来像能按出声音 */
.adv-ghost-btn, .btn-action.ghost {
  box-shadow: inset 0 1.4px 0 var(--hi), 0 2px 0 var(--side-soft) !important;
}

/* 覆盖层：背景压暗 + 卡片抬起 */
.adv-overlay, .modal-overlay, .result-overlay {
  backdrop-filter: blur(3px);
}

/* 徽章 / 金币 / 星星：果冻材质的小块 */
.coin, .badge3d, .ku-chip, .login-badge, .navbar-score {
  box-shadow:
    inset 0 1.6px 0 rgba(255, 255, 255, .8),
    inset 0 -2px 0 rgba(16, 28, 54, .16),
    0 3px 0 var(--side) !important;
}

/* 进度条：内凹（反相立体），因为"凹"＝容器、"凸"＝可点 */
.brand-prog-bar, .progress-bar, .ku-bar {
  box-shadow: inset 0 2px 4px -1px var(--occl), inset 0 -1.4px 0 var(--hi) !important;
}

/* 开关：滑块本身要有厚度 */
.toggle {
  box-shadow: inset 0 2px 5px -1px var(--occl), inset 0 -1.2px 0 var(--hi) !important;
}

/* 设置项与排行榜行：x1 */
.lb-row, .dc-card, .setting-item { --ex: 3px; }

/* ---------- 5. 抬角与视差（只给可点卡片） ---------- */
.tiltwrap { perspective: 900px; }
.tilt {
  transform-style: preserve-3d;
  will-change: transform;
  transform: rotateX(calc(var(--rx, 0) * 1deg)) rotateY(calc(var(--ry, 0) * 1deg)) translateZ(0);
  transition: transform .22s cubic-bezier(.2, .8, .3, 1);
}
/* 视差幅度 ≤35px，且只在指针移动时触发 —— 不做自动漂移，前庭敏感儿童会头晕 */
.parallax { transform: translate3d(calc(var(--px, 0) * var(--z, 0) * 35px), calc(var(--py, 0) * var(--z, 0) * 35px), 0); }

/* ---------- 6. 键帽：立体感由【厚度】承担，不是靠透视 ----------
   键盘永远 0° 正对镜头。倾斜 8° 时同一行左右两端键帽投影宽度相差约 14%，
   而"哪个键在哪"是这个产品的全部价值。 */
.akey, .keyboard-key, .block-key, .key {
  border: 0 !important;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .98),
    inset 0 -3px 0 rgba(16, 28, 54, .09),
    0 5px 0 var(--key-side, #C9D3E3),
    0 7px 4px -4px var(--occl),
    0 13px 15px -9px var(--drop) !important;
  transition: transform .08s cubic-bezier(.2, .8, .3, 1), box-shadow .08s;
}
.akey:active, .keyboard-key:active, .block-key:active, .key:active {
  transform: translateY(5px);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .9), 0 0 0 var(--key-side, #C9D3E3) !important;
}
/* 目标键【抬起】而不是压下去：只有"待按"的键才抬起，语义才一致 */
.akey.next-key, .block-key.finger-light, .keyboard-key.next-key {
  transform: translateY(-4px) scale(1.04);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .95),
    0 9px 0 var(--key-side, #C9D3E3),
    0 0 0 6px var(--key-glow, rgba(74, 108, 247, .28)),
    0 14px 18px -8px var(--drop) !important;
}
/* 目标键的颜色不靠色相单独承担：同时抬起 + 光环，色盲儿童也能分辨 */
.theme-dark .akey, .theme-dark .keyboard-key, .theme-dark .block-key {
  --key-side: #10142A;
}

/* ---------- 7. 等距场景（地图 / 工坊） ---------- */
.iso-scene { width: 100%; height: auto; display: block; }
.iso-scene .isle-hit { cursor: pointer; }
.iso-scene .isle-locked { filter: saturate(.25) brightness(.92); }
.iso-scene text { font-family: var(--font-sans); }

/* ---------- 8. 三档深度降级 ----------
   只用一个根属性切换，【不在每个组件里写媒体查询】。
   full = 完整 3D；lite = 只留受光边 + 一条接触阴影（中低端平板）；
   off  = 完全没有阴影与倾角，等同 2D 稿（低端设备 / 前庭敏感儿童）。 */
html[data-depth="lite"] .d3,
html[data-depth="lite"] .stat-card,
html[data-depth="lite"] .data-card,
html[data-depth="lite"] .chart-container,
html[data-depth="lite"] .modal-card,
html[data-depth="lite"] .result-card,
html[data-depth="lite"] .adv-overlay-card,
html[data-depth="lite"] .btn-action,
html[data-depth="lite"] .login-btn,
html[data-depth="lite"] .adv-big-btn,
html[data-depth="lite"] .akey,
html[data-depth="lite"] .keyboard-key,
html[data-depth="lite"] .block-key,
html[data-depth="lite"] .coin,
html[data-depth="lite"] .navbar-score {
  box-shadow: inset 0 1.4px 0 var(--hi), 0 3px 2px -2px var(--occl) !important;
}
html[data-depth="lite"] .akey.next-key,
html[data-depth="lite"] .keyboard-key.next-key {
  box-shadow: inset 0 1.4px 0 var(--hi), 0 0 0 3px var(--key-glow, rgba(74, 108, 247, .28)), 0 4px 3px -3px var(--occl) !important;
}
html[data-depth="lite"] .tilt,
html[data-depth="lite"] .parallax { transform: none !important; }

html[data-depth="off"] * {
  box-shadow: none !important;
  text-shadow: none !important;
}
html[data-depth="off"] .tilt,
html[data-depth="off"] .parallax,
html[data-depth="off"] .d3.press:active { transform: none !important; }
/* 平面档要靠边框重新把边界画出来，否则白卡片在白底上消失 */
html[data-depth="off"] .stat-card,
html[data-depth="off"] .data-card,
html[data-depth="off"] .chart-container,
html[data-depth="off"] .modal-card,
html[data-depth="off"] .result-card,
html[data-depth="off"] .adv-overlay-card,
html[data-depth="off"] .akey,
html[data-depth="off"] .keyboard-key,
html[data-depth="off"] .block-key,
html[data-depth="off"] .btn-action,
html[data-depth="off"] .bubble-key {
  border: 1px solid var(--border) !important;
}

/* 前庭敏感 / 低端设备：动画本身也是压力来源。
   2026-09-22 补上本轮新增的慢速回放光点 —— 只写 animation:none 不够：
   光点会停在路径起点（基准键）上变成一颗静止的实心点，
   而它标的是"起点"、手指却在目标键上，反而多出一个错位的点。
   这一档干脆不画（display:none），让静止画面里只留下虚线轨迹。 */
@media (prefers-reduced-motion: reduce) {
  .d3, .tilt, .parallax, .akey, .keyboard-key, .btn-action { transition: none !important; }
  .adv-shell.adv-island .akey.next-key,
  .adv-shell.adv-island .akey.soft-next,
  .hands-overlay .hf-trail,
  .hands-overlay .tip-halo { animation: none !important; }
  .hands-overlay .hf-replay { display: none !important; }
  .adv-shell.adv-island .akey.next-key { transform: translateY(-4px) scale(1.04) !important; }
}

/* ---------- 9. 手型层 v4（半透明皮肤 + 真实手形） ----------
   手是【提示】不是【主体】：整只手半透明，只有活动的那根手指上实色。
   覆盖 main.css 里各阶段写死的 opacity（0.55~0.95）—— 那会让透明手更透明，
   以及外层 drop-shadow —— 那会把半透明的手又压成一块实心影子。 */
/* ⚠️ 这张清单必须包含【所有可能承载 .hands-overlay 的键盘容器】。
   .hands-overlay 是 position:absolute + left/top 按键盘键包围盒算出来的，
   它的"定位祖先"必须正好是键盘容器本身。漏一个（比如岛 2/3 用的 .isk-kbd），
   手就会以更外层的盒子为原点 —— 实测岛 2 飘到视口左上角，错位 dx=-552 dy=-697，
   手压在导航栏上，而控制台一个错都不报。 */
.virtual-keyboard, .adv-kbd, .s2-kbd, .keyboard-zone, .isk-kbd { position: relative; }
.adv-kbd, .s2-kbd, .virtual-keyboard, .isk-kbd { overflow: visible; }
.hands-overlay {
  opacity: 1 !important;
  filter: none !important;
  overflow: visible;
  pointer-events: none;
}
/* ⚠️ 手已经拆成【独立图元】（掌 / 每根手指 / 掌缘描边 / 拇指），
   不再是原来那一个 class="hand" 的连续轮廓。这张选择器清单必须同步 ——
   漏一个的症状是"某一笔的圆角接头没了"，在细手指上就是指尖出现尖角。 */
.hands-overlay .hand-palm,
.hands-overlay .hand-finger,
.hands-overlay .hand-edge,
.hands-overlay .hand-dorsum,
.hands-overlay .hand-thumb { stroke-linejoin: round; stroke-linecap: round; }
.hands-overlay .hand-creases { opacity: .92; }
/* 活动手指的强调：同一条路径重描，上色加粗 */
.hands-overlay .hf.on { stroke-linejoin: round; stroke-linecap: round; }

/* ---------- 10. 深度档选择器（设置页） ---------- */
.depth-picker {
  display: flex;
  gap: 0;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-tertiary);
  box-shadow: inset 0 2px 5px -1px var(--occl), inset 0 -1.2px 0 var(--hi);
}
.depth-picker button {
  flex: 1;
  border: 0;
  background: transparent;
  font-family: inherit;
  font-size: 13px;
  font-weight: 800;
  color: var(--text-muted);
  padding: 9px 6px;
  cursor: pointer;
  transition: .14s;
}
.depth-picker button.on {
  background: var(--bg-secondary);
  color: var(--text-primary);
  box-shadow:
    inset 0 1.6px 0 var(--hi),
    0 2px 0 var(--side),
    0 3px 6px -3px var(--occl);
}
.depth-picker button:active { transform: translateY(1px); }

/* ---------- 10. 平板与窄屏 ---------- */
@media (max-width: 1100px) {
  :root { --drop: rgba(16, 28, 54, .24); }
}
@media (max-width: 760px) {
  /* 窄屏把厚度整体收一档：厚度在全宽下是层级，在小屏上只是浪费像素 */
  .x3 { --ex: 7px; }
  .x4 { --ex: 10px; }
}

/* ---------- 11. 冒险地图（等距 5 平台 × 15 岛） ----------
   这一层的几何全部由 js/advmap.js 用 Design3D 的等距投影生成，
   CSS 只负责【容器怎么放】和【动画】，绝不在这里改任何坐标。 */

/* 关键一条（v2 大舞台版）：地图不再是"一张适配宽度的整图"，
   而是【远大于屏幕的世界舞台】——svg-wrap 是窗口，viewport 负责滚动，
   stage 的尺寸由 app.js 按缩放模式显式写死（transform 不改可滚动区域，KET 同款）。
   ⚠️ svg 的 2800×2900 必须和 js/advmap.js 里的 WORLD 保持一致，改了一边就要改另一边。
   ⚠️ 优先级链：.main-content.home-mode .adv-map-iso .adv-map-svg（4 类）
   必须压过 main.css 的 .main-content.home-mode .adv-map-svg（3 类）的 width/height:100%。 */
.main-content.home-mode .adv-map-iso .adv-map-svg-wrap,
.adv-map-iso .adv-map-svg-wrap {
  flex: 1 1 0;
  min-height: 0;
  height: auto;
  max-height: none;
  aspect-ratio: auto;
}
.main-content.home-mode .adv-map-iso {
  overflow: hidden;
  padding-bottom: 0;
}
.adv-map-viewport {
  position: absolute;
  inset: 0;
  overflow: auto;
  overscroll-behavior: contain;
  cursor: grab;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
  display: flex;
}
.adv-map-viewport.is-drag { cursor: grabbing; }
/* margin:auto 是"可滚动容器里安全居中"的唯一写法：
   内容比视口小时两轴居中（看全图不贴边）；比视口大时 auto 边距归零、退化为左上对齐，
   滚动数学（scrollLeft=世界×scale）不受影响。 */
.adv-map-stage { position: relative; flex: 0 0 auto; margin: auto; }
.main-content.home-mode .adv-map-iso .adv-map-svg,
.adv-map-iso .adv-map-svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 2800px;
  height: 2900px;
  transform-origin: 0 0;
}

/* ---- （已移除）登录页左侧的品牌预览 ----
   2026-09-28：左侧品牌区改成"4 个练习模块的真实进度卡"后，这里原本给
   `.brand-map-body.iso` 那一组立体预览写的规则全成了死代码：
   它的 svg-wrap 尺寸、aspect-ratio、以及"把 svg 拉回文档流"的三条 !important，
   对象在 DOM 里已经不存在了 —— 留在文件里会让人以为登录页还有一张地图。
   地图本身没删：首页 `#page-home` 用的 island.html，以及 `#page-adventure`
   那一套 AdvMap 都照旧。 */

/* 视差：远景（云/剪影屿）动得慢、岛与节点动得快 —— 两层错开才产生纵深。
   变量由 Design3D.bindParallax 写到 <svg data-parallax> 上，靠 CSS 变量继承下来。 */
.adv-map-iso .am-back {
  transform: translate(calc(var(--px, 0) * 10px), calc(var(--py, 0) * 7px));
  transition: transform .3s ease-out;
}
.adv-map-iso .am-front {
  transform: translate(calc(var(--px, 0) * 22px), calc(var(--py, 0) * 14px));
  transition: transform .3s ease-out;
}

/* 关卡节点：可点的才有抬角。可点性靠【动】表达，比靠颜色更早被低龄孩子读到 */
.adv-map-iso .am-node { transition: transform .16s ease; }
.adv-map-iso .am-node[onclick]:hover { transform: translateY(-6px); }
.adv-map-iso .am-node[onclick]:active { transform: translateY(0); }
.adv-map-iso .am-node.is-locked { opacity: .6; }
.adv-map-iso .am-tisle.is-locked { opacity: .62; }
.adv-map-iso .am-region-tag.is-locked { opacity: .8; }

/* 云只呼吸不位移 —— 位移会和元素上的 transform 属性打架，把云全挤到原点 */
.adv-map-iso .am-cloud { animation: am-breathe 24s ease-in-out infinite; }
@keyframes am-breathe {
  0%, 100% { opacity: .5; }
  50%      { opacity: .85; }
}

/* 深度档 / 系统偏好：全部停掉，一个都不留 */
html[data-depth="off"] .adv-map-iso .am-cloud,
html[data-depth="off"] .adv-map-iso .am-back,
html[data-depth="off"] .adv-map-iso .am-front {
  animation: none !important;
  transform: none !important;
}
@media (prefers-reduced-motion: reduce) {
  .adv-map-iso .am-cloud { animation: none !important; }
  .adv-map-iso .am-node:hover { transform: none; }
}

/* ---------- 12. 地图页头：工坊入口 ----------
   次级按钮：x2 厚度、透明底 + 木色描边，不跟"继续冒险"抢第一视线，
   但必须有厚度，否则孩子会把它读成文字而不是按钮。 */
.adv-map-hero-right { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }

.adv-workshop-btn {
  --ex: 6px;
  --side: var(--wood-side);
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 11px 18px;
  border: 0;
  border-radius: 14px;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  color: #5A3A12;
  cursor: pointer;
  background: linear-gradient(180deg, #FBE7C4 0%, #F3D3A2 100%);
  box-shadow:
    inset 0 1.8px 0 var(--hi),
    inset 0 -2.6px 0 var(--sh2),
    0 var(--ex) 0 var(--wood-side),
    0 calc(var(--ex) + 10px) 18px -8px var(--drop);
  transition: transform .1s cubic-bezier(.2, .8, .3, 1), box-shadow .1s cubic-bezier(.2, .8, .3, 1);
}
.adv-workshop-btn:hover { filter: brightness(1.035); }
.adv-workshop-btn:active {
  transform: translateY(var(--ex));
  box-shadow: inset 0 1.8px 0 var(--hi), inset 0 -1px 0 var(--sh2), 0 0 0 var(--wood-side);
}
/* 提示态：只做一次呼吸，不闪不停，避免孩子盯着一闪一闪的东西走神 */
.adv-workshop-btn.hint::after {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 17px;
  border: 2.5px solid #F59E0B;
  opacity: 0;
  animation: ws-nudge 2.6s ease-out 3;
  pointer-events: none;
}
@keyframes ws-nudge {
  0%   { opacity: .85; transform: scale(1); }
  70%  { opacity: 0;   transform: scale(1.06); }
  100% { opacity: 0;   transform: scale(1.06); }
}
.adv-workshop-btn i {
  font-style: normal;
  font-size: 12px;
  font-weight: 600;
  color: #8A5A16;
  padding-left: 8px;
  margin-left: 2px;
  border-left: 1px solid rgba(138, 90, 22, .3);
}

/* ---------- 13. 能量工坊（page · iframe 宿主） ----------
   2026-09-29：工坊换成交付包那版产品（server/web/workshop.html），改成 iframe 嵌入。
   这里原来那 660 行 `.ws-*`（等距摊位场景 + 动机层）**整套删掉**了 ——
   那些样式是为旧工坊的 DOM 写的，而新工坊自带一份完整样式表（在 iframe 内部）。
   本文件现在只负责一件事：**把宿主这一格撑满**，让 iframe 吃掉整个内容区。

   ⚠️ 为什么必须有三条而不是一条：
      .page 默认是 display:block（main.css 的 `.page.active{display:block}`），
      block 元素高度由内容决定 —— iframe 不给高度就只有默认的 150px 高，
      页面看着像"只露了顶部一条"，而且不报错。 */

.main-content.ws-mode { padding: 0; overflow: hidden; }
.main-content.ws-mode ~ .app-footer { display: none; }

/* ⚠️⚠️ 选择器里的 .active 不能省。ws-mode 是加在 .main-content 上的（不是加在 page 上），
   少了 .active 就变成"只要切到工坊，别的 page 也跟着 flex"，
   而 .main-content.ws-mode 有 overflow:hidden —— 屏幕上只露最上面那个，切页像没反应。
   与 main.css 里 home-mode 那三条是同一个坑（那里的注释更详细，加产品页时要一起补）。 */
.main-content.ws-mode > .page#page-workshop.active {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  height: 100%;
}
/* 工坊是独立 html 以 iframe 嵌入（隔离 id/类名冲突，理由见 index.html 的注释）。
   flex:1 让 iframe 吃满 page 的全部高度，外层不出现双滚动条。 */
.main-content.ws-mode > .page#page-workshop.active .ws-frame {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  border: 0;
  display: block;
  border-radius: 14px;
  /* 工坊自带深蓝底（workshop.html 的 --bg-deep:#070f1f）。
     iframe 未加载完时先用同色兜底，否则会先闪一下主站的浅色页面底。 */
  background: #070F1F;
}

/* ---------- 14. 岛内层舞台（islandskin.js · T1–T7） ----------
   15 座岛的关卡页共用一套骨架：一块等距舞台 + 一条任务文案 + 一层会变的文本。
   这一节只负责"HTML 层"的立体感与排版；SVG 舞台的立体感由 Design3D 的
   同一套投影与同一套光照配比保证，两边不各写一份。 */

/* 岛模式把剧场区放大：舞台是这一页的主体，不再只是"顶上一块插画"。
   底色改用【岛所属区域】的配色（--isl-c 由 app.js 注入）——
   stage 配色是旧 6 阶段的，跟 15 岛的区域制对不上，会让"同一区域的岛长得不一样"。 */
.adv-shell.adv-island {
  gap: 6px;
}
/* ★ 剧场与键盘区【按比例分摊】多余高度。
   只用固定高度的话：高屏（1080p）上键盘+双手会停在页面上半部，底下空出一大片，
   看起来像没做完；而矮屏（638p）又必须压到最小才装得下手。
   改成两块都吃 flex，多出来的高度就平均分给"舞台"和"手型" ——
   矮屏两者都贴着下限，高屏两者都长大，不需要为某个具体分辨率写死数字。
   basis 取 0 是为了让分配只跟 flex-grow 有关：剧场的"内容高度"其实是 0
   （.adv-theater-body 是 absolute 的），写成 auto 反而会算出不稳定值。 */
.adv-shell.adv-island .adv-theater {
  flex: 1 1 0;
  min-height: 140px;
  max-height: 46vh;
  height: auto;
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--isl-c, #93C5FD) 30%, #FFFFFF) 0%,
    color-mix(in srgb, var(--isl-c, #93C5FD) 62%, #FFFFFF) 100%);
}
.adv-shell.adv-island .adv-theater-body { padding: 52px 14px 6px; }

/* ★ 键盘区：键块贴顶、剩余高度【全部留给手型】。
   justify-content 从 center 改成 flex-start 是关键的一步：
   居中时"键盘下方"和"键盘上方"各分走一半，而手只会往下画 —— 上半那半是纯浪费。 */
.adv-shell.adv-island .adv-keyboard-zone {
  flex: 1 1 auto;
  min-height: 0;
  padding: 0;
  gap: 0;
  justify-content: flex-start;
}

/* ★ 键盘：键高【固定】，不再 flex 撑满。
   原来 .adv-kbd 是 flex:1 1 auto，会把键盘区的剩余高度全部吃掉，
   于是"窗口越高键越高"，而指根线是跟着键盘下沿走的 —— 掌长会跟着键盘高度疯长
   （app.js 里记着这个坑：一只掌被拉成 214×637 的长板）。
   改成内容定高之后有两个好处：
     ① 键盘区多出来的高度自然落到"键盘下方"，正好是手的位置；
     ② 键的尺寸不再随窗口高度变 —— 孩子每次看到的键一样大，肌肉记忆才建得起来。 */
.adv-shell.adv-island .adv-kbd {
  flex: 0 0 auto;
  max-height: none;
  gap: 4px;
  padding: 6px;
}
.adv-shell.adv-island .adv-kbd-row { flex: 0 0 auto; }
.adv-shell.adv-island .akey {
  height: var(--isl-key-h, clamp(15px, 2.9vh, 30px));
  min-height: 0;
  border-radius: 8px;
  /* 字号下限保持 12px：矮窗口下 2.9vh 只有 18.5px 键高，
     1.5vh 算出来的字号约 9.6px —— 对 5 岁孩子太小了，键面上要先看得清才谈得上找键。 */
  font-size: clamp(12px, 1.5vh, 17px);
}

/* ★ 能量卡与机制条一起收一档：它们只是"看得见的反馈"，不是主角。
   四项各减 6~8px，合计又给手型让出约 20px。 */
.adv-shell.adv-island .adv-banner {
  padding: 5px 12px;
  font-size: clamp(12px, 1.5vw, 15px);
}
.adv-shell.adv-island .adv-energy { gap: 8px; }
.adv-shell.adv-island .energy-card {
  min-height: 0;
  padding: 5px 10px;
  border-width: 2px;
  border-radius: 13px;
  gap: 6px;
}
.adv-shell.adv-island .energy-icon { font-size: 20px; }
.adv-shell.adv-island .energy-label { font-size: clamp(10px, 1.3vh, 12.5px); }
.adv-shell.adv-island .energy-value { font-size: clamp(15px, 2.1vh, 20px); }

/* 顶栏两行：岛名 + 第几步。
   ⚠️ adv-stage-3 / adv-stage-6 和 adv-shell.adv-island 是【同一个元素】上的类。
   写成 `.adv-shell.adv-island .adv-stage-3 .adv-topbar-title` 是父子关系，
   永远匹配不上 —— 必须连写成 `.adv-shell.adv-island.adv-stage-3`。
   不修的话岛 7/8/9/13/14/15 的岛名会是 main.css 里给深底准备的金黄色，
   压在浅色区域底上几乎读不出来。 */
.adv-topbar-tt { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 0; }
.adv-topbar-sub { font-size: 12.5px; font-weight: 700; color: var(--isl-ink, #1E3A8A); opacity: .78; }
.adv-shell.adv-island.adv-stage-3 .adv-topbar-title,
.adv-shell.adv-island.adv-stage-6 .adv-topbar-title,
.adv-shell.adv-island.adv-stage-3 .adv-topbar-sub,
.adv-shell.adv-island.adv-stage-6 .adv-topbar-sub {
  color: var(--isl-ink, #1E3A8A);
  text-shadow: 0 1px 0 rgba(255, 255, 255, .72);
}

/* ---- partial 键盘：还没学到的字母键 ----
   设计文档岛 4/5 的要求是「完整轮廓 + 未学键灰化虚线」。用"虚线轮廓 + 去色"
   而不是单纯调低透明度：透明度在深色（stage 3）和浅色（stage 2）底上表现完全
   不同，虚线轮廓两边都读得出来。
   ⚠️ 基础规则 `.akey { border:0 !important; box-shadow: ... !important }`
   用了 !important，这里不加就整条被吃掉 —— 又是"不报错的静默失效"。 */
.adv-shell.adv-island .akey.is-unlearned {
  background: transparent !important;
  color: #94A3B8 !important;
  border: 1.5px dashed rgba(148, 163, 184, .9) !important;
  box-shadow: none !important;
  opacity: .72;
}
.adv-shell.adv-island.adv-stage-3 .akey.is-unlearned,
.adv-shell.adv-island.adv-stage-6 .akey.is-unlearned {
  color: rgba(196, 181, 253, .68) !important;
  border-color: rgba(196, 181, 253, .5) !important;
}
/* 灰键保持"看得见、按不动"：不给 hover 反馈，但按下仍能触发
   「还没学到这里」的提示，比直接吞掉输入好 */
.adv-shell.adv-island .akey.is-unlearned:hover { transform: none !important; }
.adv-shell.adv-island .akey.is-unlearned:active { transform: translateY(1px) !important; }

/* ---- 指法色卡：每颗键按【所属手指】铺一层淡染（--kf 由 app.js 写在 style 上）----
   底色只能很淡：键面上还有字母要读，"染色一重就把字母吃掉"是本末倒置。
   由此颜色的分工是：
     淡染  = "这颗键归哪根手指"（学会指法要记的东西）
     深染 + 指色环 = "现在该按哪颗"（当下的指令）
   灰键不用管：上面 `.is-unlearned` 已经用 !important 把 background 清成透明，
   那一档要说的是"这里还没开放"，再叠一层指色就是两种信息互相干扰。 */
.adv-shell.adv-island .akey[data-kfinger] {
  background: color-mix(in srgb, var(--kf, #E2E8F0) 15%, #FFFFFF);
}

/* ---- 新机制条：每关开场自报家门 ---- */
.adv-banner {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
  margin: 0 6px;
  padding: 8px 14px;
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(255,255,255,.96), rgba(255,255,255,.80));
  border: 2px solid var(--isl-c, #93C5FD);
  color: var(--isl-ink, #1E3A8A);
  font-size: clamp(13px, 1.6vw, 16px); font-weight: 800;
  box-shadow: 0 3px 0 rgba(16,28,54,.10), 0 8px 16px -10px rgba(16,28,54,.30);
}
.adv-banner-ico { font-size: 1.2em; }

/* ---- 舞台 ---- */
.isk-wrap { width: 100%; height: 100%; display: flex; flex-direction: column;
            align-items: center; justify-content: center; gap: 5px; min-height: 0; }
/* 舞台吃掉剩余高度；任务条是【同一个 flex 流】里的最后一项，
   所以两者永远不会重叠（早先把任务条 absolute 定在底部，必然压住舞台底部的进度点） */
.isk-wrap > svg, .isk-wrap > .isk-t5, .isk-wrap > .isk-t6, .isk-wrap > .isk-t7 {
  flex: 1 1 auto; min-height: 0; width: 100%; }
.isk-stage { width: 100%; height: 100%; display: block; }
.isk-task {
  flex: 0 0 auto; margin: 0; text-align: center;
  font-size: clamp(15px, 1.9vw, 21px); font-weight: 900;
  color: var(--isl-ink, #0C4A6E);
  text-shadow: 0 1px 0 rgba(255,255,255,.75);
}
.isk-t5, .isk-t6, .isk-t7 { display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; }
.isk-t5 .isk-stage, .isk-t7 .isk-stage { flex: 0 0 50%; height: 50%; }

/* ---- T1 手指条（8 根指头，点对了抬起来）---- */
.isk-fingers-pad {
  display: grid; grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: clamp(6px, .9vw, 12px); width: min(980px, 100%); margin: 0 auto;
  padding: 2px;
}
.isk-finger {
  --fc: #60a5fa;
  appearance: none; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 9px 4px 7px;
  border-radius: 16px;
  border: 2.5px solid rgba(255,255,255,.8);
  background: linear-gradient(180deg, #FFFFFF, #EEF3FB);
  color: #24334D; font-size: clamp(10px, 1.05vw, 13px); font-weight: 800;
  /* 一个"块"四个来源：受光边 + 背光边 + 挤出厚度 + 落地阴影 */
  box-shadow:
    inset 0 1.8px 0 var(--hi),
    inset 0 -2.6px 0 var(--sh2),
    0 5px 0 var(--side),
    0 7px 3px -2px var(--occl),
    0 14px 20px -10px var(--drop);
  transform: translateY(0);
  transition: transform .1s cubic-bezier(.2,.8,.3,1), box-shadow .1s cubic-bezier(.2,.8,.3,1);
}
.isk-finger:active, .isk-finger.is-target { transform: translateY(5px); box-shadow:
    inset 0 1.8px 0 var(--hi), inset 0 -1px 0 var(--sh2),
    0 0 0 var(--side), 0 2px 4px -2px var(--occl), 0 6px 10px -6px var(--drop); }
.isk-finger.is-target { border-color: var(--fc); background: linear-gradient(180deg, #FFFFFF, #EAF4FF); }
.isk-finger-dot { width: 20px; height: 20px; border-radius: 50%; background: var(--fc);
  box-shadow: inset 2.5px 2.5px 0 rgba(255,255,255,.85), 0 2px 3px rgba(16,28,54,.25); }
.isk-finger.is-done { opacity: .55; }
.isk-finger.is-done .isk-finger-dot { background: #22C55E; }

/* 提示脉冲：给"看一眼"用，不改变布局 */
@keyframes iskPulse { 0%,100% { transform: translateY(0) scale(1); } 50% { transform: translateY(-4px) scale(1.06); } }
.hint-pulse { animation: iskPulse .5s ease-in-out 3; }

/* ---- 岛 2 / 岛 3 的基准行大键键盘（8 键 / 2 键）---- */
.isk-kbd { display: flex; flex-direction: column; align-items: center;
  gap: clamp(8px, 1.2vw, 14px); padding: 6px 0; width: 100%; }
/* ★ 基准行必须在一行里排完（真键盘的 A S D F J K L ; 就是同一排）。
   nowrap + 键可收缩：窗口再窄也只会把键等比压小、不会折到第二行。
   键盘区是 overflow:hidden，一旦折行下半排会被裁掉，看起来像"键少了"。 */
.isk-kbd-row { display: flex; flex-wrap: nowrap; align-items: flex-end;
  justify-content: center; gap: clamp(8px, 1.2vw, 16px); width: 100%; }
/* 左右手分界缝：真键盘上左手四键与右手四键之间比同手相邻键松一档 */
.isk-kbd-split { flex: 0 0 auto; align-self: stretch;
  width: clamp(12px, 1.8vw, 30px); }
/* 键盘区撑满剩余高度，键少时会空一大片。按键数给一档尺寸把这个高度用起来：
   岛 3 只有 A 和 ; 两颗，就该大到一眼能盯住。 */
.isk-kbd-n2 .isk-key { width: clamp(84px, 12vw, 138px); max-width: clamp(84px, 12vw, 138px);
  height: clamp(84px, 12vw, 138px); font-size: clamp(34px, 4.8vw, 58px); border-radius: 22px; }
/* ⚠️ 两档都必须显式写 max-width：.block-key 上带着 max-width: clamp(56px, 6vw, 72px)，
   而 .isk-key 没有覆盖它 —— 于是这里写 92 / 138 也只会渲染成 72px，
   静默失效、不报错、肉眼只会觉得"键怎么这么小"（和"未学键灰化没生效"同一类坑）。
   8 键一行的上限由宽度倒推：8×112 + 7×16 + 缝 30 ≈ 1040 < 键盘区可用宽 1176。 */
.isk-kbd-n8 .isk-key {
  flex: 0 1 auto; min-width: 0;
  width: clamp(46px, 7.4vw, 112px); max-width: clamp(46px, 7.4vw, 112px);
  height: auto; aspect-ratio: 1 / 1;
  font-size: clamp(20px, 3.4vw, 46px);
}
.isk-key {
  --fc: #94a3b8;
  width: clamp(46px, 6.4vw, 74px); height: clamp(46px, 6.4vw, 74px);
  font-size: clamp(18px, 2.6vw, 30px); font-weight: 900;
  color: #1F2937;
  border-radius: 16px; cursor: pointer;
  border: 2.5px solid rgba(255,255,255,.85);
  /* 键帽材质用【键位手指色】的浅版，与手指条/手型层同一套指色语言 */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--fc) 22%, #FFFFFF) 0%,
    color-mix(in srgb, var(--fc) 34%, #FFFFFF) 100%);
  box-shadow:
    inset 0 2px 0 var(--hi),
    inset 0 -3px 0 var(--sh2),
    0 6px 0 color-mix(in srgb, var(--fc) 62%, #6B7280),
    0 8px 4px -3px var(--occl),
    0 16px 22px -12px var(--drop);
  transition: transform .1s cubic-bezier(.2,.8,.3,1), box-shadow .1s cubic-bezier(.2,.8,.3,1);
}
/* 按下：厚度收到 0，键面下沉 —— 只用于 :active（真的被手指按住那一下） */
.isk-key:active { transform: translateY(6px);
  box-shadow: inset 0 2px 0 var(--hi), inset 0 -1px 0 var(--sh2),
    0 0 0 var(--side), 0 2px 4px -2px var(--occl), 0 8px 12px -8px var(--drop); }
/* ★ 目标态（"现在该按这一颗"）：必须【抬起】，不是压下去。
   本文件第 234 行就写着这条铁律：「目标键抬起而不是压下去：只有"待按"的键才抬起，
   语义才一致」。原来这里和 :active 写在同一条规则里、transform 是 translateY(6px)，
   于是一整关里目标键看上去【像已经被按住了】—— 孩子根本读不出"这颗是答案"，
   按错了只会以为是"这个键点了没用"。

   而且必须有【不依赖动画的常驻光环】：原来唯一的环来自 @keyframes finger-glow，
   它的 0%/100% 帧是 `0 0 0 rgba(253,224,71,0)`，也就是【完全透明】——
   恰好在那一瞬间看屏幕，目标键就是一颗毫无标记的普通键。

   box-shadow 必须带 !important：本文件上方还有一条
   `.block-key.finger-light { box-shadow: … !important }`（针对旧阶段键盘写的），
   同优先级下它会把这整条光环吃掉 —— 「被覆盖的规则带 !important，你的覆盖也必须带」。
   白色内环 + 指色外环两层：色盲儿童也能靠"环"而不是"色相"分辨目标。 */
.isk-key.finger-light {
  transform: translateY(-5px);
  animation: none !important;
  box-shadow: inset 0 2px 0 var(--hi), inset 0 -3px 0 var(--sh2),
    0 10px 0 var(--side), 0 14px 6px -6px var(--occl),
    0 0 0 4px #FFFFFF,
    0 0 0 9px color-mix(in srgb, var(--fc) 80%, transparent),
    0 20px 26px -12px var(--drop) !important;
}
/* 「看一眼」专属：在目标态之上再叠一层【更粗更亮】的呼吸环。
   目标态是常驻的，提示必须比它更强，否则按了等于没按（原实现只加一个 4px 位移动画）。 */
.isk-key.hint-pulse {
  animation: iskKeyAsk 0.62s ease-in-out 4 !important;
}
@keyframes iskKeyAsk {
  0%, 100% { transform: translateY(-5px) scale(1);
    box-shadow: inset 0 2px 0 var(--hi), inset 0 -3px 0 var(--sh2),
      0 10px 0 var(--side), 0 0 0 4px #FFFFFF,
      0 0 0 9px color-mix(in srgb, var(--fc) 80%, transparent) !important; }
  50% { transform: translateY(-11px) scale(1.10);
    box-shadow: inset 0 2px 0 var(--hi), inset 0 -3px 0 var(--sh2),
      0 16px 0 var(--side),
      0 0 0 7px #FFFFFF,
      0 0 0 17px color-mix(in srgb, var(--fc) 92%, transparent),
      0 0 34px 10px color-mix(in srgb, var(--fc) 55%, transparent) !important; }
}
.isk-key.is-bump::after { content: ''; display: block; width: 34%; height: 3px;
  margin: -6px auto 0; border-radius: 2px; background: rgba(31,41,55,.42); }

/* ---- 全键盘（岛 4–15）的目标键与「看一眼」----
   旧阶段键盘的 next-key 环是 `0 0 0 6px var(--key-glow, rgba(74,108,247,.28))`：
   28% 透明度的一圈淡蓝，压在浅色键盘底上几乎看不到。岛模式统一换成
   "白内环 + 外环"两层，并把「看一眼」做成更强的呼吸环。
   ★ 2026-09-23：外环改成【指色】（--kf），与指尖光环同色。
   ★ 同时必须显式接管 background 与 animation —— 这是"黄色方块"的根因：
   旧阶段的 main.css 里有
     `.akey.next-key { background:#FDE047; animation: next-glow … }`
   而岛模式的覆盖【只改了 box-shadow】，另两条照旧生效：
   一层实心金黄底 + 一段把 scale 拉到 1.12 的金色脉冲。
   表现就是"黄色方块高亮过于生硬"。覆盖一半，不报错，只是另一半继续作数 ——
   本项目最典型的一类坑。 */
.adv-shell.adv-island .akey.next-key {
  background: color-mix(in srgb, var(--kf, #93C5FD) 44%, #FFFFFF) !important;
  color: color-mix(in srgb, var(--kf, #4F46E5) 62%, #1E293B) !important;
  transform: translateY(-4px) scale(1.04);
  animation: islKeyBreathe 1.6s ease-in-out infinite;
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .95),
    0 9px 0 var(--key-side, #C9D3E3),
    0 0 0 4px #FFFFFF,
    0 0 0 8px color-mix(in srgb, var(--kf, #93C5FD) 70%, #FFFFFF),
    0 14px 18px -8px var(--drop) !important;
}
/* 目标键的呼吸只做【位移】，不再做 box-shadow 光晕：
   光晕一帧一帧地放大缩小，在 19px 的键高上看着就是在闪。 */
@keyframes islKeyBreathe {
  0%, 100% { transform: translateY(-4px) scale(1.04); }
  50%      { transform: translateY(-7px) scale(1.06); }
}
.adv-shell.adv-island .akey.hint-pulse {
  animation: akeyAsk .62s ease-in-out 4 !important;
}
@keyframes akeyAsk {
  0%, 100% { transform: translateY(-4px) scale(1.04);
    box-shadow: inset 0 2px 0 rgba(255, 255, 255, .95), 0 9px 0 var(--key-side, #C9D3E3),
      0 0 0 4px #FFFFFF,
      0 0 0 8px color-mix(in srgb, var(--kf, #93C5FD) 70%, #FFFFFF) !important; }
  50% { transform: translateY(-11px) scale(1.12);
    box-shadow: inset 0 2px 0 rgba(255, 255, 255, .95), 0 15px 0 var(--key-side, #C9D3E3),
      0 0 0 6px #FFFFFF,
      0 0 0 16px color-mix(in srgb, var(--kf, #93C5FD) 54%, #FFFFFF),
      0 0 30px 8px color-mix(in srgb, var(--kf, #93C5FD) 40%, transparent) !important; }
}

/* ---- soft-next（"下一个该看的键"）也必须接管 ----
   main.css 给的是 `outline: 4px solid #FDE047` —— 又一处实心金黄，
   与 next-key 是同一类问题：岛模式若只覆盖其中一个，另一个照旧生效。
   它表达的是"下一个是它"，强度必须【低于】目标键，所以用指色虚线环而不是实线。 */
.adv-shell.adv-island .akey.soft-next {
  outline: 2px dashed color-mix(in srgb, var(--kf, #93C5FD) 60%, #FFFFFF);
  outline-offset: -3px;
  opacity: 1;
  animation: softNextBreathe 1.6s ease-in-out infinite;
}
@keyframes softNextBreathe {
  0%, 100% { outline-color: color-mix(in srgb, var(--kf, #93C5FD) 38%, #FFFFFF); }
  50%      { outline-color: color-mix(in srgb, var(--kf, #93C5FD) 78%, #FFFFFF); }
}

/* ---- 手型层：弧线轨迹的"流光"与指腹光环的呼吸 ----
   轨迹虚线沿【基准键 → 目标键】的方向流动，于是"手指从哪划过来"这件事
   不用动画也能读出来；配「慢速」档就是一次慢动作回放。
   ⚠️ 用负的 dashoffset 让虚线朝路径终点走：正负搞反会看着像"倒放"。 */
.hands-overlay .hf-trail { animation: trailFlow 1.1s linear infinite; }
@keyframes trailFlow {
  from { stroke-dashoffset: 18; }
  to   { stroke-dashoffset: 0; }
}
.hands-overlay .tip-halo { animation: haloBreathe 1.5s ease-in-out infinite; }
@keyframes haloBreathe {
  0%, 100% { opacity: .82; }
  50%      { opacity: 1; }
}

/* ---- 「慢速」档的慢动作回放 ----
   轨迹虚线放慢到 4.6s 一圈，再让一颗光点沿同一条弧走一遍（2.6s）。
   光点实现走 offset-path + offset-distance：【offset-path 认的就是 act.trail
   那条二次贝塞尔】，所以光点一定贴着弧走，不需要 JS 逐帧采样。
   ⚠️ offset-rotate 必须显式写 0deg：默认值是 auto，光点会被弧的切线转起来 ——
      圆点看不出差别，但以后往这条弧上挂箭头/指形就会整片翻掉。
   ⚠️ offset-path 的坐标用的是【SVG 用户坐标系】（viewBox 0 0 W H），
      和 path 的 d 同一套单位，故 d 可以原样塞进去；换成 CSS px 会整体错位。
   ⚠️ 首尾各留一段不可见：0% 时就亮会让人觉得"它本来就停在基准键上"，
      100% 后再淡出才是"划到了、然后消失"。 */
.hands-overlay.slow .hf-trail { animation-duration: 4.6s; }
.hands-overlay .hf-replay { animation: replayComet 2.6s cubic-bezier(.42, .02, .34, 1) infinite; }
@keyframes replayComet {
  0%   { offset-distance: 0%;   opacity: 0; }
  10%  { opacity: 1; }
  74%  { offset-distance: 100%; opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
/* 舞台上"当前该按的那颗方块"的提示光圈（T2/T3 单目标模板） */
.isk-stage.isk-hint .isk-live { animation: iskLiveAsk .62s ease-in-out 4; }
@keyframes iskLiveAsk {
  0%, 100% { opacity: 1; transform: translateY(0); }
  50% { opacity: .55; transform: translateY(-10px); }
}

/* ---- 标点行：语料里用到、但键面上没有独立键位的 Shift 层标点（! ? : " …）----
   它们本来要走 ⇧+1 / ⇧+/ 这种双键组合，而"键盘上没有这颗键"对孩子
   等于【根本点不到】。做成琥珀色的独立一行，点一下直接送该字符；
   主键盘的 ⇧ 路径保持不变，两条路都能过。 */
.adv-shell.adv-island .isk-punct-row { margin-top: clamp(2px, .5vw, 6px); }
.adv-shell.adv-island .isk-punct {
  background: linear-gradient(180deg, #FDE68A, #F59E0B);
  color: #7C2D12;
  font-size: clamp(20px, 2.6vw, 34px);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .9), inset 0 -3px 0 rgba(120, 53, 15, .35),
    0 6px 0 #B45309, 0 8px 4px -3px var(--occl), 0 16px 22px -12px var(--drop);
}
/* 目标就是这颗标点时：琥珀色的双层环，与主键盘的蓝环同一套"抬起 + 双环"语言 */
.adv-shell.adv-island .isk-punct.next-key {
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .95), 0 9px 0 #B45309,
    0 0 0 4px #FFFFFF,
    0 0 0 8px rgba(245, 158, 11, .8),
    0 14px 18px -8px var(--drop) !important;
}
.adv-shell.adv-island .isk-punct.hint-pulse { animation: punctAsk .62s ease-in-out 4 !important; }
@keyframes punctAsk {
  0%, 100% { transform: translateY(-4px) scale(1.04);
    box-shadow: inset 0 2px 0 rgba(255, 255, 255, .95), 0 9px 0 #B45309,
      0 0 0 4px #FFFFFF, 0 0 0 8px rgba(245, 158, 11, .8) !important; }
  50% { transform: translateY(-11px) scale(1.14);
    box-shadow: inset 0 2px 0 rgba(255, 255, 255, .95), 0 15px 0 #B45309,
      0 0 0 6px #FFFFFF, 0 0 0 16px rgba(245, 158, 11, .9),
      0 0 32px 9px rgba(245, 158, 11, .6) !important; }
}

.isk-kb-hidden { padding: 22px 16px; text-align: center; font-weight: 800;
  font-size: clamp(13px, 1.7vw, 17px); color: var(--isl-ink, #1E3A8A);
  border-radius: 16px; background: rgba(255,255,255,.78);
  border: 2px dashed var(--isl-c, #93C5FD); }

/* ---- T5：句子条 ---- */
.isk-sent {
  width: min(920px, 100%); margin: 0 auto; padding: 12px 20px;
  border-radius: 18px; text-align: center;
  background: linear-gradient(180deg, #FFFFFF, #F4F8FF);
  border: 3px solid rgba(255,255,255,.9);
  box-shadow: inset 0 2px 0 var(--hi), inset 0 -3px 0 var(--sh2),
    0 6px 0 var(--side-soft), 0 16px 24px -14px var(--drop);
  font-size: clamp(15px, 2vw, 22px); font-weight: 800; line-height: 1.7;
  color: #24334D; word-break: break-word;
}
.isk-sent .adv-ch { padding: 0 1px; border-radius: 4px; }
.isk-sent .adv-ch.done { color: #16A34A; }
.isk-sent .adv-ch.cur { background: #FDE047; box-shadow: 0 0 0 2px #F59E0B inset; color: #7C2D12; }

/* ---- T6：信纸 ---- */
.isk-t6 .isk-stage { flex: 0 0 42%; height: 42%; }
.isk-letter {
  width: min(880px, 100%); margin: 0 auto; padding: 12px 18px;
  border-radius: 18px;
  background: linear-gradient(180deg, #FFFDF6, #FFF7E8);
  border: 3px solid rgba(255,255,255,.9);
  box-shadow: inset 0 2px 0 var(--hi), inset 0 -3px 0 var(--sh2),
    0 6px 0 #E7D9BE, 0 18px 26px -16px var(--drop);
  display: flex; flex-direction: column; gap: 6px;
}
.isk-letter-head { font-size: 12.5px; font-weight: 900; color: #92400E; opacity: .85; }
.isk-line { display: flex; align-items: baseline; gap: 8px;
  font-size: clamp(12.5px, 1.55vw, 17px); font-weight: 700; line-height: 1.5;
  color: #4B3A1E; padding: 4px 8px; border-radius: 10px; }
.isk-line-no { flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%;
  background: #F5E6C8; color: #92400E; font-size: 11px; font-weight: 900;
  display: grid; place-items: center; }
.isk-line-tx { flex: 1 1 auto; word-break: break-word; }
.isk-line.todo { opacity: .45; }
.isk-line.cur { background: #FFF4D6; box-shadow: 0 0 0 2px #F59E0B inset; }
.isk-line.done .isk-line-tx { text-decoration: line-through; text-decoration-color: rgba(22,163,74,.5); }
.isk-stamp { flex: 0 0 auto; color: #16A34A; font-size: 11.5px; font-weight: 900;
  transform: rotate(-6deg); }

/* ---- T7：段落 / 竞技场 / 写作台 ---- */
.isk-para { width: min(900px, 100%); margin: 0 auto; display: flex; flex-direction: column; gap: 6px; }
.isk-para-cur, .isk-para-next {
  padding: 10px 18px; border-radius: 16px; text-align: center;
  font-weight: 800; line-height: 1.65;
}
.isk-para-cur {
  background: linear-gradient(180deg, #FFFFFF, #F6F2FF);
  border: 3px solid rgba(255,255,255,.9);
  box-shadow: inset 0 2px 0 var(--hi), inset 0 -3px 0 var(--sh2),
    0 6px 0 #D8CCF5, 0 16px 24px -14px var(--drop);
  font-size: clamp(14px, 1.9vw, 21px); color: #3B0764;
}
.isk-para-cur .adv-ch.done { color: #16A34A; }
.isk-para-cur .adv-ch.cur { background: #FDE047; box-shadow: 0 0 0 2px #F59E0B inset; color: #7C2D12; }
.isk-para-next { font-size: clamp(12px, 1.5vw, 16px); color: #6D28D9; opacity: .55; }

.isk-race { width: min(720px, 100%); margin: 0 auto; display: flex;
  align-items: center; justify-content: center; gap: 16px; }
.isk-race-num { font-size: clamp(34px, 5vw, 56px); font-weight: 900; color: #7C2D12;
  line-height: 1; text-shadow: 0 2px 0 rgba(255,255,255,.7); }
.isk-race-unit { font-size: 14px; font-weight: 900; color: #92400E; }
.isk-race-word { flex: 0 1 auto; width: min(420px, 55%); }

.isk-author { width: min(880px, 100%); margin: 0 auto; display: flex;
  flex-direction: column; gap: 8px; align-items: center; }
.isk-author-in {
  width: 100%; min-height: 74px; max-height: 22vh; resize: vertical;
  padding: 10px 14px; border-radius: 16px;
  border: 3px solid rgba(255,255,255,.9); outline: none;
  background: linear-gradient(180deg, #FFFFFF, #F8F5FF);
  box-shadow: inset 0 2px 0 var(--hi), inset 0 -3px 0 var(--sh2),
    0 6px 0 #D8CCF5, 0 16px 24px -14px var(--drop);
  font-size: clamp(13px, 1.7vw, 17px); font-weight: 700; line-height: 1.6; color: #3B0764;
  font-family: inherit;
}
.isk-author-in:focus { border-color: #8B5CF6; }
.isk-author-go {
  appearance: none; cursor: pointer; border: none;
  padding: 11px 30px; border-radius: 999px;
  font-size: clamp(14px, 1.8vw, 18px); font-weight: 900; color: #FFFFFF;
  background: linear-gradient(180deg, #A78BFA, #7C3AED 70%, #6D28D9);
  box-shadow: inset 0 2px 0 rgba(255,255,255,.55), inset 0 -3px 0 rgba(0,0,0,.16),
    0 6px 0 #5B21B6, 0 16px 22px -12px var(--drop);
  transition: transform .1s cubic-bezier(.2,.8,.3,1), box-shadow .1s cubic-bezier(.2,.8,.3,1);
}
.isk-author-go:active { transform: translateY(6px); box-shadow: inset 0 2px 0 rgba(255,255,255,.55),
  0 0 0 #5B21B6, 0 3px 6px -3px var(--drop); }

/* ---- 工具栏激活态 ---- */
.adv-tool-btn.on { background: #FDE68A; box-shadow: inset 0 1px 0 var(--hi), 0 3px 0 #D97706; }

/* ---- 窄屏：手指条与键盘降为两行 ---- */
@media (max-width: 760px) {
  .isk-fingers-pad { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .isk-kbd-row { gap: 8px; }
  .isk-key { width: 46px; height: 46px; font-size: 19px; }
  /* 岛 2 的 8 键在窄屏也要保住一行：按"键盘区可用宽 ≈ 100vw − 132px"
     倒推每颗键的宽度，8 键恰好铺满一行（不再用固定 vw，否则宽屏余一大片、
     窄屏又溢出）。宁可键小一点，也不折行 —— 键盘区是 overflow:hidden，
     折下去的键会被直接裁掉，孩子会以为这关键本来就少。 */
  .isk-kbd-n8 .isk-key {
    width: clamp(30px, calc((100vw - 132px) / 8), 68px);
    max-width: clamp(30px, calc((100vw - 132px) / 8), 68px);
    font-size: clamp(15px, 3.4vw, 26px); border-radius: 11px;
  }
  .isk-kbd-n8 .isk-kbd-row { gap: 6px; }
  .isk-kbd-split { width: 9px; }
  .adv-shell.adv-island .adv-theater { flex: 0 0 30%; min-height: 196px; }
}

/* ---- 深色 / 高对比主题：舞台底色与墨色反向 ---- */
.theme-dark .adv-banner, .theme-contrast .adv-banner,
.theme-dark .isk-sent, .theme-contrast .isk-sent,
.theme-dark .isk-para-cur, .theme-contrast .isk-para-cur,
.theme-dark .isk-letter, .theme-contrast .isk-letter,
.theme-dark .isk-kb-hidden, .theme-contrast .isk-kb-hidden {
  background: linear-gradient(180deg, #1E2440, #171C33);
  color: #E7ECF8;
}
.theme-dark .isk-task, .theme-contrast .isk-task { color: #E7ECF8; text-shadow: 0 1px 3px rgba(0,0,0,.6); }
.theme-dark .isk-line, .theme-contrast .isk-line { color: #D9E1F2; }
.theme-dark .isk-finger, .theme-contrast .isk-finger {
  background: linear-gradient(180deg, #232A46, #1A2038); color: #E7ECF8; }
.theme-dark .isk-author-in, .theme-contrast .isk-author-in {
  background: linear-gradient(180deg, #1E2440, #171C33); color: #E7ECF8; }

/* ---- 深度档 off：停掉所有动效，并把挤出厚度压平（不改变布局） ---- */
html[data-depth="off"] .isk-finger,
html[data-depth="off"] .isk-key,
html[data-depth="off"] .isk-author-go { transition: none !important; }
html[data-depth="off"] .hint-pulse { animation: none !important; }

/* ============================================================
   13. 沉浸式世界 v2（2026-09-24 五座地貌浮岛重构）
   大舞台相机 / 发光路径 / 节点三态 / 打字小精灵 / 地貌动效
   ============================================================ */

/* ---- 相机工具按钮：看全图 / 回当前关 ---- */
.adv-map-canvas { position: relative; }
.adv-map-tools {
  position: absolute;
  right: 14px;
  bottom: 14px;
  z-index: 6;
  display: flex;
  gap: 10px;
}
.adv-map-toolbtn {
  border: none;
  border-radius: 999px;
  padding: 10px 18px;
  font-size: 15px;
  font-weight: 900;
  color: #1E3A8A;
  background: rgba(255, 255, 255, .94);
  box-shadow: 0 4px 0 #93A2B8, 0 6px 16px rgba(30, 58, 138, .25);
  cursor: pointer;
  transition: transform .12s, box-shadow .12s;
}
.adv-map-toolbtn.primary {
  color: #78350F;
  background: linear-gradient(135deg, #FDE047, #FDBA74);
  box-shadow: 0 4px 0 #B45309, 0 6px 16px rgba(180, 83, 9, .3);
}
.adv-map-toolbtn:active { transform: translateY(3px); box-shadow: 0 1px 0 #93A2B8; }

/* ---- 路径三态：走过的金色发光 / 正前方流光 / 远方灰虚线 ---- */
.adv-map-iso .am-route-todo {
  fill: none; stroke: #A8B6C8; stroke-width: 6;
  stroke-linecap: round; stroke-dasharray: 4 18; opacity: .85;
}
.adv-map-iso .am-route-done {
  fill: none; stroke: #F59E0B; stroke-width: 7;
  stroke-linecap: round; filter: url(#am-glow);
}
.adv-map-iso .am-route-flow-base {
  fill: none; stroke: #CBD5E1; stroke-width: 6; stroke-linecap: round;
}
.adv-map-iso .am-route-flow {
  fill: none; stroke: #FBBF24; stroke-width: 6.5;
  stroke-linecap: round; stroke-dasharray: 13 15;
  filter: url(#am-glow);
  animation: am-flow 1.3s linear infinite;
}
@keyframes am-flow { to { stroke-dashoffset: -56; } }

/* ---- 当前关：脉冲光圈（节点本体不缩放，避免拖着小精灵一起晃） ---- */
.adv-map-iso .am-current-ring {
  transform-box: fill-box;
  transform-origin: center;
  animation: am-ring 2.2s ease-in-out infinite;
}
@keyframes am-ring {
  0%, 100% { transform: scale(1); opacity: .9; }
  50%      { transform: scale(1.18); opacity: .3; }
}

/* ---- 已通关：金色光晕呼吸 + 金星 ---- */
.adv-map-iso .am-done-halo { animation: am-breathe 5s ease-in-out infinite; }
.adv-map-iso .am-star { fill: #94A3B8; }
.adv-map-iso .am-star.on { filter: drop-shadow(0 1px 2px rgba(180, 120, 0, .45)); }

/* ---- 打字小精灵：身体上下跳、气泡轻浮、魔杖星星眨眼 ---- */
.adv-map-iso .am-mascot-body {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: am-mascot-bob 1.6s ease-in-out infinite;
}
@keyframes am-mascot-bob {
  0%, 100% { transform: translateY(0) scaleY(1); }
  50%      { transform: translateY(-5px) scaleY(1.03); }
}
.adv-map-iso .am-mascot-bubble {
  transform-box: fill-box;
  transform-origin: center;
  animation: am-bob 1.7s ease-in-out infinite;
}
@keyframes am-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-6px); }
}
.adv-map-iso .am-wand-star {
  transform-box: fill-box;
  transform-origin: center;
  animation: am-twinkle 1.8s ease-in-out infinite;
}
@keyframes am-twinkle {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.35); }
}
.adv-map-iso .am-mascot:hover .am-mascot-body { animation-duration: .75s; }

/* ---- 魔法锁：偶尔晃一下，吸引孩子去问"怎么开" ---- */
.adv-map-iso .am-lock {
  transform-box: fill-box;
  transform-origin: center;
  animation: am-lockwig 3.4s ease-in-out infinite;
}
@keyframes am-lockwig {
  0%, 88%, 100% { transform: rotate(0); }
  92%           { transform: rotate(-9deg); }
  96%           { transform: rotate(9deg); }
}

/* ---- 地貌动效：瀑布水花 / 漂浮陨石 / 星巅星光 ---- */
.adv-map-iso .am-foam { animation: am-breathe 2.6s ease-in-out infinite; }
.adv-map-iso .am-meteor { animation: am-bob 5.5s ease-in-out infinite; }
.adv-map-iso .am-tw { animation: am-star-tw 2.4s ease-in-out infinite; }
@keyframes am-star-tw {
  0%, 100% { opacity: 1; }
  50%      { opacity: .25; }
}

/* ---- 深度档 / 系统偏好：新增动效一并停掉（登记清单，漏一个就会"关不掉"） ---- */
html[data-depth="off"] .adv-map-iso .am-current-ring,
html[data-depth="off"] .adv-map-iso .am-done-halo,
html[data-depth="off"] .adv-map-iso .am-mascot-body,
html[data-depth="off"] .adv-map-iso .am-mascot-bubble,
html[data-depth="off"] .adv-map-iso .am-wand-star,
html[data-depth="off"] .adv-map-iso .am-lock,
html[data-depth="off"] .adv-map-iso .am-route-flow,
html[data-depth="off"] .adv-map-iso .am-foam,
html[data-depth="off"] .adv-map-iso .am-meteor,
html[data-depth="off"] .adv-map-iso .am-tw {
  animation: none !important;
  transform: none !important;
}
@media (prefers-reduced-motion: reduce) {
  .adv-map-iso .am-current-ring,
  .adv-map-iso .am-done-halo,
  .adv-map-iso .am-mascot-body,
  .adv-map-iso .am-mascot-bubble,
  .adv-map-iso .am-wand-star,
  .adv-map-iso .am-lock,
  .adv-map-iso .am-route-flow,
  .adv-map-iso .am-foam,
  .adv-map-iso .am-meteor,
  .adv-map-iso .am-tw { animation: none !important; }
}
