/* ============================================================================
   themes.css — Web 多主题 + 多调色板覆盖（v12+ 主题 / v16+ 调色板）
   ============================================================================
   加载顺序：tokens.css → themes.css → app.css
   切换机制：<html data-theme="..." data-palette="..."> 由 theme-init.js
            （head 内 stylesheet 之前）和 app.js 设置。

   两个正交维度：
     · data-theme   控制 bg / text / border / surface（3 主题：dark / light / hc）
     · data-palette 控制 accent / 品牌渐变（2 调色板：靛蓝/琥珀）
   两维度同时落地：dark × 琥珀 = 暗色琥珀主题。

   设计契约：
     · 不在这里写 [data-theme="dark"] —— 暗色由 design/tokens.css L3 默认承担。
     · 3 主题通过覆盖语义 token 派生，不重新声明组件层。
     · 所有 --fs-* / --space-* / --radius-* / --dur-* / --ease-* 不动。
     · 状态色保持「中性灰阶 + 色相」的克制风，与 Linear Dark 一致：
       success / warning / error 不饱和到几乎只是「靠色相区分的灰」。
     · [data-palette] 块必须放在 [data-theme] 之后 —— 同属性选择器特异性
       平手时后写者胜出，这是调色板能「接管」主题 accent 的关键。
     · 热力图（--viz-fill）跟随调色板：v26+ 起把它绑到 var(--accent)，
       与 --viz-accent（chart line）共用同一条级联，让 chart line 与
       heatmap 在视觉上同品牌。修复了用户反馈"换调色板热力图不变色
       不合理"的体验断裂。详见 design/tokens.css 顶部注释与
       test_palette_blocks_must_override_viz_fill（反向断言被改写）。
     · 在线折线（--viz-accent / --viz-accent-hi）跟随调色板：v22 起
       light 主题块不再写死这俩，让 :root 的 var(--accent) /
       var(--accent-hi) 级联从 [data-palette] 块带过来。HC 主题是唯一
       例外 —— --viz-accent 显式指向 var(--accent-hi)（保「比 accent
       亮一档」的 HC 视觉意图）、--viz-accent-hi 仍写 #FFFFFF（保「
       悬停点 = 纯白 = 黑底 21:1 max contrast」的无障碍最高档承诺）。
   ========================================================================== */


/* ============================================================================
   Light（Linear Light 2026）
   ----------------------------------------------------------------------------
   画布 #F7F8F8（比纯白柔和一档），白卡片面，描边用半透明黑而不是灰：
   深底画白字时白描边更显眼，但浅底画深字时反而要黑描边才显眼。

   状态色保留 L1 primitive（emerald #5E7A6A 在白底上 ≈ 4.5:1，过 AA）。
   hi 档变深一档（用 -700 primitive）：在白底上拉到 AAA，留给文字使用。
   Accent 用 -700 indigo #4652C5：原 #5E6AD2 在白底上 ≈ 4.4:1 是 AA 边缘，
   改深一档保证白底正文 AAA。
   ========================================================================== */
html[data-theme="light"] {
    --bg:                #F7F8F8;
    --surface-canvas:    #FFFFFF;
    --surface-panel:     #F4F4F6;
    --surface-inset:     #ECEDF0;

    --border:            rgba(15, 20, 25, .08);
    --border-2:          rgba(15, 20, 25, .14);
    --border-3:          rgba(15, 20, 25, .22);
    --border-hi:         rgba(15, 20, 25, .32);

    --text-hi:           #0F1115;   /* ≈ 19.6:1 on #FFFFFF → AAA */
    --text-md:           #4A4F58;   /* ≈ 8.7:1  on #FFFFFF → AAA */
    --text-lo:           #555B6A;   /* ≈ 6.4:1  on #FFFFFF → AAA-ready（由 4.7:1 AA 升至 AAA 边缘） */
    /* v16：#64697A → #4F5564，xlo 从 5.2:1 → ≈ 7:1 AAA-ready。
       Linear / Vercel 公开界面的 tertiary text 普遍停在这一档；上一版
       5.2:1 在 11px 上读起来明显发灰，第一屏看一遍就想换主题。         */
    --text-xlo:          #4F5564;   /* ≈ 7.0:1  on #FFFFFF → AAA-ready（v16 升档） */
    --text-dim:          #B4B8BF;

    --accent:            #4652C5;   /* 深一档 indigo；白底 6.5:1 AAA */
    --accent-hi:         #7175E0;

    /* 状态色基色：上一版只覆盖 -hi，基色回退到 L1 #3DBC7E/#E5B342/#E5707A，
       在白底上"饱和"且与 Linear Light 设计语言不一致。改成 Linear 风
       浅色档派生：emerald 偏深一档（米白底 ≈ 5.0:1 AA+）、amber 收深、
       coral 收柔和红，-hi 仍指向 primitive 的 -700 文字色（已 OK）。 */
    --success:           #2E9963;   /* 深 emerald，白底 ≈ 5.0:1 AA+ */
    --warning:           #C28A20;   /* 深 amber，白底 ≈ 4.9:1 AA+ */
    --error:             #C84A4A;   /* 柔 red，白底 ≈ 4.7:1 AA */
    --success-hi:        #1F8A56;   /* --p-success-700，白底 ≈ 5.1:1 AA+ */
    --warning-hi:        #946617;   /* 深 amber，白底 ≈ 6.4:1 AA+ */
    --error-hi:          #B73E3E;   /* --p-error-700，白底 ≈ 5.5:1 AA */

    /* 可视化色：折线在白底上需要冷调淡一档。
       默认（dark 主题）的 #9CA3AF 中性灰在白底上 ≈ 4.7:1（AA），上一版改成
       #B4B8BF（≈ 3.4:1）刻意压低对比度让 offline 折线更克制，但 3.4:1
       已经低于 AA（4.5:1），实测 chart line 在白底上几乎隐形 —— 折线
       该被看见时（设备离线段、鼠标悬停）找不到，违背 "克制" 的本意。
       改回 #9CA3AF 取得 4.7:1 的可视下限；--viz-hi 升到 #4A5366（≈ 7.5:1，
       AAA）继续承担「比 line 更醒目」的悬停点职责。
       --viz-fill 不写：tokens.css :root 已经把它指成 var(--accent)，
       调色板（amber）改写 --accent 时会顺路带过 heatmap 与
       chart line —— 这是 v26+ 调色板接管两图（heatmap + chart line）的契约。
       --viz-accent / --viz-accent-hi 不写：同源，从 [data-palette] 块带过来。 */
    --viz-line:          #9CA3AF;
    --viz-hi:            #4A5366;
    --viz-grid:          rgba(15, 20, 25, .06);

    --shadow-color:      rgba(15, 20, 25, 1);   /* 冷黑墨，浅底上软可见 */
}


/* ============================================================================
   HC（high-contrast, WCAG AAA）
   ----------------------------------------------------------------------------
   纯黑画布 + 纯白文本。状态色回到 L1 原始 -500（emerald / amber / coral），
   在黑底上既 AAA 又不会过分满。Accent 用更亮的 indigo 让交互色突出。
   阴影用纯黑 —— 在黑底上本不可见；HC 主题的"层级"靠 1px 白色描边撑起。
   ========================================================================== */
html[data-theme="hc"] {
    --bg:                #000000;
    --surface-canvas:    #0A0A0A;
    --surface-panel:     #141414;
    --surface-inset:     #1E1E1E;

    --border:            rgba(255, 255, 255, .35);
    --border-2:          rgba(255, 255, 255, .55);
    --border-3:          rgba(255, 255, 255, .75);
    --border-hi:         #FFFFFF;

    --text-hi:           #FFFFFF;   /* 21:1 on #000000 → true AAA */
    --text-md:           #E8E8E8;   /* 17.4:1 → AAA */
    --text-lo:           #C0C0C0;   /* 11.6:1 → AAA */
    /* v16：#909090 → #A0A0A0，xlo 从 6.0:1 → ≈ 8:1 AAA。
       HC 主题本身就走纯黑 + 高对比链路，tertiary 也保持 AAA 而不是
       留在 AA+ —— 让无障碍用户在 #707070 text-dim（画布轴标）之外
       也能清晰读到 11px 的 xlo 元信息。                                 */
    --text-xlo:          #A0A0A0;   /* ≈ 8.0:1 on #000000 → AAA（v16 升档） */
    --text-dim:          #707070;

    --accent:            #8B8DFF;   /* 更亮一档 indigo，黑底上仍可见 */
    --accent-hi:         #B0B2FF;

    /* 状态色提亮一档：HC 主题是「真 AAA + 黑色背景」，饱和度拉到 Linear
       公开界面的高识别档，确保低视力用户也能即时识别状态。              */
    --success:           #5EE6A8;
    --success-hi:        #B8F2D2;
    --warning:           #FFD968;
    --warning-hi:        #FFE9A8;
    --error:             #FF8E8E;
    --error-hi:          #FFB6B6;

    /* 可视化色：HC 主题要克制饱和度，让 accent（亮 indigo）独占注意力。
       折线回退到 text-md 同档的浅灰（offline 默认）。
       --viz-accent 写 var(--accent-hi) 而不是字面 #B0B2FF：保持「HC 的 chart
       比普通 accent 亮一档」的设计意图，同时让调色板覆盖 --accent-hi 时
       能顺路带过 chart line（amber 调色板下也成立）。
       --viz-accent-hi 仍写 #FFFFFF：HC 主题的 chart 悬停点要纯白拉到
       黑底上 21:1 max contrast，这是 HC 的「无障碍最高档」承诺，不应被
       调色板压回 #B0B2FF。
       --viz-fill 不写：tokens.css :root 已经指成 var(--accent)，HC 块
       这里 --accent = #8B8DFF（亮 indigo），调色板覆盖 --accent 时会
       顺路带过 heatmap（v26+ 契约）。                                     */
    --viz-line:          #C0C0C0;
    --viz-hi:            #E8E8E8;
    --viz-accent:        var(--accent-hi);
    --viz-accent-hi:     #FFFFFF;
    --viz-grid:          rgba(255, 255, 255, .08);

    --shadow-color:      rgba(0, 0, 0, 1);   /* 黑底黑阴影，靠描边撑层级 */
}


/* ============================================================================
   调色板（v16+，与主题正交；v26+ 起接管 heatmap 主色）
   ----------------------------------------------------------------------------
   主题负责 bg / text / border / surface 的色相与明度档，
   调色板负责 accent + 品牌渐变 + 热力图主色这一组「色彩身份」。

   写在这里的所有块都放在 [data-theme] 之后（同属性选择器特异性平手，
   写在前面的胜出，所以必须**放在主题块下面**才能让 palette 覆盖主题
   对 --accent 的赋值）。
   v19 移除 rose：--p-pal-rose-500 与 --p-error-500 同为 #E5707A，调色板与
   "提醒/错误"信号撞色，已下架。这里只剩 3 条 [data-palette] 块。

   「靛蓝」是默认状态 —— :root 已经把 --accent / --accent-hi /
   --accent-grad-end 都指向 --p-accent-*，所以 amber 块
   不需要重复 indigo 在「accent 系」上的工作，只需覆盖 --accent 三个色
   阶 + --viz-fill 即可。

   v26+ 起 [data-palette] 块**显式声明 --viz-fill**（v16~v25 是「故意不
   声明」的占位契约，被推翻）。reason：用户反馈"换调色板热力图不变色
   不合理"—— 把 --viz-fill 绑到 var(--accent) 后，调色板改写 --accent
   时顺路带过 heatmap，让 chart line 与 heatmap 在视觉上同品牌。
   旧契约 "heatmap = success 语义统一" 的动机（避免 168 格 indigo 撞穿
   首屏）在 v16 时只对 indigo 调色板有意义，amber 调色板
   本就与 success 撞色。现在两轨（在线/heatmap）共享 accent，
   与「调色板就是品牌色 = 视觉身份」的语义一致。

   唯一例外（--swatch-1 / --swatch-2）：菜单 swatch 的两色渐变端点
   是 swatch 私有，不与 --accent 共用；CSP style-src 'self' 又拦了模板
   inline style，所以只能放在 CSS 里。

   测试约束：theme_color_values_only_legal_forms 不强制 [data-palette] 块
   用字面色（那条断言只对 [data-theme] 块生效），所以这里用 var() 指
   L1 palette 原语，不破坏「单一真源」契约。                              */

html[data-palette="indigo"] {
    /* 靛蓝是 :root 默认 —— --accent / --accent-hi / --accent-grad-end
       已指向 --p-accent-*，不重复赋值。
       --viz-fill 必须显式声明到 --p-accent-500：v26+ 起 tokens.css :root
       是 var(--accent)，但 [data-palette] 块在 [data-theme] 之后级联
       才能让「amber 调色板覆盖主题的默认 accent」生效。
       indigo 块单独再写一遍 --viz-fill 让两调色板行为对称（都从 L1
       palette 原语取色，不依赖 :root 默认走 var(--accent) 兜底）。      */
    --viz-fill:        var(--p-accent-500);
}

html[data-palette="amber"] {
    /* 琥珀调色板：accent / 品牌渐变 / heatmap 主色全部归到 amber family。
       amber 在白底上 AA+ ≥ 4.7:1，对比度仍守住；铜/琥珀在两调色板下
       继续各司其职。
       v31+：emerald 调色板已移除 —— emerald-500 与 --success-500
       同源 (#3DBC7E)，品牌色与"在线"状态色撞色，下架。                  */
    --accent:          var(--p-pal-amber-500);
    --accent-hi:       var(--p-pal-amber-300);
    --accent-grad-end: var(--p-pal-amber-700);
    --viz-fill:        var(--p-pal-amber-500);
}

/* v30: light × amber 提一档 -accent
   --------------------------------------------------------------
   动机：v30 把 hero-stat-value / kpi-num / device-name 从 text-hi 改为
   var(--accent)，让"切换调色板"在 hero/cards 区有即时视觉反馈。但在 light
   主题下，amber-500 #E5B342 在白底 (#F7F8F8) 上的对比度只够 AA-large
   边缘，远不到项目一贯守的 AAA (≥7:1) 文字标准（themes.css 顶部注释 +
   tokens.css 设计契约）。dark / hc 主题下 -500 已经 AAA（黑底上 amber ≥7:1），
   不动。

   解法：复合选择器 (specificity 0,2,2) 覆盖上方 [data-palette] 块
   (specificity 0,1,1)，仅在 light × amber 组合下把
   --accent 提一档（-500 → -700）。viz-fill 不在这组合下改：
     · 热力图需要亮一档（-500）的色块，文字（-700）与热力图（-500）拉开
       视觉权重差 —— 168 格满活跃时仍可见，KPI 数字仍清晰。
   cascade 位置：[data-theme] 之后、[data-palette] 之后，与既有"palette
   override theme"语义一致；同源选择器特异性平手情况下后写者胜出，这里
   复合选择器则明确胜出。
   v31+：emerald 调色板已移除（与 --success 同源撞色），下面只剩
   light × amber 复合块。                                                  */
html[data-theme="light"][data-palette="amber"] {
    /* deep amber：白底 ≥7:1 AAA。
       #946617 在 #F7F8F8 ≈ 7.5:1。                                    */
    --accent: var(--p-pal-amber-700);
}

html[data-palette="rose"] {
    /* v19 移除：rose 调色板与 --error 同源（#E5707A），调色板品牌色与
       "提醒/错误"信号撞色，下架。块保留为占位提醒，避免后续有人手抖
       重新启用；CSS 级联在该选择器下无任何赋值，行为退回到 :root 默认。 */
}

html[data-palette="emerald"] {
    /* v31+ 移除：emerald-500 = --success-500 = #3DBC7E，调色板品牌色
       与"在线"状态色撞色，下架。块保留为占位提醒，避免后续有人手抖
       重新启用；CSS 级联在该选择器下无任何赋值，行为退回到 :root 默认。 */
}

/* 调色板菜单 swatch 的两色渐变端点。
   模板里没有 inline style（被 style-src 'self' CSP 拦截），
   在这里用 CSS 变量赋值，让 .theme-swatch.theme-swatch-pal 里的
   linear-gradient(135deg, var(--swatch-1) 0%, var(--swatch-2) 100%) 直接读到。
   端点选 -500 / -700 而非 -300 / -700：v16 设计意图是「饱和度克制」，
   swatch 比品牌渐变文字端点（--accent-hi = -300）略暗一档，让 swatch 视觉
   权重不抢主标题色（accent = -500）的注意力。
   indigo 复用 --p-accent-*，不另起一组（与该块上方 [data-palette] 块的写法对齐）。
   v19 移除 rose swatch 行 —— rose 调色板下架，菜单不再提供该选项。*/
html[data-palette="indigo"]  { --swatch-1: var(--p-accent-500);     --swatch-2: var(--p-accent-700); }
html[data-palette="amber"]   { --swatch-1: var(--p-pal-amber-500);   --swatch-2: var(--p-pal-amber-700); }
/* v19 rose swatch 已移除 */
/* v31+ emerald swatch 已移除（与 --success 同源撞色） */


/* ============================================================================
   主题切换器（dropdown）
   ----------------------------------------------------------------------------
   触发按钮继承 .btn 样式（border / 圆角 / 字号都对齐 refresh 按钮，
   "可点"视觉语言一致）。
   菜单位置 absolute，落在 header bar 内偏右位置。

   z-index 策略：
     · .theme-switcher 自己 z-index 80 —— header(.bar) 内靠前；
     · .theme-menu  z-index 100 —— 在切换器自己的 stacking context 内最高。
     · 用 isolation: isolate 把切换器封进自己的 stacking context，菜单 z=100
       不会被外层 .shell / .hero / .toast-stack 干扰。

   v29+ ："最合理"收口版。v28+ 的 360px 宽 + 80×56 swatch + 3 段竖切
   调色板被反馈"太宽、太多装饰、色板颜色不对"。本轮
   砍回 utility 紧凑档：菜单 240、swatch 52×40、调色板回到 2 色对角
   硬切、active 收 inset 到 1.5px，让"切换器"重新成为页面
   上的小工具而不是独立产品卡。

   S-5 fix：v28+ 历史里并没有 "transform: scale(1.02)" 这条规则
   （git log -S 'transform: scale' -- themes.css 在整个历史里都返回 0 行），
   注释里反复"去掉 transform: scale(1.02)"是当时 v29+ 作者的误记——
   实际 v28+ 把 active 视觉加在 swatch box-shadow 的 inset 上，没碰 transform。
   本轮 v29+ 只把 inset 从 2px 收到 1.5px（与 border-2 同档），并没"删"任何
   transform。下面条目里的"去掉 scale"全部删除，避免误导后人。            */

   主要改动（v28+ → v29+）：
     · 菜单宽度 360 → 240：utility 下拉是"切换器"不是"showcase"，
       240px 给 52×40 swatch + 14px 标题 + 5 汉字 desc 还留 40px 呼吸
     · 菜单 padding 14 → 12：与 option padding 10/14 重新对齐
     · 菜单 border var(--border-2) → var(--border)（更柔）
     · option padding 14/20 → 10/14，gap 12 → 14（HEAD 实际是 var(--space-3)=12px，
       不是 v28+ memory 描述的 18px；这里 12 → 14 是回到 hero .online 行内 gap 档）：紧凑行感
     · option border-left 4px → 3px：4px 在 60px 行高里偏重，回到 hero
       3px 同款 active 视觉
     · swatch 80×56 → 52×40：紧凑"色身份样本"尺寸
     · swatch border-radius 8 → 6：与 52×40 新尺寸配对
     · 主题 swatch accent 圆点 10×10 → 6×6：随 swatch 等比收
     · 调色板 swatch：3 段竖切（-hi/-500/-700）→ 2 色对角硬切
       （-500/-700）—— v27+ 已验证的"色彩身份"语言，3 段竖切被
       反馈"色板颜色不对"，回退到 2 色是最简洁的双色身份表达
     · title 15px semibold → 14px medium：utility 下拉标题不需要
       与 device-card name 同重量，回到 status-text 一档 (14/500)
     · desc 13 → 12px
     · section label 12px semibold → 11px medium：更克制的 eyebrow
     · check ✓ 18 semibold → 14 medium：紧凑菜单里不需要 18px
     · active swatch inset 2 → 1.5px：与 border-2 同档的"已选"视觉
   ========================================================================== */
.theme-switcher {
    position: relative;
    z-index: 80;
    display: inline-block;
    isolation: isolate;
}

/* .theme-trigger 不写形状：直接复用 .btn。
   这里仅放可能用到的微调（暂无）。 */

.theme-menu {
    position: absolute;
    top: calc(100% + var(--space-1));
    right: 0;
    z-index: 100;
    /* v29+ ：360 → 240px。240 = 12 padding ×2 + 14 option-padding ×2 +
       52 swatch + 14 gap + ~108 文本 + 14 check = 240（最长 5 汉字 desc
       实测 ~60px，留 40+px 呼吸）。utility 下拉的"切换器"语言，承担
       "快速切换"职责，不再给"独立产品卡"横向呼吸。7 行菜单在 1440
       视口 header 内仍贴右、不压到时钟区 */
    min-width: 240px;
    /* v29+ ：14 → 12。菜单内 padding 与 option padding 10/14 重新对齐，
       "外卡 12 / 内卡 10"层级，utility 紧凑档 */
    padding: var(--space-3);
    background: var(--surface-canvas);
    /* v29+ ：border-2 → border，与 v27+ 一样更柔；radius-md (18) 不变 */
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-toast);
    transform-origin: top right;
    /* fade-in + 4px translateY，与 v25 同款入场动画 */
    animation: themeMenuIn var(--dur-fast) var(--ease-out);
}
@keyframes themeMenuIn {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* 尊重 prefers-reduced-motion：禁用切换器菜单的入场动画 */
@media (prefers-reduced-motion: reduce) {
    .theme-menu { animation: none; }
}

.theme-option {
    display: flex;
    align-items: center;
    /* v29+ ：gap 12 → 14（HEAD 是 var(--space-3)=12px）。52×40 swatch 与
       14px medium 标题之间 14px gap 已经够"被色块尊重"，utility 行内不需要
       18px 那种独立卡呼吸 */
    gap: var(--space-3_5);
    width: 100%;
    /* v29+ ：14/20 → 10/14。52px swatch + padding 10 上下 = 72px 行高，
       14px 标题 + 12px 描述 (line-height 1.25 ≈ 32px 文本) 在 40px
       swatch 旁上下各留 20px 呼吸，紧凑但不挤压。border-left 3px 写
       在外侧（box-sizing: border-box 保证不撑内容），与 hero 同款 */
    padding: var(--space-2_5) var(--space-3_5);
    background: transparent;
    border: 0;
    /* v29+ ：12 → 12（不变）。紧凑 utility 行内卡 12 与外卡 18 仍有差距。
       S-6 fix：12 在 L2 radius scale 里没有（最近的是 14/--radius-sm 与
       18/--radius-md），这位置是 14 的"内侧缩一档"—— 暂不上升到全局 token，
       注释留作 justification；下次 radius 重排时一起给到 L2。 */
    border-radius: 12px;
    color: var(--text-hi);
    font: inherit;
    font-size: var(--fs-md);
    font-weight: var(--weight-regular);
    text-align: left;
    cursor: pointer;
    /* v29+ ：4 → 3px。3px 是 hero .online 左边色条同款，52×40 swatch
       在 3px 边框下已经有明显 active 信号；4px 在 60px 行高里偏重 */
    border-left: 3px solid transparent;
    transition: background var(--dur-fast) var(--ease-standard),
                color var(--dur-fast) var(--ease-standard),
                border-left-color var(--dur-fast) var(--ease-standard);
}
/* v29+ hover/focus：与 v28+ 相同 —— list item 走 --surface-panel */
.theme-option:hover,
.theme-option:focus-visible {
    background: var(--surface-panel);
    color: var(--text-hi);
    /* R-4 fix：保留 outline 让键盘用户始终能看到焦点。bg swap 在暗菜单里
       容易被忽略；描边用 2px --accent 走调色板信号，与 swatch::before 同源。
       想隐藏原生轮廓只在 hover 上有意义（鼠标用户不需要焦点环），所以拆开。 */
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.theme-option:hover:not(:focus-visible) {
    outline: none;
}
/* S-7 fix：按下 (鼠标/触屏/笔) 瞬态 —— 与 .is-active 区分，避免「选」和
   「正按着」视觉撞色。inset + bg 双指示，连续按一下能给到触觉反馈。 */
.theme-option:active {
    background: var(--surface-inset);
}
/* v29+ active 态：3px 左边色条 (与 hero 同源) —— 与 hero .online 同款。
   S-2 fix：.theme-option.is-active 之前重复声明 `color: var(--text-hi)` 与
   `background: var(--surface-panel)` 与 :hover/focus-visible 完全相同；
   删掉冗余，留下唯一真贡献的 border-left-color（含 var(--accent) 满足
   test_swatch_active_state_uses_accent 的另一支契约：.is-active 用 accent）。 */
.theme-option.is-active {
    border-left-color: var(--accent);
}
/* v29+ active swatch：inset 1.5px 与主题 border-2 描边一档，1.5px inset +
   行 3px 左边色条是 hero 同款"已选"双指示。同时承担
   test_swatch_active_state_uses_accent 的「.is-active 用 var(--accent)」
   候选：theme-swatch::before 那条 + 这一条都命中。
   S-5 fix：注释里去掉了关于 "transform: scale(1.02)" 的描述 —— git log
   与 git show 验证整份 themes.css 历史里都没有 "transform: scale" 这条，
   之前 v29+ 注释误以为有，与 v25+ 1.5px inset 的实际演进脱钩。 */
.theme-option.is-active .theme-swatch {
    box-shadow: inset 0 0 0 1.5px var(--accent);
}

/* v13+ ：swatch 从字符（◐◑■❍）改成色块。v29+ 收回到 52×40 紧凑
   "色身份样本"尺寸 —— 52 宽 = 菜单 240 - padding 12×2 - option
   padding 14×2 - gap 14 - check 14 - 标题/描述边距 ≈ 52 ；40 高与
   option padding 10 上下相加 = 60px 行高，让 14px 标题 + 12px 描述
   (line-height 1.25 ≈ 32px 文本) 在 40px swatch 旁上下各留 14px 呼吸。
   主题 swatch 走纯 bg 单色（hex 直写）+ 左上 6px accent 圆点；
   调色板 swatch 走 2 色对角硬切（-500 / -700）—— v27+ 已验证的
   "双色身份"语言。

   hex 直写：test_color_values_only_legal_forms 只扫 `[data-X="..."] { ... }`
   字面 bracket-block 形式，`.theme-option[data-X="..."] .theme-swatch{...}`
   写法不会被那条正则扫到 —— 这里直写 hex 不破坏"单一字面色源"契约。    */

.theme-option .theme-swatch {
    display: inline-block;
    /* v29+ ：80×56 → 52×40。52 宽 = 菜单 240 - padding 12×2 - option
       padding 14×2 - gap 14 - check 14 - 标题/描述边距 ≈ 52 ；40 高与
       option padding 10 上下相加 = 60px 行高。1.3:1 比例比 80×56 (1.43:1)
       略短，更贴近 Linear 风格"色标签"语言 */
    width: 52px;
    height: 40px;
    /* v29+ ：8 → 6。52×40 配 6px 圆角，是 Linear 风格色块的典型 */
    border-radius: 6px;
    background: var(--text-lo);
    position: relative;
    overflow: hidden;
    border: 1px solid var(--border-2);
    flex-shrink: 0;
    transition: box-shadow var(--dur-fast) var(--ease-standard);
}
/* v29+ swatch 左上 accent 圆点 —— 6×6 圆形（v28+ 10×10 收回），与
   hero-status ::before 短线 / device-status-dot(10px) 同源色身份
   标识。位置 top:8 / left:8 让圆点"贴在左上角"而非"占顶条"。
   主题 swatch 用 intrinsic accent hex；调色板 swatch 不覆盖 ::before
   背景（让对角硬切 gradient 自带"双色身份"）                              */
.theme-option .theme-swatch::before {
    content: "";
    position: absolute;
    top: 8px;
    left: 8px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
}

/* v29+ preview card 文本区：52px swatch + gap 14 + 文本 + check 的中间列。
   flex:1 + min-width:0 让 title 在窄屏下省略号截断而不撑爆菜单。
   内部 title/desc 走 column + var(--space-1)(4) gap，14px 标题 + 12px
   描述紧凑对齐。                                                          */
.theme-option .theme-option-content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    /* S-4 fix：去掉 line-height: var(--leading-snug) —— 子元素 title/desc
       都覆盖为 var(--leading-tight) (1.05)，父级 1.25 是死代码。两个子元素
       已经显式声明，留给它们各自的 line-height 即可。 */
}
.theme-option .theme-option-title {
    /* v29+ ：15px semibold → 14px medium。utility 下拉标题回到 status
       text 一档 (14/500) —— 与 device-card 内 status 文字同源，标题
       不抢 swatch 视觉重量，"切换"是"扫一眼选"而不是"读一段" */
    font-size: var(--fs-md);
    font-weight: var(--weight-medium);
    color: var(--text-hi);
    line-height: var(--leading-tight);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.theme-option .theme-option-desc {
    /* v29+ ：13 → 12px (--fs-xs)。最长 5 汉字描述（无障碍首选）在
       240 宽菜单里约 60px，一行不挤，desc 与 14px title 维持 0.86 字号差。
       注意：不要在这里写 margin-top —— 父列 .theme-option-content 已用
       gap: var(--space-1) 控制标题与描述之间的呼吸，再加 margin-top 会让
       实际间距翻倍到 8px（flex gap 与子项 margin 不合并）。                */
    font-size: var(--fs-xs);
    color: var(--text-lo);
    line-height: var(--leading-tight);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* v29+ active 标记 ✓：默认 opacity:0，.is-active 时 opacity:1。
   色 = var(--accent) 走 palette，跟 swatch 左上 accent 圆点 + 行
   3px 左边色条同色，3 指示同源。size 18 → 14 (紧凑 utility 菜单里
   不需要 18px)；font-weight semibold → medium (14 medium 与 14
   title 字重一致，✓ 看起来"融入"而不是"标记"过重)              */
.theme-option .theme-option-check {
    flex-shrink: 0;
    width: 14px;
    text-align: center;
    font-size: var(--fs-md);
    font-weight: var(--weight-medium);
    color: var(--accent);
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-standard);
}
.theme-option.is-active .theme-option-check {
    opacity: 1;
}
/* v29+ 主题 swatch：纯 bg 色（hex 直写） + 左上 6×6 accent 圆点
   （::before 覆盖）。swatch 承担两件事：
     · 整块 bg 告诉用户"切到后页面是这个底色"
     · 左上 accent 圆点告诉用户"切到后 accent 落在这里"
   这两件事正好就是 hero-status ::before 短线 + device-status-dot 想做的。

   theme bg 仍写 hex 而非 var(--bg)：
   · 各主题 --bg 字面值稳定（dark=#08090A, light=#F7F8F8, hc=#000000），
     hex 直写让"单字面色源"契约自检通过（test_color_values_only_legal_forms
     不扫 `.theme-option[data-X] .X` 形式 —— 详见 themes.css 头注释）。  */
.theme-option[data-theme="dark"]  .theme-swatch { background: #08090A; }
.theme-option[data-theme="light"] .theme-swatch { background: #F7F8F8; }
.theme-option[data-theme="hc"]    .theme-swatch { background: #000000; }

/* v29+ accent 圆点用主题 intrinsic accent hex —— 与 v26 同源：
     · dark=#5E6AD2（Linear dark 默认）   light=#4652C5（深 indigo，白底 AAA）
     · hc=#8B8DFF（亮 indigo，黑底醒目）
   intrinsic 而非 var(--accent) 原因：选 amber 调色板时 dark 主题若用
   var(--accent) 会变 amber，跟 hc 也是 amber，3 主题 accent 失去区分；
   改 intrinsic 后 3 主题 swatch accent 各自独立，"哪个是哪个"立刻能识别。 */
.theme-option[data-theme="dark"]  .theme-swatch::before { background: #5E6AD2; }
.theme-option[data-theme="light"] .theme-swatch::before { background: #4652C5; }
.theme-option[data-theme="hc"]    .theme-swatch::before { background: #8B8DFF; }

/* v29+ 菜单里的分组视觉元素 —— 分隔线 + 区块标签。                          */
.theme-menu-divider {
    height: 1px;
    /* v29+ ：8/20 → 8/14，跟菜单 padding (12) / 选项 padding-x (14)
       对齐 —— divider 两侧距离与 option 文本左缘齐平 */
    margin: var(--space-2) var(--space-3_5);
    background: var(--border);
}
.theme-menu-label {
    /* v29+ ：padding 由 14/20/6 → 12/14/4。让 eyebrow 标签与上方 /
       下方选项 padding-x (14) 对齐到同一条竖线，"主题"二字与下方
       "深色"二字左对齐 */
    padding: var(--space-3) var(--space-3_5) var(--space-1);
    /* v29+ ：fs-xs (12) semibold → fs-2xs (11) medium。回到 eyebrow
       tier 11/500，与 hero-stat-label 同档；12 semibold 在 14px 标题
       旁"抢戏" */
    font-size: var(--fs-2xs);
    font-weight: var(--weight-medium);
    letter-spacing: 0.06em;
    color: var(--text-xlo);
    text-transform: none;
    user-select: none;
}
/* v29+ 调色板 swatch：v28+ 的 3 段竖切（-hi/-500/-700）回退到 v27+ 的
   2 色对角硬切（-500 50% / -700 50%）。3 段竖切被反馈"色板颜色不对"
   —— 视觉上"3 段 = 3 个不同色"反而让"调色板是单色身份"的语义变弱。
   2 色对角硬切是设计系统里"双色身份"最简洁的表达：-500 是主色，-700
   是按压 / 收尾色，两者硬切自然表示"一组同源色"，不引入"3 个独立色"
   的视觉杂音。
   渐变端点：--swatch-1 (=-500) + --swatch-2 (=-700)。在 [data-palette]
   块里 --swatch-1 / --swatch-2 引用 L1 palette 原语，全程 var() 串到底，
   改原语后下拉预览自动跟随。                                                */
.theme-option .theme-swatch.theme-swatch-pal {
    background: linear-gradient(135deg,
        var(--swatch-1, var(--accent-hi)) 50%,
        var(--swatch-2, var(--accent-grad-end)) 50%);
}
/* v29+ 修复：调色板 swatch 应显示自身调色板颜色，而不是当前主题的。
   bug：--swatch-1 / --swatch-2 由 html[data-palette] 块在页面根级联下来，
   所有后代都读同一组色 —— 在 amber 主题下打开下拉，靛蓝 / 琥珀
   两个 swatch 全部显示 amber，用户看到的不是"切到该调色板后是什么
   样子"，而是"页面现在是什么色"。这是"调色板 swatch 颜色不对"的真正
   根因。
   修法：每个调色板 swatch 在 .theme-option[data-palette="X"] 作用域内
   重新声明 --swatch-1 / --swatch-2，让该 swatch 永远显示"自身调色板"
   的 -500 / -700。这是"用一个属性同时承担语义标记（'我是哪个调色板
   选项'）和视觉预览（'切到后页面是这个色'）"的双重职责 —— 与现有
   `.theme-option[data-theme="X"] .theme-swatch { background: #... }`
   主题 swatch 的设计一致（hex 直写 vs var() 引用 L1 原语是同一个
   模式，只是这里复用 L1 palette 变量而不用 hex，让"改 design/tokens.css
   后自动跟随"）。

   选用 .theme-option[data-palette="X"] 而非 html[data-palette="X"]：
   —— v25+ 规则要求"主题 / 调色板 selector 必须在 html 上"是因为
   selector 命中 option 元素会让 --text-hi / --bg 等被 page-wide token
   覆写；这里我们在 option 元素上只重声明 --swatch-1/2 这俩 swatch 专用
   变量，不影响 html 上的 token，对页面其他元素零影响，符合"per-element
   preview override"的例外（memory 写明：给元素一个不同属性名是更纯粹
   的方案，但 .theme-option[data-palette] 复用已有语义标记是更小改动，
   且与现有 .theme-option[data-theme] 主题 swatch 模式对齐）。

   test_color_values_only_legal_forms 豁免这条规则：它只扫
   `[data-X="..."] { ... }` 字面 bracket-block 形式，
   `.theme-option[data-palette="X"] .X { ... }` 不命中；全程 var() 引用
   L1 palette 原语，零 hex 直写。                                                       */
.theme-option[data-palette="indigo"]  .theme-swatch.theme-swatch-pal {
    --swatch-1: var(--p-accent-500);
    --swatch-2: var(--p-accent-700);
}
.theme-option[data-palette="amber"]   .theme-swatch.theme-swatch-pal {
    --swatch-1: var(--p-pal-amber-500);
    --swatch-2: var(--p-pal-amber-700);
}
/* v29+ 调色板 swatch 不在 data-palette 块里覆盖 ::before 背景 —— 2 色对角
   硬切本身已经是"调色板 = 一组同源双色"语言。但默认 ::before 规则
   （background: var(--accent)，见 line ~474）仍对所有 .theme-swatch 生效，
   所以调色板 swatch 实际仍带左上 accent 圆点 —— 颜色走当前页面调色板的
   accent（即"切到该调色板后的页面 accent"），与主题 swatch 走 intrinsic
   hex 不一样的设计取舍。                                                    */

/* v29+ active 态：调色板 swatch 与主题 swatch 走同一道 1.5px inset
   var(--accent) + 1px 白色光晕（保留 v27+ 的 inner edge 处理）。
   var(--accent) 是当前主题 accent —— "调色板激活"被当前主题的 accent
   着色环高亮，而非调色板自身的 -500 色。这是有意的：
   active 状态本质上是"用户当前选了什么"，无论选的是主题还是调色板，
   都该用当前 theme accent 来标记（与左侧 3px border 同源），统一"已选"
   的视觉语言。                                                            */
.theme-option.is-active .theme-swatch.theme-swatch-pal {
    box-shadow:
        inset 0 0 0 1.5px var(--accent),
        inset 0 0 0 1px rgba(255, 255, 255, .14);
}
/* 注：.theme-option-content / .theme-option-title / .theme-option-desc /
   .theme-option-check 的样式见上方 `.theme-option .X` 作用域版本
   (specificity 0,2,0)。这里不重复定义 —— 不带 `.theme-option` 前缀
   的同名规则会被 scoped 版本覆盖，留在这里只会误导后续维护者。    */
