/* motion enhancement pack */
    /* ════════════ 丝滑动效增强包（动效语言学习自 lite.ego.app，旁挂式纯追加） ════════════ */

    /* ① Hero 标题遮罩揭示 —— ego terminalFlyInUp 同款缓动 cubic-bezier(.22,1,.36,1) */
    @media (prefers-reduced-motion: no-preference) {
      /* ㊶ 中文的揭示必须落在**渐变载体自己**身上（2026-09-21 第十轮）。
         ── 用户反馈：「你这改的是啥？英文界面是什么动画你就弄成什么动画，别瞎改」。
         实测（探针，见文件下方 ㊵）：把三行钉在揭示起点（`clip-path: inset(0 -0.15em
         102% -0.15em)`，按说是「一个字都看不见」），画面里仍有 105,244 个墨迹像素；
         而一旦撤掉父元素 h1 的 `background-image:none`，整段标题 **0 像素**。
         即：㊴ 把渐变搬到父元素之后，标题的颜色**全部由父元素 h1 的背景绘制**，
         行元素自己的文字是 transparent（不画东西）。而 `clip-path` 只裁
         「元素自己 + 它的后代」的绘制 —— 父元素的背景绘制既不是行的绘制也不是
         行的后代，**完全不受行级 clip-path 管辖**。于是从 ㊴ 那一轮起：
           · 行上的 clip-path / translateY / animation 全部空转（看不见）；
           · 中文标题实际只剩「容器 .reveal 的原地淡入」—— 没有位移、没有揭示，
             与英文那种「整块自下而上顶出遮罩」完全不同。
         （上一轮 ㊵ 只治了「有温度」的双影，因为它被遮罩多上了一遍色；揭示本身没修。）
         【修法】把揭示搬回**渐变载体**上，让「负责上色」和「负责被裁」是同一个元素 ——
         这正是英文版的结构：英文的渐变在 `.hero-title > span` 上，揭示动画也挂在
         同一个 span 上（见下方那条），所以英文一直是对的。
         落地：中文改用 `.hero-title`（h1，渐变就铺在它身上）承载揭示，
         延迟/时长/缓动与英文**逐值相同**（0.3s / 0.92s / cubic-bezier(.22,1,.36,1)），
         于是中英两版的入场节奏完全一致。
         ⚠️ 代价（如实记）：那时判断三行**不能再各错开 0.12s** 分别揭示（理由：渐变
         在父元素上一笔画出，裁切只能一刀切）。**这个判断在 2026-10-27 ⑧ 被推翻了**：
         渐变重新铺回每一行的 span 后，逐行错峰完全可行，见下方 `.hero-title > span`
         那组规则。现在四行是 0.11s 一层的瀑布式揭示。 */
      /* ⚠️ 2026-10-27 ⑥ 起，中英两版的首屏标题**结构相同**（都是两个无 class 的
         span），原来那条 `:has(> .hero-title-line)` 的限定已经没有必要 ——
         下面的通用规则对两种语言都成立，删掉以免同值规则重复。 */
      /* from 必须写全：h1 同时带 `.reveal`，它的 `.reveal.visible{transform:translateY(0)}`
         (0,2,0) 会压掉 `.hero-title` (0,1,0) 的静态起点 —— 不显式声明就会「原地淡入」，
         位移消失。
         ⚠️ 左右外扩用 -1em 而不是 -0.15em：裁切挂在 h1 盒子上，而盒子宽 = 容器宽，
         最长行只比容器窄 0.6%（8cqw 的余量）。外扩太小时，字体 fallback 稍宽一点
         就会把行尾裁掉 —— 左右本来也不需要精裁，给足余量没有副作用。 */
      @keyframes hero-line-up {
        from { clip-path: inset(0 -1em 102% -1em); transform: translateY(0.38em); }
        to { clip-path: inset(-0.14em -1em -0.6em -1em); transform: translateY(0); }
      }
      /* ⑲ 揭示载体：2026-10-27 从 span **统一上移到 h1**。
         现象：换成长英文句后，第一行 "Engineering with restraint" 在截图里糊成
         一团重影（逐字量 Range 矩形，187→782px 顺序完全正常，说明布局没错，
         是**绘制**错），第二行因为含 <em class="ink-hl"> 反而干净。
         定位：元素同时承担 `background-clip: text` 与 `clip-path` 时，Chromium 会
         把文字栅格化两遍且两次的宽度基准不一致；把 span 的 clip-path 一撤，立刻
         恢复正常（A/B 截图见 /tmp/t-A-asis.png、t-B-noanim.png）。
         修法：与中文同构 —— h1 负责裁切 + 位移，span 只负责上色（渐变 +
         transparent），两者不再同层打架。时机/缓动逐值不动：0.3s / 0.92s /
         cubic-bezier(.22,1,.36,1)。
         ⚠️ 由此 span 不再需要 display:inline-block（当初只是为了让 transform 生效），
         行的排列统一由 layout.css 的 `.hero-title > span` 决定。 */
      /* 2026-10-27 ⑧ 逐行揭示（用户要求「一层一层逐次显示」）—— 载体从 h1 下放回
         每行的 span，用 nth-child 把四行错开 0.11s。
         ⚠️ 当初把 clip-path 从 span 上撤走、上移到 h1 的唯一理由，是「span 同时承担
         background-clip:text 与 clip-path → Chromium 把文字栅格化两遍、横向错位，
         第一行糊成重影」。实测（/tmp/axis-spanclip.png）现在不会重影：
         那时 span 是 inline-block，长句还在 span 内部折成两行，一个 span 横跨两行，
         裁切框与渐变框各以不同的盒为基准；现在 span 是 `display:block; width:fit-content`
         的**单行块**（layout.css `.hero-title > span` 已 nowrap 锁死），两个框的参考盒
         重合，裁切干净。
         ⚠️ 所以别再让 span 变成 inline-block 或允许它折行 —— 那是重影复发的条件。
         时间线（总节奏与 ㊶ 那条基本一致，只是把 0.92s 拆成四段错峰）：
           行1 0.26→1.04s  行2 0.37→1.15s  行3 0.48→1.26s  行4 0.59→1.37s
           波浪线落笔 1.50s（layout.css `.ink-hl` 的 ink-draw 延迟已同步） */
      .hero-title > span {
        clip-path: inset(0 -1em 102% -1em);
        transform: translateY(0.34em);
        animation: hero-line-up 0.78s cubic-bezier(0.22, 1, 0.36, 1) both;
      }
      .hero-title > span:nth-child(1) { animation-delay: 0.26s; }
      .hero-title > span:nth-child(2) { animation-delay: 0.37s; }
      .hero-title > span:nth-child(3) { animation-delay: 0.48s; }
      .hero-title > span:nth-child(4) { animation-delay: 0.59s; }
      /* h1 自己也带 `.reveal`（全站统一入场类）：位移已经交给每行了，父级只保留
         opacity 淡入 —— 否则 18px 的整体位移会和行的 0.34em 上浮叠成「双段上浮」。
         同特异性 (0,2,0) 且写在 components.css 的 `.reveal.visible` 之后，覆盖成立。 */
      .hero-title.reveal { transform: none; }

      /* 2026-10-27 ⑩ 副文本也逐行出现（用户要求「副标题也加上这个动画」）。
         段落是**自动折行**的，浏览器没有行级钩子（`::first-line` 只管第一行），
         标题那套「一行一个 span + clip-path」套不上 —— 真要逐行只能 JS 按行拆 DOM，
         而拆完一旦 resize / 字体加载引起重排，span 就会跨行，正好落回 ⑧ 记的
         「跨行 → 裁切框与渐变框基准不同 → 重影」那个坑，代价太大。
         改用**垂直方向渐变遮罩**做擦除：遮罩是水平的，同一个 y 上的像素同时出现，
         所以肉眼看就是「一行一行显出来」，0.45em 的羽化只够柔化行边界（行高 1.78em）。
         时间线：0.34s → 1.29s，与原 `.reveal` 的窗口（0.24→0.79s）基本重合，
         不会打乱「标题 → 副文本 → 按钮」的先后。
         `--desc-reveal` 必须用 @property 注册成 <percentage> 才能插值（见文件顶部的
         ⑩ 那条），否则只会 0%/130% 两帧硬跳。 */
      .hero-desc {
        -webkit-mask-image: linear-gradient(180deg, #000 calc(var(--desc-reveal) - 0.45em), rgba(0,0,0,0) var(--desc-reveal));
        mask-image: linear-gradient(180deg, #000 calc(var(--desc-reveal) - 0.45em), rgba(0,0,0,0) var(--desc-reveal));
        -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
        -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
        /* ⚠️ 缓动**不能**沿用标题那条 `cubic-bezier(.22,1,.36,1)`：它的前段更陡，
           实测 0.34→0.43s 这 90ms 里第一行就从「几乎不可见」跳到全亮，三行挤成一坨，
           看着是「闪一下」而不是「一层一层」。标题之所以能用这条曲线，是因为每行
           是独立元素、各自 0.78s，行与行靠 0.11s 的 delay 拉开层次；连续扫过的遮罩
           没有这个天然间隔，只能靠曲线本身把速度摊匀。 */
        animation: desc-wipe 0.95s cubic-bezier(0.4, 0, 0.3, 1) 0.34s both;
      }
      /* to 取 130% 而不是 100%：100% 时渐变末端正好压在段落底边上，
         最后一行底部 0.45em 会留一道渐隐的淡影；越过 100% 才能整段是实心遮罩。 */
      @keyframes desc-wipe {
        from { --desc-reveal: 0%; }
        to { --desc-reveal: 130%; }
      }
      /* p 自己的 `.reveal`（opacity 0→1 + translateY 26px）必须让位：
         否则先整块淡入一遍、再被遮罩从上到下擦一遍，等于播两次。
         transition 也一并撤掉 —— 留着只会空跑一条 0.55s 的无意义过渡。 */
      .hero-desc.reveal { opacity: 1; transform: none; transition: none; }
      /* ㊷ 【历史说明，2026-10-27 起已不适用】「切语言时把 2.3s 的入场动画重播一遍」
         曾经是特性：那时揭示按 lang 各自命中、setHeroTitle 还会重建 span，所以切一次
         语言动画就跑一遍。现在首屏标题写死在 index.html、切语言不重建 DOM（用户明确
         要求「主/副文本不重新加载」），入场动画只在首次加载播一次 —— 这是期望行为，
         别再为了「切语言重播」去重建节点。时长与延迟（0.30 / 0.92s）仍是可调项。 */

      /* ⑦ 原「拍立得呼吸浮动」随头像一起下线（2026-10-27）：右侧换成了标本展台，
         展台里是一块一直在动的水，外面再叠一层浮动只会互相打架。
         keyframes 一并删除，别再为已不存在的 .avatar-stack 保留死代码。 */
    }

    /* ⑩ 副文本逐行擦除的自定义属性（2026-10-27）。
       ⚠️ @property **只能写在样式表顶层**，不能嵌进 @media / @supports —— 放进去会被
       整条忽略，`--desc-reveal` 退回未注册的离散类型，动画就只剩 0% 和 130% 两帧硬跳。
       注册成 <percentage> 之后才能逐帧插值，渐变遮罩才会跟着平滑下移。 */
    @property --desc-reveal {
      syntax: "<percentage>";
      inherits: false;
      initial-value: 0%;
    }

    /* ⑨ 逐行揭示在「减弱动效」下必须直接终态：
       responsive.css 的 reduce 块只把 animation-duration 压到 0.001ms，
       **没动 animation-delay** —— 那样四行仍会按 0.26/0.37/0.48/0.59s 依次「闪」出来，
       反而比一段完整动画更扎眼。这里连延迟和初始裁切一起撤掉。 */
    @media (prefers-reduced-motion: reduce) {
      .hero-title > span {
        animation-delay: 0s !important;
        clip-path: none !important;
        transform: none !important;
      }
    }

    /* ② 作品卡：鼠标跟随光斑（.card-spot 由旁挂脚本补挂）+ 微倾斜（CSS 过渡即柔化） */
    .card-spot {
      position: absolute; inset: 0; z-index: 2; border-radius: inherit; pointer-events: none;
      background: radial-gradient(430px circle at var(--mx, 50%) var(--my, 50%),
                  color-mix(in srgb, var(--blue-400) 9%, transparent), transparent 48%);
      opacity: 0; transition: opacity 0.45s var(--ease-out);
    }
    html[data-theme="dark"] .card-spot {
      background: radial-gradient(430px circle at var(--mx, 50%) var(--my, 50%),
                  color-mix(in srgb, var(--blue-300) 11%, transparent), transparent 48%);
    }
    .portfolio-card:hover .card-spot { opacity: 1; }
    /* ⑬ 倾斜跟手 v2 —— 先解决「不跟鼠标」，再谈幅度。
       滞后真凶（逐帧实测「目标角度 vs 渲染角度」的时间偏移）：
       (a) 入场错峰用的内联 transition-delay（0 / 0.1s / 0.2s，写进元素后从未清除）
           会拖累之后的每一次过渡，使 hover 起步就晚 0.1~0.2 秒；
       (b) transform 上的过渡在 pointermove 连续改目标时会不停重启，永远追不上鼠标。
       实测滞后 = 残留 delay + 过渡时长：443px 卡 240ms、629px 卡 320ms、1096px 卡 320ms，
       而当时 delay 恰好为 0 的那张卡只有 45ms —— 这就是「只有某几张顺」的由来。
       对策：
       (a) hover 期间 transform 不参与任何过渡 → 鼠标到哪卡片到哪，零滞后（实测 0ms / 误差 0.000°）；
           只有「离开卡片回正」才用 0.5s spring 柔化。
       (b) 幅度按卡宽「线性」归一化（透视距离 = 1.45×卡宽），各卡形变都占自身宽约 1.8%；
           原先的 w² 归一化会把 1096px 宽卡放大到近 40px 位移，明显过猛，已收回。
       (c) will-change 提到 hover 之外 —— 写在 :hover 里会在刚悬停的一瞬间触发图层
           创建 + 整卡重光栅化，实测出现 386ms / 853ms 的单帧停顿（现在首悬最慢帧 14ms）。 */
    @media (hover: hover) and (pointer: fine) {
      /* 注意：will-change 这条选择器权重是 0,3,0，必须与 transition 分开写，
         否则会把下面的 :hover（0,2,0）一起盖掉，hover 期间又套上 0.5s 的 transform 过渡。 */
      .portfolio-card,
      .portfolio-card.reveal.visible {
        will-change: transform;
        backface-visibility: hidden;
      }
      .portfolio-card {
        /* opacity 必须留着，否则会把 .reveal 的入场淡入一起顶掉（卡片会“啪”地出现） */
        transition: transform 0.5s var(--ease-spring),
                    translate 0.38s var(--ease-spring),
                    box-shadow 0.42s var(--ease-out),
                    border-color 0.3s var(--ease-out),
                    opacity 0.55s var(--ease-out);
      }
      .portfolio-card:hover {
        transform: perspective(var(--persp, 900px)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
        translate: 0 -4px;
        /* 只过渡平移/阴影/边框；transform 由旁挂脚本逐帧直写，1:1 跟手 */
        transition: translate 0.38s var(--ease-spring),
                    box-shadow 0.42s var(--ease-out),
                    border-color 0.3s var(--ease-out);
      }
    }

    /* ③ Hero 按钮磁吸 —— 位移上限 5px，由 .36s spring 过渡负责柔化回弹 */
    @media (hover: hover) and (pointer: fine) {
      .hero-actions .btn-solid { transform: translate(var(--bx, 0px), var(--by, 0px)); }
      .hero-actions .btn-solid:hover { transform: translate(var(--bx, 0px), calc(var(--by, 0px) - 2.5px)); }
      .hero-actions .btn-solid:active { transform: translate(var(--bx, 0px), calc(var(--by, 0px) - 0.5px)) scale(0.99); }
      .hero-actions .btn-line { transform: translate(var(--bx, 0px), var(--by, 0px)); }
      .hero-actions .btn-line:hover { transform: translate(var(--bx, 0px), calc(var(--by, 0px) - 2.5px)); }
    }

    /* ④ 鼠标跟随环境光晕（rAF 插值跟随，z-index:-1 垫在内容之下；body 已透明故可见） */
    .cursor-aura {
      position: fixed; left: 0; top: 0; width: 560px; height: 560px;
      margin: -280px 0 0 -280px; border-radius: 50%;
      pointer-events: none; z-index: -1; opacity: 0;
      background: radial-gradient(circle, color-mix(in srgb, var(--blue-300) 13%, transparent) 0%, transparent 60%);
      transition: opacity 0.9s var(--ease-out);
      will-change: transform;
    }
    html[data-theme="dark"] .cursor-aura {
      background: radial-gradient(circle, color-mix(in srgb, var(--blue-400) 9%, transparent) 0%, transparent 60%);
    }

    /* ⑤ 回顶按钮 + 滚动进度环 */
    .to-top {
      position: fixed; right: 1.6rem; bottom: 1.6rem; z-index: 1600;
      width: 46px; height: 46px; border-radius: 50%;
      display: grid; place-items: center; padding: 0;
      background: color-mix(in srgb, var(--surface) 88%, transparent);
      backdrop-filter: blur(10px) saturate(1.4); -webkit-backdrop-filter: blur(10px) saturate(1.4);
      border: 1px solid var(--line-strong); color: var(--ink-muted);
      box-shadow: var(--shadow-card); cursor: pointer;
      opacity: 0; transform: translateY(14px) scale(0.9); pointer-events: none;
      transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-spring),
                  color 0.25s var(--ease-out), border-color 0.25s var(--ease-out), box-shadow 0.3s var(--ease-out);
    }
    .to-top.show { opacity: 1; transform: none; pointer-events: auto; }
    .to-top:hover { color: var(--blue-700); border-color: color-mix(in srgb, var(--blue-500) 45%, transparent); box-shadow: var(--shadow-lift); }
    .to-top svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
    .to-top .ring-bg { fill: none; stroke: var(--line-strong); stroke-width: 2; opacity: 0.55; }
    .to-top .ring-fg { fill: none; stroke: var(--blue-500); stroke-width: 2; stroke-linecap: round;
      stroke-dasharray: 125.66; stroke-dashoffset: 125.66; }
    .to-top .arrow { position: relative; font-size: 15px; line-height: 1; font-weight: 600; }

    /* ⑥ 路由切换：轻微模糊淡入（同名 keyframes 后定义覆盖原定义） */
    @keyframes sectionFadeIn {
      from { opacity: 0; transform: translateY(10px); filter: blur(5px); }
      to   { opacity: 1; transform: none; filter: blur(0); }
    }


    /* ⑧ 首页 SCROLL 提示：移除（用户反馈该组件多余；原 DOM 保留、纯 CSS 隐藏）。
       ⚠️ 2026-09-21 收尾：这条 `display: none` 与窄屏媒体查询里的同值声明都已删除，
       隐藏开关统一收到 `.hero-scroll` 基础规则那一处（搜「复活只需」）。 */

    /* ⑨ 导航入场：首屏 hero 各元素都是错峰入场，导航不再「凭空出现」，补齐同一序列 */
    @media (prefers-reduced-motion: no-preference) {
      .nav { animation: nav-drop-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; }
      @keyframes nav-drop-in {
        from { opacity: 0; transform: translateY(-14px); }
        to   { opacity: 1; transform: none; }
      }
    }


    /* ⑩ 页脚社交链接：去按钮化，改为带下划线动效的纯文字链接。
       2026-09-21 收尾：旧版「药丸按钮」的全套声明（padding / border / `background` 简写 /
       hover 的 translateY + 阴影）及本节为抵消它而写的补偿声明都已删除 ——
       hover 现在只剩「换色 + 下划线拉满」两件事。`padding: 0.12rem 0` 只为给下划线留间隙。 */
    .footer-links { gap: 2.1rem; }
    .footer-links a {
      padding: 0.12rem 0;
      color: var(--ink-muted); font-weight: 450; letter-spacing: 0.02em;
      background-image: linear-gradient(currentColor, currentColor);
      background-repeat: no-repeat; background-size: 0% 1px;
      background-position: left calc(100% - 0.05em);
      transition: color 0.24s var(--ease-out), background-size 0.32s var(--ease-out);
    }
    .footer-links a:hover {
      color: var(--blue-700);
      background-size: 100% 1px;
    }

    /* ⑪ 手机端导航拥挤修复（≤480px，中英双语）：缩小字号/内边距/按钮，消除启发/Ideas 与搜索图标的重叠 */
    @media (max-width: 480px) {
      .container, .nav-inner { padding-inline: 0.75rem; }
      /* 🔴 ≤768 段的 `.nav.scrolled .nav-inner { padding: .32rem .9rem }` 在滚动时把内边距
         从 0.75 撑到 0.9rem（+2.4px/侧），375px 中文滚动态因此压搜索按钮 3.9px ——
         窄屏统一按静态档（≤360 段另有更紧的 0.55rem，写在其后仍会覆盖这里）。 */
      .nav.scrolled .nav-inner { padding-inline: 0.75rem; }
      .nav-inner { gap: 0.5rem; }
      .nav-logo { font-size: 0.98rem; gap: 0.4rem; }
      .nav-logo::before { width: 6px; height: 6px; box-shadow: 0 0 0 2px color-mix(in srgb, var(--blue-500) 12%, transparent); }
      .nav-actions { gap: 0.32rem; }
      .nav-links { gap: 0.04rem; }
      .nav-links a { font-size: 0.76rem; padding: 0.3rem 0.42rem; }
      /* 🔴 英文标签（Work / Travel / Ideas / Log）比中文双字宽得多，同样的让位量不够：
         375px 英文实测压搜索按钮 14.2px、390px 滚动态 3.8px。
         这里按语言再收一档 —— 字距归零 + 左右内边距 0.42 → 0.26rem，四项共让出 ~27px。
         中文（lang="zh-CN"）不受影响，仍用上面的 .nav-links a。 */
      html[lang="en"] .nav-links { gap: 0; }
      html[lang="en"] .nav-links a { letter-spacing: 0; padding-left: 0.26rem; padding-right: 0.26rem; }
      .nav-search, .theme-toggle, .lang-toggle { width: 32px; height: 32px; flex: 0 0 32px; }
      .nav-search svg { width: 16px; height: 16px; }
      .theme-toggle svg { width: 14.5px; height: 14.5px; }
      .lang-toggle { min-width: 32px; font-size: 0.72rem; }
      .lang-toggle .lang-text.is-zh { font-size: 0.8rem; }
    }

    /* ⑪-b 极窄屏导航（≤360px：320px 老机型 / iPhone SE）：⑪ 收完一轮后仍不够，
       「启发」会压到搜索按钮上 —— 实测 320px 中文压 15.5px、英文压 33.9px。
       本案只做「再收一圈」：缩 logo、图标按钮与标签内边距，数值均按实测校准。
       ⚠️ 必须写在 ⑪ 之后：两者都命中 ≤360px，靠文档顺序覆盖才生效。 */
    @media (max-width: 360px) {
      .container, .nav-inner { padding-inline: 0.6rem; }
      /* 滚动态那条 `.nav.scrolled .nav-inner{padding:.32rem .9rem}`（≤768 段）会反过来
         把内边距撑大 5.6px/侧，实测 320px 英文滚动态因此还压 6.5px —— 这里压回去。 */
      .nav.scrolled .nav-inner { padding-inline: 0.55rem; }
      .nav-inner { gap: 0.4rem; }
      .nav-logo { font-size: 0.88rem; }
      .nav-actions { gap: 0.24rem; }
      .nav-links { gap: 0.02rem; }
      /* 2026-10-05 新增第 4 个标签「日志」后，320px 中文会压搜索按钮 ~10px ——
         左右内边距 0.32 → 0.2rem，四个标签共让出 ~15px。 */
      .nav-links a { font-size: 0.72rem; padding: 0.28rem 0.2rem; }
      /* 🔴 320px 英文仍差 12.2px（「Log」被搜索按钮压住，中文那档是 -1.8px 有余量）——
         再收一档内边距 0.2 → 0.06rem，四项共让出 ~18px。同样只作用于英文。 */
      html[lang="en"] .nav-links a { padding-left: 0.06rem; padding-right: 0.06rem; }
      .nav-search, .theme-toggle, .lang-toggle { width: 30px; height: 30px; flex: 0 0 30px; }
      .nav-search svg { width: 15px; height: 15px; }
      .theme-toggle svg { width: 13.5px; height: 13.5px; }
      .lang-toggle { min-width: 30px; font-size: 0.68rem; }
      .lang-toggle .lang-text.is-zh { font-size: 0.76rem; }
    }

    /* ⑪-c ≤300px 兜底（低于任何主流机型：最小量产机 375px，此档仅供桌面极限拉窄）：
       实测 300px 英文压搜索 16.9px、280px 中/英压 19.4/36.9px —— 只在这一档再收一圈。
       ⚠️ 必须写在 ⑪-b 之后：两者都命中 ≤300px，靠文档顺序覆盖才生效。 */
    @media (max-width: 300px) {
      .nav-inner { gap: 0.3rem; }
      .nav.scrolled .nav-inner { padding-inline: 0.4rem; }
      .nav-logo { font-size: 0.8rem; }
      .nav-actions { gap: 0.2rem; }
      .nav-links { gap: 0; }
      .nav-links a { font-size: 0.63rem; padding: 0.24rem 0.18rem; }
      .nav-search, .theme-toggle, .lang-toggle { width: 27px; height: 27px; flex: 0 0 27px; }
      .nav-search svg { width: 13px; height: 13px; }
      .theme-toggle svg { width: 12px; height: 12px; }
      .lang-toggle { min-width: 27px; font-size: 0.62rem; }
      .lang-toggle .lang-text.is-zh { font-size: 0.7rem; }
    }


