/* ══════════════════════════════════════════════════════════════════════
   Echo · reskin 适配层
   ──────────────────────────────────────────────────────────────────────
   index.html 是一份打包好的静态产物（HTML/CSS 都是编译结果，改结构等于改源），
   所以这一层的纪律是【只覆盖，不改源】：index.html 只在 </body> 前追加两个
   <link> 和两个 <script>，其余一个字不动。

   本文件做三件事：
     ① 把 scrollTop 锁在**一个可移动的目标值**上 —— 粒子场的唯一输入就是
        main.scrollTop，于是"挪动这个目标"就等于在六种粒子形态之间转场。
       初始目标 = 0（origin 紫色整球）；点输入框时由 skin.js 把它挪到
        04 machine（白色流光带）。
     ② 藏掉为"六章滚动叙事"服务的那批 UI —— 它们现在全是错的行为提示：
        滚动条 / 章节导航点 / 进度条 / SCROLL 提示 / 吸附点 / 章节正文。
     ③ 首屏中间靠下那颗流光描边输入框的样式。

   为什么是 main.scrollTop 而不是改 canvas：
     原版粒子场每帧读 main.scrollTop / maxScroll 算 progress，再按 progress
     在六种几何之间连续插值。所以 scrollTop 不只是"滚动位置"，它就是
     形态的控制器 —— 这一步不需要碰压缩代码。
   ══════════════════════════════════════════════════════════════════════ */

/* ── ① 锁滚动 ────────────────────────────────────────────────────────
   overflow-y:hidden 不改变 scrollHeight（粒子场照样算得出 maxScroll），
   只让人推不动滚动条。scrollTop 现在由 skin.js 推 —— 它设多少就是多少，
   人在触控板上的滑动不会把它带歪（overscroll-behavior 一并掐掉，
   防止惯性把整页弹一下）。 */
main.matter-reel {
  overflow-y: hidden !important;
  overscroll-behavior: none !important;
  scroll-snap-type: none !important;
}

/* ── ② 章节文字全部收起 ──────────────────────────────────────────────
   原来只写 `:not(--origin)`（想着"只留第一章当主角"）。但锁定 scrollTop=0
   时第一章的文字**恰好**是 --matter-opacity:0（静态值），看着像被藏了；
   一旦切章，React 按 data-state 把第一章重新点亮 —— 过渡的 300ms 里
   原版的整块标题、描述、主按钮全闪出来。

   这是"内容泄漏"，不是排版问题：那段文字是原作者的自我介绍，任何时候
   都不该出现在这一屏。所以连 origin 一起收，六章一律不显示，首屏文案
   由我们自己的层来写。

   连带影响：ENTER THE FIELD 也随之不可见。它本来就是"往下进入"的入口，
   单章叙事里没有"下"，而输入框已经承担了这个动作（skin.js 的点击拦截
   仍保留，作键盘/脚本的兜底）。 */
main.matter-reel .matter-reel__chapter {
  display: none !important;
}

/* 为六章叙事服务的配件，单章下全是错误提示，一并藏掉。
   注意：这些是 stage 内的绝对定位元素，藏它们不碰 canvas 的尺寸。 */
main.matter-reel .matter-reel__rail,
main.matter-reel .matter-reel__progress,
main.matter-reel .matter-reel__snap-points,
main.matter-reel .matter-reel__scroll-cue,
main.matter-reel .matter-reel__mobile-chapter-meta {
  display: none !important;
}

/* 原站的分析同意条（右下，760×66）—— 属于功能性组件，不是排版，
   但它正好压在输入框右下和 telemetry 上。原版是本地副本，没有分析脚本，
   留着只有一个坏处：分神。 */
.cookie-consent { display: none !important; }

/* ── ③ 流光描边输入框 ────────────────────────────────────────────────
   位置依据（1440×900 实测，不是估的）：
     origin 章最低点 actions  y=704
     telemetry              y=825
     ⇒ 704–825 之间是空的，56px 高的框以 786 为底边，上下各留 20 / 39。
   bottom 用 clamp 而不是写死 114px：视口变矮时它会跟着抬，但不会低于
   telemetry 那条线。

   ⚠ 底色：设计沉淀里那颗样张是 #1f1f1f 实心深底，直接搬过来在原版上
   就是一块"贴纸"——原版这一屏的球中心几乎纯白，实心方块压上去没有厚度。
   这里做【毛玻璃】，四个手段叠起来才是玻璃而不是"深色片"：

     ① alpha 给 0.36→0.66 的竖向渐变（上浅下深）：一整块均匀的半透明
        看起来还是塑料片，有极轻微的上下落差才有"片材"的感觉。
     ② 压暗交给 backdrop-filter 里的 brightness(.54)，不靠加黑 alpha ——
        乘法压暗保留色相（球的紫光还在），叠加黑色只会把紫压成灰。
     ③ blur 23px：把粒子颗粒糊成一层"砂面"。
     ④ saturate(.75)：这一条是【第二轮加的】，也是最容易被忽略的一条 ——
        球本身是紫的，玻璃全盘继承它的色相，saturate 1.42 时整块框读起来
        是"淡紫薰衣草"，怎么压暗都是浅的（见下面第二组数）。把饱和度压到
        .75，玻璃退成中性的烟灰，描边的暖色才有地方站。
     再加一道顶缘 1px 高光 + 内发光，玻璃才有厚度（都已改成暖白，
     见下面的 box-shadow —— 冷白高光会和暖描边打架）。

   【两轮实测，单位是 1440×900 截图的箱内区】
     第一轮（只调 alpha / blur / brightness，sat 固定 1.42）：
        裸背景            L 32.1  颗粒 sd 31.2
        blur34/b.66/α.30  L 19.9  颗粒 sd  3.2  ← 颗粒全抹平 = 把背景换成
                                                   一块均匀暗面，又变回"暗板"
        blur17/b.85/α.12  L 34.8  颗粒 sd 11.1  ← 当时采用
     第二轮（Han："颜色太浅"）—— 关掉光束、取【无字区】x790–880 / y740–770
     （避开 placeholder 与发送键，否则文字像素会把均值抬上去，我第一遍
     就是这么量错的：带字的区域 F0→F2 只差 1 L，看起来像没改）：
        F0 现状 α.12/.26 b.85 sat1.42   L 75.1  rgb(95,63,131)   色偏 45%
        F1      α.34/.62 b.58 sat.70   L 39.0  rgb(44,36,56)    色偏 36%
        F2      α.36/.66 b.54 sat.75   L 36.1  rgb(41,33,52)    色偏 40% ← 采用
        F3      α.40/.70 b.50 sat.80   L 32.1  rgb(36,29,48)    色偏 40%
     F0 的问题是它比裸背景【还亮】（rgb 95,63,131 就是一层淡紫）—— "太浅"
     不是感觉，是量出来的：F2 比它低 52%。F3 只再低 4 L，却要多让出 10%
     的通透，不值得，所以停在 F2。

   背面是"稀疏亮点撒在黑底"的粒子场，不是连续的光。所以 blur 不能一路往
   上堆（第一轮踩过），要压暗就往 alpha + brightness 走，blur 只负责糊颗粒。 */
#echo-ask {
  position: fixed;
  left: 50%;
  bottom: clamp(96px, 12.6vh, 118px);
  transform: translateX(-50%);
  z-index: 50;

  width: min(520px, calc(100vw - 48px));

  /* ── 光束参数：这一组是【在亮背景上重算的】，不是设计沉淀里那组 ──────
     设计沉淀的默认值是 .26 / .42 / .24，那是给 #1f1f1f 深底卡调的。
     这框坐在一个中心接近纯白的球上，0.26 的描边会被完全淹没（实测：
     三档 A(.26)/B(.60)/C(.90) 里 A 几乎看不见）。而且卡是 520×64 的扁条，
     色斑 20–180px 的绝对尺寸相对 348×122 的样张偏小 ⇒ scale 按宽度比
     520/348 ≈ 1.5 放到 1.6。

     为什么描边给到 1.0 而内发光反而降到 .30：
       描边要高，否则亮背景上看不见线；
       内发光要低，否则 28px 的边缘带会把整块染成一片糊色 —— 那看起来
       像"卡片边缘染色"，不像"1px 的光在走"。宁可细、硬、清楚。
     底色压多暗见上面那一段（两轮实测都在那里）。⚠️ 那里两组读数
     **不在同一把尺子上**：球本身每次载入的亮度会漂，所以只比同一次会话内的
     相对差值，别跨会话抄绝对数。描边强度也是同一个道理 —— 第一轮说
     "1.0 在亮底上仍是全场最亮的一圈"是对的，但底子换成深玻璃（−47% L）后
     描边的绝对亮度没变、对比反而更强，所以这一档不用跟着调。 */
  --beam-radius: 20px;
  --beam-scale: 1.6;
  --beam-stroke: 1;
  --beam-inner: 0.3;
  --beam-bloom: 0.4;
  /* 一圈的秒数。原生默认 1.96s（设计沉淀给 #1f1f1f 小卡片调的节奏），
     520×64 的扁条上 1.96s 会显得在赶。3.6s = 光"慢慢爬"，才看得出流向。
     skin.js 的回执（submit 时甩一下）也读同一个常数，注意一起改。 */
  --beam-duration: 3.6s;

  /* ── 描边只留红 / 橙 / 黄 / 紫 ──────────────────────────────────────
     beam.css 那套 colorful 九色是原站给深色卡配的：c2/c6 是两个蓝、
     c3 一个绿、c4 一个青、c5 是"蓝紫偏蓝"。在这个框上转起来就是一条
     小彩虹 —— 而且因为色斑会互相叠、再被 hue-rotate ±30° 扫一遍，
     读起来最显眼的恰恰是蓝绿段，跟这屏的紫球完全不搭。

     只覆盖 --beam-c1…c9 九个三元组：色斑的位置和尺寸全在
     beam.css 的 --beam-b1…b9 里，它们引用 c1…c9，所以这里换变量就够了，
     几何一行不动（也不用碰设计沉淀那份原件）。

       红   c1 255 62 92    c8 255 68 138
       橙   c2 255 140 46   c6 255 100 56   c7 255 132 44
       黄   c3 255 190 64   c4 250 176 74
       紫   c5 152 72 255   c9 186 58 246

     c5 从 100 70 255 往紫挪到 152 72 255：原来那个 hue 247° 落在蓝里，
     转起来会在右下拖出一条蓝尾巴。 */
  --beam-c1: 255 62 92;
  --beam-c2: 255 140 46;
  --beam-c3: 255 190 64;
  --beam-c4: 250 176 74;
  --beam-c5: 152 72 255;
  --beam-c6: 255 100 56;
  --beam-c7: 255 132 44;
  --beam-c8: 255 68 138;
  --beam-c9: 186 58 246;
  --beam-saturate: 1.38;

  /* 内层 inset 那道白光（beam.css 里是 rgba(255,255,255,.27)）同样换成暖白：
     冷白贴边 + 暖描边 = 一圈脏边，两种白在同一颗框上互相拆台。 */
  --beam-shade: rgba(255, 226, 200, 0.22);

  /* ── 去掉"白色区域"：把白光改成暖金 ─────────────────────────────
     环上最亮的那一下不是色斑给的，是 beam.css 的 --beam-spike 和
     --beam-bloom-spike 两片**纯白**锥形叠在最上层（峰值 .75 / .85）。
     它们压住色斑，于是每个色斑经过时最亮的一刻都被漂成白色 ——
     这就是"白色区域"的来源，不是配色表里缺了一个色。

     做法：保留两片光的形状和位置（弧里必须有明暗，否则整条线是平的色带），
     只把颜色换成暖金、峰值降下来：
       spike        .75 → .52
       bloom-spike  .85 → .52
     这样最亮处仍是最亮处，但它是"金子最亮的那一下"，不是白。 */
  --beam-spike: conic-gradient(from var(--beam-angle),
      transparent 0%, transparent 54%,
      rgba(255, 214, 160, 0.08) 57%, rgba(255, 206, 146, 0.24) 60%,
      rgba(255, 198, 138, 0.42) 63%, rgba(255, 196, 134, 0.52) 66%,
      rgba(255, 198, 138, 0.42) 69%, rgba(255, 206, 146, 0.24) 72%,
      rgba(255, 214, 160, 0.08) 75%, transparent 78%, transparent 100%);
  --beam-bloom-spike: conic-gradient(from var(--beam-angle),
      transparent 0%, transparent 58%,
      rgba(255, 214, 160, 0.02) 62%, rgba(255, 206, 146, 0.06) 65%,
      rgba(255, 198, 138, 0.14) 67%, rgba(255, 194, 132, 0.30) 69%,
      rgba(255, 190, 128, 0.52) 70%, rgba(255, 190, 128, 0.52) 70.5%,
      rgba(255, 194, 132, 0.30) 71.5%, rgba(255, 198, 138, 0.14) 73%,
      rgba(255, 206, 146, 0.06) 75%, rgba(255, 214, 160, 0.02) 78%,
      transparent 82%);

  /* 左右内距提到变量：placeholder 的假光标要跟真输入文字左对齐，
     两处各写一遍 22px 迟早会分叉。 */
  --ask-pad-l: 22px;
  --ask-pad-r: 12px;

  background: linear-gradient(
    180deg,
    rgba(14, 9, 26, 0.36),
    rgba(7, 5, 16, 0.66)
  );
  -webkit-backdrop-filter: blur(23px) saturate(0.75) brightness(0.54);
  backdrop-filter: blur(23px) saturate(0.75) brightness(0.54);
  box-shadow:
    inset 0 1px 0 rgba(255, 232, 204, 0.14),
    inset 0 -1px 0 rgba(255, 232, 204, 0.03),
    inset 0 0 26px -12px rgba(196, 168, 255, 0.28),
    0 22px 56px -24px rgba(0, 0, 0, 0.86);

  font-family: "PingFang SC", -apple-system, BlinkMacSystemFont,
               "Helvetica Neue", "Hiragino Sans GB", sans-serif;

  /* 插入即有、但先不显形：原版的 loader 正在收束，此刻冒出一块亮边会抢戏。
     skin.js 在 load 之后才加 is-ready。 */
  opacity: 0;
  transition: opacity 0.9s ease, box-shadow 0.4s ease;
}
#echo-ask.is-ready { opacity: 1; }

/* 聚焦态：底色压实一档（字更压得住）。
   ⚠️ 现在聚焦时那圈流光已经**整层熄掉**（`[data-off]`，见下），所以"让已有的
   光变亮"这条不再成立，也不需要再加焦点环 —— 框的"醒了"由两件事表达：
   玻璃变实、以及那颗到处跑的亮点消失（安静下来本身就是信号）。
   毛玻璃之后 focus 只微调 brightness（.54→.46），
   不回到实心 —— 一聚焦就变成塑料片，整颗框的厚度就没了。 */
#echo-ask:focus-within {
  -webkit-backdrop-filter: blur(25px) saturate(0.78) brightness(0.46);
  backdrop-filter: blur(25px) saturate(0.78) brightness(0.46);
}

/* ── 磨砂颗粒 ────────────────────────────────────────────────────────
   一层 160×160 的 feTurbulence 噪点，灰度化后 overlay 在玻璃面上。
   opacity 只给 .045：磨砂是"表面有极细的砂"，不是"撒了一把盐"；
   超过 .08 就会看到脏点，尤其在球最亮的位置。
   挂在 form 上而不是 #echo-ask 上：beam 已经吃掉了 ::before/::after 两层，
   #echo-ask 没有伪元素可用了。form 的盒子正好等于整个框，z-index 4 也
   正好压在光束之上 —— 颗粒盖住那 1px 描边反而对，同一块玻璃。 */
#echo-ask .echo-ask__form::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  opacity: 0.045;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* 兜底：浏览器不支持 mask-composite 时 beam.css 会整层不画（有意为之 ——
   三层遮罩解成实心彩色是最常见的线上事故）。这时补一条静态细边，
   别让输入框失去边界、悬在球上。 */
#echo-ask.is-flat {
  border: 1px solid rgba(255, 226, 200, 0.2);
  box-shadow: 0 20px 54px -22px rgba(0, 0, 0, 0.86),
              inset 0 0 22px -6px rgba(190, 150, 255, 0.3);
}

/* ── 沉浸输入态：熄掉那圈流光，换一条静止的 1px 暖白细边 ──────────────
   为什么要留边：玻璃是半透明的，边一撤就变成一块糊在球上的污渍，人找不到
   输入区的范围。0.18 这一档是"看得见但不抢"—— 比 .is-flat 的 0.2 再低一点点，
   因为此时框里正有人在打字，边不该比字更亮。实测：边线 L≈58 / 框内 L≈25，
   背景亮时靠明暗差收边，背景暗时靠这条线收边。

   熄的方式走 beam 自己的 `data-off`：它带 0.5s 淡出、`--beam-angle` 继续走，
   不是 display:none 那种硬切（硬切会在眼角闪一下）。 */
#echo-ask[data-off] {
  box-shadow:
    inset 0 0 0 1px rgba(255, 226, 200, 0.18),
    inset 0 1px 0 rgba(255, 232, 204, 0.12),
    0 22px 56px -24px rgba(0, 0, 0, 0.86);
}

/* beam 的三层伪元素带 z-index（1/2/3），流内内容默认会被 on 态的描边盖住。
   内容层必须显式抬到 bloom(3) 之上。 */
#echo-ask .echo-ask__form {
  position: relative;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 64px;
  margin: 0;
  padding: 0 var(--ask-pad-r) 0 var(--ask-pad-l);
}

#echo-ask .echo-ask__input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: #f2edff;
  font-family: inherit;
  font-size: 15px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.02em;
  caret-color: #c9b3ff;
  -webkit-appearance: none;
  appearance: none;
}
/* ── 打字机 placeholder ──────────────────────────────────────────────
   原生 placeholder 做不了逐字出现（它是一整块文字，没有"当前打到第几个字"
   这个中间态）。所以这里把它彻底藏掉，用一颗叠在输入框上的假光标承载文案，
   文字内容由 skin.js 一个字一个字喂进去。
   ⚠ 藏掉原生 placeholder 而不是删属性：删了以后 aria 上没有线索，
     藏掉颜色既保留可访问性注解、又不会和假光标重影。 */
#echo-ask .echo-ask__input::placeholder {
  color: transparent;
}
#echo-ask .echo-ask__input::-webkit-search-cancel-button { display: none; }

#echo-ask .echo-ask__ph {
  position: absolute;
  top: 50%;
  left: var(--ask-pad-l);
  right: calc(var(--ask-pad-r) + 50px);   /* 让开 40px 发送键 + 10px gap */
  transform: translateY(-50%);
  pointer-events: none;                   /* 点在字上也要落到 input 上 */
  overflow: hidden;
  white-space: nowrap;
  /* 0.5 而不是 0.4：玻璃比原来那块 rgba(.82) 的实心底亮（箱内 L 19.9→34.8），
     同一个 0.4 压不住，实测对比只有 3.2:1。0.5 约 4.4:1，是"比正文弱一档、
     但仍然清楚"的位置 —— 它是提示语，不该跟正文抢，但也不能糊。 */
  color: rgba(242, 237, 255, 0.5);
  font-family: inherit;
  font-size: 15px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.04em;

  /* 淡入淡出只给 opacity：文字长度在变，任何位移/尺寸过渡都会抖。 */
  opacity: 0;
  transition: opacity 0.22s ease;
}
#echo-ask .echo-ask__ph.is-on { opacity: 1; }

/* 假光标。1px 竖条 + 硬切闪烁（steps，不做渐变）—— 真光标就是硬切的，
   渐变闪会像呼吸灯，反而假。 */
#echo-ask .echo-ask__ph::after {
  content: "";
  display: inline-block;
  width: 1px;
  height: 14px;
  margin-left: 3px;
  vertical-align: -2px;
  background: currentColor;
  animation: echo-ph-caret 1.06s steps(1, end) infinite;
}
@keyframes echo-ph-caret {
  0%, 49.9% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

#echo-ask .echo-ask__go {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin: 0;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
  transition: background 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}
#echo-ask .echo-ask__go:hover {
  background: rgba(255, 255, 255, 0.13);
  border-color: rgba(255, 255, 255, 0.3);
  color: #fff;
}
#echo-ask .echo-ask__go:active { transform: translateY(1px); }
#echo-ask .echo-ask__go svg { display: block; }

/* 只给读屏看 */
#echo-ask .echo-ask__sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ── 中宽让位 ────────────────────────────────────────────────────────
   桌面 1440 是唯一"天生不打架"的宽度（实测 actions 底 704 / 输入框顶 723，
   中间 19px 是空的）。宽度一收，左侧文字换行变多、整块变高，而章节是
   bottom 对齐 —— 它的底边反而上移得慢，于是压到输入框上：
     800  → 重叠 14px
     1024 → 只剩 3px
     1280 → 13px
   输入框的 bottom 原本只跟视口高挂钩，管不了这件事，所以按宽度补三档。
   同步撤掉 telemetry：让位之后输入框会贴到它那条线上。 */
@media (max-width: 1300px) {
  main.matter-reel .matter-reel__telemetry { display: none !important; }
  #echo-ask { bottom: 88px; }
}
@media (max-width: 1100px) {
  #echo-ask { bottom: 76px; }
}
@media (max-width: 900px) {
  #echo-ask { bottom: 58px; }
}

/* ── 窄屏 ────────────────────────────────────────────────────────────
   原版在 600px 以下把章节的 bottom 抬到 max(92px, safe+58px)。输入框如果
   也用同一个区间就会压上去，所以窄屏把框压到更贴近底部、并缩一档高度。 */
@media (max-width: 600px) {
  /* 窄屏的账，实测 390×844 算过两遍：
       origin 章是 bottom 对齐的，底边恒定在 732（藏 actions 也一样 ——
       下面的元素会补位上来，重叠依旧是 10px）。
       ⇒ 唯一的变量是输入框自己的 bottom。
     bottom 64 → 顶边 722，压住章节底 10px；
     bottom 44 → 顶边 742，只有 10px 间隙，太挤；
     bottom 36 → 顶边 750，间隙 18px ✓，但底边 808 会撞 telemetry(803)。
     ⇒ 移动端把 telemetry 一并撤掉：那行是 原作者的职业年数与粒子数，本来就是别人的数据，Echo 上迟早要换。 */
  main.matter-reel .matter-reel__telemetry {
    display: none !important;
  }

  #echo-ask {
    width: calc(100vw - 40px);
    bottom: max(36px, env(safe-area-inset-bottom) + 20px);
    --beam-scale: 1.1;
    --beam-radius: 16px;
    --ask-pad-l: 18px;
    --ask-pad-r: 10px;
  }
  #echo-ask .echo-ask__form {
    height: 58px;
    padding: 0 var(--ask-pad-r) 0 var(--ask-pad-l);
  }
  #echo-ask .echo-ask__input { font-size: 16px; }   /* 低于 16px 时 iOS 聚焦会自动放大整页 */
  #echo-ask .echo-ask__ph { font-size: 16px; }      /* 假光标必须跟真文字同字号，否则可见跳位 */
  #echo-ask .echo-ask__go { width: 36px; height: 36px; border-radius: 10px; }
}

/* 动效敏感：把流光停在固定角度（beam.css 里已有这条，这里只是把
   输入框自己的过渡也停掉，保持一致）。 */
@media (prefers-reduced-motion: reduce) {
  #echo-ask .echo-ask__go { transition: none; }
  /* 打字机整体降级成"一句话常驻"（文案由 skin.js 直接铺满，不做逐字），
     光标也停成一个常亮的竖条。 */
  #echo-ask .echo-ask__ph { transition: none; }
  #echo-ask .echo-ask__ph::after { animation: none; opacity: 1; }
}

/* ══════════════════════════════════════════════════════════════════════
   ② 对话记录（访谈录，不是气泡）
   ──────────────────────────────────────────────────────────────────────
   为什么不做成聊天气泡：这一屏的调性是"展览"，气泡会立刻把它拉成
   一个 IM 应用。也想过把回答直接铺在球上（那才是"对话是主角"的终态），
   但球中心接近纯白、04 章还是一条白流光带 —— 文字压上去会糊。
   所以先给一块**比输入框更深的玻璃**，让文字有地方站；等排版定稿
   再决定要不要撤掉这层、让字直接落在背景上。

   位置 = 输入框上方。两者共用同一套 bottom 分档（下面每个断点都同步
   改了），因为原版在窄屏会把章节底边抬高、输入框跟着挪，如果这块
   只写一个固定值，某一档就会压到框上去。
   ══════════════════════════════════════════════════════════════════════ */
#echo-thread {
  position: fixed;
  left: 50%;
  bottom: calc(clamp(96px, 12.6vh, 118px) + 86px);   /* 64 框高 + 22 间隙 */
  transform: translate(-50%, 6px);
  z-index: 49;

  width: min(620px, calc(100vw - 48px));
  max-height: min(46vh, 400px);
  /* 必须显式写：原版没有全局 border-box，而这块有 24px 内距 + 1px 边。
     不写的话 min(620px, …) 只是内容宽，外框实测 670 —— 比输入框宽 50px，
     两块玻璃对不齐，一眼就能看出来。 */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;

  padding: 16px 24px 20px;
  border-radius: 18px;
  border: 1px solid rgba(255, 232, 204, 0.1);

  /* ── 毛玻璃面板（10-25 11:1x Han：这里的背景也要毛玻璃）────────────
     原来是 0.78→0.90 的厚色层，等于一块实心深色卡片：blur 是有的，但
     0.78 的不透明度把它全盖住了，背后的球/流光带只剩下一点点亮度差
     （实测面板内三点亮度极差 0.011 —— 肉眼读作"纯色"）。
     现在把色层降到 0.50→0.62，让模糊后的背景真正透出来（极差 0.079，
     约等于背景亮度的 45%）。backdrop 的 brightness 同时从 0.52 抬到
     0.72：压得太狠时透出来也是黑的，玻璃就白做了。
     透明度是量过的：0.40 那档（极差 0.148）已经跟背景几乎一样亮，
     读作"半透明窗"而不是玻璃，字也开始被亮部吃 —— 停在 0.50。 */
  background: linear-gradient(
    180deg,
    rgba(11, 8, 22, 0.5),
    rgba(7, 5, 16, 0.62)
  );
  -webkit-backdrop-filter: blur(28px) saturate(0.85) brightness(0.72);
  backdrop-filter: blur(28px) saturate(0.85) brightness(0.72);
  box-shadow:
    inset 0 1px 0 rgba(255, 232, 204, 0.1),
    0 30px 64px -32px rgba(0, 0, 0, 0.9);

  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, transform 0.3s ease, visibility 0s linear 0.3s;
}

#echo-thread.is-on {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
  transition: opacity 0.3s ease, transform 0.3s ease, visibility 0s;
}

#echo-thread .echo-thread__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 0 0 auto;
  padding-bottom: 12px;
  margin-bottom: 14px;
  border-bottom: 1px solid rgba(255, 232, 204, 0.08);
}

#echo-thread .echo-thread__tag {
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* 10-25 11:1x Han：这股暗黄提亮一点点。只动 alpha（0.42→0.56），
     色相 (255,232,204) 一点没碰 —— 换色相就成了另一个品牌色，
     而这里要的只是"看得见是黄铜，不是脏灰"。 */
  color: rgba(255, 232, 204, 0.56);
}

/* 标题栏右侧两颗动作（10-25 11:31 Han 选甲）：
   「新对话」只在有内容时出现，是**唯一**会清空的动作；
   × 改成"收起"—— 语义变了，但视觉上仍是"关掉这块玻璃"，不需要重新学。 */
#echo-thread .echo-thread__acts {
  display: flex;
  align-items: center;
  gap: 14px;
}

#echo-thread .echo-thread__new {
  appearance: none;
  border: 0;
  background: none;
  padding: 2px 0;
  margin: 0;
  cursor: pointer;
  font: inherit;
  font-size: 10.5px;
  letter-spacing: 0.12em;
  color: rgba(255, 232, 204, 0.45);
  transition: color 0.2s;
}
#echo-thread .echo-thread__new:hover { color: rgba(255, 232, 204, 0.9); }
#echo-thread .echo-thread__new:focus-visible {
  outline: none;
  color: rgba(255, 232, 204, 0.9);
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* hidden 在 flex 子项上有时会被 display 规则盖掉，写死一次 */
#echo-thread .echo-thread__new[hidden] { display: none; }

#echo-thread .echo-thread__x {
  appearance: none;
  border: 0;
  background: none;
  padding: 2px 4px;
  margin: -2px -4px -2px 0;
  cursor: pointer;
  font: inherit;
  font-size: 17px;
  line-height: 1;
  color: rgba(242, 237, 255, 0.34);
  transition: color 0.2s;
}
#echo-thread .echo-thread__x:hover { color: rgba(242, 237, 255, 0.85); }

#echo-thread .echo-thread__body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 232, 204, 0.18) transparent;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 10px);
  mask-image: linear-gradient(180deg, transparent 0, #000 10px);
}
#echo-thread .echo-thread__body::-webkit-scrollbar { width: 4px; }
#echo-thread .echo-thread__body::-webkit-scrollbar-thumb {
  background: rgba(255, 232, 204, 0.18);
  border-radius: 2px;
}

/* 一问一答为一"折"。问题缩一号并挂一道竖线 —— 竖线是访谈录的本子，
   不是引用块装饰：它标记"谁在问"，且让问题的左缘和回答的左缘对齐
   （问题缩进 12px 后与回答同一条左基线）。 */
#echo-thread .echo-turn + .echo-turn {
  margin-top: 24px;
  padding-top: 22px;
  border-top: 1px solid rgba(255, 232, 204, 0.07);
}

#echo-thread .echo-turn__q {
  margin: 0;
  padding-left: 12px;
  border-left: 1px solid rgba(255, 190, 140, 0.45);
  font-size: 12.5px;
  line-height: 1.6;
  letter-spacing: 0.02em;
  color: rgba(255, 232, 204, 0.55);
}

#echo-thread .echo-turn__a {
  margin: 12px 0 0;
  font-size: 15.5px;
  line-height: 1.85;
  letter-spacing: 0.01em;
  color: rgba(245, 240, 255, 0.92);
  white-space: pre-wrap;      /* 模型偶尔会给换行，别把它压成一行 */
  word-break: break-word;
}

#echo-thread .echo-turn__a.is-error { color: rgba(255, 168, 152, 0.92); }

/* 光标：等第一个字和正在吐字是**同一个**视觉 —— 一根 1px 竖条硬切闪。
   好处是"等待"与"正在写"之间没有跳变，人感觉不到阶段切换；
   也顺便说明一件事：这里没有伪造的"思考中"文案，它就是在等下一条数据。 */
#echo-thread .echo-turn__a.is-live::after {
  content: '';
  display: inline-block;
  width: 1px;
  height: 1em;
  margin-left: 2px;
  vertical-align: -2px;
  background: currentColor;
  opacity: 0.7;
  animation: echo-thread-caret 1.05s steps(1) infinite;
}
@keyframes echo-thread-caret {
  0%, 49% { opacity: 0.7; }
  50%, 100% { opacity: 0; }
}

/* 输入框上方那块玻璃和对话记录会贴在一起，留 22px 见上面的 bottom。 */

@media (max-width: 1300px) {
  #echo-thread { bottom: calc(88px + 86px); }
}
@media (max-width: 1100px) {
  #echo-thread { bottom: calc(76px + 86px); }
}
@media (max-width: 900px) {
  #echo-thread { bottom: calc(58px + 86px); }
}
@media (max-width: 600px) {
  #echo-thread {
    width: calc(100vw - 40px);
    bottom: calc(max(36px, env(safe-area-inset-bottom) + 20px) + 72px);
    max-height: 40vh;
    padding: 14px 18px 16px;
    border-radius: 16px;
  }
  #echo-thread .echo-turn__a { font-size: 15px; line-height: 1.8; }
}

@media (prefers-reduced-motion: reduce) {
  #echo-thread { transition: none; }
  #echo-thread .echo-turn__a.is-live::after { animation: none; opacity: 0.7; }
}

/* ══════════════════════════════════════════════════════════════════════
   ③ 首屏文案层 · 照 Figma `hero` 帧（138:5，1920×1080 原稿）还原
   ──────────────────────────────────────────────────────────────────────
   设计稿给的是**几个位置**，不是一段流式版面，所以这层用绝对定位照抄
   （数值 = 2026-10-23 16:1x 更新后那版 138:5，Han 收紧了行距、缩小了 logo）：

     左上  圆钮 30×30      x=80    y=51
     左上  The Echo 字标   x=122   y=58    96×16
     左上  音乐提示        x=80    y=93    12/24 · 字距 .24 · 宽 414
     左中  标题 三行       x=80    y=346   46px / 行距 70px（旧版 78）
     左中  说明            x=80    y=590   16/24 · 字距 .32 · 宽 414
     左下  10 YEARS…       x=80.2  顶 1008 166×9.2
     右下  Experience…     右 81.4 顶 1008 268.5×11.9

   三条必须解释的决定：

   ① **尺寸一律换成 vw，不写死 px。** 稿子是 1920 宽的，而"还原"要的是
      **比例**一致：46px 在 1920 上占 2.40% 宽，在 1440 屏上也该占 2.40%
      （= 34.5px），而不是仍然是 46px。写死 px 的版本量过一遍：文字块占
      屏宽 28.8%，而稿子里是 21.6% —— 大了三分之一，肉眼就是"没还原"。
      `clamp()` 里那个 px 是地板（标题不低于 32、说明不低于 14 —— 13px 在实际屏幕上偏小，14 是可读下限），
      防止 1200 以下的屏把字缩成标点。
      例外只有两颗：**圆钮 30px 与字号无关**（控件，且要保点击面积），
      **音乐提示**给了 11px 地板（9px 在这个尺寸上不成立）。

   ② **行距 70 不是 64.45。** Figma 给的 lineHeightPx 是 64.4547，但那三行
      在稿子里是**三个独立文本节点**，节点顶边 346 / 416 / 486 —— 实际
      视觉行距是 70（= 1.5217em）。行盒变高会让字心整体下移
      (70−64.45)/2 = 2.77px，所以整块用 −0.0603em 补回去，
      第一行的字心才落在设计的位置上。

   ③ **三个字标是矢量轮廓，不是字体。** 原作者那套字体不可得，Figma 里
      已经把它们 outline 成 path 了 —— 所以直接导出 SVG 用（_skin/copy/），
      这是唯一能 100% 还原字形的办法；用任何系统字体重排都是"像，但不是
      那个"。标题与说明反过来保留成**真文本**：可选中、可被读屏与搜索读到。

   ⚠ 变量写在 :root 上而不是 #echo-hero 上 —— 因为左上那颗圆形菜单钮
     （.portal-menu，原版自己的元素）也得吃同一套坐标，它不在这一层里。
   ══════════════════════════════════════════════════════════════════════ */
:root {
  --hero-pad: clamp(24px, 4.1667vw, 80px);          /* 80 @1920 */
  --hero-mark: 30px;                                /* 圆形菜单钮直径 30（稿 144:89） */
  --hero-mark-top: clamp(20px, 4.7222vh, 51px);     /* 钮顶边 51 */
  --hero-title-top: clamp(190px, 32.037vh, 346px);  /* 标题顶边 346 */
  --hero-bottom: clamp(26px, 5.8vh, 63px);          /* 底部两条小字 62.8 */
  --hero-gap: 0.625vw;                              /* 钮右缘 110 → 字标 122 = 12 @1920 */
  /* 紫：圆钮内圈（稿里 144:89 是灰色占位，Han 指定改紫）。
     ⚠ 写成逗号三元组，不要写成 `180 120 255` —— 后者配 `rgba(var(--x), .6)`
     会展开成 `rgba(180 120 255, .6)`（空格与逗号混用）＝整条声明非法，
     边框会静默变成 currentColor 黑。踩过一次。 */
  --echo-accent: 180, 120, 255;
}

#echo-hero {
  position: fixed;
  inset: 0;
  z-index: 20;                 /* 在画布之上、输入框(50)与菜单(1200)之下 */
  pointer-events: none;        /* 这一层只给人看，不吃鼠标 */
  color: #fff;
  font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
               system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

#echo-hero .echo-hero__text {
  position: absolute;
  left: var(--hero-pad);
  top: var(--hero-title-top);
  max-width: min(46vw, 620px);
}

#echo-hero .echo-hero__title {
  margin: -0.0603em 0 0;       /* 见上面 ②（行距 70 的补正） */
  font-size: clamp(32px, 2.3958vw, 46px);
  /* 原版有一条 `h1,h2,h3,h4,h5,h6{font-weight:100!important}`，不加
     !important 会把它压成 Ultralight，和稿子的 200（Thin）差一档。 */
  font-weight: 200 !important;
  line-height: 1.5217;         /* 70 / 46 */
  letter-spacing: 0;
  /* 原版还有一条 `h1{text-transform:uppercase}` —— 不写 none，
     "Echo" 会被渲染成 "ECHO"。 */
  text-transform: none;
  color: #fff;
}

#echo-hero .echo-hero__note {
  margin: 1.9151vw 0 0;        /* 590 − (343.23 + 210) = 36.77 @1920 */
  width: 21.5625vw;            /* 414 @1920 */
  max-width: 100%;
  font-size: clamp(14px, 0.8333vw, 16px);
  font-weight: 200;
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-transform: none;
  color: #fff;
  /* 稿子的背景是设计里那颗球，左缘很淡；原版的球更大更亮，13–16px 白字
     压上去有几处只剩 1.1:1。设计稿不需要阴影，这里需要 —— 只在字形
     周围压一圈极淡的暗，深色底上看不出，亮处才有可读性。 */
  text-shadow: 0 0 14px rgba(8, 3, 20, 0.72), 0 1px 2px rgba(8, 3, 20, 0.5);
}

/* 字标顶边不是写死的 58，而是**跟圆钮同心**：31.5 是把 16px 高的字标
   居中到 30px 钮上的偏移。宽高比固定，真稿是 95.3×16。 */
#echo-hero .echo-hero__wordmark {
  position: absolute;
  left: calc(var(--hero-pad) + var(--hero-mark) + var(--hero-gap));
  top: calc(var(--hero-mark-top) + (var(--hero-mark) - 0.8333vw) / 2);
  width: 4.9635vw;             /* 95.3 @1920 */
  height: auto;
}

/* ── 音乐提示：logo 下方那一行，**可点** ─────────────────────────────
   稿子只给了位置与字号，没给"关闭后变成什么"—— 关闭后换一句"点击可以
   重新开启"，让它是个双向开关，而不是一次性动作。
   整层 #echo-hero 是 pointer-events:none（它只是给人看的），
   所以这一颗必须单独开 auto，否则点不到。 */
#echo-hero .echo-hero__hint {
  position: absolute;
  left: var(--hero-pad);
  top: calc(var(--hero-mark-top) + var(--hero-mark) + var(--hero-gap));
  width: 21.5625vw;
  max-width: calc(100vw - 2 * var(--hero-pad));
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: clamp(11px, 0.625vw, 12px);   /* 12 @1920；9px 不成立，11 是地板 */
  font-weight: 200;
  line-height: 2;                          /* 24 / 12 */
  letter-spacing: 0.02em;
  text-align: left;
  color: #fff;
  cursor: pointer;
  pointer-events: auto;
  transition: color 0.2s ease, opacity 0.2s ease;
  text-shadow: 0 0 12px rgba(8, 3, 20, 0.6);
}
#echo-hero .echo-hero__hint:hover,
#echo-hero .echo-hero__hint:focus-visible {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
}
#echo-hero .echo-hero__hint:focus-visible { outline: none; }
#echo-hero .echo-hero__hint.is-off { color: rgba(255, 255, 255, 0.62); }
/* 还没开始的那一态：它只是**说明**，不是开关（音乐由点输入框触发）。
   所以不吃鼠标、不显示手形、也不进 Tab 序列 —— 一个点不动的按钮是骗人。 */
#echo-hero .echo-hero__hint.is-idle {
  color: rgba(255, 255, 255, 0.5);
  pointer-events: none;
  cursor: default;
}

#echo-hero .echo-hero__foot {
  position: absolute;
  bottom: var(--hero-bottom);
  height: auto;
}
#echo-hero .echo-hero__foot--l { left: var(--hero-pad); width: 8.6458vw; }   /* 166 */
#echo-hero .echo-hero__foot--r { right: var(--hero-pad); width: 14.0104vw; } /* 269 */

/* ── 文字渐现的单元（word-bloom.js 把每个词/字包成 .wb-u）────────────
   出处：设计沉淀 L3-motion / word-bloom（07）。

   inline-block 是硬要求：`transform: scale()` 对**纯 inline 盒不生效**
   （字不缩），`filter` 会作用到整行而不是这一颗字。transform-origin 居中
   才对得上原站"从自身中心浮起来"的观感。

   初始的看不见**不写在 CSS 里**，由 word-bloom.js 在挂载时写内联样式 ——
   这样脚本一旦 404，文字照常可读（宁可没有动画，不能没有字）。 */
#echo-hero .wb-u {
  display: inline-block;
  transform-origin: 50% 50%;
  /* 🔴 必须显式写死白色。原版有一条给 h1/h2 内 span 上色的规则（c2 青，
     实测 rgb(85,239,228)），特异性高于 `.echo-hero__title` 上的 color，
     所以标题自己写 #fff 也没用 —— 字被拆成 span 之后整行都变青。
     17:5x Han：标题和描述去掉颜色，白色就行。 */
  color: #fff;
}

/* 底部两条小字：细长条 SVG，逐字模糊没有意义，用 CSS 过渡就够。
   出场比标题晚 0.16s（让第一行先落定）；退场时立刻走 —— 收的时候
   它得抢在标题前面消失，否则会看见"小字还在，大字已经没了"。 */
#echo-hero .echo-hero__foot {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.62s ease,
              transform 0.62s cubic-bezier(0.22, 0.61, 0.36, 1);
}
#echo-hero .echo-hero__foot.is-in {
  opacity: 1;
  transform: none;
  transition-delay: 0.16s;
}
@media (prefers-reduced-motion: reduce) {
  #echo-hero .echo-hero__foot { transition: none; }
}

/* ── 左上那颗圆形菜单钮：从右上搬到左上，和字标组成一个 lockup ──────
   原版把它固定在右上（--portal-edge / right），这里镜像到左边。
   钮收到 30px（稿 144:89 就是 30×30）：38px 会压到字标，而稿子里
   钮右缘 110 → 字标左缘 122，间距 12。
   **位置与尺寸变了，交互一点没变** —— 点击展开菜单的逻辑在原版 React 里，
   我们只动 CSS。

   ⚠ 钮的尺寸不跟 vw 缩：它是控件不是字号，18px 的点击目标在 1440 上
     已经不合手，30 是这条线的下限。
   ⚠ 原版窄屏那套写死了 `.portal-menu__trigger{width:38px}`，我们的
     --portal-size 会被它盖掉 —— 所以下面用两个类名压回去。

   ⚠ "MENU" 那行小字必须藏（它在钮的左侧，搬到左边缘后会跑出屏幕），
     而且设计稿在这个位置放的是 "The Echo" 字标，不该两个都留。 */
.portal-menu {
  --portal-size: var(--hero-mark);
  --portal-radius: calc(var(--hero-mark) / 2);
  left: var(--hero-pad);
  right: auto;
  top: var(--hero-mark-top);
}
.portal-menu .portal-menu__trigger {
  width: var(--portal-size);
  height: var(--portal-size);
  /* 原版这里是青绿（85,239,228）—— 整屏是紫金，青绿是唯一一处撞色。
     连同里面那颗 dot 的辉光一起换成紫。 */
  box-shadow: inset 0 0 0 1px rgba(var(--echo-accent), 0.10),
              0 0 22px rgba(var(--echo-accent), 0.10);
}
.portal-menu .portal-menu__trigger:hover {
  box-shadow: inset 0 0 0 1px rgba(var(--echo-accent), 0.20),
              0 0 28px rgba(var(--echo-accent), 0.26);
}
/* 内圈：`::after` 是贴着中心 dot 的那一圈，`::before` 是悬停/展开时才亮
   的外一圈 —— 两圈都改紫。
   ⚠ inset 不能照抄原版：它是按 42px 的钮写的（13px）。钮缩到 30 之后
     13px 只剩 4px 的圈，被 5px 的 dot 整个盖住 —— 非要按比例重算不可。
     现在 14px / 24px 两级，和 30px 外框、5px 心构成四层同心。 */
.portal-menu .portal-menu__trigger::after {
  inset: 8px;
  border-color: rgba(var(--echo-accent), 0.82);
}
.portal-menu .portal-menu__trigger::before {
  inset: 3px;
  border-color: rgba(var(--echo-accent), 0.9);
}
.portal-menu .portal-menu__trigger-core {
  box-shadow: 0 0 10px rgba(236, 226, 255, 0.55),
              0 0 18px rgba(var(--echo-accent), 0.55);
}
.portal-menu__label { display: none !important; }

/* 菜单展开后的两行抬头/落款是原作者的署名与所在地 —— 不属于这一屏，藏掉。 */
.portal-menu__meta,
.portal-menu__footer { display: none !important; }

/* 菜单展开时把文案层收掉：原版的 overlay 是 rgba(3,3,4,.97)，不是纯黑，
   底下这层字会以 3% 的强度透上来（截图里能看见"你好，这是 Echo"的鬼影）。
   不写 JS，用 :has 跟着原版那个 class 走。 */
body:has(.portal-menu--open) #echo-hero {
  opacity: 0;
  transition: opacity 0.18s ease;
}
/* 跟着淡出的还有那颗可点的音乐提示 —— 它 pointer-events 是 auto，
   不关掉的话会在全屏菜单上"隔空"接走一次点击。 */
body:has(.portal-menu--open) #echo-hero .echo-hero__hint { pointer-events: none; }

/* ── 清场：原版自己的那几行首屏文字全部撤掉 ──────────────────────────
     masthead  : 字标 + 职位 + 右上技术栈
     skip      : SKIP EXPERIENCE / VIEW ALL WORK
     telemetry : 职业年数 —— 粒子总数
   三条都是原作者的履历与体量，设计稿里没有它们的位置。
   （loader「Allocating persistent matter」不动：那是加载态，不是首屏文案。） */
main.matter-reel .matter-reel__masthead,
main.matter-reel .matter-reel__skip,
main.matter-reel .matter-reel__telemetry {
  display: none !important;
}

/* ── 窄屏 ─────────────────────────────────────────────────────────────
   设计稿只有 1920 一稿，窄屏按同一套层级重排，三处必须改：
     ① 字号不跟 vw 走（2.4vw 在 390 上是 9px），直接给 px；
     ② 字标给固定 px（4.96vw 在 390 上只有 19px，等于没有）；
     ③ 底部两条 9px 的矢量小字撤掉（390 宽下等于看不见，只是脏点）。 */
@media (max-width: 600px) {
  :root {
    --hero-pad: 24px;
    --hero-mark: 30px;
    --hero-mark-top: 24px;
    --hero-gap: 10px;
    /* 窄屏的球是**居中且更小**的，不像桌面那样让出左侧 —— 照桌面 1/3
       的位置排，整块字正好压在球心的白上（实测：说明那几行底下亮度
       到 200+，白字等于消失）。所以窄屏把文案整块提到球的上缘之上。 */
    --hero-title-top: 132px;
    --hero-bottom: 26px;
  }
  #echo-hero .echo-hero__title {
    margin-top: 0;
    font-size: 30px;
    line-height: 46px;
  }
  #echo-hero .echo-hero__note {
    margin-top: 24px;
    width: 100%;
    font-size: 14px;
    line-height: 22px;
    letter-spacing: 0.02em;
  }
  #echo-hero .echo-hero__text { max-width: calc(100vw - 48px); }
  /* 钮高 30、字标高 15（90 × 16/96）：居中偏移用 px 算，别用桌面那个 vw 式子。 */
  #echo-hero .echo-hero__wordmark {
    top: calc(var(--hero-mark-top) + (var(--hero-mark) - 15px) / 2);
    width: 90px;
  }
  /* 提示行窄屏给满宽：21.56vw 在 390 上只有 84px，会折成四行。 */
  #echo-hero .echo-hero__hint {
    width: calc(100vw - 48px);
    font-size: 12px;
    line-height: 20px;
  }
  #echo-hero .echo-hero__foot { display: none; }

  /* 窄屏还得压一层暗角。理由不是审美，是读数：球在窄屏上是**居中**的，
     文案挪到球上方之后，说明那三行底下仍有 p90=224 的亮点 —— 14px 白字
     压上去等于没有。这层渐变只在字后面从 0.86 淡到 0，深色底上看不出
     边界，亮处才有对比。桌面不需要（球给左边让了位置）。 */
  #echo-hero::before {
    content: "";
    position: absolute;
    left: 0; right: 0; top: 0;
    height: 62vh;
    z-index: -1;
    background: linear-gradient(180deg,
      rgba(3, 3, 4, 0.86) 0%,
      rgba(3, 3, 4, 0.62) 45%,
      rgba(3, 3, 4, 0) 100%);
  }
}

/* ══════════════════════════════════════════════════════════════════════
   DESIGN THINKING 页（10-25 · Figma 148-90）
   ──────────────────────────────────────────────────────────────────────
   这块**替代**原版那五行导航（HOME/WORK/ABOUT/COMMUNITY/CONTACT），
   不是加在它旁边 —— 所以第一步是 .portal-menu__links 整个 hidden。

   🔴 网格背景不在 links 里。它在 .portal-menu__overlay 的四层
   background 上（横线 + 竖线，各 72px 一格）⇒ 隐藏 links 之后网格
   原样留着，这块一个字都没碰。开关逻辑仍是原版 React 的
   .portal-menu--open，这里只加样式 + 一个 class 监听。

   坐标全部来自稿子的 absoluteBoundingBox，**已减去帧原点 (2220,0)**：
     横线    x=410  y=179  w=1100   stroke .5 @ 40% 白
     标题    x=450  y=199  w=1060   68 / 95.28 / w200
     段 1    x=450  y=334  w=1060   18 / 30   （2 行）
     段 2    x=450  y=434  w=1060   18 / 30   （1 行）
     段 3    x=450  y=504  w=1060   18 / 30   （3 行）
     段 4    x=450  y=634  w=1060   18 / 30   （2 行）
   段间垂直规律：每段底 + 40 = 下一段顶（394→434→464→504→594→634）。

   为什么用「1920×1080 固定画布 + 整体 scale」而不是 vw/clamp：
   稿子是 16:9 的整屏排版，垂直位置不是居中（内容块中心 y≈436，
   视口中心 540，偏上 104px）。用 vw 会把这个偏移压扁，用 vh 又会让
   字号跟着视口高度涨。只有固定画布能保证任何窗口下**比例一模一样**。
   scale 由 skin.js 的 fitDT() 算：min(vw/1920, vh/1080)。
   ══════════════════════════════════════════════════════════════════════ */

.portal-menu__links { display: none !important; }

/* 底色：稿子是 SOLID #141034。原版那层 rgba(3,3,4,.97) 只有 97% 不透明，
   底下原版自己的章节字（"//01 Technical depth." 那一串）会以 3% 透上来，
   在近黑底上能看出字影 —— 之前首屏是靠把 #echo-hero 设 opacity:0 绕过的，
   这里躲不开（透的是原版正文，不是我们的层）。
   ⚠ 只换颜色，**网格线（下面两个 linear-gradient 层）与 72px 间距原样保留**。 */
.portal-menu__overlay { background-color: #141034; }

#echo-dt {
  position: fixed;
  inset: 0;
  z-index: 1;                 /* 原版 overlay 没写 z-index（=auto），
                                 trigger 是 2 —— 这块正好压在两者之间：
                                 盖住 panel，又不挡左上那颗菜单钮 */
  /* 🔴 显隐**不由 CSS 硬切**。原先写 :has(--open) 会让这块在点击那一帧
     就 visibility:hidden，撤退动画根本来不及播（原版自己还要 0.36s
     淡出，文字要是比背景先没，看着像闪一下）。改由 skin.js 管：
     开 → visible，关 → 等撤退播完（340ms）再 hidden。 */
  visibility: hidden;
  pointer-events: none;       /* 只读层：点击穿透到 overlay，保持"点空白关闭" */
}

/* 画布：稿子的坐标系。里面一律写设计稿 px，不做 clamp。 */
#echo-dt .dt-canvas {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1920px;
  height: 1080px;
  transform-origin: 50% 50%;
  /* scale 由 JS 写在 var 上；没 JS 时按 1:1 摆着，不会塌 */
  transform: translate(-50%, -50%) scale(var(--dt-scale, 1));
}

#echo-dt .dt-rule {
  position: absolute;
  left: 410px;
  top: 179px;
  width: 1100px;
  height: 0;
  border-top: 0.5px solid rgba(255, 255, 255, 0.4);
}

/* 字体：稿子写的是 PingFang SC / w200。**必须显式写**，不能继承 ——
   原版 body 是 JetBrains Mono（等宽），"Design Thinking" 会变成打字机体。
   原版还有 h1..h6{font-weight:100!important} 和 uppercase，都得压回来。 */
#echo-dt .dt-title {
  position: absolute;
  left: 450px;
  top: 199px;
  width: 1060px;
  margin: 0;
  /* 🔴 原版给 h2 写了 padding:20px 0 10px（实测），不去掉就比稿子高 30px，
     而且文字整体下沉 20px。这一条只有量了才知道，看稿看不出来。 */
  padding: 0;
  font-family: "PingFang SC", "Helvetica Neue", Arial, sans-serif;
  font-size: 68px;
  line-height: 95.28px;
  font-weight: 200 !important;
  letter-spacing: 0;
  text-transform: none;
  color: #fff;
}

/* 🔴🔴 原版有一条「h1/h2 里的 span 用 c2 青（85,239,228）」的规则。
   word-bloom 把字切成 span.wb-u 之后整段就变青了 —— 而**父元素的
   computed color 仍然是白**，所以查父会得出"没问题"的假结论。
   实测：父 rgb(255,255,255)，子 rgb(85,239,228)。这条必须在。 */
#echo-dt .wb-u { color: #fff; }

#echo-dt .dt-p {
  position: absolute;
  left: 450px;
  width: 1060px;
  margin: 0;
  font-family: "PingFang SC", "Helvetica Neue", Arial, sans-serif;
  font-size: 18px;
  line-height: 30px;
  font-weight: 200;
  letter-spacing: 0;
  text-transform: none;
  color: #fff;
}
#echo-dt .dt-p1 { top: 334px; }
#echo-dt .dt-p2 { top: 434px; }
#echo-dt .dt-p3 { top: 504px; }
#echo-dt .dt-p4 { top: 634px; }

/* 窄屏（<900 或高 <620）：固定画布 scale 会把字缩到 15px 以下，读不了。
   这一档放弃画布，改成流式单栏 —— 排版不再逐像素对齐稿子，
   但保证能读。桌面永远走不到这条。 */
@media (max-width: 900px), (max-height: 620px) {
  #echo-dt .dt-canvas {
    left: 24px;
    right: 24px;
    top: 50%;
    width: auto;
    height: auto;
    transform: translateY(-50%);
  }
  #echo-dt .dt-rule {
    position: static;
    width: auto;
    margin: 0 0 22px;
  }
  #echo-dt .dt-title,
  #echo-dt .dt-p {
    position: static;
    width: auto;
  }
  #echo-dt .dt-title {
    margin: 0 0 26px;
    font-size: 34px;
    line-height: 46px;
  }
  #echo-dt .dt-p {
    margin: 0 0 20px;
    font-size: 14px;
    line-height: 24px;
  }
  #echo-dt .dt-p4 { margin-bottom: 0; }
}
