/* 知识星空样式（批次173）
 *
 * ── 为什么单独一个文件，而不是写进 app.css ────────────────────
 * 星空是一块**自带夜空**的沉浸视口：它的配色不跟品牌换肤、不跟浅深主题走
 * （理由与 --lp-* 岛屿同一套：海和夜空是自然景物，被染成紫色就假了）。
 * 单独成文件让这套"与主题无关"的规则有一个明确的边界，改星空不会碰到全站样式。
 * ⚠️ 新增/删除 .st-* 类名时，`_b173star.cjs` 的「类名 ↔ CSS 一一对应」那条断言会抓 ——
 *    它读的就是这个文件。
 *
 * ── 配色不变量 ───────────────────────────────────────────────
 * 已掌握 = 暖金 #FFD98A（学生自己的证据）
 * 在学   = 天蓝 #7DD3FC
 * 未学   = 白点
 * 依赖弧线 = 白虚线；高亮时青绿 #5EEAD4（与金/蓝都不撞）
 * ★ 亮度只来自进度，不来自"这个知识点很重要" —— 后者是外部激励，本项目禁止。
 */

.st-wrap {
  --st-bg: #080D1A;
  --st-bg-2: #0E1630;
  --st-star: #FFFFFF;
  --st-done: #FFD98A;
  --st-learning: #7DD3FC;
  --st-up: #7DD3FC;
  --st-down: #FDE68A;
  --st-dep: rgba(255, 255, 255, .34);
  --st-dep-hi: #5EEAD4;
  --st-txt: rgba(255, 255, 255, .82);
  --st-txt-2: rgba(255, 255, 255, .46);
  --st-txt-3: rgba(255, 255, 255, .28);
  --st-line: rgba(255, 255, 255, .10);
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  border-radius: 16px;
  overflow: hidden;
  background: var(--st-bg);
  border: 1px solid var(--st-line);
}

/* ================= 工具条 ================= */
.st-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 12px;
  border-bottom: 1px solid var(--st-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, .045), rgba(255, 255, 255, 0));
  flex: none;
}
.st-tabs { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: rgba(255, 255, 255, .06); }
.st-tabs button {
  border: 0; background: transparent; color: var(--st-txt-2);
  font: inherit; font-size: 12.5px; padding: 5px 12px; border-radius: 999px;
  cursor: pointer; transition: background .15s, color .15s;
}
.st-tabs button:hover { color: var(--st-txt); }
.st-tabs button.on { background: rgba(125, 211, 252, .16); color: #BFE7FF; font-weight: 600; }

/* ================= 领域胶囊（批次178）=================
 * ★ 领域色**只出现在这里和画布标题上**，不进星域 ——
 *   星域里的颜色语义只有一个：学习进度（暖金 / 天蓝 / 白）。
 *   再塞一套"领域色"进去，学生会以为颜色在说另一件事，而图上没有图例解释它。
 * ★ 不写 white-space:nowrap 就会在窄屏被拆成两行，胶囊变成方块。 */
.st-doms { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: rgba(255, 255, 255, .06); }
.st-doms button {
  border: 0; background: transparent; color: var(--st-txt-2);
  font: inherit; font-size: 12.5px; padding: 5px 11px; border-radius: 999px;
  cursor: pointer; white-space: nowrap; transition: background .15s, color .15s;
}
.st-doms button:hover { color: var(--st-txt); }
.st-doms button.on { background: rgba(255, 255, 255, .13); color: #fff; font-weight: 600; }
.st-doms button.on[data-d="num"] { background: rgba(125, 211, 252, .18); color: #BFE7FF; }
.st-doms button.on[data-d="func"] { background: rgba(196, 181, 253, .18); color: #DDD2FE; }
.st-doms button.on[data-d="geo"] { background: rgba(110, 231, 183, .18); color: #A7F3D0; }
.st-doms button.on[data-d="stat"] { background: rgba(252, 165, 165, .18); color: #FECACA; }
.st-doms button.on[data-d="other"] { background: rgba(148, 163, 184, .20); color: #CBD5E1; }

.st-searchbox { flex: 1; min-width: 140px; max-width: 320px; }
.st-search {
  width: 100%; box-sizing: border-box;
  padding: 6px 11px; border-radius: 999px;
  border: 1px solid var(--st-line);
  background: rgba(255, 255, 255, .05);
  color: var(--st-txt); font: inherit; font-size: 12.5px;
}
.st-search::placeholder { color: var(--st-txt-3); }
.st-search:focus { outline: none; border-color: rgba(125, 211, 252, .5); background: rgba(125, 211, 252, .07); }

/* ★★ 批次200：.st-chk（考试节点 / 跨年级 / 按学段上色三个复选框）整条撤了 ——
   用户原话「上边按键太密集……按学段上色去掉（没看到有什么用）」。
   控件没了，规则留着就是死代码（哪天有人照它写新控件，会继承一份没人审的样式）。 */
.st-reset {
  background: rgba(255, 255, 255, .06) !important;
  border-color: var(--st-line) !important;
  color: var(--st-txt) !important;
}
.st-reset:hover { background: rgba(255, 255, 255, .12) !important; }
.st-sum { margin-left: auto; font-size: 12px; color: var(--st-txt-3); white-space: nowrap; }

/* ================= 视口 ================= */
.st-view { position: relative; flex: 1; min-height: 0; overflow: hidden; }
.st-svg { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; }
.st-svg:active { cursor: grabbing; }
.st-loading { padding: 16vh 20px; text-align: center; color: var(--st-txt-2); font-size: .88rem; }

/* ================= 星 ================= */
.st-star { cursor: pointer; }
.st-core { fill: var(--st-star); fill-opacity: 1; stroke: rgba(255, 255, 255, .55); stroke-width: .9; }
.st-glow { fill: var(--st-star); fill-opacity: .10; }
/* ★ 批次176：十字 flare（对照原版 variation-constellation 的亮星四芒）。
   只给 .st-mastered（JS 也只给 mastered 画），因为亮度必须来自证据。
   pointer-events:none —— 它比星核大一圈，绝不能抢走点击。 */
.st-flare { fill: var(--st-done); opacity: .8; pointer-events: none;
  animation: st-twinkle 4.2s ease-in-out infinite; animation-delay: calc(var(--ph, 0) * -4.2s); }
.st-hit { fill: transparent; }

/* 已掌握：暖金实心 + 柔光（这是全图最亮的东西，因为它是学生自己的证据） */
.st-mastered .st-core { fill: var(--st-done); fill-opacity: 1; stroke: rgba(255, 217, 138, .85); stroke-width: 1.1; }
.st-mastered .st-glow { fill: var(--st-done); fill-opacity: .22; animation: st-twinkle 4.2s ease-in-out infinite; animation-delay: calc(var(--ph, 0) * -4.2s); }
/* 在学：天蓝半亮 */
.st-learning .st-core { fill: var(--st-learning); fill-opacity: .95; stroke: rgba(125, 211, 252, .8); stroke-width: 1; }
.st-learning .st-glow { fill: var(--st-learning); fill-opacity: .16; animation: st-twinkle 5s ease-in-out infinite; animation-delay: calc(var(--ph, 0) * -5s); }
/* 未学：白点 + 极淡晕 */
.st-new .st-glow { animation: st-twinkle 5.5s ease-in-out infinite; animation-delay: calc(var(--ph, 0) * -5.5s); }

/* 血缘高亮：来路天蓝、去路琥珀、其余压暗 */
.st-star.st-up .st-core { fill: var(--st-up); fill-opacity: 1; stroke: rgba(125, 211, 252, .9); }
.st-star.st-up .st-glow { fill: var(--st-up); fill-opacity: .2; }
.st-star.st-down .st-core { fill: var(--st-down); fill-opacity: 1; stroke: rgba(253, 230, 138, .9); }
.st-star.st-down .st-glow { fill: var(--st-down); fill-opacity: .2; }
.st-star.st-focus .st-core { fill: #fff; stroke: #fff; stroke-width: 1.6; }
.st-star.st-focus .st-glow { fill: #fff; fill-opacity: .26; }
.st-star.st-dim { opacity: .16; }
.st-star.st-found .st-core { fill: #fff; stroke: var(--st-down); stroke-width: 2; }
.st-star.st-found .st-glow { fill: var(--st-down); fill-opacity: .3; }
.st-star { transition: opacity .18s; }

/* 标签：默认只有"已掌握"常显，其余 hover / 命中 / 血缘才出现 —— 362 个标签全显必糊 */
.st-label {
  font-size: calc(11px * var(--st-ts, 1)); fill: var(--st-txt);
  opacity: 0; pointer-events: none;
  paint-order: stroke; stroke: rgba(4, 8, 18, .85); stroke-width: 3px; stroke-linejoin: round;
  transition: opacity .15s;
}
.st-mastered .st-label { opacity: .8; }
.st-star:hover .st-label,
.st-star.st-focus .st-label,
.st-star.st-up .st-label,
.st-star.st-down .st-label,
.st-star.st-found .st-label { opacity: 1; }

/* ================= 单元与册 ================= */
/* ★ 册名/学段名用 calc 乘 --st-ts（由 applyCam 按缩放写进来）：缩到整幅时
   13px 的字只剩 3px，那就等于没写。反向放大后屏幕字号恒定 ~13px。
   单元名不做反向放大 —— 它只在近景出现，那里本来就在 1:1 附近。 */
.st-unit {
  font-size: calc(11.5px * var(--st-ts, 1)); fill: var(--st-txt-2); letter-spacing: .02em;
  paint-order: stroke; stroke: rgba(4, 8, 18, .8); stroke-width: 3px; stroke-linejoin: round;
  transition: opacity .18s;
}
.st-unit.st-dim { opacity: .22; }

/* ================= 单元 hub + 星链（章节的实体，批次178）=================
 * 三层层级：册（大而极淡的光晕）→ 单元（hub 圆）→ 知识点（小点）。
 * ★ hub 用**中性色**，不按领域上色：星域里的颜色语义只有一个 —— 进度。
 *   领域色只出现在工具条胶囊与画布领域标题上（UI 外壳），不进场。
 * ★ 描边一律 vector-effect: non-scaling-stroke：缩到整幅时 1 单位描边会变成
 *   0.2px 的断线，hub 圈直接消失。 */
.st-hub {
  fill: rgba(255, 255, 255, .07);
  stroke: rgba(255, 255, 255, .34);
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
  transition: opacity .18s, stroke .18s, fill .18s;
}
.st-hub-hit { fill: transparent; cursor: pointer; }
.st-hub-arc {
  fill: none; stroke-width: 2.8; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  transition: opacity .18s, stroke-width .18s;
}
/* 进度三档 —— 与星点同一套证据口径，不另造一套 */
.st-hub-none { stroke: rgba(255, 255, 255, .22); }
.st-hub-part { stroke: var(--st-learning); }
.st-hub-full { stroke: var(--st-done); }
.st-hub-wrap.st-dim { opacity: .15; }
.st-hub-wrap.st-on .st-hub { fill: rgba(255, 255, 255, .10); stroke: rgba(255, 255, 255, .55); }
.st-hub-wrap.st-on .st-hub-arc { stroke-width: 3.4; }
.st-hub-wrap:hover .st-hub { fill: rgba(255, 255, 255, .09); stroke: rgba(255, 255, 255, .45); }

/* 星链：hub → 该单元每一颗星。结构线，比依赖边淡、比光晕实。 */
.st-chain {
  fill: none; stroke: rgba(255, 255, 255, .13); stroke-width: 1;
  vector-effect: non-scaling-stroke;
  transition: opacity .18s, stroke .18s;
}
.st-chain.st-dim { opacity: .12; }
.st-chain.st-on { stroke: rgba(255, 255, 255, .34); stroke-width: 1.4; }

/* 领域标题（只在筛选态出现，见 star.js 的说明） */
.st-domain-t {
  font-size: calc(22px * var(--st-ts, 1)); font-weight: 700;
  fill: rgba(255, 255, 255, .34); letter-spacing: .12em;
}
.st-domain-t[data-d="num"] { fill: var(--d-num); fill-opacity: .46; }
.st-domain-t[data-d="func"] { fill: var(--d-func); fill-opacity: .46; }
.st-domain-t[data-d="geo"] { fill: var(--d-geo); fill-opacity: .46; }
.st-domain-t[data-d="stat"] { fill: var(--d-stat); fill-opacity: .46; }
.st-domain-t[data-d="other"] { fill: var(--d-other); fill-opacity: .46; }
.st-book { cursor: pointer; }
.st-book-hit { fill: transparent; }
.st-book:hover .st-book-t { fill: rgba(255, 255, 255, .9); }
.st-book-t {
  font-size: calc(13px * var(--st-ts, 1)); font-weight: 600;
  fill: rgba(255, 255, 255, .62); letter-spacing: .04em;
  paint-order: stroke; stroke: rgba(4, 8, 18, .7); stroke-width: 3px; stroke-linejoin: round;
}
.st-book-s { font-size: calc(10.5px * var(--st-ts, 1)); fill: var(--st-txt-3); }
/* 短名：只在远景出现（近景画完整书名）。★ 默认隐藏 —— 近景若两份都画，
   "数学 一年级 上册" 和 "一上" 会上下叠在一起。 */
.st-book-t2 {
  display: none;
  font-size: calc(13px * var(--st-ts, 1)); font-weight: 700;
  fill: rgba(255, 255, 255, .74); letter-spacing: .06em;
  paint-order: stroke; stroke: rgba(4, 8, 18, .8); stroke-width: 3.4px; stroke-linejoin: round;
}
.st-svg.st-far .st-book-t { display: none; }
/* ★ 远景的册短名落在星系正中，而整幅视角下一个星系只有 ~50px 宽、
   星点离书名只有 ~20px ⇒ 书名被星点围住。这里用**加粗描边**把字"挖"出来：
   比塞一个背景 <rect> 稳 —— 不需要量文本宽度（font-size 是 calc(13px * --st-ts)，
   渲染时才知道，量出来的矩形换个视口就错位）。 */
.st-svg.st-far .st-book-t2 {
  display: inline;
  stroke: rgba(3, 6, 14, .95); stroke-width: 5.5px; stroke-linejoin: round;
  fill: rgba(255, 255, 255, .92);
}

/* ================= 分级显示（LOD）=================
 * 整幅视角下 40~91 个单元名会糊成一团墨 —— 远景只留册名，文字细节交给
 * 左上角徽章（HTML，屏幕字号恒定）和详情卡。放大到近景再画单元名。 */
.st-svg.st-far .st-unit { opacity: 0; }
.st-svg.st-far .st-label { opacity: 0 !important; }
/* 远景里"每册几点"这行小字只会糊成噪点 —— 顶部汇总条已经说了总数，这里省掉 */
.st-svg.st-far .st-book-s { display: none; }
/* ── 远景减边密度（批次178 A 项）─────────────────────────────────
 * 226 条边在整幅视角下就是一张网：教材顺序边全是册内的短弧、低置信依赖边
 * 只有 0.5~0.75 的把握，两者在这个尺度上都不传达信息，只制造噪点。
 * ★ 用 CSS 而不是 JS 少画：远近景是缩放时**高频**切换的状态，
 *   每次重建 226 个 <path> 会掉帧（零逐帧纪律）。
 * ★ 只减不删：DOM 里还在，近景一放大就回来，也还能被搜索/血缘命中。 */
.st-svg.st-far .st-order { opacity: 0; }
.st-svg.st-far .st-dep-lo { opacity: 0; }
.st-order, .st-dep-lo { transition: opacity .2s; }
/* ── 远景 hub 降噪 ────────────────────────────────────────────
 * 整幅视角下 hub 只有 4px，进度弧（2.2 单位描边）在这个尺寸下既看不出比例、
 * 又会压在册名上 —— 实测首轮截图里像一串彩色括号粘在书名上。
 * 所以远景只留**中性圆点**（"这里有一个章节节点"），进度留给近景。 */
.st-svg.st-far .st-hub-arc { opacity: 0; }
/* ★ 远景整颗 hub 都收起来（不只是进度弧）：4px 的圆挤在册短名两侧，
   读起来像"书名被括号夹住"。远景的信息是"23 册的全景"，
   节点属于近景 —— 与 `.st-far .st-unit { opacity: 0 }` 同一条 LOD 逻辑。 */
.st-svg.st-far .st-hub { display: none; }

/* ================= 边 ================= */
.st-order { fill: none; stroke: rgba(255, 255, 255, .18); stroke-width: 1; vector-effect: non-scaling-stroke; }
.st-dep {
  fill: none; stroke: var(--st-dep); stroke-width: 1.1;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 3 7;
  animation: st-flow 3.4s linear infinite;
  transition: stroke .18s, stroke-opacity .18s;
}
/* ★ 批次200：边高亮分两色——上游链(先学)走蓝、下游链(后学)走黄，与星点 st-up/st-down 同一套配色。
 *   .st-hi 只给宽度/虚线/不透明度（两种模式边共用），颜色交给 .st-hi-up / .st-hi-down。 */
.st-dep.st-hi { stroke-width: 1.7; stroke-opacity: 1; stroke-dasharray: 4 5; }
.st-dep.st-hi-up { stroke: var(--st-up); }
.st-dep.st-hi-down { stroke: var(--st-down); }
/* ★ 批次199：螺旋模式的边（.st-sp-s / .st-sp-x）与书图的 .st-dep 共用同一个
 *   highlight()，压暗时加的都是 st-fade ⇒ 三条选择器并成**一条**规则。
 *   （曾经分成两条，_b173starnegative 的变异只改前一条、后一条兜底 ⇒ 咬不住；
 *     合并后"st-fade 在 CSS 里没有规则"这件事又能被一条变异干净地造出来。）
 *   值仍取书图那档 .1；.st-sp-x 的基准不透明度因此从 .10 抬到 .16，
 *   否则"淡出"对它等于没变化（.10 → .1）。 */
.st-dep.st-fade, .st-sp-s.st-fade, .st-sp-x.st-fade { stroke-opacity: .1; animation: none; }
/* 上游链粒子走蓝、下游链粒子走黄（.st-particle-up/down 由 paintParticles 按与边同口径打标） */
.st-particle { opacity: .9; }
.st-particle-up { fill: var(--st-up); }
.st-particle-down { fill: var(--st-down); }

/* ================= 当前关的呼吸环 ================= */
.st-open-ring {
  fill: none; stroke: var(--st-done); stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
  animation: st-pulse 2.6s ease-out infinite;
}

/* ================= 星尘 ================= */
.st-dust { fill: #fff; fill-opacity: .5; animation: st-twinkle 6s ease-in-out infinite; animation-delay: calc(var(--ph, 0) * -6s); }

/* ================= 学段带 ================= */
.st-band-line { stroke: rgba(125, 211, 252, .16); stroke-width: 1; stroke-dasharray: 2 10; vector-effect: non-scaling-stroke; }
.st-band-t { font-size: calc(12px * var(--st-ts, 1)); fill: rgba(125, 211, 252, .5); letter-spacing: .34em; }

/* ================= 悬浮信息 ================= */
.st-badge {
  position: absolute; left: 14px; top: 12px;
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 12px; border-radius: 999px;
  background: rgba(8, 13, 26, .82); border: 1px solid var(--st-line);
  color: var(--st-txt); font-size: 12.5px; backdrop-filter: blur(6px);
  pointer-events: none;
}
/* ★ 类名不能叫 .st-focus：那是"星被选中"的状态类（.st-star.st-focus），
   两者同名时 querySelector('.st-focus') 先命中 SVG 里的星，徽章就永远渲染不出来。
   display:flex 还会盖掉 UA 的 [hidden]{display:none} —— 空胶囊常驻，必须补一条。 */
.st-badge[hidden] { display: none; }
.st-badge b { font-weight: 600; }
.st-badge-u { color: var(--st-up); }
.st-badge-d { color: var(--st-down); }

.st-hint {
  position: absolute; left: 14px; bottom: 10px;
  font-size: 12px; color: var(--st-txt-3); white-space: nowrap;
  pointer-events: none;
}

/* ================= 详情卡 ================= */
.st-card {
  position: absolute; right: 12px; top: 12px; width: min(320px, calc(100% - 24px));
  max-height: calc(100% - 24px); overflow-y: auto;
  padding: 13px 14px 15px;
  border-radius: 14px;
  background: rgba(11, 17, 32, .94);
  border: 1px solid rgba(255, 255, 255, .13);
  box-shadow: 0 18px 50px -18px rgba(0, 0, 0, .9);
  backdrop-filter: blur(10px);
  color: var(--st-txt);
}
.st-card-h { display: flex; align-items: flex-start; gap: 8px; }
.st-card-u { flex: 1; font-size: 11.5px; color: var(--st-txt-3); line-height: 1.4; }
.st-card-x {
  flex: none; border: 0; background: transparent; color: var(--st-txt-3);
  font-size: 15px; line-height: 1; cursor: pointer; padding: 2px 4px; border-radius: 6px;
}
.st-card-x:hover { color: var(--st-txt); background: rgba(255, 255, 255, .08); }
.st-card-t { margin: 6px 0 7px; font-size: 15.5px; font-weight: 600; line-height: 1.35; }
.st-card-s { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
.st-pill { font-size: 11.5px; padding: 2px 8px; border-radius: 999px; }
.st-pill-mastered { background: rgba(255, 217, 138, .16); color: var(--st-done); }
.st-pill-learning { background: rgba(125, 211, 252, .14); color: var(--st-learning); }
.st-pill-new { background: rgba(255, 255, 255, .08); color: var(--st-txt-2); }
.st-card-a { font-size: 11.5px; color: var(--st-txt-3); }
.st-card-p { margin: 0 0 8px; font-size: 12.8px; line-height: 1.65; color: var(--st-txt-2); }
.st-card-task { color: #BFE7FF; }
.st-card-pit { color: #FBD5A5; }
.st-card-lock { margin: 4px 0 0; font-size: 12.4px; line-height: 1.6; color: var(--st-txt-3); }
.st-card-go { width: 100%; margin-top: 4px; justify-content: center; }

/* 批次203：详情卡上的「听讲解 / 做测验」两个选择（.st-card-acts 里平分宽度）。
   测验是次要动作 —— 实心主色只给「听讲解」，两个都实心会让学生不知道先点哪个。 */
.st-card-acts { display: flex; gap: 8px; margin-top: 4px; }
.st-card-acts .btn { flex: 1 1 0; min-width: 0; justify-content: center; }
.st-card-quiz {
  background: rgba(255, 255, 255, .09); color: var(--st-txt);
  border: 1px solid rgba(255, 255, 255, .17);
}
.st-card-quiz:hover { background: rgba(255, 255, 255, .15); }
.st-card-learn {
  display: block; width: 100%; margin-top: 7px; padding: 4px 0;
  background: none; border: 0; cursor: pointer; text-align: center;
  font-size: 12.2px; color: var(--st-txt-3);
}
.st-card-learn:hover { color: var(--st-txt); }

/* 批次204：从星空点进来的纯讲解页要盖住护照引导浮层。
   .gp-layer 是 position:fixed; inset:0; z-index:95，而 .iv-panel 只有 60 ⇒ 新学生
   引导没走完时点「听讲解」，气泡（「第 1/7 关 · 跟着路走」+「暂时跳过」）正压在
   讲解正文上挡字（2026-10-25 截图实测）。
   只提讲解面板这一个，不动通用的 .iv-panel.full —— 讲义/识图那些面板与引导层的
   关系维持原样。引导层本身不 hidden，关掉讲解后照常可见、照常能点「暂时跳过」
   （已用「强制显示引导层」的 A/B 验过：讲解开着时 skip 点不到，关掉后立刻可点）。 */
#tutorPanel.full { z-index: 96; }

/* ================= 动画（全部 CSS，零逐帧 JS）================= */
@keyframes st-twinkle { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@keyframes st-flow { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -20; } }
@keyframes st-pulse {
  0% { transform: scale(.55); opacity: .9; }
  70% { transform: scale(1.7); opacity: 0; }
  100% { transform: scale(1.7); opacity: 0; }
}
.st-open-ring { transform-box: fill-box; transform-origin: center; }

@media (prefers-reduced-motion: reduce) {
  .st-dust, .st-glow, .st-dep, .st-open-ring, .st-flare { animation: none !important; }
  .st-star, .st-unit, .st-label, .st-dep { transition: none !important; }
}

/* ================= 学习页的模式切换 ================= */
.learn-mode {
  display: inline-flex; gap: 2px; padding: 3px; margin: 10px 16px 0;
  border-radius: 999px; background: var(--panel-2); border: 1px solid var(--line);
  align-self: flex-start; flex: none;
}
.learn-mode button {
  border: 0; background: transparent; color: var(--dim);
  font: inherit; font-size: 12.5px; padding: 5px 14px; border-radius: 999px; cursor: pointer;
  transition: background .15s, color .15s;
}
.learn-mode button:hover { color: var(--txt); }
.learn-mode button.on { background: var(--panel); color: var(--txt); font-weight: 600; box-shadow: var(--shadow); }

#learnStar { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 12px 16px 16px; }
#learnStar[hidden] { display: none; }

/* 窄屏：工具条折行、卡片占满 */
@media (max-width: 720px) {
  .st-sum { width: 100%; margin-left: 0; }
  .st-card { left: 10px; right: 10px; width: auto; top: auto; bottom: 10px; max-height: 58%; }
  .st-hint { display: none; }
  .st-searchbox { max-width: none; }
}
/* ================= 批次196：跨年级 / 学段上色 / 全屏 / 主题句 / 推荐起点 =================
 *
 * ★ 颜色语义的**分工**（改这段前先读 star.js 顶部「四条硬规矩」第 1 条）：
 *     亮度 + 金/天蓝 = 学生的进度（唯一来自证据的东西）
 *     色相（冷蓝/青绿/暖紫）= 学段（先学 → 后学）
 *   两套编码同时用，所以学段色**只上给「还没学」的星**（.st-new）：
 *   已掌握/在学的星保持金与天蓝 —— 否则学生一开"按学段上色"会以为
 *   「颜色变了 = 我学会了」，那是把证据和分类搅在一起。
 *
 * ★ 为什么要有这个开关：跨年级铺开以后一张图上有 700+ 颗星，
 *   「哪些是初中的、哪些是高中的」光看位置要靠猜（学段带只在远景可见）。
 *   色相是唯一能同时给出「属于哪一段」和「先学还是后学」的通道。
 */
.st-wrap.st-by .st-star.st-new.st-g0:not(.st-up):not(.st-down) .st-core { fill: #8FB8E8; stroke: rgba(143, 184, 232, .65); }
.st-wrap.st-by .st-star.st-new.st-g1:not(.st-up):not(.st-down) .st-core { fill: #6EE7B7; stroke: rgba(110, 231, 183, .65); }
.st-wrap.st-by .st-star.st-new.st-g2:not(.st-up):not(.st-down) .st-core { fill: #C4B5FD; stroke: rgba(196, 181, 253, .65); }
.st-wrap.st-by .st-star.st-new.st-g0:not(.st-up):not(.st-down) .st-glow { fill: #8FB8E8; }
.st-wrap.st-by .st-star.st-new.st-g1:not(.st-up):not(.st-down) .st-glow { fill: #6EE7B7; }
.st-wrap.st-by .st-star.st-new.st-g2:not(.st-up):not(.st-down) .st-glow { fill: #C4B5FD; }
/* ★★ 批次200：上面这几条**特异性更高**（5 个类），会把「先学 / 后学」的蓝/黄压掉
   （.st-star.st-up .st-core 只有 2 个类）—— 悬停一颗没学的星时，上游那一串
   仍旧是学段色，用户看到的就是"颜色没区分"（截图原话：「用不同的颜色区分」）。
   两条修法选一：① 给 st-up/st-down 加 !important；② 让学段色**不碰**正在高亮的星。
   选 ② —— !important 会连带压掉将来的状态色，而 :not() 把边界说得很清楚：
   学段色只服务"没人悬停时的没学的星"，高亮一来就让位。 */

/* 图例。★ 批次200：「按学段上色」开关撤了，S.byStage 恒 true ⇒ 图例**常显**
   （原来"跟着开关一起出现/收起"的写法留着无害：.st-by 类一直在 .st-wrap 上）。
   ★ 图例说的必须和画面一致 —— 画面上先学/后学的蓝/黄只在**悬停**时出现，
     所以那两色不进图例（进了就等于承诺"随时可见"），由悬停徽章自带文字说明。 */
.st-legend { display: none; align-items: center; gap: 10px; font-size: 11.5px; color: var(--st-txt-2); }
.st-wrap.st-by .st-legend { display: inline-flex; }
.st-lg { display: inline-flex; align-items: center; font-style: normal; }
.st-lg::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 5px; vertical-align: 1px;
}
.st-lg-p::before { background: #8FB8E8; }
.st-lg-j::before { background: #6EE7B7; }
.st-lg-s::before { background: #C4B5FD; }
.st-lg-t { font-style: normal; color: var(--st-txt-3); }

/* 学科主题句：一条窄带，压在工具条下面、画布上面。
   不做浮层 —— 浮层会和左上角徽章、右侧详情卡抢位置（390px 上必撞）。 */
.st-motto {
  flex: none;
  padding: 7px 12px 8px;
  text-align: center;
  font-size: 12.5px;
  letter-spacing: .06em;
  color: var(--st-txt-2);
  border-bottom: 1px solid var(--st-line);
  background: rgba(255, 255, 255, .015);
}
.st-motto:empty { display: none; }

/* 推荐起点：一圈呼吸的白边。★ 只加描边与晕，不改亮度 —— 亮度仍然只属于证据。 */
.st-star.st-next .st-core { stroke: #FFFFFF; stroke-width: 1.8; }
.st-star.st-next .st-glow { animation: st-next-pulse 2.6s ease-in-out infinite; }
@keyframes st-next-pulse {
  0%, 100% { fill-opacity: .10; }
  50% { fill-opacity: .42; }
}

/* 全屏：原生 Fullscreen API 撑满屏幕。Esc 由浏览器负责退出。 */
.st-wrap:fullscreen { width: 100vw; height: 100vh; border-radius: 0; border: 0; }
.st-wrap:-webkit-full-screen { width: 100vw; height: 100vh; border-radius: 0; border: 0; }
.st-wrap.st-fs .st-motto { font-size: 13.5px; padding: 9px 12px 10px; }

@media (max-width: 720px) {
  .st-motto { font-size: 11.5px; letter-spacing: .02em; }
  .st-lg-t { display: none; }
}

/* ================= 批次199：参照站式星空（域簇 + 黄金角螺旋）=================
 * 用户 2026-10-23：「我希望你1比1 复刻这个知识星空」。参照物
 * github.com/weizunwhite/k12coding-knowledge-starry 与 physics.0oneup.com。
 *
 * ── 颜色分工（用户拍板「拆分」，改这段前先读）──────────────────
 *     大小 / 十字光芒 = 依赖度数（结构重要性）—— JS 写 r，tier 定画不画光芒
 *     颜色 / 亮度     = 学习进度（唯一来自证据的东西，本仓铁律）
 *   领域色只上**簇光晕 / 簇名 / 同域边**（= 参照站主题色的三个落点），
 *   **绝不上星点** —— 星点的颜色语义只有一个：进度。
 *   ★ 所以这一整段里， tier 规则**一个颜色字都没有**：只有半径、描边粗细、
 *     标签字号。光晕的 fill-opacity 仍然归 .st-mastered/.st-learning/.st-new 管
 *     （批次176 E4 守的就是那三条）—— 枢纽星的晕**更大**，但不**更亮**。
 *
 * ── 领域色真源 ───────────────────────────────────────────────
 * ★ 全仓新增的领域色字面量**只在这一块**。_b178check F5 靠一条断言钉住它：
 *   规则体里每一条声明都必须是一条 --d-* 定义，别的一个字都不许有。
 *   这样「领域色只有一个出处」才是一条能断言的规矩，而不是一句口号。
 *   （.st-doms 胶囊的 rgba 底色没法用变量表达，仍是老字面量 —— _b199check
 *     有一条逐个域比色，防止它和这里漂开。）
 */
.st-wrap {
  --d-num: #7DD3FC;
  --d-func: #C4B5FD;
  --d-geo: #6EE7B7;
  --d-stat: #FCA5A5;
  --d-other: #94A3B8;
}

/* ── 视图切换：星空 / 书图 ────────────────────────────────────
 * ★ 做成和 .st-tabs 同款的分段控件，不做下拉：工具条上每多一个下拉都会把
 *   标题压小（批次176 量过 390px 的账），分段控件不占这额度。 */
.st-modes { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: rgba(255, 255, 255, .06); }
.st-modes button {
  border: 0; background: transparent; color: var(--st-txt-2);
  font: inherit; font-size: 12.5px; padding: 5px 12px; border-radius: 999px;
  cursor: pointer; white-space: nowrap; transition: background .15s, color .15s;
}
.st-modes button:hover { color: var(--st-txt); }
.st-modes button.on { background: rgba(125, 211, 252, .16); color: #BFE7FF; font-weight: 600; }

/* 模式类挂在 .st-wrap 上（JS: syncOpts）。
 * ★ 类名带 mode- 前缀：.st-book 早就是书图里「册」这个 SVG 分组的类名，
 *   同名会让整张画布继承 cursor:pointer。 */
/* 书图模式显式回到纯色底：把上面那条深空渐变撤掉，别让两种模式的底色互相漏。
 * ★ 也顺便让 .st-mode-book 成为一个有规则的类 —— _b173star 的 E 组要求
 *   classList 增删的每个 st-* 类名在 star.css 里都有规则，空类名会直接红。 */
.st-wrap.st-mode-book .st-view { background: var(--st-bg); }

.st-wrap.st-mode-spiral .st-view {
  /* 深空底。画布比视口大时 fitCamera 会把 viewBox 撑到盖住画布，但竖方向
 *   多出来的那条得由 .st-view 自己补 —— 不然缩到底会露出一道浅色。 */
  background: radial-gradient(125% 95% at 50% 38%, #161E44 0%, #0B1026 52%, #05030F 100%);
}

/* ── 簇光晕 + 簇名（= 参照站 cluster aura / cluster label）────── */
.st-sp-sky { stroke: none; }
.st-cluster { transition: opacity .2s; }
.st-cluster.st-dim { opacity: .1; }
.st-clabel {
  font-size: calc(14px * var(--st-ts, 1)); font-weight: 500;
  letter-spacing: .3em;              /* 参照站 4px 字距；用 em 才跟着字号一起缩放 */
  text-anchor: middle; pointer-events: none;
  transition: opacity .2s;
}
.st-clabel[data-d="num"] { fill: var(--d-num); fill-opacity: .55; }
.st-clabel[data-d="func"] { fill: var(--d-func); fill-opacity: .55; }
.st-clabel[data-d="geo"] { fill: var(--d-geo); fill-opacity: .55; }
.st-clabel[data-d="stat"] { fill: var(--d-stat); fill-opacity: .55; }
.st-clabel[data-d="other"] { fill: var(--d-other); fill-opacity: .55; }
.st-clabel.st-dim { opacity: .12; }

/* ── 边：跨域虚线 / 同域实线（= 参照站 STAR_CROSS / STAR_LINKS）──
 * ★ 跨域先画、压最底、更淡；同域后画、用领域色。
 * ★ 高亮/压暗的类名（st-hi / st-fade）与书图模式的 .st-dep 共用同一套，
 *   因为 highlight() 是同一个函数、按 data-e 配边，颜色语义不能分家。 */
.st-sp-x {
  fill: none; stroke: #fff; stroke-opacity: .16; stroke-width: .6;
  stroke-dasharray: 2 6; vector-effect: non-scaling-stroke;
  transition: stroke-opacity .18s, stroke-width .18s;
}
.st-sp-s {
  fill: none; stroke-width: .9; stroke-opacity: .40;
  vector-effect: non-scaling-stroke;
  transition: stroke-opacity .18s, stroke-width .18s;
}
.st-sp-s[data-d="num"] { stroke: var(--d-num); }
.st-sp-s[data-d="func"] { stroke: var(--d-func); }
.st-sp-s[data-d="geo"] { stroke: var(--d-geo); }
.st-sp-s[data-d="stat"] { stroke: var(--d-stat); }
.st-sp-s[data-d="other"] { stroke: var(--d-other); }
/* ★ 批次200：螺旋边高亮与书图同口径——.st-hi 给宽度/虚线，颜色交给 .st-hi-up(蓝)/.st-hi-down(黄) */
.st-sp-s.st-hi, .st-sp-x.st-hi { stroke-opacity: .95; stroke-width: 1.4; stroke-dasharray: 4 5; }
.st-sp-s.st-hi-up, .st-sp-x.st-hi-up { stroke: var(--st-up); }
.st-sp-s.st-hi-down, .st-sp-x.st-hi-down { stroke: var(--st-down); }

/* ── 高中「进阶星区」分隔 ─────────────────────────────────────
 * ★ 参照站用玫红 #e11d48（它的 application 主题色）。我们**不跟着用彩色**：
 *   玫红在这张图上没有图例、也不属于任何一个领域，摆上去就是第六个没人解释
 *   的颜色 —— 本项目禁止「图上出现没有图例的语义色」。分隔线是**结构**，
 *   用中性白，粗细和字距照抄参照站。 */
.st-senior-line { stroke: rgba(255, 255, 255, .26); stroke-width: 1; stroke-dasharray: 2 9; vector-effect: non-scaling-stroke; }
.st-senior-t {
  font-size: calc(15px * var(--st-ts, 1)); fill: rgba(255, 255, 255, .55);
  letter-spacing: .46em;              /* 参照站 7px */
}

/* ── 四档星等：只定大小 / 描边 / 标签显著性，**不定颜色不定亮度** ── */
.st-star.st-t-pillar .st-core { stroke-width: 1.8; }
.st-star.st-t-major  .st-core { stroke-width: 1.5; }
.st-star.st-t-branch .st-core { stroke-width: 1.2; }
.st-star.st-t-leaf   .st-core { stroke-width: 1; }
.st-star.st-t-pillar .st-label { font-size: calc(13px * var(--st-ts, 1)); font-weight: 600; }
.st-star.st-t-major  .st-label { font-size: calc(12px * var(--st-ts, 1)); }
.st-star.st-t-branch .st-label { font-size: calc(11.5px * var(--st-ts, 1)); }
.st-star.st-t-leaf   .st-label { font-size: calc(11px * var(--st-ts, 1)); }

/* 枢纽星的名字常显（= 参照站 TIER_STYLE.alwaysLabel）。
 * ★ 阈值按比例定过（见 star.js starTier 的注释）：真实数据下枢纽只有 9~12 颗，
 *   整幅视角下 12 个标签正好「能看清结构、又不糊」。
 * ★ 必须 !important + 更长的选择器才压得住 `.st-svg.st-far .st-label { opacity: 0 !important }`
 *   —— 那条是书图模式的 LOD（362 个标签全显必糊），两条的出发点是同一个：
 *   标签数量必须和缩放下能读懂的容量匹配。 */
.st-wrap.st-mode-spiral .st-svg.st-far .st-star.st-t-pillar .st-label { opacity: .82 !important; }
.st-wrap.st-mode-spiral .st-star.st-t-pillar .st-label { opacity: .82; }

/* 十字光芒（pillar 专属，JS 只给 pillar 画）。**亮度仍归进度**：
 *   已掌握最亮、在学中等、未学最淡 —— 这就是用户拍的「拆分」落到 CSS 的那一半。 */
.st-star.st-t-pillar .st-flare { opacity: .8; }
.st-star.st-t-pillar.st-learning .st-flare { opacity: .45; }
.st-star.st-t-pillar.st-new .st-flare { opacity: .2; }

/* 远景减噪：跨域虚线在这个尺度上只传达「有联系」，不传达「谁连谁」 */
.st-svg.st-far .st-sp-x { stroke-opacity: .05; }

/* ── 单元卡里的知识点清单（一颗星 = 一个单元）─────────────────
 * ★ 小圆点颜色与星点**同一套口径**（金=已掌握、蓝=在学、白=未学），
 *   不另造一套 —— 卡片和图必须说的是同一件事。 */
.st-card-pts { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.st-card-pt { position: relative; padding-left: 15px; font-size: 12.4px; line-height: 1.5; color: var(--st-txt-2); }
.st-card-pt::before {
  content: ''; position: absolute; left: 3px; top: .52em;
  width: 6px; height: 6px; border-radius: 50%; background: rgba(255, 255, 255, .45);
}
.st-card-pt-mastered::before { background: var(--st-done); }
.st-card-pt-learning::before { background: var(--st-learning); }
.st-card-pt-new::before { background: rgba(255, 255, 255, .45); }
/* 考试/复习节点是菱形 —— 和书图里考试星用菱形是同一条记号 */
.st-card-pt-review::before, .st-card-pt-exam::before {
  border-radius: 1px; background: rgba(255, 255, 255, .3); transform: rotate(45deg) scale(.8);
}
.st-card-pt-more { color: var(--st-txt-3); }
.st-card-pt-more::before { display: none; }
.st-card-next { margin: 7px 0 0; font-size: 12.2px; color: var(--st-txt-3); }
