/* ============================================================================
   设计令牌(单一可信源)。所有窗口(备稿 index / 输出 output / 投放 cast / 遥控 remote)
   都在自身 HTML 里最先加载本文件 → 组件只用 var(--语义令牌),永不写字面色。
   两层:primitive 色板 + semantic 语义;主题 = 只重映射 semantic 一处(:root.theme-feishu)。
   护栏:npm run lint:tokens —— 令牌定义区(本文件 + -- 行)之外禁止有色字面。
   ============================================================================ */
:root {
  /* ---- 原始色板 primitive ---- */
  --coral-700: #b1542f;
  --coral-600: #c55c3b;
  --coral-500: #d97757;
  --coral-400: #e4835c;
  --coral-300: #e8875f;
  --blue-600: #245bdb;
  --blue-500: #3370ff;
  --blue-400: #4e83fd;
  --blue-300: #7aa5ff;

  /* ---- 语义 · 强调色(摄影棚=珊瑚;.theme-feishu 换蓝。组件只用这些)---- */
  --accent: var(--coral-500);
  --accent-strong: var(--coral-600);
  --accent-ink: var(--coral-700); /* 强调色当文字(浅底) */
  --accent-weak: rgba(217, 119, 87, 0.1); /* 强调色淡底 */
  --accent-line: rgba(217, 119, 87, 0.4); /* 强调色描边 */
  --accent-glow: rgba(217, 119, 87, 0.5); /* 强调色光晕(按钮阴影) */
  --on-accent: #fff;
  /* 强调色在深色 HUD 上:文字亮一档、淡底/描边更浓 */
  --accent-ondark: var(--coral-300);
  --accent-ondark-weak: rgba(217, 119, 87, 0.14);
  --accent-ondark-line: rgba(217, 119, 87, 0.42);
  --focus: var(--accent);
  /* 提词舞台焦点标记:随 app 主题翻(飞书→蓝,v1.3 ① 主题贯穿输出屏);
     预览与输出必须同色 → appTheme 会同步进输出/投放窗 */
  --tp-accent: var(--coral-500);
  /* 飞书品牌色:飞书来源稿的入口图标恒用它(=品牌标识,不是 app 强调色,两主题都蓝)*/
  --feishu-brand: var(--blue-500);

  /* ---- 语义 · 备稿面/字/线(摄影棚暖色;.theme-feishu 换冷色)---- */
  --desk: #f1eee7;
  --desk-deep: #e9e5da;
  --panel: #f7f5ef;
  --side: #efebe1;
  --paper: #ffffff;
  --ink: #211f1b;
  --ink-2: #57534a;
  --ink-3: #a39e92;
  --hair: rgba(30, 25, 15, 0.07);
  --hair-2: #ddd9ce; /* 实体描边:段控/键帽/幽灵钮(比 --hair 重一档) */

  /* ---- 语义 · 状态色(语义固定,别拿来当交互强调色)---- */
  --mint: #2aae68;
  --mint-deep: #1e8f53;
  --mint-bg: rgba(42, 174, 104, 0.1);
  --red: #e8584a;
  --amber: #e9b93c;

  /* ---- 主 CTA(上镜大钮):摄影棚=亮面黑药丸;飞书=蓝 ---- */
  --primary-bg: linear-gradient(180deg, #403d38, #181613);
  --primary-fg: #f7f5ef;
  --primary-shadow: 0 8px 20px -6px rgba(20, 15, 5, 0.45);
  /* ---- 彩色阴影(第二十七轮用户拍板「年轻化」)------------------------------
     纪律:**着色阴影只跟着强调色走,而且只给「能按的主动作」与「被按下的卡片」。**
     它不是装饰 —— 一片到处发光的界面等于没有重点(与「蓝只做主动作,绝不当装饰」同一条)。
     摄影棚主题是暖珊瑚,飞书主题是蓝;两套都在这里定,组件只认令牌。 */
  --sh-accent-1: 0 4px 14px -6px rgba(217, 119, 87, 0.45), 0 1px 3px rgba(20, 15, 5, 0.08);
  --sh-accent-2: 0 10px 26px -8px rgba(217, 119, 87, 0.5), 0 2px 6px rgba(20, 15, 5, 0.1);
  --sh-accent-soft: 0 6px 20px -10px rgba(217, 119, 87, 0.4);

  /* ---- 语义 · HUD 深色(上镜,两主题都深,不随主题变;只 accent 随主题)---- */
  --hud-bg: #0c0b09;
  --hud-surface: #17140f;
  --hud-surface-2: #1b1814;
  --hud-surface-3: #242019;
  --hud-popover: #1f1b16;
  --hud-line: #241f19;
  --hud-line-2: #2c2823;
  --hud-line-3: #34302a;
  --hud-text-0: #f5f3ec;
  --hud-text-1: #edebe4;
  --hud-text-2: #a8a59c;
  --hud-text-3: #6d6a62;
  --hud-text-4: #57544c;
  --hud-ok: #4fc487;
  --hud-scrim: rgba(18, 15, 12, 0.84); /* 舞台上的深色浮签底(待命带/角标) */
  --hud-scrim-red: rgba(30, 10, 8, 0.82); /* 告警遮罩底 */

  /* ---- 语义 · 场记板(稿头数据板,0714 #4):深底白字的桌面道具,面色随主题冷暖 ---- */
  --slate-bg: #1b1814;
  --slate-bg-2: #262119;
  --slate-text: #f5f3ec;
  --slate-dim: #a8a59c;
  --slate-line: rgba(245, 243, 236, 0.12);
  /* ---- 语义 · 状态色扩展变体(--mint/--red/--amber 的淡底/描边/光晕/深色面文字;语义固定,别当交互色)---- */
  --mint-bg-strong: rgba(42, 174, 104, 0.16);
  --hud-ok-line: rgba(79, 196, 135, 0.28);
  --amber-ink: #8a6d16;
  --amber-bg: #f7ebd2;
  --amber-line: rgba(233, 185, 60, 0.38);
  --red-bg: rgba(232, 88, 74, 0.1);
  --red-line: rgba(232, 88, 74, 0.42);
  --red-glow: rgba(232, 88, 74, 0.55);
  --red-ondark: #f0b4ab;
  --red-ondark-weak: #c99a92;
  /* 提词舞台强调线(章节分隔渐隐):随 --tp-accent 一起翻主题(① 主题贯穿输出屏)*/
  --tp-accent-line: rgba(217, 119, 87, 0.55);
  /* 飞书品牌淡底(来源稿行 hover;恒蓝,不随 app 主题)*/
  --feishu-brand-weak: rgba(51, 112, 255, 0.12);
  /* 画布暖色颗粒(仅摄影棚;飞书主题移除)*/
  --grain: rgba(60, 50, 30, 0.015);

  /* ---- 外拍 · 灯下玻璃(第二十八轮,仅 pad/remote 经 outdoor.css 消费)------------
     世界观见 docs/视觉方向-灯下玻璃.md。Mac 不加载 outdoor.css,这些令牌对它是死代码。
     纪律:玻璃只有两种厚度(bg / bg-strong);光晕只给状态与主动作(tally 律)。 */
  /* 光场:打了灯的浅色现场(摄影棚主题=暖珊瑚光;飞书主题在下方重映射为蓝光场)。
     第二十九轮收敛:**一枚标志性主光**(左上),不许再撒一地光斑(用户:太散太随意)。
     饱和度仍要敢给 —— 淡到看不出=没有打灯(第二十八轮实证)。 */
  --field-bg:
    radial-gradient(64% 52% at 12% 0%, rgba(217, 119, 87, 0.3), transparent 62%),
    linear-gradient(172deg, #fbf9f4, #efece3);
  /* 玻璃面(白系,主题无关):两种厚度 + 受光内边 + 冷调外轮廓 */
  --glass-bg: rgba(255, 255, 255, 0.62);
  --glass-bg-strong: rgba(255, 255, 255, 0.86);
  /* 浅色世界的遮罩:轻黑 + 霜化(0.84 深黑会把玻璃压成灰泥 —— 第一版实证)。
     深色舞台上同一枚遮罩照常成立:黑上加轻黑无感,霜化反而柔化背后正文。 */
  --field-scrim: rgba(0, 0, 0, 0.32);
  --glass-line: rgba(255, 255, 255, 0.75); /* 顶部受光边(inset) */
  --glass-ring: rgba(255, 255, 255, 0.55); /* 整圈内描边(inset) */
  --glass-edge: rgba(32, 28, 20, 0.07); /* 外轮廓发丝(浅底上定形) */
  --glass-blur: saturate(1.7) blur(26px);
  --glass-blur-strong: saturate(1.8) blur(36px);
  /* 冰晶芯片(软立体图标底座/键帽面):白→冷白微渐变 */
  --chip-bg: linear-gradient(180deg, #ffffff, #f2f0ea);
  --chip-bg-active: linear-gradient(180deg, #f4f2ec, #ece9e0);
  /* 深色玻璃(黑舞台上的控制坞/深色抽屉) */
  --glass-d-bg: rgba(24, 22, 18, 0.6);
  --glass-d-bg-strong: rgba(21, 19, 16, 0.82);
  --glass-d-line: rgba(255, 255, 255, 0.12); /* 深玻璃受光边 */
  --glass-d-ring: rgba(255, 255, 255, 0.09);
  --glass-d-key: rgba(255, 255, 255, 0.065); /* 深玻璃键面 */
  --glass-d-key-2: rgba(255, 255, 255, 0.12); /* 深玻璃键按下 */
  /* tally 二色制的中性极(第三十四轮):暂停点亮用它,不用 --ink。
     --ink 是正文墨(#211f1b,近黑),做成整块键面后是全屏最重的一坨 ——
     而「停住」是安静的事实,不该压过任何东西。层级反了,用户报「颜色太深」。
     降一阶到石墨:仍是明确的中性实心(与未点亮的浅键一眼可分),但不再抢主。
     第三十轮「暂停=中性墨面」的裁决不动,动的只是这一阶明度。 */
  --tally-neutral: #57534a;
  --tally-neutral-ink: #ffffff;
  /* tally 光晕:只给「正在生效的状态」与「主动作」。强调色系随主题重映射 */
  --glow-accent: 0 10px 28px -8px rgba(217, 119, 87, 0.55);
  --glow-accent-big: 0 18px 44px -12px rgba(217, 119, 87, 0.55);
  --glow-ok: 0 8px 22px -8px rgba(42, 174, 104, 0.5);
  --glow-amber: 0 8px 22px -8px rgba(233, 185, 60, 0.55);
  --glow-red: 0 8px 24px -8px rgba(232, 88, 74, 0.55);
  /* 玻璃复合投影(受光内边 + 内描边 + 外轮廓 + 灰阶浮影,一整条成套 ——
     几何护栏要求 box-shadow 整段走令牌,复合材质只能在这里合成):
     face = 悬浮卡;face-2 = 主板/坞/监视器(更高悬浮);inset = 哑光只读仪表面。 */
  --sh-glassface:
    inset 0 1px 0 rgba(255, 255, 255, 0.75), inset 0 0 0 1px rgba(255, 255, 255, 0.42),
    0 0 0 1px rgba(32, 28, 20, 0.07), 0 2px 8px rgba(0, 0, 0, 0.05),
    0 22px 48px -20px rgba(0, 0, 0, 0.16);
  --sh-glassface-2:
    inset 0 1px 0 rgba(255, 255, 255, 0.8), inset 0 0 0 1px rgba(255, 255, 255, 0.5),
    0 0 0 1px rgba(32, 28, 20, 0.07), 0 4px 12px rgba(0, 0, 0, 0.06),
    0 32px 64px -24px rgba(0, 0, 0, 0.24);
  --sh-glassinset:
    inset 0 1px 3px rgba(0, 0, 0, 0.06), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
  /* 冰晶键帽(不透明微渐变面):静止 / 按下 */
  --sh-glasskey:
    inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 0 0 1px rgba(32, 28, 20, 0.08),
    0 2px 6px rgba(0, 0, 0, 0.07);
  --sh-glasskey-down: inset 0 1.5px 4px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(32, 28, 20, 0.08);
  /* 深色玻璃(黑舞台上的控制坞 / 深色抽屉)与深玻璃键 */
  --sh-d-glassface:
    inset 0 1px 0 rgba(255, 255, 255, 0.12), inset 0 0 0 1px rgba(255, 255, 255, 0.07),
    0 12px 44px -12px rgba(0, 0, 0, 0.65);
  --sh-d-glasskey:
    inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.07),
    0 2px 6px rgba(0, 0, 0, 0.3);
  --sh-d-glasskey-down: inset 0 1.5px 4px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.06);
  /* 监视器机身(remote 的 hero):深色实体 + 大浮影(比玻璃卡更重的存在) */
  --sh-monitor:
    inset 0 1px 0 rgba(255, 255, 255, 0.09), inset 0 0 0 1px rgba(255, 255, 255, 0.06),
    0 6px 16px rgba(0, 0, 0, 0.12), 0 36px 72px -28px rgba(0, 0, 0, 0.4);
  /* tally 点亮键(实心强调色 + 光晕;随主题重映射) */
  --sh-lit: inset 0 1px 0 rgba(255, 255, 255, 0.32), 0 10px 28px -8px rgba(217, 119, 87, 0.55);

  --serif: 'Noto Serif SC', 'Songti SC', 'STSong', serif;
  /* 器材读数的语言(字数/速度/配对码 …)。第二十七轮补:此前 `var(--mono)` 在
     pad.html 与共享层各被引用过,却**从没有定义过** —— 那些「mono 读数」一直在用
     继承字体。没有一道护栏能拦「引用了不存在的令牌」,已补进 lint:tokens。 */
  --mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  /* —— 动效系统:统一缓动与时长令牌 —— */
  --ease: cubic-bezier(0.22, 1, 0.36, 1); /* 出场/移动:后段舒缓 */
  --ease-io: cubic-bezier(0.6, 0, 0.35, 1); /* 状态切换 */
  --dur-1: 0.12s; /* 微交互:hover / 按下 */
  --dur-2: 0.2s; /* 小元素:开关 / 角标 */
  --dur-3: 0.34s; /* 面板 / 转场 */
  --dur-morph: 0.44s; /* 一镜到底:纸张渐黑成屏幕 */
}

/* 挂在 :root(documentElement):主题预染脚本在 <head> 里同步执行,那一刻 body 还不存在。
   (0720 首帧闪摄影棚根治:主题类必须在任何渲染发生前就位) */
:root.theme-feishu {
  --accent: var(--blue-500);
  --accent-strong: var(--blue-600);
  --accent-ink: var(--blue-500);
  --accent-weak: rgba(51, 112, 255, 0.1);
  --accent-line: rgba(51, 112, 255, 0.4);
  --accent-glow: rgba(51, 112, 255, 0.5);
  --accent-ondark: var(--blue-300);
  --accent-ondark-weak: rgba(51, 112, 255, 0.16);
  --accent-ondark-line: rgba(51, 112, 255, 0.55);
  --tp-accent: var(--blue-500);
  --tp-accent-line: rgba(51, 112, 255, 0.5);
  --desk: #f4f5f7;
  --desk-deep: #eaecef;
  --panel: #ffffff;
  --side: #f7f8fa;
  --paper: #ffffff;
  --ink: #1f2329;
  --ink-2: #51565d;
  --ink-3: #8f959e;
  --hair: #e4e6eb;
  --hair-2: #d5d8dd;
  --mint: #34c724;
  --mint-deep: #2ea121;
  --primary-bg: #3370ff;
  --primary-fg: #fff;
  --primary-shadow: 0 6px 18px -7px rgba(51, 112, 255, 0.5);
  --sh-accent-1: 0 4px 14px -6px rgba(51, 112, 255, 0.42), 0 1px 3px rgba(31, 35, 41, 0.06);
  --sh-accent-2: 0 10px 26px -8px rgba(51, 112, 255, 0.48), 0 2px 6px rgba(31, 35, 41, 0.08);
  --sh-accent-soft: 0 6px 20px -10px rgba(51, 112, 255, 0.38);
  /* 状态色淡底饱和度 +1 档(用户「UI 太呆」的一部分:淡到看不出是状态) */
  --mint-bg: rgba(52, 199, 36, 0.16);
  --amber-bg: #fbeccd;
  --red-bg: rgba(232, 88, 74, 0.14);
  --slate-bg: #1e232b;
  --slate-bg-2: #29303a;
  --slate-text: #f2f4f7;
  --slate-dim: #9aa3af;
  --slate-line: rgba(240, 244, 250, 0.12);
  /* tally 中性极(飞书主题):同 :root 的理由,取本主题的冷石墨 */
  --tally-neutral: #51565d;
  --tally-neutral-ink: #ffffff;
  /* 外拍 · 灯下玻璃(飞书主题):蓝系光场 + 蓝 tally。玻璃面白系不随主题。
     一枚标志性蓝主光(左上,第二十九轮收敛),底是干净的冷白渐变。 */
  --field-bg:
    radial-gradient(64% 52% at 12% 0%, rgba(51, 112, 255, 0.26), transparent 62%),
    linear-gradient(172deg, #f8fafd, #ebeef5);
  --chip-bg: linear-gradient(180deg, #ffffff, #f1f4fa);
  --chip-bg-active: linear-gradient(180deg, #f2f5fb, #e8edf6);
  --glass-edge: rgba(31, 35, 41, 0.07);
  --glow-accent: 0 10px 28px -8px rgba(51, 112, 255, 0.5);
  --glow-accent-big: 0 18px 44px -12px rgba(51, 112, 255, 0.52);
  --glow-ok: 0 8px 22px -8px rgba(52, 199, 36, 0.45);
  --sh-glassface:
    inset 0 1px 0 rgba(255, 255, 255, 0.75), inset 0 0 0 1px rgba(255, 255, 255, 0.42),
    0 0 0 1px rgba(31, 35, 41, 0.07), 0 2px 8px rgba(0, 0, 0, 0.05),
    0 22px 48px -20px rgba(0, 0, 0, 0.16);
  --sh-glassface-2:
    inset 0 1px 0 rgba(255, 255, 255, 0.8), inset 0 0 0 1px rgba(255, 255, 255, 0.5),
    0 0 0 1px rgba(31, 35, 41, 0.07), 0 4px 12px rgba(0, 0, 0, 0.06),
    0 32px 64px -24px rgba(0, 0, 0, 0.24);
  --sh-glasskey:
    inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 0 0 1px rgba(31, 35, 41, 0.08),
    0 2px 6px rgba(0, 0, 0, 0.07);
  --sh-glasskey-down: inset 0 1.5px 4px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(31, 35, 41, 0.08);
  --sh-lit: inset 0 1px 0 rgba(255, 255, 255, 0.32), 0 10px 28px -8px rgba(51, 112, 255, 0.55);
}
/* ============================================================================
   D1 · 形的单一可信源(第二十五轮建立)。tokens.css 守「值(颜色)」,本文件守「形」——
   间距 / 圆角 / 阴影 / 字阶 / 字重 / 控件高度 / 图标尺寸 / 层叠 / 边框。

   为什么必须有它(可证伪的诊断,不是审美主张):
     动手前对三端全部样式做过频次统计 ——
       圆角 19 种 · 字号 26 种 · 字重 7 种 · 间距 32 种 · 控件高度 43 种 ·
       **阴影 71 条各不相同,分布在 79 次使用里(90% 只用过一次)**。
     用户的原话是「太像 AI 临时 vibe coding 出来的」「每个元素都要考虑到」。
     那个观感的物理成因就是这组数:**每一个数都是当场新编的,彼此没有关系**。
     顶尖产品的「精雕细琢」恰恰相反 —— 每一个数都来自一张很短的表。所以解法不是
     逐处打磨(那是局部最优),是**把形收敛成真源,再让偏离它的写法当场失败**
     (第六道护栏 lint:geometry,与 lint:tokens 同一个形状)。

   纪律:
     1. 组件区只准用 var(--r-* / --t-* / --sp-* / --h-* / --sh-* / --z-*),不准写裸值;
     2. 加档比用错档更糟 —— 先问「已有的哪一档最接近」,真的没有再加,加了要在这里写明为什么;
     3. 本表服务浅色(备稿面)与深色(HUD 舞台)两套表面,阴影分 --sh-* / --sh-d-*,其余共用。

   护栏:`npm run lint:geometry`(迁移清单见 scripts/lint-geometry.mjs 顶部)。
   ============================================================================ */
:root {
  /* ---- 间距 9 档(吸收原 32 种)----------------------------------------------
     1,2→sp-1 | 3,4→sp-2 | 5,6,7→sp-3 | 8,9→sp-4 | 10,11,12,13→sp-5 |
     14,15,16,18→sp-6 | 20,22,26,28→sp-7 | 32,34,40→sp-8 | 46,48,56→sp-9      */
  --sp-1: 2px;
  --sp-2: 4px;
  --sp-3: 6px;
  --sp-4: 8px;
  --sp-5: 12px;
  --sp-6: 16px;
  --sp-7: 24px;
  --sp-8: 32px;
  --sp-9: 48px;

  /* ---- 圆角 6 档 + 圆(吸收原 19 种)------------------------------------------
     2,3→xs | 5,6,7,8,9→sm | 10,11,12,13→md | 14,16→lg | 18→xl | 99,999→full  */
  /* 第二十七轮用户拍板「大圆角 + 彩色阴影」:md 12→16、lg 16→20、xl 20→26。
     只调形,不动世界观(场记板 / 文稿浅上镜深 / 蓝只做主动作 三条不变)。
     纪律不变:**圆角表达「这是什么构件」,不表达「这个构件多大」** —— 按钮只有一个圆角;
     唯一的例外是 `.sj-btn.lg`(= 一个界面的主动作,走胶囊),那是**角色**差异不是尺寸差异。 */
  --r-xs: 5px;
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 20px;
  --r-xl: 26px;
  --r-full: 999px;
  --r-circle: 50%;

  /* ---- 字阶 8 阶(吸收原 26 种)。按**语义**命名,不按尺寸 —— 换档时改的是
     「这行字是什么身份」,不是「它多大」。中文界面 11px 以下不可读,故下限即 --t-label。 */
  --t-label: 11px; /* 字距标签 / 角标 / 序号     ← 9.5,10,10.5,11,11.5 */
  --t-caption: 12.5px; /* 辅助说明 / meta / chip     ← 12,12.5          */
  --t-body-s: 13.5px; /* 次级正文 / 键帽文字       ← 13,13.5,14       */
  --t-body: 15px; /* 正文 / 列表行             ← 14.5,15          */
  --t-title-s: 17px; /* 卡片标题 / 弹层小标题     ← 16,17            */
  --t-title: 20px; /* 面板标题 / 品牌           ← 18,19,20         */
  --t-display: 26px; /* 大读数(剩余字数)         ← 22,25,26,28      */
  --t-hero: 34px; /* 配对码等超大数字          ← 32               */

  /* ---- 字重 4 档(吸收原 7 种:650,750→bold;800→bold+字距)------------------
     中文小字用 500 比 400 清晰,故 normal 只给长正文。 */
  --w-normal: 400;
  --w-medium: 500;
  --w-semibold: 600;
  --w-bold: 700;

  /* ---- 控件高度 5 档(吸收原 43 种里真正属于「可点控件」的那些)---------------
     44 = iOS HIG 触控下限,也是本项目 P30 断言的那条线。 */
  --h-xs: 28px; /* 内联小键(撤销/链接式) */
  --h-sm: 34px; /* chip / 状态键          */
  --h-md: 44px; /* 标准键(触控下限)      */
  --h-lg: 52px; /* 主 CTA                 */
  --h-xl: 60px; /* 走带键(现场盲按)      */
  --tap: 44px; /* 触屏可点元素的最小命中区(护栏与验收共用同一个数) */

  /* ---- 可操作面板的限宽(第三十四轮)---------------------------------------
     手指的够得着范围是**物理量**,不随承载它的抽屉有多宽而变。抽屉在 iPad 与横屏手机上
     都是 min(560px,100vw),照着铺就得到 448px 的滑杆 —— 拇指要横跨大半块屏才拖得动一档。
     components.ts:337 早就把这条纪律写成注释了(「面板要限宽…滑杆 1162px」),
     但没有档、也没有护栏,于是同一个病换个表面又长出来。
     430 的来历:实测标签+读数占 80px,滑杆落在 ~350px,在 DENSITY 断言的 360 上限内留出余量
     (初值取 460 时实测滑杆 380px,仍越界 —— 数是量出来的,不是估出来的)。 */
  --panel-max: 430px;

  /* ---- 操作台密度阶梯 --deck-*(第三十五轮)---------------------------------
     用户第三十四轮第 8 条的真因,不是「缩放没处理好」,是**保真层与操作层用了同一套尺度法则**:
       · 舞台正文走 cqw(比例)—— 对的,天生抗缩放;
       · 操作台走**固定 px + 两个硬断点**(max-height: 520/620),而 body{height:100dvh;
         overflow:hidden} 让溢出**静默裁掉,连滚都不给**。
     缩放只是「可用空间变了」的一种:横屏 / iPhone SE / 微信内嵌浏览器的导航条 / iPad 分屏
     全是同一个变量。所以解法不是给每个尺寸打补丁,是让操作台**对可用高度连续插值**。

     四个量一起走,才不会出现「键缩了、间距没缩,于是更挤」:
       --deck-h    键高      地板 --tap(44px 是 iOS 命中区下限,永不跌破)
       --deck-gap  键间距
       --deck-pad  模块内边距
       --deck-t    操作台字阶(读数与键帽)

     dvh 而不是 vh:iOS Safari 的地址栏收放会改变可用高度,dvh 跟着变,vh 不变
     (「导航条一出现就被裁一截」正是这条)。上下限来自实测:
       340px 高(iPhone 横持)时 --deck-h 落到 44 地板;852px 高(iPhone 竖持)时封顶 56。 */
  --deck-h: clamp(var(--tap), calc(3.2dvh + 33px), 56px);
  /* 走带键那一档:现场盲按要更大,所以封顶取 --h-xl(60);但同样有地板,
     不会在横持手机上把整条轨顶出视口。852px 高时 = 60(与旧值一致,竖屏零变化)。 */
  --deck-h-lg: clamp(var(--tap), calc(4.4dvh + 33px), 60px);
  --deck-gap: clamp(6px, 1.4dvh, 14px);
  --deck-pad: clamp(8px, 1.8dvh, 20px);
  --deck-t: clamp(13px, calc(1.1dvh + 9px), 17px);

  /* ---- 弹窗宽度档表(第三十六轮,用户第三十四轮第 20 条)---------------------
     此前全仓 11 个 dialog 的宽度是六个散落像素(340/400/420/480/560/660),
     而 `#confirm-dlg` **一个宽度都没写** —— 它收缩到内容宽,于是 218×171,
     比例 1.28 = 近方形。用户说的「所有弹窗都应该像 iOS 那样是长矩形」就是这一处。
     440 的来历:标题 + 一行事实在 --t-body-s 下约 400px,加两侧内边距,
     比例落到 1.8:1 上下(iOS Alert 的观感区间)。
     ⚠️ 圆角**不进这张表** —— 弹窗圆角是世界观的一部分:Mac(摄影棚桌面)16、
     外拍(灯下玻璃)26。收成一个值等于把两个世界拌在一起(CLAUDE.md 第一条纪律)。 */
  --dlg-confirm: 440px; /* 破坏性确认(三端同一枚构件) */
  --dlg-form: 420px; /* 表单类:新建文稿 / 反馈 / 快捷键 */
  --dlg-wide: 560px; /* 宽面:配对 / 回执 / 设置 */

  /* ---- 图标 3 档(吸收原 14~21 的七种)-------------------------------------- */
  --ic-sm: 16px;
  --ic-md: 20px;
  --ic-lg: 24px;

  /* ---- 阴影:浅色面 4 档 + 键帽材质 1 -----------------------------------------
     **纪律:内高光(inset 白)只属于「键」这一阶。** 现状是灯/chip/卡片/面板人人都有
     内高光,材质因此失去含义 —— 这正是「每个元素单独想的」那种观感的来源之一。
     飞书式克制:分层主要靠 1px 描边 + 大留白,阴影只在真正「浮起」时出现。 */
  --sh-hair: 0 0 0 1px var(--hair); /* 贴合:不浮起,只有边界 */
  --sh-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 4px 12px -6px rgba(0, 0, 0, 0.1); /* 卡片(极轻) */
  /* 文稿卡:比 --sh-1 更有体积、暖调、仍克制(不是重投影)——「软阴影塑体积」在浅底上的落地 */
  --sh-card: 0 1px 2px rgba(40, 33, 20, 0.05), 0 14px 32px -16px rgba(40, 33, 20, 0.2);
  --sh-2: 0 8px 28px -10px rgba(0, 0, 0, 0.22), 0 0 0 1px var(--hair); /* 浮层/抽屉 */
  --sh-3: 0 24px 64px -20px rgba(0, 0, 0, 0.3), 0 0 0 1px var(--hair); /* 模态 */
  --sh-key: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 1px 2px rgba(0, 0, 0, 0.06),
    0 0 0 1px var(--hair); /* 键帽:唯一允许内高光的材质 */

  /* ---- 阴影:深色 HUD 面 4 档 + 键帽材质 1 ------------------------------------ */
  --sh-d-hair: 0 0 0 1px var(--hud-line-2);
  --sh-d-1: 0 2px 6px rgba(0, 0, 0, 0.34), 0 0 0 1px var(--hud-line-2);
  --sh-d-2: 0 -8px 28px -12px rgba(0, 0, 0, 0.7), 0 0 0 1px var(--hud-line-2);
  --sh-d-3: 0 18px 48px -14px rgba(0, 0, 0, 0.75), 0 0 0 1px var(--hud-line-2);
  --sh-d-key: inset 0 1px 0 rgba(255, 255, 255, 0.07), 0 2px 5px rgba(0, 0, 0, 0.35),
    0 0 0 1px var(--hud-line-2);
  /* 内嵌面(只读仪表:哑光、凹进去、绝不像键) */
  --sh-d-inset: inset 0 1px 3px rgba(0, 0, 0, 0.35), inset 0 -1px 0 rgba(255, 255, 255, 0.03);
  /* ---- 阴影:场记板面(--slate-*)的键帽 1 档 -----------------------------------
     第二十七轮新增。为什么要第三套而不是复用 --sh-d-key:场记板与 HUD 是两族令牌
     (板随主题换冷暖、HUD 恒深),混用会让顶栏在飞书主题下偏暖(复盘 89 同族的坑)。
     内高光比 HUD 更弱 —— 板比 HUD 亮一档,同样的高光在板上会糊成一条白边。 */
  --sh-s-key: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 0 0 1px var(--slate-line);

  /* ---- 层叠 6 档(原先散落 4/5/6/7/8/10/19/20/30/40/50)----------------------- */
  --z-under: -1; /* 沉底层:光场颗粒(在内容之下、本上下文之内) */
  --z-base: 0; /* 舞台 / 内容             */
  --z-chrome: 10; /* 常驻控制层(不夺焦)     */
  --z-scrim: 20; /* 遮罩                    */
  --z-layer: 30; /* 抽屉 / sheet            */
  --z-pop: 40; /* 锚定浮层 / 自检          */
  --z-toast: 50; /* 回执条 / 撤销条(最高)  */

  /* ---- 边框 ---------------------------------------------------------------- */
  --bd-hair: 1px solid var(--hair);
  --bd-solid: 1px solid var(--hair-2);
  --bd-d-hair: 1px solid var(--hud-line-2);
  --bd-d-solid: 1px solid var(--hud-line-3);
}

/* 触屏密度:小档控件抬到触控下限。**只改高度,不改语义** ——
   「因为是触屏所以重写一个组件」是第二十四轮定性的病根,这里只走参数。 */
:root.d-touch,
.d-touch {
  --h-xs: var(--tap);
  --h-sm: var(--tap);
}
/* ============================================================================
   D2 · 构件目录(第二十五轮建立)。scale.css 守「形的值」,本文件守「形的种类」。

   为什么必须有它:
     统计过 —— 三端一共 90 个自定义类,按钮就有 21 个各写各的
     (.keycap .cta .cta-primary .ghost .card-act .tkey .mkey .skey .thin .step
      .stepbtn .btn .seg-btn .dw-x .sheet-x .linklike .qmark .crow .pgauge .rst .opd-reset)。
     而且 `remote.html` **根本没 link 共享层** —— 这就是用户说的
     「手机端遥控器像没人管一样,跟 iPad 端的设计是完全断裂的」的**结构成因**:
     不是谁疏忽了,是两页本来就没有共同的构件词汇表,各自漂是必然。

   本文件定死构件的**种类与阶层**:
     按钮恰好 4 阶(主实心 / 次描边 / 三级纯文字 / 危险)× 3 尺寸;
     灯与键是两种材质,永不混用(灯=哑光平贴只陈述,键=有材质可按压);
     卡片 1 种 · 列表行 1 种 · 输入 1 种 · 弹层 3 形态(模态 / 锚定 / 抽屉)。

   浅色面直接用;深色 HUD 面在容器上加 `.on-dark`,构件内部自动切到 --hud-* 与 --sh-d-*。
   触屏差异只走 `.d-touch`(scale.css 里把小档抬到 44),**不许因为是触屏就重写一个**。

   护栏:`npm run lint:geometry` + `npm run verify:shared`(构件同源条)。
   ============================================================================ */

/* ===========================================================================
   1. 按钮 —— 恰好 4 阶。多一阶就等于没有阶。
      主实心:一个界面(或一个对象内部)只允许一枚;
      次描边:并列的平权动作;
      三级纯文字:低频、可放弃的动作;
      危险:只在确认层与二级菜单里出现,永不上一级平面。
   =========================================================================== */
.sj-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  min-height: var(--h-md);
  padding: 0 var(--sp-6);
  border: none;
  border-radius: var(--r-md);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--t-body-s);
  font-weight: var(--w-semibold);
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background var(--dur-1) var(--ease-io),
    color var(--dur-1) var(--ease-io),
    box-shadow var(--dur-1) var(--ease-io),
    transform var(--dur-1) var(--ease-io),
    opacity var(--dur-2) var(--ease);
}
.sj-btn:active:not(:disabled) {
  transform: translateY(1px);
}
/* 焦点环只给键盘导航:弹层 showModal() 会自动聚焦首个可聚焦元素,
   鼠标/手指打开时那个蓝框是纯噪音(确认弹窗上尤其扎眼)。 */
.sj-btn:focus:not(:focus-visible) {
  outline: none;
}
.sj-btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.sj-btn:disabled {
  opacity: 0.38;
  cursor: default;
  box-shadow: none;
}
.sj-btn svg {
  width: var(--ic-sm);
  height: var(--ic-sm);
  display: block;
}

/* 阶 1 · 主实心。阴影走**强调色着色**(--sh-accent-*,第二十七轮用户拍板「年轻化」)。
   注意:`--primary-shadow` 这个着色令牌其实早就存在于 tokens.css,而构件一直用的是
   中性灰的 --sh-1 —— 又一次「令牌在那儿、构件没用它」。 */
.sj-btn.primary {
  background: var(--primary-bg);
  color: var(--primary-fg);
  font-weight: var(--w-bold);
  box-shadow: var(--sh-accent-1);
}
/* hover 抬升只给真鼠标(Mac)。触屏上 :hover 会「点一下就粘住」= iOS 反模式;
   外拍端(pad/remote)恒触屏,靠 :active 按压反馈,不靠 hover。 */
@media (hover: hover) {
  .sj-btn.primary:hover:not(:disabled) {
    box-shadow: var(--sh-accent-2);
  }
}
.sj-btn.primary:active:not(:disabled) {
  box-shadow: var(--sh-accent-soft);
}
/* 阶 2 · 次描边(键帽材质:静止即可按感,不靠 hover —— hover 在触屏上不存在) */
.sj-btn.secondary {
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--sh-key);
}
.sj-btn.secondary:active:not(:disabled) {
  background: var(--desk-deep);
}
/* 阶 3 · 三级纯文字 */
.sj-btn.ghost {
  color: var(--ink-2);
  font-weight: var(--w-medium);
}
.sj-btn.ghost:active:not(:disabled) {
  background: var(--panel);
}
/* 阶 4 · 危险 */
.sj-btn.danger {
  color: var(--red);
  background: var(--red-bg);
  box-shadow: 0 0 0 1px var(--red-line);
}
.sj-btn.danger.solid {
  background: var(--red);
  color: var(--on-accent);
  box-shadow: none;
}
/* 选中态(分段控件 / 走带点亮):强调色淡底 + 强调色描边,不改尺寸不改字重 */
.sj-btn.on {
  background: var(--accent-weak);
  color: var(--accent-ink);
  box-shadow: 0 0 0 1.5px var(--accent-line);
}

/* 尺寸 3 档 —— **只改高度与字号,不改圆角**(第二十七轮校正)。
   用户第 8 条:「同样是上镜的按钮,一会圆角矩形一会胶囊」。查下来不是谁写错了,
   是这张表本身把圆角绑在了尺寸上(sm=8 / md=12 / lg=999),于是同一枚「上镜」
   在长条里是圆角矩形、在阅读态底栏是胶囊 —— **两处都"合规",观感却是乱的**。
   纪律:**圆角表达「这是什么构件」,不表达「这个构件多大」。** 按钮只有一个圆角。 */
.sj-btn.sm {
  min-height: var(--h-sm);
  padding: 0 var(--sp-5);
  font-size: var(--t-caption);
}
/* `.lg` = **一个界面的主动作**(不是「大一号的按钮」)。只有它走胶囊 ——
   角色差异,不是尺寸差异;别给普通按钮加 .lg 来「让它更圆」。 */
.sj-btn.lg {
  min-height: var(--h-lg);
  padding: 0 var(--sp-7);
  font-size: var(--t-title-s);
  border-radius: var(--r-full);
}
.sj-btn.xl {
  min-height: var(--h-xl);
  font-size: var(--t-title-s);
}
.sj-btn.block {
  width: 100%;
}
.sj-btn.icon {
  padding: 0;
  width: var(--h-md);
  min-width: var(--h-md);
}
.sj-btn.sm.icon {
  width: var(--h-sm);
  min-width: var(--h-sm);
}

/* ===========================================================================
   2. 灯 与 状态键 —— 两种材质,一眼分层(第十九轮校准的纪律,这里成为构件)。
      灯 = 哑光平贴,只陈述,不可点,没有阴影没有内高光;
      状态键 = 灯 + 可按压(点开看证据) —— 用户对状态没有安全感,要能点开看事实。
   =========================================================================== */
.sj-lamp {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--h-sm);
  padding: 0 var(--sp-4);
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink-2);
  font-size: var(--t-caption);
  font-variant-numeric: tabular-nums;
}
.sj-lamp > i.dot {
  width: var(--sp-4);
  height: var(--sp-4);
  border-radius: var(--r-circle);
  flex-shrink: 0;
  background: var(--ink-3);
  transition: background var(--dur-1) var(--ease-io);
}
.sj-lamp.ok > i.dot {
  background: var(--mint);
}
.sj-lamp.warn {
  color: var(--amber-ink);
}
.sj-lamp.warn > i.dot {
  background: var(--amber);
}
.sj-lamp.bad {
  color: var(--red);
}
.sj-lamp.bad > i.dot {
  background: var(--red);
}
.sj-lamp svg {
  width: var(--ic-sm);
  height: var(--ic-sm);
}
/* 可点的灯:加一层极轻的面,告诉用户"这个能按" */
.sj-lamp.tappable {
  cursor: pointer;
  border: none;
  font: inherit;
  background: var(--panel);
  box-shadow: var(--sh-hair);
  transition: background var(--dur-1) var(--ease-io);
}
.sj-lamp.tappable:active {
  background: var(--desk-deep);
}

/* ===========================================================================
   3. 卡片 —— 1 种。飞书式克制:1px 描边 + 大圆角 + 极轻阴影,层级靠字阶与留白。
      「一个对象两个动作」:卡面点击=打开,卡上直达键=该对象的主动作。
   =========================================================================== */
.sj-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-6);
  border-radius: var(--r-lg);
  background: var(--paper);
  /* iOS 卡片靠柔和阴影抬升分层,不靠 1px 描边 —— 场记板浅底 --desk 与白卡片明度差 <3%,
     发丝描边是「最flat的卡片」(审计截图指出的成因)。--sh-1 是极轻阴影,不是重投影。 */
  box-shadow: var(--sh-1);
  text-align: left;
  transition:
    box-shadow var(--dur-1) var(--ease-io),
    transform var(--dur-1) var(--ease-io),
    opacity var(--dur-2) var(--ease);
}
.sj-card.tappable {
  cursor: pointer;
}
/* 按下去只换面,**不缩放**(用户第 27 轮第 5 条:「卡片会抖一下,有点过于重了」)。
   缩放整块内容会让每一行文字同时位移 —— 一枚 44px 的键抬 1px 是手感,
   一整张卡缩 1% 是**画面在动**。反馈的重量要配得上动作的分量。 */
.sj-card.tappable:active {
  background: var(--panel);
  box-shadow: var(--sh-accent-soft), var(--sh-1);
}
.sj-card > .t {
  font-size: var(--t-title-s);
  font-weight: var(--w-bold);
  line-height: 1.4;
  color: var(--ink);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.sj-card > .m {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  flex-wrap: wrap;
  font-size: var(--t-caption);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* ===========================================================================
   4. 列表行 —— 1 种(章节行 / 菜单项 / 设置项共用)。
      当前项靠**整块淡底**表示,不贴左侧竖条(第十八轮已定,遥控页曾重犯)。
   =========================================================================== */
.sj-row {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  width: 100%;
  min-height: var(--h-md);
  padding: var(--sp-4) var(--sp-5);
  border: none;
  border-radius: var(--r-md);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-body);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease-io);
}
.sj-row:active {
  background: var(--desk-deep);
}
.sj-row > .no {
  flex-shrink: 0;
  width: var(--sp-7);
  font-size: var(--t-caption);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.sj-row.cur {
  background: var(--accent-weak);
  color: var(--ink);
  font-weight: var(--w-semibold);
}
.sj-row.cur > .no {
  color: var(--accent-ink);
}
.sj-row.past {
  color: var(--ink-3);
}
.sj-row.danger {
  color: var(--red);
}

/* ===========================================================================
   5. 输入 —— 1 种。
   =========================================================================== */
.sj-field {
  width: 100%;
  min-height: var(--h-md);
  padding: var(--sp-4) var(--sp-5);
  border: var(--bd-solid);
  border-radius: var(--r-md);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-body);
  line-height: 1.6;
  outline: none;
  resize: none;
  transition: border-color var(--dur-1) var(--ease-io);
}
.sj-field:focus {
  border-color: var(--accent);
}
.sj-field::placeholder {
  color: var(--ink-3);
}

/* ===========================================================================
   6. 横栏(顶栏 / 底栏)—— 布局宪法的载体(批 A 全面落位):
      顶栏 左=身份 右=环境状态与全局动作;底栏 左=次级动作 右=本界面主动作。
   =========================================================================== */
.sj-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  flex-shrink: 0;
  min-height: var(--h-lg);
}
.sj-bar > .grow {
  flex: 1;
}
.sj-brand {
  font-size: var(--t-title);
  font-weight: var(--w-bold);
  letter-spacing: 0.02em;
  color: var(--ink);
  white-space: nowrap;
}

/* ===========================================================================
   7. 弹层 3 形态。形态由**内容形状**决定,不由习惯决定:
      模态 sheet = 需要专注、有取消/确认两枚出口;
      锚定 pop   = 从被按下的那枚键长出来(空间语法,第十九轮);
      抽屉 drawer= 从屏幕边缘滑起的长列表(章节 / 显示设置)。
   =========================================================================== */
.sj-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-scrim);
  background: var(--hud-scrim);
  animation: sjScrimIn var(--dur-2) var(--ease);
  transition: opacity var(--dur-2) var(--ease-io);
}
.sj-scrim.out {
  opacity: 0;
}
@keyframes sjScrimIn {
  from {
    opacity: 0;
  }
}

.sj-sheet {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  padding: var(--sp-7);
  border-radius: var(--r-xl);
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--sh-3);
}
.sj-sheet > h3 {
  font-size: var(--t-title-s);
  font-weight: var(--w-bold);
}
.sj-sheet > .sub {
  font-size: var(--t-body-s);
  line-height: 1.7;
  color: var(--ink-2);
}
.sj-foot {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}
.sj-foot > .grow {
  flex: 1;
}

.sj-pop {
  position: fixed;
  z-index: var(--z-pop);
  padding: var(--sp-4);
  border-radius: var(--r-lg);
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--sh-2);
  transform-origin: var(--pop-ox, 50%) 100%;
  animation: sjPopRise var(--dur-2) var(--ease-io) both;
}
.sj-pop[data-side='below'] {
  transform-origin: var(--pop-ox, 50%) 0%;
  animation-name: sjPopDrop;
}
.sj-pop.out {
  animation: sjPopFade var(--dur-1) var(--ease-io) both;
}
@keyframes sjPopRise {
  from {
    opacity: 0;
    transform: translateY(var(--sp-4)) scale(0.96);
  }
}
@keyframes sjPopDrop {
  from {
    opacity: 0;
    transform: translateY(calc(var(--sp-4) * -1)) scale(0.96);
  }
}
@keyframes sjPopFade {
  to {
    opacity: 0;
    transform: scale(0.97);
  }
}

.sj-drawer {
  position: fixed;
  left: 50%;
  bottom: 0;
  z-index: var(--z-layer);
  display: flex;
  flex-direction: column;
  width: min(560px, 100vw);
  max-height: 74%;
  padding: var(--sp-4) var(--sp-6) calc(env(safe-area-inset-bottom) + var(--sp-5));
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--sh-2);
  transform: translateX(-50%);
  transition: transform var(--dur-3) var(--ease-io);
}
.sj-drawer.out {
  transform: translateX(-50%) translateY(100%);
}
.sj-drawer > .hd {
  display: flex;
  align-items: center;
  padding: var(--sp-5) var(--sp-1);
  font-size: var(--t-caption);
  letter-spacing: 0.08em;
  color: var(--ink-3);
}
.sj-drawer > .bd {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: var(--sp-5);
  overscroll-behavior: contain;
}

/* 锚定菜单(卡片右上「⋯」)= sj-pop 里塞一列 sj-row。触屏没有右键,
   显性入口比隐藏手势可靠 —— 这是触控差异,不是照抄鼠标端的右键菜单。 */
.sj-menu {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 176px;
}
.sj-menu .sj-row {
  background: none;
  font-size: var(--t-body-s);
  /* 同心圆角:菜单壳 .sj-pop 是 --r-lg(20) + 内边距 --sp-4(8) → 理想内圆角 ≈12,取最近档 --r-sm(10)。
     原 .sj-row 的 --r-md(16) 在 8px 内边距里与外角打架 = 审计「⋯ 菜单圆角别扭」的成因。 */
  border-radius: var(--r-sm);
}
.sj-menu .sj-row:active {
  background: var(--panel);
}
.sj-menu > .sep {
  height: 1px;
  margin: var(--sp-2) 0;
  background: var(--hair);
}

/* ===========================================================================
   8. 危险确认 —— 破坏性动作的**唯一**形态。
      删除文稿 / 结束上镜 共用同一枚构件:全局一致的确认方式,
      比「这里长按、那里直接删」可预期得多(长按还不可发现)。
   =========================================================================== */
.sj-confirm {
  /* 第三十六轮:宽度从「max-width 380」改成**显式宽度**并走档表。
     旧写法只封了上限,而承载它的 dialog 壳没写宽度 —— 于是整个弹窗
     收缩到内容宽(实测 218×171,比例 1.28 = 近方形)。
     用户第 20 条说的就是它:iOS 的 Alert 是长矩形,不是方块。 */
  width: min(92vw, var(--dlg-confirm));
  max-width: min(92vw, var(--dlg-confirm));
}
.sj-confirm > .fact {
  font-size: var(--t-body-s);
  line-height: 1.7;
  color: var(--ink-2);
}

/* ===========================================================================
   9. 深色 HUD 变体 —— 容器上加 `.on-dark`,构件内部整体切换。
      舞台/遥控/上镜台都是深色面;**同一套构件、同一套几何**,只换面与影。
   =========================================================================== */
.on-dark .sj-btn {
  color: var(--hud-text-1);
}
.on-dark .sj-btn.primary {
  background: var(--accent-ondark-weak);
  color: var(--accent-ondark);
  box-shadow: 0 0 0 1.5px var(--accent-ondark-line);
}
.on-dark .sj-btn.secondary {
  background: var(--hud-surface-2);
  color: var(--hud-text-1);
  box-shadow: var(--sh-d-key);
}
.on-dark .sj-btn.secondary:active:not(:disabled) {
  background: var(--hud-surface-3);
}
.on-dark .sj-btn.ghost {
  color: var(--hud-text-2);
}
.on-dark .sj-btn.ghost:active:not(:disabled) {
  background: var(--hud-surface-2);
}
.on-dark .sj-btn.danger {
  color: var(--red-ondark);
  background: none;
  box-shadow: 0 0 0 1px var(--red-line);
}
.on-dark .sj-btn.danger:active:not(:disabled) {
  background: var(--hud-scrim-red);
}
/* 确认层的实心红在深色面上仍是实心 —— 本节按源序盖过了上面的 .solid,得声明回来
   (第三十三轮「结束上镜」弹窗实证:实心确认键被画成了描边键) */
.on-dark .sj-btn.danger.solid {
  background: var(--red);
  color: var(--on-accent);
  box-shadow: none;
}
.on-dark .sj-btn.danger.solid:active:not(:disabled) {
  background: var(--red);
  opacity: 0.9;
}
.on-dark .sj-btn.on {
  background: var(--accent-ondark-weak);
  color: var(--accent-ondark);
  box-shadow: 0 0 0 1.5px var(--accent-ondark-line);
}
.on-dark .sj-lamp {
  color: var(--hud-text-2);
}
.on-dark .sj-lamp > i.dot {
  background: var(--hud-text-4);
}
.on-dark .sj-lamp.ok > i.dot {
  background: var(--hud-ok);
}
.on-dark .sj-lamp.warn {
  color: var(--amber);
}
.on-dark .sj-lamp.bad {
  color: var(--red-ondark);
}
.on-dark .sj-lamp.tappable {
  background: var(--hud-surface);
  box-shadow: var(--sh-d-hair);
}
.on-dark .sj-card {
  background: var(--hud-surface);
  box-shadow: var(--sh-d-hair);
}
.on-dark .sj-card > .t {
  color: var(--hud-text-0);
}
.on-dark .sj-card > .m {
  color: var(--hud-text-3);
}
.on-dark .sj-row {
  background: var(--hud-surface);
  color: var(--hud-text-1);
}
.on-dark .sj-row:active {
  background: var(--hud-surface-3);
}
.on-dark .sj-row > .no {
  color: var(--hud-text-4);
}
.on-dark .sj-row.cur {
  background: var(--accent-ondark-weak);
  color: var(--hud-text-0);
}
.on-dark .sj-row.cur > .no {
  color: var(--accent-ondark);
}
.on-dark .sj-row.past {
  color: var(--hud-text-3);
}
.on-dark .sj-field {
  background: var(--hud-surface);
  border: var(--bd-d-solid);
  color: var(--hud-text-0);
}
.on-dark .sj-field:focus {
  border-color: var(--accent-ondark-line);
}
.on-dark .sj-brand {
  color: var(--hud-text-0);
}
.on-dark .sj-sheet,
.on-dark .sj-pop,
.on-dark .sj-drawer {
  background: var(--hud-popover);
  color: var(--hud-text-1);
}
.on-dark .sj-sheet {
  box-shadow: var(--sh-d-3);
}
.on-dark .sj-pop {
  box-shadow: var(--sh-d-3);
}
.on-dark .sj-drawer {
  box-shadow: var(--sh-d-2);
}
.on-dark .sj-drawer > .hd {
  color: var(--hud-text-3);
}
.on-dark .sj-menu > .sep {
  background: var(--hud-line-2);
}
.on-dark .sj-sheet > .sub,
.on-dark .sj-confirm > .fact {
  color: var(--hud-text-2);
}

/* 只读仪表面(哑光内嵌,与「能按的键」材质分明 —— 第二十轮的信息板/键盘分层) */
.sj-gauge {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}
.sj-gauge > .lab {
  font-size: var(--t-label);
  font-weight: var(--w-bold);
  letter-spacing: 0.16em;
  color: var(--ink-3);
}
.sj-gauge > .val {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: var(--t-display);
  font-weight: var(--w-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--ink);
}
.sj-gauge > .val > small {
  font-size: var(--t-caption);
  font-weight: var(--w-normal);
  color: var(--ink-3);
}
.sj-panel-inset {
  padding: var(--sp-5) var(--sp-6);
  border-radius: var(--r-md);
  background: var(--panel);
  box-shadow: var(--sh-hair);
}
.on-dark .sj-gauge > .lab {
  color: var(--hud-text-4);
}
.on-dark .sj-gauge > .val {
  color: var(--hud-text-0);
}
.on-dark .sj-gauge > .val > small {
  color: var(--hud-text-4);
}
.on-dark .sj-panel-inset {
  background: var(--hud-scrim);
  box-shadow: var(--sh-d-inset);
}

/* ===========================================================================
   10. 状态键 —— 状态的唯一材质,**两阶信息**(主行=状态本身,次行小字=细节)。

   第二十七轮补进目录。此前状态在三端有五种长相:Mac 的 `.pgauge`、稿头的 `.rh-st`、
   iPad 顶栏的 `.board-chip`、iPad 底栏的 `.pgauge`(另一份)、手机的 `.lamp`。
   用户第 10 条「既不明确、又很难 get 到那是个按钮、信息层级也不对」——
   三句话说的是同一件事:**没有一种材质专门表示"可点的状态"**,于是每处现编一个。

   信息层级的纪律(用户第 2 条):**一级是状态,二级才是实例名。**
   「飞书已登录」是状态,「王志超」是实例名 —— 反过来写,用户读不出这是不是登录了。

   材质与阶层沿用既有纪律:能点 = 键帽(内高光 + 抬起);只陈述 = `.flat`(哑光平贴)。
   面变体三套:浅色面(默认) / 场记板 `.on-slate` / 深色 HUD `.on-dark`。
   =========================================================================== */
.sj-status {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-4);
  min-height: var(--h-sm);
  padding: 0 var(--sp-5);
  border: none;
  border-radius: var(--r-sm);
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--sh-key);
  font: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background var(--dur-1) var(--ease-io),
    box-shadow var(--dur-1) var(--ease-io),
    transform var(--dur-1) var(--ease-io);
}
.sj-status:active {
  transform: translateY(1px);
}
.sj-status:focus:not(:focus-visible) {
  outline: none;
}
.sj-status:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.sj-status > .ico {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  flex-shrink: 0;
  color: var(--ink-3);
}
.sj-status > .ico svg {
  width: var(--ic-sm);
  height: var(--ic-sm);
  display: block;
}
.sj-status > .ico > i.dot {
  display: block;
  width: var(--sp-4);
  height: var(--sp-4);
  border-radius: var(--r-circle);
  background: var(--ink-3);
}
/* 两阶文字:主行 = 状态(读得出成败),次行 = 实例名/细节(小、灰、可截断) */
.sj-status > .tx {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  line-height: 1.24;
}
.sj-status > .tx > .t {
  font-size: var(--t-caption);
  font-weight: var(--w-semibold);
  white-space: nowrap;
}
.sj-status > .tx > .s {
  font-size: var(--t-label);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 11em;
}
.sj-status > .tx > .s:empty {
  display: none;
}
.sj-status.ok > .ico {
  color: var(--ink);
}
.sj-status.ok > .ico > i.dot {
  background: var(--mint);
}
.sj-status.warn {
  color: var(--amber-ink);
}
.sj-status.warn > .ico {
  color: var(--amber-ink);
}
.sj-status.warn > .ico > i.dot {
  background: var(--amber);
}
.sj-status.bad {
  color: var(--red);
}
.sj-status.bad > .ico {
  color: var(--red);
}
.sj-status.bad > .ico > i.dot {
  background: var(--red);
}
/* 只陈述、不可点:哑光平贴 —— 绝不长得像键(灯与键两个阶层,第十九轮纪律) */
.sj-status.flat {
  background: none;
  box-shadow: none;
  cursor: default;
}
.sj-status.flat:active {
  transform: none;
}

/* ===========================================================================
   11. 滑杆 —— 1 种。第二十七轮补进目录。

   用户第 13 条「进度条里面还是亮白色,风格很割裂」的真因:此前 `.opd-slider` 只写了
   `width:100%` + `accent-color`,**渲染出来是浏览器原生控件**(实测 computed
   `appearance:auto` / `background:rgb(255,255,255)`)。深色 HUD 面上那条白轨不是设计,
   是没有设计。Mac 与 iPad 同一条规则,所以这是两端共病 —— 收进目录一次修两端。

   轨的填充用 `--fill`(0..1,由 JS 或 CSS 变量给),不靠 accent-color 交给系统去猜。
   =========================================================================== */
.sj-slider {
  --fill: 0.5;
  /* 轨高走档表(--sp-3 = 6px);拇指居中的负 margin 由它与 --ic-md 算出来,不写死 */
  --track: var(--sp-3);
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  min-width: 0;
  height: var(--h-md);
  background: none;
  cursor: pointer;
  touch-action: pan-y;
}
.sj-slider::-webkit-slider-runnable-track {
  height: var(--track);
  border-radius: var(--r-full);
  background: linear-gradient(
    to right,
    var(--accent) 0 calc(var(--fill) * 100%),
    var(--desk-deep) calc(var(--fill) * 100%) 100%
  );
}
.sj-slider::-moz-range-track {
  height: var(--track);
  border-radius: var(--r-full);
  background: var(--desk-deep);
}
.sj-slider::-moz-range-progress {
  height: var(--track);
  border-radius: var(--r-full);
  background: var(--accent);
}
.sj-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--ic-md);
  height: var(--ic-md);
  margin-top: calc((var(--track) - var(--ic-md)) / 2);
  border-radius: var(--r-circle);
  background: var(--paper);
  box-shadow:
    0 0 0 1px var(--hair-2),
    0 1px 3px rgba(0, 0, 0, 0.22);
  transition: transform var(--dur-1) var(--ease-io);
}
.sj-slider::-moz-range-thumb {
  width: var(--ic-md);
  height: var(--ic-md);
  border: none;
  border-radius: var(--r-circle);
  background: var(--paper);
  box-shadow:
    0 0 0 1px var(--hair-2),
    0 1px 3px rgba(0, 0, 0, 0.22);
}
.sj-slider:active::-webkit-slider-thumb {
  transform: scale(1.1);
}
.sj-slider:focus:not(:focus-visible) {
  outline: none;
}
.sj-slider:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* ===========================================================================
   12. 分段控件 —— 1 种(二选一 / 三选一的平权切换)。
       与按钮 4 阶不冲突:分段里的每一格**不是动作,是取值**,所以不用 .sj-btn。
   =========================================================================== */
.sj-seg {
  display: flex;
  gap: var(--sp-3);
}
.sj-seg > button {
  flex: 1;
  min-width: 0;
  min-height: var(--h-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-5);
  border: none;
  border-radius: var(--r-sm);
  background: var(--panel);
  color: var(--ink-2);
  box-shadow: var(--sh-hair);
  font: inherit;
  font-size: var(--t-body-s);
  font-weight: var(--w-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--dur-1) var(--ease-io),
    color var(--dur-1) var(--ease-io),
    box-shadow var(--dur-1) var(--ease-io);
}
.sj-seg > button svg {
  width: var(--ic-sm);
  height: var(--ic-sm);
  display: block;
}
.sj-seg > button:active {
  background: var(--desk-deep);
}
.sj-seg > button.on {
  background: var(--accent-weak);
  color: var(--accent-ink);
  box-shadow: 0 0 0 1.5px var(--accent-line);
}
.sj-seg > button:focus:not(:focus-visible) {
  outline: none;
}

/* ===========================================================================
   13. 布局原子。`.spacer` 此前只活在 desk.css(Mac 专属)——
       `components.ts` 产出的弹层底行 `[取消][spacer][添加]` 到了 iPad 上因此塌成左对齐
       (用户第 14 条里那排挤在左边的按钮)。布局原子必须住在两端都 link 的层。
   =========================================================================== */
.spacer,
.sj-spacer {
  flex: 1;
}

/* ===========================================================================
   14. 场记板面变体 `.on-slate` —— 构件落在深色场记板上时的第三套面。
       为什么不复用 .on-dark:HUD(--hud-*)与场记板(--slate-*)是两族令牌,
       场记板随主题换冷暖、HUD 恒深。混用会让顶栏在飞书主题下偏暖(复盘 89 同族的坑)。
   =========================================================================== */
.on-slate {
  color: var(--slate-text);
}
.on-slate .sj-status {
  background: var(--slate-bg-2);
  color: var(--slate-text);
  box-shadow: var(--sh-s-key);
}
.on-slate .sj-status:active {
  background: var(--slate-line);
}
.on-slate .sj-status > .ico {
  color: var(--slate-dim);
}
.on-slate .sj-status > .ico > i.dot {
  background: var(--slate-dim);
}
.on-slate .sj-status.ok > .ico {
  color: var(--slate-text);
}
.on-slate .sj-status.ok > .ico > i.dot {
  background: var(--mint);
}
.on-slate .sj-status.warn,
.on-slate .sj-status.warn > .ico {
  color: var(--amber);
}
.on-slate .sj-status.bad,
.on-slate .sj-status.bad > .ico {
  color: var(--red-ondark);
}
.on-slate .sj-status > .tx > .s {
  color: var(--slate-dim);
}
.on-slate .sj-btn.ghost {
  color: var(--slate-dim);
}
.on-slate .sj-btn.ghost:active:not(:disabled) {
  background: var(--slate-bg-2);
}

/* 深色 HUD 面上的状态键 / 滑杆 / 分段(接第 9 节的 .on-dark 家族) */
.on-dark .sj-status {
  background: var(--hud-surface);
  color: var(--hud-text-1);
  box-shadow: var(--sh-d-key);
}
.on-dark .sj-status:active {
  background: var(--hud-surface-3);
}
.on-dark .sj-status > .ico,
.on-dark .sj-status > .tx > .s {
  color: var(--hud-text-3);
}
.on-dark .sj-status > .ico > i.dot {
  background: var(--hud-text-4);
}
.on-dark .sj-status.ok > .ico > i.dot {
  background: var(--hud-ok);
}
.on-dark .sj-status.warn,
.on-dark .sj-status.warn > .ico {
  color: var(--amber);
}
.on-dark .sj-status.bad,
.on-dark .sj-status.bad > .ico {
  color: var(--red-ondark);
}
.on-dark .sj-status.flat {
  background: none;
  box-shadow: none;
}
.on-dark .sj-slider::-webkit-slider-runnable-track {
  background: linear-gradient(
    to right,
    var(--accent-ondark) 0 calc(var(--fill) * 100%),
    var(--hud-surface-3) calc(var(--fill) * 100%) 100%
  );
}
.on-dark .sj-slider::-moz-range-track {
  background: var(--hud-surface-3);
}
.on-dark .sj-slider::-moz-range-progress {
  background: var(--accent-ondark);
}
.on-dark .sj-slider::-webkit-slider-thumb,
.on-dark .sj-slider::-moz-range-thumb {
  background: var(--hud-text-1);
  box-shadow:
    0 0 0 1px var(--hud-line-3),
    0 1px 4px rgba(0, 0, 0, 0.5);
}
.on-dark .sj-seg > button {
  background: var(--hud-surface);
  color: var(--hud-text-2);
  box-shadow: var(--sh-d-hair);
}
.on-dark .sj-seg > button:active {
  background: var(--hud-surface-3);
}
.on-dark .sj-seg > button.on {
  background: var(--accent-ondark-weak);
  color: var(--accent-ondark);
  box-shadow: 0 0 0 1.5px var(--accent-ondark-line);
}

@media (prefers-reduced-motion: reduce) {
  .sj-btn,
  .sj-card,
  .sj-row,
  .sj-lamp,
  .sj-status,
  .sj-slider,
  .sj-seg > button,
  .sj-scrim,
  .sj-pop,
  .sj-drawer {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* meta 标签(来源 / 状态的名词短语)。收在构件目录里而不是某一个页面里 ——
   它会出现在稿卡、列表行、稿头,长在页面里必然三处三个样。 */
.sj-tag {
  font-size: var(--t-label);
  font-weight: var(--w-semibold);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-xs);
  white-space: nowrap;
}
.sj-tag.feishu {
  color: var(--feishu-brand);
  background: var(--feishu-brand-weak);
}
.sj-tag.warn {
  color: var(--amber-ink);
  background: var(--amber-bg);
}
.sj-tag.local {
  color: var(--ink-2);
  background: var(--panel);
}
.on-dark .sj-tag.local {
  color: var(--hud-text-2);
  background: var(--hud-surface-2);
}
