/* ============================================================
   theme.css — 暖石墨 / 单一色温
   加载在 ui-v2.css 之后，是「皮肤层」；ui-v2.css 保持为结构层不动。

   ---- 这一版修的核心问题：冷暖打架 ----
   上一版中性色全是【冷灰】（底 #f6f7f9、字 #16181d、线 #e6e8ee、
   阴影 rgba(16,18,24,…)），却只配了一个【暖】强调色 #eda64d，
   深色舞台又是【冷】蓝黑 #15171c。三处色温互不相干，页面就散了。
   这一版把中性色整体推到暖侧（米/砂/石墨），舞台改成暖棕黑，
   阴影带暖棕色相 —— 全页只剩一个色温，琥珀才有立足之地。

   ---- 对比度预算（WCAG AA 正文 4.5:1，大字 3:1）----
   全部实算过，脚本 work/atk-capture/palette-check.cjs 可重跑：
     --text         #1A1815 / #F7F5F2   16.28:1 ✓
     --text         #1A1815 / #FFFFFF   17.72:1 ✓
     --muted        #6B6459 / #F7F5F2    5.37:1 ✓
     --muted        #6B6459 / #FFFFFF    5.85:1 ✓
     --muted        #6B6459 / #FAF0E4    5.19:1 ✓  ← 琥珀淡底，最易翻车的一格
     --dim          #8D8478 / #F7F5F2    3.38:1 ✓ 但仅限大字/图形，禁止正文
     --accent-text  #8A4F16 / #F7F5F2    6.01:1 ✓ 光底上的琥珀小字
     --accent-display #B0661F / #FFFFFF  4.41:1 ✓ 仅大字（≥24px）
     --accent-ink   #2E1C08 / #C97A2B    4.90:1 ✓ 琥珀按钮上的文字
     --on-stage     #F0EBE4 / #1A1613   15.16:1 ✓
     --on-stage-dim #9A918A / #1A1613    5.81:1 ✓
   注意 --accent #C97A2B 本身在白底上只有约 3.0:1，**不能当文字用**，
   只做填充/描边/发光；要当文字用 --accent-text 或 --accent-display。

   ---- 字体 ----
   上一版 --font 以 "Segoe UI Variable Display" 打头，那是 Windows 系统
   默认字体，没有性格，是「AI 味」的来源之一。这一版自托管 Geist。
   为什么是自托管而不是引 CDN：本工具用户多在国内，引 Google Fonts 会慢
   甚至不通；把 woff2 放进 vendor/fonts/ 就完全没有运行时网络依赖了。
   两个文件合计 52KB（都是 latin 子集，变体字重 wght 300–900）。
   中文字形不在 Geist 覆盖范围内，会落到后面的 PingFang SC / 微软雅黑 ——
   这是有意的：中文 webfont 动辄几 MB，不做子集化不可能塞进静态站，
   而系统中文黑体（苹方 / 雅黑）本身质量就够。所以：
     · 拉丁字母、数字、标点 → Geist（有性格）
     · 汉字 → 系统黑体（干净、加载零成本）
   ============================================================ */

/* 自托管字体。font-display:swap —— 字体没就绪时先用回退字体渲染文字，
   绝不出现「白屏等字体」。变体字体只需声明一个 wght 范围。 */
@font-face{
  font-family:"Geist";font-style:normal;font-weight:300 900;font-display:swap;
  src:url("vendor/fonts/geist-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Geist Mono";font-style:normal;font-weight:300 900;font-display:swap;
  src:url("vendor/fonts/geist-mono-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

:root{
  /* ---- 暖石墨中性阶 ---- */
  --bg:#f7f5f2;
  --surface:#ffffff;
  --surface-2:#f2eee8;
  --line:#e8e2d9;
  --line-hi:#d6cec2;
  --text:#1a1815;
  --muted:#6b6459;
  --dim:#8d8478;   /* 3.38:1 —— 仅装饰，禁止做正文 */

  /* ---- 单一强调色：赤陶琥珀（比上一版 #eda64d 降了饱和，不再刺眼）---- */
  --accent:#c97a2b;
  --accent-ink:#2e1c08;
  --accent-text:#8a4f16;
  --accent-display:#b0661f;
  --accent-soft:#faf0e4;

  /* ---- 暖棕黑舞台 ---- */
  --stage:#1a1613;
  --stage-2:#262019;
  --on-stage:#f0ebe4;
  --on-stage-dim:#9a918a;

  /* ui-v2.css 的历史变量名，保留别名，省掉 26 处引用改动。
     填充/描边/发光都靠这两个别名自动转成琥珀；当文字用的那几处
     对比度不够，在下面单独覆盖。 */
  --lime:var(--accent);
  --lime-ink:var(--accent-ink);
  --orange:#a8451a;   /* 旧主题的警示橙，压暗一点融进暖色系 */

  --font:"Geist","PingFang SC","Microsoft YaHei",-apple-system,system-ui,sans-serif;
  --mono:"Geist Mono","Cascadia Code",Consolas,monospace;

  color-scheme:light;

  /* 圆角与投影词汇。投影全部带暖棕色相 —— 纯黑低透明度的阴影
     压在暖底上会发灰发脏，这是「便宜感」的常见来源。 */
  --r-card:14px;
  --r-ctl:9px;
  --r-stage:18px;
  --r-pill:999px;
  --sh-card:0 1px 2px rgba(74,56,38,.05),0 4px 12px -4px rgba(74,56,38,.09);
  --sh-raise:0 2px 4px rgba(74,56,38,.07),0 14px 32px -10px rgba(74,56,38,.20);
  --sh-stage:0 2px 4px rgba(46,34,22,.14),0 22px 48px -16px rgba(46,34,22,.40);
  --sh-accent:0 1px 2px rgba(90,52,12,.22),0 8px 20px -6px rgba(201,122,43,.50);

  /* 缓动。全站禁止 linear / ease-in-out —— 那两条是「模板感」最明显的信号。
     这两条一条用于进场（起步慢、收尾更慢），一条用于位移与悬停。 */
  --ease-out:cubic-bezier(.22,.68,0,1);
  --ease-io:cubic-bezier(.32,.72,0,1);
}

html{color-scheme:light}

/* ---------- 基础 ---------- */
body{
  background:var(--bg);
  color:var(--text);
  /* 暖色顶光 + 极淡的中性网格。网格线也带暖调，不再是冷蓝灰。 */
  background-image:
    radial-gradient(ellipse 70% 44% at 50% -8%,rgba(201,122,43,.12),transparent 62%),
    linear-gradient(rgba(58,44,30,.022) 1px,transparent 1px),
    linear-gradient(90deg,rgba(58,44,30,.022) 1px,transparent 1px);
  background-size:auto,64px 64px,64px 64px;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* 纸纹颗粒层。要的是「印刷品」的微质感，不是滤镜特效 —— soft-skill 里那条经验：
   纯平面的矢量背景永远显得「数字、无菌」，一层极淡噪点就能把它压回「实物」。
   幅度必须小到静止看察觉不到，只在纯色大面积上感觉得到「不是死平」。
   必须是 fixed + pointer-events:none：挂在滚动容器上会导致持续重绘、手机掉帧。

   为什么用位图而不是 SVG feTurbulence：先写的正是那段到处能搜到的
   feTurbulence + rect 的 data URI，实测【画不出来】。debug-grain.mjs 查过：
   伪元素生成了、样式全对、data URI 也能解码成 300x150 —— 但把解码结果画到
   canvas 上读回来，45000 个像素的 alpha 全是 127.5，也就是一块均匀半透明灰，
   不是噪点。所以不是 CSS 的问题，是那个 SVG 滤镜在这个内联 data URI 形态下
   没产出噪点，叠上去等于一层均匀灰，和关掉它像素级一致（对照实验的
   grain-on / grain-off 文件字节数完全相同，都是 390）。别再试 SVG 那条路了。

   现在这张是 ffmpeg 生成的真噪点，64x64 灰度、平铺：
     ffmpeg -f lavfi -i "color=c=gray:s=64x64" -vf "noise=alls=100:allf=t+u" \
            -frames:v 1 -pix_fmt gray noise64.png
   3983 字节。判据是压缩率：真噪点几乎不可压缩（128x128 那版 14989 字节 /
   16384 像素 ≈ 7.3 bit/px），早先用 geq=random() 生成的只有 927 字节，
   那种压缩率本身就说明它根本不是随机像素。base64 内联 5.3KB，
   跟 theme.css 一起长缓存，不额外发请求。

   z-index:1 而不是 -1：body 的背景色会传播到 canvas，负 z-index 的伪元素
   会被它盖住 —— 这正是第一版没效果的原因之一。opacity 刻意压在 .03，
   超过 .05 在奶白底上就开始显脏，像屏幕没擦干净。 */
/* 纸纹颗粒层。要的是「印刷品」的微质感，不是滤镜特效 —— 幅度必须小到
   静止看察觉不到，只在纯色大面积上感觉得到「不是死平」。

   为什么用位图而不是 SVG feTurbulence：先写的正是那段到处能搜到的
   feTurbulence + rect 的 data URI，实测【画不出来】。debug-grain.mjs 查过：
   伪元素生成了、样式全对、data URI 也能解码成 300x150 —— 但把解码结果画到
   canvas 上读回来，45000 个像素的 alpha 全是 127.5，也就是一块均匀半透明灰，
   不是噪点。所以不是 CSS 的问题，是那个 SVG 滤镜在这个内联 data URI 形态下
   没产出噪点，叠上去等于一层均匀灰，和关掉它像素级一致（对照实验的
   grain-on / grain-off 文件字节数完全相同，都是 390）。别再试 SVG 那条路了。

   现在这张是 ffmpeg 生成的真噪点，64x64 灰度、平铺：
     ffmpeg -f lavfi -i "color=c=gray:s=64x64" -vf "noise=alls=100:allf=t+u" \
            -frames:v 1 -pix_fmt gray noise64.png
   3983 字节。判据是压缩率：真噪点几乎不可压缩（128x128 那版 14989 字节 /
   16384 像素 ≈ 7.3 bit/px），早先用 geq=random() 生成的只有 927 字节，
   那种压缩率本身就说明它根本不是随机像素。base64 内联 5.3KB，
   跟 theme.css 一起长缓存，不额外发请求。

   z-index:1 而不是 -1：body 的背景色会传播到 canvas，负 z-index 的伪元素
   会被它盖住 —— 这正是第一版没效果的原因之一。opacity 刻意压在 .03，
   超过 .05 在奶白底上就开始显脏，像屏幕没擦干净。 */
/* 纸纹颗粒层。要的是「印刷品」的微质感，不是滤镜特效 —— 幅度必须小到
   静止看察觉不到，只在纯色大面积上感觉得到「不是死平」。

   为什么用位图而不是 SVG feTurbulence：先写的正是那段到处能搜到的
   feTurbulence + rect 的 data URI，实测【画不出来】。debug-grain.mjs 查过：
   伪元素生成了、样式全对、data URI 也能解码成 300x150 —— 但把解码结果画到
   canvas 上读回来，45000 个像素的 alpha 全是 127.5，也就是一块均匀半透明灰，
   不是噪点。所以不是 CSS 的问题，是那个 SVG 滤镜在这个内联 data URI 形态下
   没产出噪点，叠上去等于一层均匀灰，和关掉它像素级一致（对照实验的
   grain-on / grain-off 文件字节数完全相同，都是 390）。别再试 SVG 那条路了。

   现在这张是 ffmpeg 生成的真噪点，64x64 灰度、平铺：
     ffmpeg -f lavfi -i "color=c=gray:s=64x64" -vf "noise=alls=100:allf=t+u" \
            -frames:v 1 -pix_fmt gray noise64.png
   3983 字节。判据是压缩率：真噪点几乎不可压缩（128x128 那版 14989 字节 /
   16384 像素 ≈ 7.3 bit/px），早先用 geq=random() 生成的只有 927 字节，
   那种压缩率本身就说明它根本不是随机像素。base64 内联 5.3KB，
   跟 theme.css 一起长缓存，不额外发请求。

   z-index:1 而不是 -1：body 的背景色会传播到 canvas，负 z-index 的伪元素
   会被它盖住 —— 这正是第一版没效果的原因之一。opacity 刻意压在 .03，
   超过 .05 在奶白底上就开始显脏，像屏幕没擦干净。 */
/* 纸纹颗粒层。要的是「印刷品」的微质感，不是滤镜特效 —— 幅度必须小到
   静止看察觉不到，只在纯色大面积上感觉得到「不是死平」。

   为什么用位图而不是 SVG feTurbulence：先写的正是那段到处能搜到的
   feTurbulence + rect 的 data URI，实测【画不出来】。debug-grain.mjs 查过：
   伪元素生成了、样式全对、data URI 也能解码成 300x150 —— 但把解码结果画到
   canvas 上读回来，45000 个像素的 alpha 全是 127.5，也就是一块均匀半透明灰，
   不是噪点。所以不是 CSS 的问题，是那个 SVG 滤镜在这个内联 data URI 形态下
   没产出噪点，叠上去等于一层均匀灰，和关掉它像素级一致（对照实验的
   grain-on / grain-off 文件字节数完全相同，都是 390）。别再试 SVG 那条路了。

   现在这张是 ffmpeg 生成的真噪点，64x64 灰度、平铺：
     ffmpeg -f lavfi -i "color=c=gray:s=64x64" -vf "noise=alls=100:allf=t+u" \
            -frames:v 1 -pix_fmt gray noise64.png
   3983 字节。判据是压缩率：真噪点几乎不可压缩（128x128 那版 14989 字节 /
   16384 像素 ≈ 7.3 bit/px），早先用 geq=random() 生成的只有 927 字节，
   那种压缩率本身就说明它根本不是随机像素。base64 内联 5.3KB，
   跟 theme.css 一起长缓存，不额外发请求。

   z-index 为什么是 100 —— 这是它第二版还是看不见的真正原因，别再调回去：
     .device-gate 自己带实心 background:rgb(247,245,242) 且 position:fixed;
     z-index:18，整块盖住连接页 y=72 以下。颗粒层写在 z-index:1 时被它整个
     吃掉，只在顶栏那条 72px 缝里露出来 —— 对照实验的最大差分只有 2/255，
     而把伪元素背景换成纯红才看出它压根没铺到页面上。用 elementFromPoint
     逐层打出来才定位到元凶是那块实心面板（见 grain-cover.mjs）。
   也不能写 -1：body 的背景色会传播到 canvas，负 z-index 同样会被盖住。
   所以正确的层是【全站最顶层】—— 全项目最大 z-index 是 40，这里取 100。
   压在弹层之上是有意的：胶片颗粒本来就该在画面上，不在画面下。
   pointer-events:none 保证它不吃任何交互。

   opacity 刻意压在 .03，超过 .05 在奶白底上就开始显脏，像屏幕没擦干净。
   注意 .03 会在整屏叠出一层极淡的暖灰，这是有意的压暗，不是 bug。 */
body::after{
  content:"";position:fixed;inset:0;z-index:100;pointer-events:none;
  opacity:.03;background-repeat:repeat;
  background-image:url("data:image/png;base64,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");
}

/* 数字统一用等宽数字。配置页全是数据（DPI、回报率、消抖毫秒、按键编号），
   比例数字在数值变化时会左右抖动，等宽数字不会，且天然更「仪表」。
   这一条是零成本的质感提升。 */
.dpi-item strong,.dpi-item input,.matrix-badge,.button-id strong,
output,#brightnessOut,#debounceOut{font-variant-numeric:tabular-nums}

/* ---------- 顶栏：暖白毛玻璃 ---------- */
.topbar{
  border-bottom:1px solid var(--line);
  background:rgba(255,253,251,.80);
  -webkit-backdrop-filter:blur(16px) saturate(1.6);
  backdrop-filter:blur(16px) saturate(1.6);
}
.outline-button{border-radius:var(--r-ctl)}
.outline-button:hover{
  color:var(--accent-text);
  border-color:var(--accent);
  background:var(--accent-soft);
}
/* 原为硬编码 rgba(201,255,61,.65) 荧光绿辉光，别名覆盖不到，单独换琥珀 */
.connection.connected #statusDot{
  box-shadow:0 0 0 3px rgba(201,122,43,.22),0 0 10px rgba(201,122,43,.50);
}

/* ---------- 文字层级 ---------- */
/* 以下几处 var(--lime) 是当「文字色」用的，光底上只有约 3.0:1，必须换深琥珀 */
.eyebrow{color:var(--accent-text)}
.brand-slash{color:var(--accent-text)}
.notice>span{color:var(--accent-text)}
.gate-copy h1 em{color:var(--accent-display)}
.gate-index span{color:var(--accent-text)}
.macro-loading{color:var(--accent-text)}

/* 小号标注类文字（eyebrow / 键头 / mono 标签）加一点字距。
   大写字母在小字号下会挤成一团，+.14em 左右的字距是这类标签的标准做法。 */
.eyebrow,.gate-index,.matrix-head span,.brand small{letter-spacing:.14em}

/* 以下 5 处原用 var(--dim) 当文字色。--dim 只有 3.38:1，达不到 AA 的
   4.5:1（要达标得暗到跟 --muted 差不多，那第三级就失去意义了）。
   结论：浅色主题只保留两级正文色，这 5 处统一提到 --muted。 */
.brand small,.profiles>span,.section-title>span,.dpi-item span,.detail-empty,.matrix-head span{color:var(--muted)}

/* 深色舞台内部把中性色变量重新绑定。
   用自定义属性的作用域来做，比逐个选择器打补丁更稳：舞台上任何引用
   --muted / --line 的规则都自动拿到「深底上的浅色版」，不用去数有几个选择器。
   --line 必须一起绑 —— 它默认是 #e8e2d9（浅暖灰），直接用在深底上就是两条
   扎眼的白线（.model-switch 的按钮分隔线就是这样漏出来的）。
   深底上的分隔线要用「浅色的低透明度」，不是浅色的实心。 */
.device-view,.stage-media{
  --muted:var(--on-stage-dim);
  --dim:var(--on-stage-dim);
  --line:rgba(255,246,235,.10);
  --line-hi:rgba(255,246,235,.20);
  --surface:rgba(255,246,235,.05);
  --surface-2:rgba(255,246,235,.08);
}

/* ---------- 连接页 ---------- */
.device-gate{background:var(--bg)}

.gate-connect{
  border-radius:var(--r-ctl);
  background:var(--accent);
  color:var(--accent-ink);
  box-shadow:var(--sh-accent);
  border:0;
}
.gate-connect:hover{background:var(--accent-display);color:#fff}
.gate-connect:disabled{box-shadow:none;opacity:.55}

/* ---------- 配置页外壳 ---------- */
.command-bar h1,.section-title h2{letter-spacing:-.022em}

/* 原 .profiles>div 是硬编码 #0d0f13（近黑），会在浅色页面上留下一条黑条 */
.profiles>div{background:transparent}
.profiles button{
  border-radius:var(--r-pill);
  border:1px solid var(--line);
  background:var(--surface);
}
.profiles button.active{
  background:var(--text);
  color:#fff;
  border-color:var(--text);
}

/* ---------- 卡片：Double-Bezel（外托 + 内芯）----------
   soft-skill 的核心手法：卡片不要平铺在背景上，要像「一块玻璃嵌在铝托里」。
   做法是两层同心圆角 —— 外层壳体比自己大一圈，带发丝描边；内芯有自己的底色
   和一道内高光。这里没有加新 DOM，用 box-shadow 的 spread 当外层壳体：
     第 1 层 0 0 0 1px  → 发丝描边
     第 2 层 inset 0 1px 0 → 顶部内高光（模拟光从上方来）
     第 3 层起 → 由 --sh-card 提供的外投影
   圆角用 calc 减掉描边宽度，保证内外圆角是同心而不是平行线。 */
.surface{
  position:relative;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-card);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9),var(--sh-card);
}

/* ---------- 设备舞台：唯一保留深色的一块 ----------
   inset 0 1px 0 rgba(255,255,255,.07) 是「机加工边缘」的内高光 ——
   深色面板顶部有一道若隐若现的亮边，才像有厚度的实物而不是一块色块。
   （ui-v2.css 里 .device-view::after 那条 150×3px 的琥珀横杠是上一版
   荧光绿主题的遗留装饰，没有功能含义，在下面显式关掉。） */
.device-view{
  background:linear-gradient(158deg,var(--stage-2) 0%,var(--stage) 62%);
  border:1px solid rgba(255,255,255,.07);
  border-radius:var(--r-stage);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.07),var(--sh-stage);
  color:var(--on-stage);
}
.device-view::after{display:none}
.device-view .device-heading h2{color:#fff}
.device-view .eyebrow{color:var(--accent)}
/* 舞台内的网格轴线：原为 #242933 冷蓝暗线，深底上改用暖白 */
.axis{background:rgba(255,246,235,.10)}
/* 圆形刻度参考线，原为 #20242c / #2a2f39 暗线，深底上几乎看不见 */
.mouse-canvas::before{border-color:rgba(255,246,235,.09)}
.mouse-canvas::after{border-color:rgba(255,246,235,.15)}
.model-switch{border-top:1px solid rgba(255,246,235,.09)}
.model-switch button{color:var(--on-stage-dim)}
.model-switch button.active{
  color:#fff;
  background:rgba(255,246,235,.06);
  box-shadow:inset 0 -2px 0 var(--accent);
}

/* ---------- 表单控件 ---------- */
.dpi-item{
  background:var(--surface-2);
  border:1px solid var(--line);
  border-radius:var(--r-ctl);
}
.dpi-item.active{
  background:var(--accent-soft);
  border-color:var(--accent);
}
.dpi-item input[type=checkbox]{accent-color:var(--accent)}

.sensor-row select,.function-select,.detail-select,.macro-row select{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-ctl);
  color:var(--text);
}
.sensor-row select:focus,.function-select:focus,.detail-select:focus,.macro-row select:focus{
  border-color:var(--accent);
  outline:2px solid rgba(201,122,43,.28);
  outline-offset:1px;
}
.debounce-field input{accent-color:var(--accent)}

/* 按钮矩阵行 */
.matrix-head{border-bottom:1px solid var(--line)}
.button-row{border-bottom:1px solid var(--line)}
.button-row:hover{background:var(--accent-soft)}
/* 按键编号「键帽」。
   原样式是 background:#0a0c0f（硬编码近黑）+ color:var(--text)，换浅色后
   --text 变深 → 黑底黑字，数字整片消失。这里显式指定：暖黑底 + 白字。 */
.button-id strong{
  background:var(--text);
  color:#fff;
  border-radius:8px;
  font-weight:700;
}
.matrix-badge,.map-option{border-radius:var(--r-ctl)}

/* ---------- 按钮：Button-in-Button ----------
   soft-skill：按钮里的箭头/加号不能裸放在文字旁边，要嵌进自己的圆形底座，
   像一块「岛」。好处是悬停时这个底座自己能动 —— 平移＋微放大产生内部张力，
   比整颗按钮只换背景色高级得多。
   结构不动（还是 <span>文字</span><b>图标</b>），把 <b> 本身做成圆底座。 */
.apply-button b,.gate-connect b{
  display:grid;place-items:center;
  width:28px;height:28px;border-radius:50%;
  background:rgba(46,28,8,.13);
  transition:transform .34s var(--ease-out),background .34s var(--ease-out);
}
.apply-button:hover b,.apply-button:focus-visible b{transform:translateX(3px);background:rgba(46,28,8,.22)}
.gate-connect:hover b,.gate-connect:focus-visible b{transform:translate(2px,-2px) scale(1.06);background:rgba(46,28,8,.22)}
/* 悬停时按钮整体轻微回落再弹起，模拟按住 —— 由 connect.css 里的
   .gate-connect / footer .apply-button 各自处理，避免这里的 transform 打架。 */

/* ---------- 固件 / 文件按钮 ---------- */
.file-button{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-ctl);
  color:var(--text);
}
.file-button:hover{
  border-color:var(--accent);
  background:var(--accent-soft);
  color:var(--accent-text);
}

/* ---------- 底栏 ---------- */
footer{
  background:rgba(255,253,251,.90);
  border-top:1px solid var(--line);
  -webkit-backdrop-filter:blur(18px) saturate(1.5);
  backdrop-filter:blur(18px) saturate(1.5);
}
.text-button{border-radius:var(--r-ctl)}
.text-button:hover{color:var(--accent-text)}

.apply-button{
  border-radius:var(--r-ctl);
  background:var(--accent);
  color:var(--accent-ink);
  border:0;
  box-shadow:var(--sh-accent);
}
.apply-button:hover{background:var(--accent-display);color:#fff}
.apply-button:disabled{box-shadow:none;opacity:.55}

/* ---------- 浮层 ---------- */
#toast{
  border-radius:var(--r-ctl);
  background:var(--text);
  color:#fff;
  box-shadow:var(--sh-raise);
}
dialog{
  border-radius:var(--r-card);
  border:1px solid var(--line);
  background:var(--surface);
  color:var(--text);
  box-shadow:var(--sh-raise);
}
dialog::backdrop{background:rgba(26,22,19,.44);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}

/* ---------- 焦点可见性（键盘操作）---------- */
:where(button,select,input,a,[tabindex]):focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:var(--r-ctl);
}

/* ============================================================
   克制的微交互（全站）
   ============================================================ */

/* 1) 指针下的卡片聚光：卡片在光标位置透出一团暖光。
      由 motion.js 把光标位置写成 --mx/--my（卡片内百分比），这里只负责画。
      默认 --spot 为 0，所以没鼠标（触屏 / 键盘）时它完全不出现。

      实现上刻意【不用】「mask 挖空只剩边框」那套写法。试过：
        padding:1px + 双层 mask + mask-composite:exclude
      本意是只点亮 1px 的边框圈，但多层 mask 简写与 -webkit-mask-composite
      在各家实现里对不齐，实测整个伪元素直接不渲染（截图里一点光都没有）。
      改成不依赖 mask 的两层，都稳：
        ::before  内部辉光，跟着光标走，只过渡 opacity
        :hover    inset 内描边，给「边缘亮起来」的那一下
      内部辉光才是真正被眼睛看到的部分，边缘描边只是收口。 */
.spotlight{position:relative}
.spotlight::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  opacity:var(--spot,0);transition:opacity .38s var(--ease-out);
  background:radial-gradient(280px circle at var(--mx,50%) var(--my,50%),
              rgba(201,122,43,.15),rgba(201,122,43,.05) 45%,transparent 70%);
}

/* 2) 按键行悬停微抬。1px 就够 —— 多了会显得在「跳」。 */
.button-row{transition:background .22s var(--ease-out),transform .22s var(--ease-out)}
.button-row:hover{transform:translateX(2px)}

/* 3) 卡片悬停：边缘亮一下 + 投影加深。
      用 box-shadow 而不是 transform 位移 —— 卡片在栅格里一挪会把相邻元素
      挤动，整页跟着抖，在工具型界面里很烦。 */
.surface{transition:box-shadow .34s var(--ease-out)}
.surface:hover,.surface:focus-within{
  box-shadow:inset 0 0 0 1px rgba(201,122,43,.32),inset 0 1px 0 rgba(255,255,255,.9),var(--sh-raise);
}

/* 4) 状态点呼吸。只在已连接时跑 —— 未连接时它是灰的，动起来会误导。 */
@media (prefers-reduced-motion:no-preference){
  .connection.connected #statusDot{animation:dotBreath 2.6s var(--ease-io) infinite}
  /* DPI 选中的那一格：选中瞬间给一次轻微的「落定」脉冲 */
  .dpi-item.active{animation:chipSettle .42s var(--ease-out)}
}
@keyframes dotBreath{
  0%,100%{box-shadow:0 0 0 3px rgba(201,122,43,.22),0 0 10px rgba(201,122,43,.50)}
  50%    {box-shadow:0 0 0 5px rgba(201,122,43,.10),0 0 16px rgba(201,122,43,.70)}
}
@keyframes chipSettle{
  from{transform:scale(.965)}
  to{transform:none}
}

/* ---------- 自定义光标 ----------
   从新版桌面驱动的 .cur 提取，内联为 data URI。
   内联原因：Cloudflare Pages 把 .cur 当 application/octet-stream 下发，
   且带 X-Content-Type-Options:nosniff，Chrome 可能拒绝解码成一幅图
   → 光标静默失效。热点坐标取自 .cur 文件头的 xHotspot/yHotspot。 */
button,.gate-connect,.apply-button,.file-button,.outline-button,
.model-switch button,.profiles button,select,
input[type=checkbox],input[type=range],.dpi-item
{cursor:url("data:image/x-icon;base64,AAACAAEAEBACAAgABQCwAAAAFgAAACgAAAAQAAAAIAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAPwAAAD8AAAB/AAAA/4AAAf+AAAH/wAAD/8AAB3/AAAZ/4AAAf2AAANtgAADbIAABmwAAAZsAAAAYAAAAAAAAD4BwAA+AcAAPAHAADgAwAAwAMAAMABAACAAQAAAAEAAAAAAACQAAAA4AAAAOAAAADABQAAwAcAAOQPAAD+fwAA") 8 4,pointer}

button:active,.gate-connect:active,.apply-button:active,.file-button:active,
.outline-button:active,.profiles button:active
{cursor:url("data:image/x-icon;base64,AAACAAEAICACAAcAAwAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAB+AAAA/gAAAf8AAAP/AAAD/4AAA/+AAAD/gAAA/4AAAf6AAAG2AAAAAAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////////////////////AP///wD///4A///8AH//+AB///gAP//4AD///AA///4AP//8AD///AB///5J////////8=") 7 3,pointer}

.text-button,#resetBtn
{cursor:url("data:image/x-icon;base64,AAACAAEAICAQAAcAAwDoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAAAIAAAAAAAAAAAAAEAAAAAAAAAAAAAAAgICAAP///wAAAP8AQED/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEAABEAAAAAAAAAAAAAAAAQ0AENAAAAAAAAAAAAAAAAAQ0Q0AAAAAAAAAAAAAAAiIiQzQAAAAAAAAAAAAAAAIiIkM0AAAAAAAAAAAAAAAiIiQ0Q0AAAAAAAAAAAAACIiJDQhQ0AAAAAAAAAAAAIiIiRCIARAAAAAAAAAAAACIiIiIiIAAAAAAAAAAAAAAiIiIiIiAAAAAAAAAAAAAAACIiIiIgAAAAAAAAAAAAAAAiIiIiIAAAAAAAAAAAAAACIiIiICAAAAAAAAAAAAAAAiAiAiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////////////////////////////////////////////////////////////////////////////zz///8Y////gf//8AP///AD///gAf//wAD//4AE//+AA///gAP//8AD///gA///wAP//8AH///kn////////w==") 7 3,pointer}

/* ---------- 动效偏好 ---------- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
}
