/* ============================================================================
   U22 · 手绘漫画美术层（art.css）

   来由：用户给的抖音参考片（玖寅绘画《千万不要让美术生用上 codex》，
   aweme_id 7686060774763557614）拿到 10 帧后，可复用的美术语言是四条：
     ① 纸面网点（半调点 + 纸张横纹，像丝网印刷的底）
     ② 套色错位（标题/贴纸带品红—青的偏移描边）
     ③ 手绘线稿（不规则墨线、速度线、随手勾的边角）
     ④ 分镜切换（场景一格格切，切的时候闪一下 + 网点扫过 + 场景名贴纸）
   落到本站：画布 / 网格 / HUD 仍是原来的科技层（tech.css + hero.js 不动），
   这一层只负责纸面、套色、墨线与分镜。

   契约（与 U20 一致，改动前先读 INTERFACES.md §1.11）：
   · 色值只引用 var(--art-*)，定义在 theme.css；这里不新增令牌。
   · 装饰节点一律 aria-hidden + pointer-events:none，绝不吃点击、绝不做焦点目标。
   · 全部动效能被 html.no-motion / prefers-reduced-motion 关掉；
     场景 calm 等价于「关掉整层美术效果」，仍是完整的页面。
   · 纸面质感用 body 背景层（不是 position:fixed 遮罩）：不参与层叠、
     不挡点击、不产生溢出。
   ============================================================================ */

/* ------------------------------------------------ 1. 纸面：网点 + 横纹 ------ */
/* U24 起这张「纸」再叠三层，全部走 body 背景层（不新增 DOM、不改层叠）：
     ① 颗粒：内联 base64 的 SVG 噪声（feTurbulence），overlay 混合 —— 只加「牙」，
        中灰在 overlay 下近似不变，所以底色不会被洗灰；
     ② 大气：三团大光斑，background-attachment:fixed，滚页面时背景不动 ——
        内容因此有了纵深，而且这是纯合成成本，不参与重排；
     ③ 四角压暗：一处放射渐变，做成照片式的收边。
   颜色全在 theme.css 的 --atmo-* / --art-* 令牌里，明暗两套。 */

body {
  background-color: var(--bg);   /* 显式声明：防别处的 background 简写把底色重置掉 */
  background-image:
    url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxODAiIGhlaWdodD0iMTgwIj48ZmlsdGVyIGlkPSJuIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iMC44NSIgbnVtT2N0YXZlcz0iNCIgc3RpdGNoVGlsZXM9InN0aXRjaCIvPjxmZUNvbG9yTWF0cml4IHR5cGU9InNhdHVyYXRlIiB2YWx1ZXM9IjAiLz48L2ZpbHRlcj48cmVjdCB3aWR0aD0iMTgwIiBoZWlnaHQ9IjE4MCIgZmlsdGVyPSJ1cmwoI24pIiBvcGFjaXR5PSIwLjQyIi8+PC9zdmc+"),
    radial-gradient(circle at center, var(--art-dot) .9px, transparent 1.05px),
    repeating-linear-gradient(0deg, var(--art-paper) 0 1px, transparent 1px 4px),
    radial-gradient(58vmax 42vmax at 10% -6%, var(--atmo-a), transparent 68%),
    radial-gradient(44vmax 36vmax at 96% 8%, var(--atmo-b), transparent 70%),
    radial-gradient(64vmax 44vmax at 48% 112%, var(--atmo-c), transparent 72%),
    radial-gradient(130vmax 100vmax at 50% 50%, transparent 38%, var(--atmo-vig) 100%);
  background-size:
    180px 180px,
    var(--art-dot-pitch) var(--art-dot-pitch),
    auto, auto, auto, auto, auto;
  background-repeat: repeat, repeat, repeat, no-repeat, no-repeat, no-repeat, no-repeat;
  background-attachment: scroll, scroll, scroll, fixed, fixed, fixed, fixed;
  background-position: 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0;
  background-blend-mode: overlay, normal, normal, normal, normal, normal, normal;
}

/* ------------------------------------------------ 1b. 低画质档（U27） ------- */
/* ux.js 的 initPerf 实测帧时间后加 html.perf-low；也可用 ?perf=low 手动强制。
   纸感留下来（颗粒 + 网点 + 纸纹），去掉三团大气光与四角压暗 ——
   那四层是 background-attachment:fixed，滚动时要整屏重绘，是最贵的一笔。 */

html.perf-low body {
  background-color: var(--bg);
  background-image:
    url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxODAiIGhlaWdodD0iMTgwIj48ZmlsdGVyIGlkPSJuIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iMC44NSIgbnVtT2N0YXZlcz0iNCIgc3RpdGNoVGlsZXM9InN0aXRjaCIvPjxmZUNvbG9yTWF0cml4IHR5cGU9InNhdHVyYXRlIiB2YWx1ZXM9IjAiLz48L2ZpbHRlcj48cmVjdCB3aWR0aD0iMTgwIiBoZWlnaHQ9IjE4MCIgZmlsdGVyPSJ1cmwoI24pIiBvcGFjaXR5PSIwLjQyIi8+PC9zdmc+"),
    radial-gradient(circle at center, var(--art-dot) .9px, transparent 1.05px),
    repeating-linear-gradient(0deg, var(--art-paper) 0 1px, transparent 1px 4px);
  background-size: 180px 180px, var(--art-dot-pitch) var(--art-dot-pitch), auto;
  background-repeat: repeat, repeat, repeat;
  background-attachment: scroll, scroll, scroll;
  background-position: 0 0, 0 0, 0 0;
  background-blend-mode: overlay, normal, normal;
}

/* ------------------------------------------------ 1c. 手机档（U28） ---------- */
/* 手机上 `background-attachment: fixed` 是滚动卡顿的头号来源：移动浏览器常常
   在每一帧里重新绘制整屏固定背景。三团大气光与四角压淡照旧留着，只是跟着页面
   一起滚 —— 手机视口小，看不出「背景不动」那层细节，但滚动代价差一个量级。
   注意这条与 html.perf-low 不冲突：那一档本来就只剩三层（都是 scroll）。 */

@media (max-width: 979px), (pointer: coarse) {
  body {
    background-attachment: scroll, scroll, scroll, scroll, scroll, scroll, scroll;
  }
}

/* ------------------------------------------------ 2. 套色错位：标题 ---------- */
/* 只动 text-shadow：正文颜色/对比度仍由 theme.css 的令牌保证（不改可读性）。 */

.hero h1, .page-head h1, .sec-head h2, .notes h2 {
  text-shadow: -1.5px 0 var(--art-off-a), 1.5px 0 var(--art-off-b);
}
/* 首屏大标题原来有一条浅色高光（tech.css:127），别把它顶掉 */
.stage-in h1 {
  text-shadow: -1.5px 0 var(--art-off-a), 1.5px 0 var(--art-off-b), 0 1px 0 var(--stage-hi);
}

/* 章节编号做成贴纸：墨底 + 硬投影，浮在发丝线上方 */
.sec-head .idx {
  display: inline-block;
  padding: 1px 8px 2px;
  background: var(--art-ink);
  color: var(--bg);
  border-left: 0;
  box-shadow: 2.5px 2.5px 0 var(--art-off-a);
  transform: rotate(-1.6deg);
}
.sec-more { border-bottom-style: dashed; }
.sec-more:hover { border-bottom-style: solid; }

/* ------------------------------------------------ 3. 首屏分镜层（#art-ink） --- */
/* 由 assets/js/art.js 在 #stage 里建；z-index 3 落在 stage-aura(2) 与
   stage-nodes(4) 之间 —— 墨线画在作品之上、贴纸节点之下。 */

#art-ink {
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none; overflow: hidden;
  color: var(--art-ink);
  opacity: .84;
  transform: translate3d(calc(var(--art-px, 0) * 7px), calc(var(--art-py, 0) * 5px), 0);
  /* 跟手优先：旧值是 .5s 三次缓动，指针一动墨线要半秒才跟上（用户反馈「不跟手」） */
  transition: transform .12s linear;
}
#art-ink svg { display: block; width: 100%; height: 100%; }
#art-ink .ink-far {
  transform: translate3d(calc(var(--art-px, 0) * 3px), calc(var(--art-py, 0) * 2px), 0);
}
#art-ink path, #art-ink circle, #art-ink line {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
#art-ink .ink-soft { opacity: .16; }
#art-ink .ink-mid { opacity: .26; }
#art-ink .ink-hard { opacity: .28; }

/* 墨线「画出来」：进场一次性描线（分镜切换时重放） */
#art-ink .ink-draw {
  stroke-dasharray: var(--ink-len, 1400);
  stroke-dashoffset: var(--ink-len, 1400);
  animation: art-draw 1.5s cubic-bezier(.3, .7, .2, 1) forwards;
  animation-delay: calc(var(--ink-i, 0) * 90ms);
}
#art-ink.is-redraw .ink-draw { animation: none; }

@keyframes art-draw {
  from { stroke-dashoffset: var(--ink-len, 1400); }
  to { stroke-dashoffset: 0; }
}

/* 网点块：手绘框里的一块半调底（图案在 art.js 建的 <defs> 里） */
#art-ink .ink-halftone { fill: url(#art-halftone); stroke: none; opacity: .6; }

/* ------------------------------------------------ 4. 分镜条（.art-rail） ----- */
/* 文档流里的普通一节（不是绝对定位）：不参与舞台几何，不会压到 HUD/节点。 */

.art-rail {
  border-top: 1px solid var(--line);
  padding: 14px 0 2px;
}
.art-rail-in {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px 18px;
}
.art-rail-label {
  display: flex; align-items: baseline; gap: 8px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted); margin: 0;
}
.art-rail-label b { color: var(--ink); font-weight: 700; }
.art-cells { display: flex; flex-wrap: wrap; gap: 8px; }

.art-cell {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 5px 11px 5px 7px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font: inherit; font-size: 13px; line-height: 1.2;
  border-radius: 2px;
  cursor: pointer;
  transform: none;
  transition: transform .16s ease, box-shadow .16s ease, background-color .16s ease;
}
.art-cell:nth-child(even) { transform: none; }
.art-cell:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--scene-accent) 55%, var(--line-strong));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--scene-accent) 22%, transparent);
}
.art-cell[aria-pressed="true"] {
  background: var(--art-ink); color: var(--bg);
  border-color: var(--art-ink); transform: none;
  box-shadow: inset 0 -2px 0 var(--scene-accent);
}
.art-cell-idx {
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .08em;
  opacity: .7;
}
.art-cell-mini {
  width: 30px; height: 20px; flex: none;
  border: 1px solid currentColor; border-radius: 2px;
  opacity: .8;
}
.art-cell-mini svg { display: block; width: 100%; height: 100%; }
.art-cell-mini * { fill: none; stroke: currentColor; stroke-width: 1.4; }
.art-cell-mini .fill { fill: currentColor; stroke: none; opacity: .55; }
.art-cell:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* 「自动分镜」（U35）：JS 只建 button.art-auto > i.art-auto-dot + span.art-auto-label
   并切 aria-pressed，外观全部在这里。它和分镜格并排，靠 margin-left:auto 推到行尾。 */
.art-auto {
  display: inline-flex; align-items: center; gap: 8px;
  margin-left: auto;
  padding: 5px 11px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font: inherit; font-size: 13px; line-height: 1.2;
  border-radius: 2px;
  cursor: pointer;
  transition: border-color .16s ease, box-shadow .16s ease;
}
.art-auto:hover {
  border-color: color-mix(in srgb, var(--scene-accent) 55%, var(--line-strong));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--scene-accent) 22%, transparent);
}
.art-auto:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.art-auto-dot {
  width: 8px; height: 8px; flex: none; border-radius: 50%;
  background: var(--muted);
}
.art-auto[aria-pressed="true"] {
  background: var(--art-ink); color: var(--bg);
  border-color: var(--art-ink);
  box-shadow: inset 0 -2px 0 var(--scene-accent);
}
.art-auto[aria-pressed="true"] .art-auto-dot {
  background: var(--scene-accent);
  animation: deco-core 1.6s ease-in-out infinite;
}

.art-rail-note {
  margin: 0; font-size: 12.5px;
  color: color-mix(in srgb, var(--scene-accent) 68%, var(--ink));
  font-family: var(--font-mono); letter-spacing: .04em;
}

/* ------------------------------------------------ 5. 分镜转场（#art-cut） ----
   U31：从「闪白 + 网点圆擦 + 贴纸」改成 2001 星门式的**狭缝扫描**：
   一道亮缝从上往下扫过，缝里是被拉成竖条的光斑，场景名以宽字距浮现。
   （slit-scan 的原做法就是让光通过一条窄缝做长曝光，这里用同样的视觉逻辑。） */

#art-cut { position: fixed; inset: 0; z-index: 80; pointer-events: none; opacity: 1; }
#art-cut[hidden] { display: none; }
#art-cut .cut-flash,
#art-cut .cut-dots,
#art-cut .cut-label { position: absolute; opacity: 0; }

#art-cut .cut-flash {
  left: 0; right: 0; top: 50%; height: 2px;
  margin-top: -1px;
  background: linear-gradient(90deg,
    transparent 0%, var(--astro-streak) 18%, var(--astro-win-white) 50%, var(--astro-streak) 82%, transparent 100%);
  box-shadow: 0 0 24px 6px color-mix(in srgb, var(--astro-streak) 32%, transparent);
}
#art-cut .cut-dots {
  left: 0; right: 0; top: 50%; height: 34vh;
  margin-top: -17vh;
  background-image: repeating-linear-gradient(90deg,
    color-mix(in srgb, var(--astro-streak) 70%, transparent) 0 1px,
    transparent 1px 11px);
  transform-origin: 50% 50%;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent);
}
#art-cut .cut-label {
  left: 50%; top: 44%;
  transform: translate(-50%, -50%);
  padding: 6px 2px;
  background: transparent;
  color: var(--astro-streak);
  font-family: var(--font-mono); font-size: 13px; letter-spacing: .42em;
  text-transform: uppercase;
  text-shadow: 0 0 18px color-mix(in srgb, var(--astro-win-void) 90%, transparent);
  white-space: nowrap;
}
#art-cut.is-live .cut-flash { animation: art-slit .8s cubic-bezier(.3, .7, .2, 1) both; }
#art-cut.is-live .cut-dots { animation: art-streak .72s cubic-bezier(.2, .9, .2, 1) both; }
#art-cut.is-live .cut-label { animation: art-label .88s cubic-bezier(.2, .9, .2, 1) both; }

@keyframes art-slit {
  0%   { transform: translateY(-38vh) scaleY(.4); opacity: 0; }
  14%  { opacity: .95; }
  52%  { transform: translateY(0) scaleY(2.6); opacity: 1; }
  100% { transform: translateY(42vh) scaleY(.5); opacity: 0; }
}
@keyframes art-streak {
  0%   { transform: scaleX(.05) scaleY(.3); opacity: 0; }
  30%  { transform: scaleX(1) scaleY(1); opacity: .55; }
  100% { transform: scaleX(1.08) scaleY(.16); opacity: 0; }
}
@keyframes art-label {
  0%   { opacity: 0; letter-spacing: .62em; }
  28%  { opacity: 1; letter-spacing: .34em; }
  78%  { opacity: 1; }
  100% { opacity: 0; letter-spacing: .40em; }
}

/* ------------------------------------------------ 6. 分镜五档：三层同时换装 --
   U35。站长反馈「底下这些全没效果啊」，读码确认根因有两条：
     ① 老实现每档只改 #stage-canvas 的 CSS filter（saturate 1.05 这种量级）、
        #art-ink 的整层 opacity/color 与 --art-dot-pitch，量级全在肉眼阈以下；
     ② 其中「整层 opacity/color」还被 `.stage.hero #art-ink`（0,2,1）压掉，
        每档实际生效的 alpha 只剩 7%–13% —— 等于没改。
   现在每档在三个层面同时换装，截图摆在一起必须一眼分辨：
     ① #art-grade   调色层：每档不同的 background-image + mix-blend-mode + opacity
     ② #art-deco    装饰件：**结构**不同 —— 引力透镜环 / 轨道 + 飞行器 /
                    飘尘 canvas / 3D 线框晶格 / 空
     ③ body::after  页面图案：半调网点 / 轨道弧 / 沙尘噪点 / 晶格网格 / 无
   色值一律走 theme.css 的 --scene-* 令牌，这一层不写色值。
   只动表现：几何、尺寸、层级、布局一个都不动。 */

/* ---- 6a. 调色层 #art-grade -------------------------------------------------
   z-index 2：垫在网格(0)与画布(1)之上、光晕(2，DOM 在后)/墨线(3)/节点(4)之下，
   所以它只给「舞台底」上色，不会洗掉文字与节点。
   inset:0 贴着舞台盒，不产生任何溢出。.stage 自带 isolation:isolate，
   混合只在舞台这一层里发生，不会串到页面底纹。 */
#art-grade {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none;
  background-repeat: repeat;
  mix-blend-mode: overlay;
  opacity: .85;
}

html[data-art-scene="gargantua"] #art-grade {
  /* 吸积盘：一圈暖→冷的锥形扫光，加中心辉光与极细的盘面纹 */
  background-image:
    radial-gradient(34% 30% at 70% 50%, var(--scene-glow) 0%, transparent 72%),
    conic-gradient(from 196deg at 70% 50%,
      var(--scene-grade-a) 0deg, transparent 78deg,
      var(--scene-grade-b) 168deg, transparent 292deg, var(--scene-grade-a) 360deg),
    repeating-linear-gradient(102deg, var(--scene-grade-b) 0 1px, transparent 1px 22px);
  mix-blend-mode: overlay;
  opacity: .85;
}

html[data-art-scene="endurance"] #art-grade {
  /* 轨道带：两条横向环带 + 等距航向刻度 */
  background-image:
    radial-gradient(58% 20% at 30% 62%, var(--scene-grade-a) 0%, transparent 74%),
    radial-gradient(52% 16% at 74% 32%, var(--scene-grade-b) 0%, transparent 76%),
    repeating-linear-gradient(90deg, var(--scene-grade-b) 0 2px, transparent 2px 34px);
  mix-blend-mode: soft-light;
  opacity: .95;
}

html[data-art-scene="dust"] #art-grade {
  /* 尘暴幕：斜向飞沙条 + 上下压暗的尘雾；hard-light 在浅窗上压暗、深窗上发亮 */
  background-image:
    repeating-linear-gradient(74deg, var(--scene-grade-a) 0 6px, transparent 6px 17px),
    repeating-linear-gradient(74deg, var(--scene-grade-b) 0 2px, transparent 2px 9px),
    linear-gradient(180deg, var(--scene-grade-a) 0%, transparent 46%, var(--scene-grade-b) 100%);
  mix-blend-mode: hard-light;
  opacity: .55;
}

html[data-art-scene="tesseract"] #art-grade {
  /* 立方网格：两向硬边格 + 中心冷光。normal 混合，明暗两档都保证看得见 */
  background-image:
    linear-gradient(to right, var(--scene-grade-a) 0 1px, transparent 1px),
    linear-gradient(to bottom, var(--scene-grade-a) 0 1px, transparent 1px),
    radial-gradient(46% 40% at 50% 50%, var(--scene-grade-b) 0%, transparent 70%);
  background-size: 46px 46px, 46px 46px, auto;
  mix-blend-mode: normal;
  opacity: .7;
}

html[data-art-scene="calm"] #art-grade { background-image: none; opacity: 0; }

/* ---- 6b. 装饰件 #art-deco --------------------------------------------------
   z-index 3。overflow:hidden 把装饰裁在舞台盒里，pointer-events 全关。
   --dec-rx / --dec-ry 由 assets/js/art.js 写在 #art-deco 上（∈[-1,1]），
   这里只负责把它翻译成 transform —— JS 不直接写 transform。 */
#art-deco {
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none; overflow: hidden;
  color: var(--scene-line);
  --dec-rx: 0;
  --dec-ry: 0;
}

/* 引力透镜（gargantua）：三条被拉扁的同心光子环 + 两条双曲线 + 视轴刻痕。
   SVG 是 preserveAspectRatio="none" 拉满整个舞台盒的，所以描边走
   vector-effect:non-scaling-stroke，线宽不跟着拉伸。 */
.deco-lens { position: absolute; inset: 0; }
.deco-lens-svg { display: block; width: 100%; height: 100%; }
.deco-lens path, .deco-lens ellipse {
  fill: none; stroke: currentColor;
  stroke-linecap: round; stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.deco-lens-ring { stroke-width: 1.5; opacity: .6; }
.deco-lens-ring:nth-child(2) { opacity: .4; stroke-dasharray: 4 10; animation: deco-flow 22s linear infinite; }
.deco-lens-ring:nth-child(3) { opacity: .24; stroke-dasharray: 1 16; animation: deco-flow 34s linear infinite reverse; }
.deco-lens-arc { stroke-width: 2; opacity: .45; stroke-dasharray: 26 14; animation: deco-flow 16s linear infinite; }
.deco-lens-line { stroke-width: 1; opacity: .34; stroke-dasharray: 2 7; }
.deco-lens::after {
  content: ""; position: absolute;
  left: 70%; top: 50%; width: 30%; height: 42%;
  margin: -21% 0 0 -15%;
  border-radius: 50%;
  background: radial-gradient(circle, var(--scene-glow) 0%, transparent 68%);
  filter: blur(6px);
  animation: deco-core 6.5s ease-in-out infinite;
}

/* 轨道（endurance）：三层同心轨道 + 一枚沿虚线轨道飞行的飞行器。 */
/* 直径跟着「舞台盒的高」走（vmin/vh 双限 + 500px 上限）：
   舞台高是 clamp(520px,62vh,680px)，用 58vh 保证圆盘永远落在盒里 ——
   契约 §2 要求装饰件的几何不许越出舞台盒（U22 的坑），所以这里不图大。 */
.deco-orbit {
  position: absolute; left: 50%; top: 50%;
  width: min(58vmin, 58vh, 500px);
  height: min(58vmin, 58vh, 500px);
  transform: translate(-50%, -50%);
}
.deco-orbit-ring {
  position: absolute; inset: 0; display: block;
  border: 1px solid currentColor; border-radius: 50%;
  opacity: .22;
}
.deco-orbit-ring:nth-child(1) { inset: 22%; opacity: .62; }
.deco-orbit-ring:nth-child(2) { inset: 11%; opacity: .42; border-style: dashed; }
.deco-orbit-arm {
  position: absolute; inset: 11%; display: block;
  animation: deco-spin 17s linear infinite;
}
.deco-craft {
  position: absolute; left: 50%; top: 0;
  width: 8px; height: 8px; margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 10px 2px currentColor;
}

/* 尘暴（dust）：只负责给 canvas 定尺寸与底色 —— 像素由 art.js 每帧画
   （走 SiteMotion.raf，off 档一帧都不跑）。下面这层静态飞沙保证
   「即使飘尘没在跑，这一档也认得出来」。 */
.deco-dust { position: absolute; inset: 0; }
.deco-dust-canvas { display: block; width: 100%; height: 100%; }
.deco-dust::before {
  content: ""; position: absolute; inset: 0;
  background-image:
    linear-gradient(90deg, transparent 0%, var(--scene-grade-a) 42%, transparent 78%),
    repeating-linear-gradient(74deg, var(--scene-paper) 0 4px, transparent 4px 15px);
  opacity: .85;
}

/* 尺寸只用一个 --lat，面的 translateZ 与八个角点位移都从它算出来 ——
   指针角度来自 --dec-rx / --dec-ry，所以整块晶格会跟着指针轻微转头。
   --lat 同样按「舞台盒的高」封顶（vh + vmin 双限），且透视距离写成
   --lat 的 3.1 倍：这样近端缩放恒为 3.1/2.6 ≈ 1.19 倍，投影尺寸永远与
   --lat 成正比 —— 任何视口下晶格都不会胀出舞台盒。 */
.deco-lattice {
  position: absolute; left: 50%; top: 50%;
  --lat: min(30vmin, 30vh, 260px);
  width: var(--lat); height: var(--lat);
  transform: translate(-50%, -50%) perspective(calc(var(--lat) * 3.1))
             rotateX(calc(var(--dec-ry, 0) * -15deg))
             rotateY(calc(var(--dec-rx, 0) * 22deg));
  transform-style: preserve-3d;
}
.deco-cell {
  position: absolute; inset: 0; display: block;
  border: 1px solid currentColor; opacity: .34;
}
.deco-cell:nth-child(1) { transform: translateZ(calc(var(--lat) / 2)); }
.deco-cell:nth-child(2) { transform: rotateY(180deg) translateZ(calc(var(--lat) / 2)); }
.deco-cell:nth-child(3) { transform: rotateY(-90deg) translateZ(calc(var(--lat) / 2)); }
.deco-cell:nth-child(4) { transform: rotateY(90deg) translateZ(calc(var(--lat) / 2)); }
.deco-cell:nth-child(5) { transform: rotateX(90deg) translateZ(calc(var(--lat) / 2)); }
.deco-cell:nth-child(6) { transform: rotateX(-90deg) translateZ(calc(var(--lat) / 2)); }
/* 7–14：立方体的八个角点 */
.deco-cell:nth-child(n+7) {
  inset: auto; left: 50%; top: 50%;
  width: 8px; height: 8px; margin: -4px 0 0 -4px;
  background: currentColor; border: 0; border-radius: 50%; opacity: .55;
}
.deco-cell:nth-child(7)  { transform: translate3d(calc(var(--lat) / -2), calc(var(--lat) / -2), calc(var(--lat) / -2)); }
.deco-cell:nth-child(8)  { transform: translate3d(calc(var(--lat) / -2), calc(var(--lat) / -2), calc(var(--lat) / 2)); }
.deco-cell:nth-child(9)  { transform: translate3d(calc(var(--lat) / -2), calc(var(--lat) / 2), calc(var(--lat) / -2)); }
.deco-cell:nth-child(10) { transform: translate3d(calc(var(--lat) / -2), calc(var(--lat) / 2), calc(var(--lat) / 2)); }
.deco-cell:nth-child(11) { transform: translate3d(calc(var(--lat) / 2), calc(var(--lat) / -2), calc(var(--lat) / -2)); }
.deco-cell:nth-child(12) { transform: translate3d(calc(var(--lat) / 2), calc(var(--lat) / -2), calc(var(--lat) / 2)); }
.deco-cell:nth-child(13) { transform: translate3d(calc(var(--lat) / 2), calc(var(--lat) / 2), calc(var(--lat) / -2)); }
.deco-cell:nth-child(14) { transform: translate3d(calc(var(--lat) / 2), calc(var(--lat) / 2), calc(var(--lat) / 2)); }
.deco-lattice-core {
  position: absolute; left: 50%; top: 50%;
  width: 30%; height: 30%; margin: -15% 0 0 -15%;
  border-radius: 50%;
  background: radial-gradient(circle, var(--scene-glow) 0%, transparent 70%);
  filter: blur(5px);
  animation: deco-core 3.4s ease-in-out infinite;
}

/* ---- 6c. 页面图案（body::after）-------------------------------------------
   一层 position:fixed + z-index:-1 的纯渐变图案：画在 body 底纹之上、所有内容
   之下。fixed 元素不参与文档宽度（溢出检查跳过），也不吃点击。
   background-attachment 一律 scroll —— 手机上 fixed 背景是滚动卡顿头号来源
   （U29 契约），而这一层本身已经固定，不需要再来一次 fixed 贴图。 */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: -1;
  pointer-events: none;
  background-repeat: repeat;
  background-attachment: scroll;
}

html[data-art-scene="gargantua"] body::after {
  /* 半调网点：两组不同间距的点阵互相干涉，出印刷网屏的摩尔纹 */
  background-image:
    radial-gradient(circle at 50% 50%, var(--scene-paper) 1.3px, transparent 1.4px),
    radial-gradient(circle at 50% 50%, var(--scene-paper) .9px, transparent 1px);
  background-size: 11px 11px, 17px 17px;
  background-position: 0 0, 6px 6px;
}

html[data-art-scene="endurance"] body::after {
  /* 轨道弧：两组同心圆，圆心分别落在视口上方与下方之外 */
  background-image:
    repeating-radial-gradient(circle at 50% 118%, transparent 0 52px, var(--scene-paper) 52px 53px),
    repeating-radial-gradient(circle at 50% -18%, transparent 0 52px, var(--scene-paper) 52px 53px);
}

html[data-art-scene="dust"] body::after {
  /* 沙尘噪点：细颗粒 + 斜向飞沙条 */
  background-image:
    radial-gradient(circle at 50% 50%, var(--scene-paper) .7px, transparent .8px),
    repeating-linear-gradient(74deg, var(--scene-paper) 0 1px, transparent 1px 13px);
  background-size: 4px 4px, auto;
  background-position: 0 0, 7px 0;
}

html[data-art-scene="tesseract"] body::after {
  /* 晶格网格：两向正交线 + 一组斜线 */
  background-image:
    linear-gradient(to right, var(--scene-paper) 0 1px, transparent 1px),
    linear-gradient(to bottom, var(--scene-paper) 0 1px, transparent 1px),
    linear-gradient(45deg, var(--scene-paper) 0 1px, transparent 1px);
  background-size: 38px 38px, 38px 38px, 38px 38px;
}

html[data-art-scene="calm"] body::after { background-image: none; }

/* ---- 6d. 墨线的「每档颜色 / 浓度」------------------------------------------
   这一组选择器带上了 .stage.hero，是为了压过上面那条 `.stage.hero #art-ink`
   （0,2,1）—— 老版本这里少了 .stage.hero，所以五档的 color/opacity
   一直被 (0,2,1) 吃掉，等于没写。
   墨线是**辅助**层（U24 才降过噪），所以浓度仍然压在 .34–.54，
   真正拉开差异的是 #art-grade 与 #art-deco。 */
.stage.hero #art-ink { color: var(--astro-ring); opacity: .46; }

html[data-art-scene="gargantua"] { --art-dot-pitch: 7px; }
html[data-art-scene="gargantua"] #stage-canvas { filter: saturate(1.05) contrast(1.04); }
html[data-art-scene="gargantua"] .stage.hero #art-ink { color: var(--astro-ring); opacity: .40; }

html[data-art-scene="endurance"] {
  --art-dot-pitch: 8px;
  --art-dot: color-mix(in srgb, var(--astro-win-ring) 5.5%, transparent);
}
html[data-art-scene="endurance"] #stage-canvas { filter: saturate(.82) contrast(1.08) brightness(1.05); }
html[data-art-scene="endurance"] .stage.hero #art-ink { color: var(--astro-instrument); opacity: .54; }
html[data-art-scene="endurance"] .stage.hero #art-ink .ink-soft { opacity: .3; }

html[data-art-scene="dust"] {
  --art-dot-pitch: 5px;
  --art-dot: color-mix(in srgb, var(--astro-win-amber) 16%, transparent);
}
html[data-art-scene="dust"] #stage-canvas { filter: sepia(.22) saturate(.86) contrast(1.03); }
html[data-art-scene="dust"] .stage.hero #art-ink { color: var(--astro-dust); opacity: .34; }

html[data-art-scene="tesseract"] { --art-dot-pitch: 9px; }
html[data-art-scene="tesseract"] #stage-canvas { filter: contrast(1.12) saturate(1.06); }
html[data-art-scene="tesseract"] .stage.hero #art-ink { color: var(--astro-disk); opacity: .38; }

html[data-art-scene="calm"] body { background-image: none; }
html[data-art-scene="calm"] #art-ink { display: none; }
html[data-art-scene="calm"] #stage-canvas { filter: none; }
html[data-art-scene="calm"] .stage-in h1,
html[data-art-scene="calm"] .hero h1,
html[data-art-scene="calm"] .page-head h1,
html[data-art-scene="calm"] .sec-head h2 { text-shadow: none; }
html[data-art-scene="calm"] .stage-in h1 { text-shadow: 0 2px 30px color-mix(in srgb, var(--astro-win-void-deep) 72%, transparent); }

/* 手机档（U28）：给一个每帧都在变的画布挂 CSS filter 等于每帧重新栅格化，
   手机上纯亏。窄屏画布本来就压到 45~55% 不透明度垫在文字后面，色偏几乎看不出。 */
@media (max-width: 979px), (pointer: coarse) {
  html[data-art-scene] #stage-canvas { filter: none; }
}

/* 暗色主题里轨道档的线要更亮一点，否则等于没有 */
html[data-theme="dark"][data-art-scene="endurance"] #art-ink { opacity: .72; }

/* ---- 6e. 装饰件的动效键 --------------------------------------------------- */
@keyframes deco-flow { to { stroke-dashoffset: -160; } }
@keyframes deco-spin { to { transform: rotate(360deg); } }
@keyframes deco-core {
  0%, 100% { opacity: .55; transform: scale(.94); }
  50%      { opacity: 1;   transform: scale(1.06); }
}

/* ------------------------------------------------ 7. 降级 -------------------- */
/* 低动效偏好：墨线直接画完、分镜切换不播转场（只换画面）。 */

html.no-motion #art-ink .ink-draw,
html.no-motion #art-cut .cut-flash,
html.no-motion #art-cut .cut-dots,
html.no-motion #art-cut .cut-label,
html.no-motion #art-deco *,
html.no-motion #art-deco *::before,
html.no-motion #art-deco *::after { animation: none !important; }
html.no-motion #art-ink .ink-draw { stroke-dashoffset: 0; }
html.no-motion #art-ink { transition: none; }

@media (prefers-reduced-motion: reduce) {
  #art-ink .ink-draw { animation: none; stroke-dashoffset: 0; }
  #art-ink { transition: none; }
  #art-cut { display: none; }
  .art-cell { transition: none; }
  #art-deco *, #art-deco *::before, #art-deco *::after { animation: none !important; }
}

@media (max-width: 979px) {
  #art-ink { opacity: .7; }
  #art-ink .ink-soft { opacity: .16; }
}

@media (max-width: 640px) {
  #art-ink { display: none; }
  .art-cell-mini { display: none; }
  .art-cell { padding: 6px 11px; }
  .art-rail-in { gap: 8px 12px; }
}

@media print {
  body { background-image: none; }
  body::after { display: none !important; }
  #art-grade, #art-deco, #art-ink, #art-cut, .art-rail { display: none !important; }
  .hero h1, .page-head h1, .sec-head h2, .notes h2 { text-shadow: none; }
}

/* ==========================================================================
   U24 · 舞台视差（第一轮迭代）
   --------------------------------------------------------------------------
   首屏的四层（网格 / 光晕 / 画布 / 边框）跟着指针错开，越远动得越多：
   网格 -26px、光晕 -14px、边框 +8px（反向，像镜头在推）。位移量全部是
   1～2 个 CSS 变量的乘积，由 assets/js/ux.js 的输入总线写入；这里只做转换，
   不碰几何、不碰 z-index、不碰 hero.js 画的画布内容。
   指针离开窗口（html.ptr-out）就归零，不留一个歪掉的首屏。
   ========================================================================== */

html.js:not(.no-motion) .stage-grid,
html.js:not(.no-motion) .stage-aura,
html.js:not(.no-motion) .stage-frame {
  /* 旧值是 --dur-3(420ms)：首屏整层比指针慢半拍，是「不跟手」的主因之一 */
  transition: transform .12s linear;
}

html.js:not(.no-motion) .stage-grid {
  transform: translate3d(calc(var(--ptr-dx, 0) * -26px), calc(var(--ptr-dy, 0) * -16px), 0);
}

html.js:not(.no-motion) .stage-aura {
  transform: translate3d(calc(var(--ptr-dx, 0) * -14px), calc(var(--ptr-dy, 0) * -9px), 0);
}

html.js:not(.no-motion) .stage-frame {
  transform: translate3d(calc(var(--ptr-dx, 0) * 8px), calc(var(--ptr-dy, 0) * 5px), 0);
}

html.ptr-out .stage-grid,
html.ptr-out .stage-aura,
html.ptr-out .stage-frame {
  transform: none;
}

html.no-motion .stage-grid,
html.no-motion .stage-aura,
html.no-motion .stage-frame {
  transform: none !important;
  transition: none !important;
}

@media (prefers-reduced-motion: reduce) {
  .stage-grid,
  .stage-aura,
  .stage-frame {
    transform: none !important;
    transition: none !important;
  }
}

/* 窄屏没有指针（也没有 hover），位移一律关掉，省掉一次合成层 */
@media (max-width: 979px) {
  .stage-grid,
  .stage-aura,
  .stage-frame {
    transform: none !important;
  }
}

/* ==========================================================================
   U24 · 线条降噪（第四轮迭代，用户反馈「线条有点杂乱」）
   --------------------------------------------------------------------------
   只调「表现参数」，不改结构、不碰 hero.js 画的内容：
     · 蓝图网格：46px → 64px（线少三成）并把整层压到 .72
     · 画布：#stage-canvas 压到 .78 —— 满屏长弧线主要来自这一层
     · 墨线：stroke-width 2 → 1.5、各层浓度下调（见第 3 节）
   限定宽屏生效，避免盖掉 tech.css 里窄屏的 .stage-canvas{opacity:.45}。
   ========================================================================== */

@media (min-width: 981px) {
  .stage-grid { background-size: 64px 64px; opacity: .72; }
  .stage-canvas { opacity: .78; }
}
