/* ============================================================================
   沐雪 AI · 通用组件层（跨页面复用，不属于任何单页/单功能）
   ----------------------------------------------------------------------------
   为什么单独成文件（team-lead 2026-10-26 批准）：
     · 这里放的是**跨 6+ 屏复用**的组件（.badge / .skeleton / .sheet / .drawer /
       .wave / .banner / .seg），塞进 pwa.css（安装/更新专用）语义不对；
     · app.css 同期有 315 行在途改动 + 7 处渐变文字待改 + 行号已漂移，是最不该动的文件；
     · 加载顺序：app.css（Token 底座）→ **components.css（本文件，基础层）** → pwa.css（应用层），
       这样应用层可以覆盖组件默认值，反过来不行。

   ★Token 依赖：本文件用到的 `--radius-pill` / `--fs-2xs` / `--radius-sm` 尚未进 app.css 的
     `:root`（记为 OD-006 / OD-007，等那一单批）。**这里一律写成 `var(--token, 同值字面量)`**：
       · token 未定义时 → 用字面量，组件照常可用；
       · token 定义后   → 字面量自动失效（同值，视觉零变化），不需要改本文件。
     ★注意这跟"嵌套兜底"不同：`var(--a, var(--b))` 在 --b 缺失时会**整条失效**；
       而 `var(--a, 11px)` 的兜底是**字面量**，永远有效。（本仓 2026-10-26 实测踩过前者。）

   落地节奏：逐屏增量。当前只落 `.badge`（⑥ 任务中心先跑通 7 态，再复制到 ⑦⑲⑳㉕㉘）。
   ========================================================================== */

/* ── .badge：状态徽章 7 态 ───────────────────────────────────────────────────
   设计依据：DESIGN-SPEC-v2 §6（颜好看 2026-10-26 定稿）。

   ★三条硬约束（不是审美）：
   1. **图标 stroke-width 必须是 2.5，不是 2**。badge 图标 12px，按 24 viewBox 算
      effective = 2×(12/24)=1.0px 太糊；2.5 → 1.25px。换算：24/20/16px→2.0、12px→2.5、10px→3.0
      （10px 是图标下限，再小别画图标）。
   2. **不许挂在任何"宿主作用域"下**（如 `.task .badge` / `.sow-tl-row .badge`）。
      实测踩过：图例里的状态点因为选择器写窄了，CSS 里查得到、页面上渲染成透明空点，
      **看图看不出来**。本文件一律用裸类名。
   3. **状态不能只靠颜色**：7 态各自带独立图标（形状不同）+ 3 种边框（无/实线/虚线），
      灰度与色弱下都能分。断言口径：任意两个 state 至少在 1 个非颜色属性上不同。
   ------------------------------------------------------------------------ */
.badge {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  min-height: 20px; padding: 2px 8px;
  border-radius: var(--radius-pill, 999px);   /* @pending-token OD-007 */
  font-size: var(--fs-2xs, 11px);   /* @pending-token OD-006 */
  font-weight: 600; letter-spacing: .2px;
  white-space: nowrap;
}
.badge svg {
  width: 12px; height: 12px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ① 运行中：实心圆点，无边框 */
.badge.run { background: var(--brand-soft); color: var(--brand-strong); }
/* ② 已完成：对勾，无边框 */
.badge.done { background: var(--success-soft); color: var(--success); }
/* ③ 等待中：时钟，实线描边（与 ⑤ 风险靠图标区分，与 ⑥⑦ 靠边框虚实区分） */
.badge.wait { background: var(--warn-soft); color: var(--warn); border: 1px solid var(--warn-line); }
/* ④ 草稿：减号圆，实线描边（弱化） */
.badge.draft { background: var(--panel-2); color: var(--muted); border: 1px solid var(--line); }
/* ⑤ 风险：感叹号，实线描边 + danger 色 */
.badge.risk { background: var(--danger-soft); color: var(--danger); border: 1px solid var(--danger); }
/* ⑥ 已暂停：双竖线 + **实线**边框（终止态：确定停住了） */
.badge.paused { background: var(--panel-2); color: var(--text-mid); border: 1px solid var(--line-3); }
/* ⑦ 暂停中：未闭合圆弧 + **虚线**边框（过渡态：还没停稳，连点会重复下发） */
.badge.pausing { background: transparent; color: var(--brand); border: 1px dashed var(--brand); }

/* ── .skeleton：骨架屏 ────────────────────────────────────────────────────────
   三条来自实测的约束（都不是审美）：
   1. **底色用 `--line`，不用 `--panel-2`**。`--panel-2` (rgba(20,26,48,.58)) 与卡片底
      `--panel` (rgba(14,19,36,.72)) 只差一点明度 ⇒ 骨架块会被读成"已经是内容"；
      `--line` (rgba(120,165,255,.13)) 才有"待填充"的浅描边感。
   2. **扫光受 R4-ex1 约束**：transparent → 单色相 → transparent（仅 .skeleton 允许三色标）。
      中间色相只能有一个，不许出现第二个色相。
   3. **reduced-motion 下连伪元素一起不渲染**（`display:none`），不是只关 animation。
      ⚠ **不要改成 `animation: none`** —— 扫光画在 `::after` 上，关掉动画只会让它定在
        某个位移、在原位留下一个静止的白色小方块，比不降级更难看。
      （`display:none` 在不懂原因的人眼里像"多余的写法"，所以这里写清"改了会怎样"。）
   ------------------------------------------------------------------------ */
.skeleton {
  position: relative; overflow: hidden;
  background: var(--line);
  border-radius: var(--radius-sm, 8px);   /* @pending-token OD-007 */
}
.skeleton::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 42%;
  /* 链尾是 token 而非字面量：`--brand-soft` 是 app.css 48 个核心 token 之一，恒存在。
     若要严格满足"链尾必须落地"，需把 `--sk-sweep-mid` 加进 app.css :root —— 已记 OD-017。 */
  background: linear-gradient(90deg, transparent, var(--sk-sweep-mid, var(--brand-soft)), transparent);
  animation: sk-sweep 1.4s linear infinite;
}
@keyframes sk-sweep { from { transform: translateX(-120%); } to { transform: translateX(340%); } }
/* 低端机降级：位移实心光斑。transform 走合成层，比 background-position 的绘制更省。 */
.skeleton.is-soft::after {
  background: var(--brand-soft);
  filter: blur(10px);
}
@media (prefers-reduced-motion: reduce) {
  .skeleton::after { display: none; }   /* ★不是 animation:none —— 否则留下静止白块 */
}

/* ============================================================================
   待实现的五个组件（占位：不标注的空白会被当成"不需要"）
   ----------------------------------------------------------------------------
   落地顺序（team-lead 2026-10-26 定）：
     1. .skeleton  ← **已实现**（见上方）
     2. .badge 复制到第二宿主（在最复杂宿主 ⑲ .field 上验虚线边是否打架）
     3. .seg       ← **等 app.css 那一单定了 .filters 的去留再做**
                     否则会造一个"和 .filters 并存的新组件"，没人知道该用哪个
     · .sheet / .drawer / .wave / .banner  ← 随对应屏逐屏落

   .skeleton  骨架屏：底色 **--line 不是 --panel-2**（后者在 --panel 卡上读成"已是内容"）；
              扫光受 R4-ex1 约束（transparent → 单色相 → transparent，仅 .skeleton）；
              prefers-reduced-motion 下整条 display:none（模糊方块只要在位移就仍是动画）
   .sheet     底部半屏：74% 高 / 顶部 grab 38×4 / Esc + 焦点陷阱 + 焦点归还 + 遮罩不穿透
   .drawer    侧滑抽屉：306px（390 的 78%）/ 同上四条交互
   .wave      录音波形：24 根 × 3px 柱 / gap 3px / 柱高由 JS 每帧写 --h；
              **CSS 里一条 animation 都没有**（区别于 .aintro-eq 那种固定 keyframes 假动画）
   .banner    全局横幅：data-tone=info|warn|danger / 高 40px / 插在 topbar 下方推移内容不覆盖；
              离线横幅用 danger 且**不可手动关闭**（能关掉用户就不知道自己还在离线态）
   ========================================================================== */
