/* ==========================================================================
   medradar 亮色主题
   --------------------------------------------------------------------------
   分层靠「浅灰画布 + 纯白卡片 + 阴影阶梯」，不靠深色块、也**不靠描边**
   （2026-09-23 边框饮食：容器一律不描边，边框只留输入框、表行分隔线、聚焦环）。
   表格是这套系统的主体，所以表头吸顶、行悬停、数值列等宽数字这些细节
   比配色更影响观感，都单独处理过。
   ========================================================================== */

:root {
  /* ======================================================================
     设计变量：**与 Arco Design 同构**（2026-09-18）
     ----------------------------------------------------------------------
     分两层，各有各的用途：
       ① 第一层用 **Arco 的名字与刻度**（色阶是 RGB 三元组、语义别名、
          圆角 0/2/4/8、字号 12/14/16/20/24）——这样两条产品线（geohub 用 Arco）
          在视觉上是一个系统；将来这一页真要换成 Arco 组件时，这层能 1:1 喂给
          Arco 的 `ConfigProvider` token，不用重新调色。
       ② 第二层是 **medradar 既有的变量名，全部保留为别名**——app.js 与 theme.css
          里几千处引用的是它们，改名等于全站重写一遍，收益为零。

     取值来源：Arco 编译产物（`@arco-design/web-react@2 dist/css/arco.css`）
     **实测抄录**，不是凭印象写的。两处**有意偏离**都写在下面各自的注释里。
     ====================================================================== */

  /* ── Arco 中性色阶（RGB 三元组：可拼 rgba(var(--gray-6), .5) 叠透明度）── */
  --gray-1: 247, 248, 250;
  --gray-2: 242, 243, 245;
  --gray-3: 229, 230, 235;
  --gray-4: 201, 205, 212;
  --gray-5: 169, 174, 184;
  --gray-6: 134, 144, 156;
  --gray-7: 107, 119, 133;
  --gray-8: 78, 89, 105;
  --gray-9: 39, 46, 59;
  --gray-10: 29, 33, 41;

  /* ── 品牌主色：医知雷达绿 #0f9d78 的 Arco 式色阶 ──────────────────────
     不是 Arco 默认蓝（--arcoblue-6 = 22,93,255 / #165DFF）——主色是品牌资产，
     改成蓝等于换品牌。1..10 按 Arco 的明度阶梯生成（1-5 向白、7-10 向黑），
     这样它和 Arco 的其它色阶放在一起是同族的。 */
  --primary-1: 231, 245, 242;
  --primary-2: 178, 224, 212;
  --primary-3: 130, 204, 185;
  --primary-4: 87, 186, 161;
  --primary-5: 51, 172, 140;
  --primary-6: 15, 157, 120;
  --primary-7: 13, 133, 102;
  --primary-8: 11, 110, 84;
  --primary-9: 8, 86, 66;
  --primary-10: 6, 63, 48;

  /* ── 语义色（Arco 的 danger/warning/success 就是 red/orange/green 色阶）── */
  --danger-1: 255, 236, 232;
  --danger-6: 245, 63, 63;
  --warning-1: 255, 247, 232;
  --warning-6: 255, 125, 0;
  --success-1: 232, 255, 234;
  --success-6: 0, 180, 42;

  /* ── 语义别名（映射关系同样抄自 Arco：text-1..4 = gray-10/8/6/4 等）── */
  --color-text-1: rgb(var(--gray-10));
  --color-text-2: rgb(var(--gray-8));
  --color-text-3: rgb(var(--gray-6));
  --color-text-4: rgb(var(--gray-4));
  --color-bg-1: #ffffff;
  --color-bg-2: rgb(var(--gray-1));
  --color-bg-3: rgb(var(--gray-2));
  --color-bg-4: rgb(var(--gray-3));
  --color-border-1: rgb(var(--gray-2));
  --color-border-2: rgb(var(--gray-3));
  --color-border-3: rgb(var(--gray-4));
  --color-border-4: rgb(var(--gray-6));
  --color-fill-1: rgb(var(--gray-1));
  --color-fill-2: rgb(var(--gray-2));
  --color-fill-3: rgb(var(--gray-3));
  --color-fill-4: rgb(var(--gray-4));

  /* ── 圆角：Arco 的刻度名，取值从 0/2/4/8 抬到 0/4/6/12（2026-09-23）─────
     改的是**取值**不是名字——名字本来就是 Arco 的，将来喂 ConfigProvider 是同一次改动。
     为什么抬：2~4px 的控件圆角是"老一辈后台"的第一眼来源；面板用 8px 又配 1px 边框时，
     看着像表格的分格线，不像一张卡片。全站引用 --radius-sm / --radius 的地方一起生效。 */
  --border-radius-none: 0;
  --border-radius-small: 4px;
  --border-radius-medium: 6px;
  --border-radius-large: 12px;
  --border-radius-circle: 50%;

  /* ── 字号与间距：按 Arco 的刻度定义 ───────────────────────────────────
     Arco 没把字号/间距导出成 CSS 变量（编译产物里是字面 px，实测），所以这两个刻度
     用的是它的规范值、名字是我们自己的。 */
  --font-size-sm: 12px;
  --font-size-base: 14px;
  --font-size-lg: 16px;
  --font-size-xl: 20px;
  --font-size-2xl: 24px;
  /**
   * **第二处有意偏离：全站正文 13px**（Arco 的正文是 14px）。
   * 本项目表格密集，13px 是刻意的密度选择——登记成变量而不是散落的字面量：
   * 偏离是有意的，而且要能一处改。
   */
  --font-size-table: 13px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;

  /* ══ 以下为 medradar 既有变量名（别名，保持不变）══════════════════════
     旧名 → Arco 层。颜色都走 rgb(var(--…))，与 Arco 的合成方式一致。 */
  /**
   * `--bg` 的角色是**浅填充**：悬停底、chip 底、进度槽、代码底，全站 27 处引用它。
   * 页面画布另走 `--color-bg-3`（见 body 一条）——原先两者共用一个值，于是卡片与背景
   * 几乎同色，只能靠 1px 边框分开。拆开之后边框才能退场（2026-09-23）。
   */
  --bg: var(--color-bg-2);
  --card: var(--color-bg-1);
  /* 原 #fbfcfd（几乎纯白）→ Arco 的 fill-1：表头/斑马纹的底，略微加深一档 */
  --card-2: var(--color-fill-1);
  --border: var(--color-border-2);
  --border-strong: var(--color-border-3);

  --text: var(--color-text-1);
  --dim: var(--color-text-2);
  --dim-2: var(--color-text-3);

  /**
   * 品牌绿**分两个角色**（2026-09-18 按实测对比度定的，见各值后面的数字）：
   *   · `--accent`      = -6 #0f9d78：**填充与图形**（图标、进度条、选中底、logo 信号点、
   *                        边框、焦点环）。3.43:1 —— 图形/大字够（门槛 3.0），**当正文不行**
   *   · `--accent-text` = -8 #0b6e54：**浅底上的绿字**（链接、当前导航、hover、
   *                        绿底徽标上的字）。6.23:1 —— 过 AA（门槛 4.5）
   *   · `--accent-strong` = -7 #0d8566：**绿底白字的填充**（主按钮）。白字在其上 4.60:1 ✓
   * 原先三者是同一个绿：绿字只有 3.43、白字在绿底上也只有 3.43，**都没过 AA**。
   * 所以这不是调色偏好，是把「色块」和「文字」拆开——同一个色相，不同的明度职责。
   */
  --accent: rgb(var(--primary-6));
  --accent-text: rgb(var(--primary-8));
  --accent-strong: rgb(var(--primary-7));
  /**
   * Arco 的 hover 取 -5（更亮）；我们取 -7（更深）。
   * 有意偏离：主色按钮是「绿底白字」，浅底上悬停变亮会压低对比度，加深更稳。
   */
  --accent-hover: rgb(var(--primary-7));
  --accent-weak: rgb(var(--primary-1));

  /* 分数档位（高/中/低）借用 Arco 的语义色，不再是各自拍的颜色 */
  --high: rgb(var(--danger-6));
  --high-weak: rgb(var(--danger-1));
  --mid: rgb(var(--warning-6));
  --mid-weak: rgb(var(--warning-1));
  --low: var(--color-text-3);
  --low-weak: var(--color-fill-2);
  /** 小计行底色：要明显区别于白底，否则小计会被当成明细读 */
  --row-sum: var(--color-fill-2);

  /**
   * 阴影阶梯（2026-09-23）：原来只有一档 `0 1px 2px rgba(29,33,41,.04)`——
   * 4% 透明度的一像素阴影在浅底上等于不可见，卡片于是只能靠边框立起来，
   * "边框密集"的观感就是这么来的。三层各管一件事，别互相顶替：
   *   -1 静置卡片 / -2 悬浮与下拉 / -3 弹层与抽屉。
   * 旧名 `--shadow`、`--shadow-lg` 保留为别名，全站上千处引用不用动。
   */
  --shadow-1: 0 1px 2px rgba(16, 24, 40, 0.06), 0 1px 3px rgba(16, 24, 40, 0.1);
  --shadow-2: 0 4px 16px -4px rgba(16, 24, 40, 0.12);
  --shadow-3: 0 12px 32px -8px rgba(16, 24, 40, 0.18);
  --shadow: var(--shadow-1);
  --shadow-lg: var(--shadow-3);
  --radius: var(--border-radius-large);
  --radius-sm: var(--border-radius-medium);

  /**
   * 动效：原来 12 处各写各的（.12s / .15s / .22s / ease / linear）。
   * 统一到两档时长 + 一条缓出曲线——"手感"就来自这两件事，
   * 不统一时同一屏会出现快慢不一的三种节奏。文件末尾有 prefers-reduced-motion 兜底。
   */
  --motion-fast: 120ms;
  --motion-base: 180ms;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  /** 顶栏高度。侧栏的 sticky/height 都要减掉它，改这里一处即可 */
  --topbar-h: 52px;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  /* 画布比浅填充（--bg）再深一档：白卡片靠明度差自己浮起来，不必再描边 */
  background: var(--color-bg-3);
  color: var(--text);
  font: 14px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
    "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}

a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ------------------------------- 布局 ------------------------------- */

/* 整屏顶栏 + 下方「侧栏 | 内容」两栏。
   顶栏横跨整个宽度（含侧栏上方），品牌在左、账号在右——对齐 Beisen 那类
   后台的骨架：顶栏是全局条（我是谁、跨模块入口），侧栏是模块内导航。
   历史：账号身份原先浮在内容区右上角，为了不和页头按钮撞车，页头得留 216px
   右内边距，窄屏还会把主操作挤到第二行。上顶栏后这些避让全部不需要了。 */
.shell { display: flex; flex-direction: column; min-height: 100vh; }

.topbar {
  position: sticky; top: 0; z-index: 30;
  flex: none; height: var(--topbar-h);
  display: flex; align-items: center; gap: 12px;
  padding: 0 18px;
  background: var(--card);
  border-bottom: 1px solid var(--color-border-1);
}
.topbar .spacer { flex: 1; }
/* .brand 的基础布局必须挂在通用选择器上：登录页也用 .brand，而它自己的
   `.brand { justify-content: center; gap: 11px }` 是**假设**这里已经是 flex 才写的。
   历史上 display:flex 只挂在 .side 下，所以登录页那两条声明一直是失效的——
   品牌块实际是行内布局、左对齐，只是行内基线 + .names 的间距碰巧看着不别扭，才没被发现。
   把它收窄回 .topbar 会让登录页退回那个状态（品牌块不再居中）。 */
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; }
.topbar .brand { gap: 9px; }
.topbar .brand .cn { font-size: var(--font-size-lg); }
.topbar .brand .en { font-size: var(--font-size-sm); }

.layout { flex: 1; display: flex; min-height: 0; }

.side {
  width: 208px;
  flex: 0 0 208px;
  background: var(--card);
  border-right: 1px solid var(--color-border-1);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: var(--topbar-h);
  height: calc(100vh - var(--topbar-h));
}

/* 品牌名排版：中文名为主、英文名为辅。
   英文名跟着 .names 自己的 4px 间距走，不吃 .brand 的 gap——
   之前英文名跟着 flex gap 走，和中文名之间隔了 17px，看着像两个不相干的词。 */
/* 品牌标记：雷达量程圈 + 医疗十字（见 docs/logo.md）
   缺口环缓慢旋转，读作「波束在扫」；再慢也必须有 prefers-reduced-motion 开关——
   主题动效缺失这一条在 web-design-guidelines 里是明确要修的。 */
.brand .names { display: inline-flex; align-items: baseline; gap: 4px; }
.brand .cn { font-weight: 700; letter-spacing: .2px; white-space: nowrap; }
.brand .en { font-weight: 400; letter-spacing: .2px; white-space: nowrap; color: var(--dim-2); }
.login-page .brand .cn, .p-brand .cn { font-size: var(--font-size-lg); }

/* 标记配色：结构（同心环 + 虚线外环）走 --mark-structure，中心点走 currentColor。
   深色底上把 --mark-structure 换成浅色即可，点色不用动。 */
.brand .mark { color: var(--accent); --mark-structure: #101a2e; flex: none; }
/* 转的是虚线外环（.ring-scan）——自转读作「正在扫描」，而且虚线没有明确的前缘，
   天生不会像「圆心射出的实线」那样被看成钟表指针。
   transform-box 必须是 view-box：fill-box 会拿虚线环自己的包围盒当原点，
   而它的包围盒中心不在圆心（虚线有缺口），一转就歪出画面。 */
.brand .mark .ring-scan {
  transform-box: view-box;
  transform-origin: 32px 32px;
  animation: sweep 6s linear infinite;
}
@keyframes sweep {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .brand .mark .ring-scan { animation: none; }
}

/* hidden 属性必须真的能藏东西。
   浏览器对 [hidden] 的默认 display:none 来自 UA 样式表，**任何作者样式里的 display 都会盖掉它**——
   于是 nav.hidden = true 这种写法"属性生效了、视觉没生效"（.nav a 有 display:flex）。
   实测踩到：团队入口本该只对主管可见，销售登录后照样看得见、点进去被弹回机构线索。
   这条兜底一劳永逸，别再逐个元素去猜它有没有 display 规则。 */
[hidden] { display: none !important; }

/* 顶部留白原来由品牌块撑开（品牌块已上顶栏），所以这 14px 得自己给 */
.nav { padding: 14px 10px 4px; display: flex; flex-direction: column; gap: 2px; }
.nav a {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 12px; border-radius: var(--radius-sm);
  color: var(--dim); font-weight: 500;
}
.nav a:hover { background: var(--bg); color: var(--text); text-decoration: none; }
.nav a.active { background: var(--accent-weak); color: var(--accent-text); font-weight: 600; }
.nav .ico { width: 16px; text-align: center; font-size: var(--font-size-base); }

/* 顶栏右侧：账号入口
   --------------------------------------------------------------------------
   位置：侧栏底部 → 内容区右上角浮层 → 顶栏。
   形态：带框的账号 chip → 素圆头像（对齐 geohub 的做法）。
   右上角不该出现一个"单独的框"：那是把「设置面板」的语汇放进了全局条，
   而且 260px 的带框块和左侧品牌块各占一头，看着像两个平级的模块。
   一个圆头像就够了——它读起来是「操作者」，点开才是「账号与操作」。

   账号名和角色移进下拉（头部），头像 title 上也带着。
   这个取舍是有代价的：多账号下「我现在是哪个账号」不再一眼可见——
   如果后面觉得销售容易拿错账号，就在头像左边补一行不带框的小字（.idwho），
   不要退回带边框的块。 */
.idbar { position: relative; flex: none; }
.idav {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: 0; border-radius: 50%;
  /* 实心饱和圆（对齐 geohub）：淡底 + 同色字太轻，会被读成"在线状态点"而不是账号入口 */
  background: var(--accent); color: #fff;
  cursor: pointer;
}
.idav .av { font-size: var(--font-size-sm); font-weight: 700; line-height: 1; }
/* hover 用加深而不是加边框：边框一出现就又变回"框"了 */
.idav:hover { background: var(--accent-hover); }
.idav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.idmenu {
  position: absolute; top: calc(100% + 8px); right: 0; width: 208px;
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--border-radius-large); padding: 5px; box-shadow: 0 10px 28px rgba(16, 24, 40, .14);
}
.idmenu .hd { padding: 8px 10px 7px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.idmenu .hd .n { font-size: var(--font-size-table); font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.idmenu .hd .r { font-size: var(--font-size-sm); color: var(--dim-2); margin-top: 1px; }
.idmenu button {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  font: inherit; font-size: var(--font-size-table); padding: 8px 10px; border-radius: var(--border-radius-medium);
  cursor: pointer; color: var(--text);
}
.idmenu button:hover { background: var(--accent-weak); color: var(--accent-text); }
.idmenu button.danger:hover { background: var(--low-weak); color: var(--low); }

.side .links button:hover { color: var(--accent-text); }
.side .links button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.main { flex: 1; min-width: 0; padding: 24px 28px 64px; }

/* 折叠区：低频的东西（渠道覆盖、名单导入）收在这里，默认收起，不占导航层级 */
details.fold {
  background: var(--card); border: 1px solid transparent; border-radius: var(--radius);
  margin-bottom: 16px; box-shadow: var(--shadow-1); overflow: hidden;
}
details.fold > summary {
  padding: 13px 16px; cursor: pointer; font-size: var(--font-size-base); font-weight: 600;
  display: flex; align-items: center; gap: 9px; list-style: none; user-select: none;
}
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary::before {
  content: '▸'; color: var(--dim-2); font-size: var(--font-size-sm); transition: transform var(--motion-fast) var(--ease);
}
details.fold[open] > summary::before { transform: rotate(90deg); }
details.fold > summary:hover { background: var(--card-2); }
details.fold > summary .sub { font-weight: 400; color: var(--dim-2); font-size: var(--font-size-sm); }
details.fold[open] > summary { border-bottom: 1px solid var(--color-border-1); }
/* 折叠体自己带内距：`.body` 的 16/18 是挂在 `.panel` 下的，折叠区里得单独给一次，
   否则内容贴着边（2026-09-23 把折叠区收进原语时补的） */
details.fold > .body { padding: 14px 18px; }
details.fold .table-wrap { max-height: 460px; }
/* 页头右侧不再需要给身份块留白：身份已上顶栏，页头整行都归它自己 */
.page-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.page-head h1 { margin: 0; font-size: var(--font-size-xl); font-weight: 650; letter-spacing: -.2px; }
.page-head .sub { color: var(--dim-2); font-size: var(--font-size-sm); }
.page-head .spacer { flex: 1; }

/**
 * 胶囊选择组（记一笔的「这次是什么接触」与「拨打结果」）。
 *
 * 为什么用胶囊而不是下拉：这两个选择**都要一眼看全**——下拉只显示当前项，
 * 而"记电话时能选什么结果"本身就是在教销售该记什么（接通/没人接/空号/转错人/一听就挂）。
 * 单选、可再点一次取消（允许"先记内容，结果待补"）。
 */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 6px; }
.chip {
  font: inherit; font-size: var(--font-size-sm); line-height: 1.5;
  padding: 3px 10px; border-radius: 14px; cursor: pointer;
  border: 1px solid var(--border-strong); background: var(--card); color: var(--dim);
}
.chip:hover { border-color: var(--accent); color: var(--accent-text); }
.chip.active { background: var(--accent-weak); border-color: var(--accent-weak); color: var(--accent-text); font-weight: 600; }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/**
 * 侧栏：5 个一级菜单（2026-09-20 三改，按用户给的参照形态）。
 *
 * 走过的三个版本，记着别再绕回去：
 *   一版：13 项平铺 → 靠小灰字分组。问题：**层次倒挂**（组标题 13px 最浅灰，条目 14px 带图标），
 *         一级像装饰、二级像主体。
 *   二版：组标题变可折叠的父级、子项缩进。层次对了，但组标题**没有图标**、
 *         子项一直摊开——还是"一列文字"，不是"可扫的功能入口"。
 *   三版（本版）：一级项统一成「图标 + 标题」（带子项的右边一个 ›），子项默认折起。
 *         与同类产品的侧栏形态一致：一级是可扫的入口，点开才看到子项。
 *
 * 两条护栏保留（改了这块别拆）：
 *   ① **当前页所在组自动展开**（app.js 的 setActiveTab）——折起后看不到自己在哪
 *      是折叠唯一的真代价，用自动展开消掉；
 *   ② 空组自动隐藏（app.js 按角色算）——销售不该看到空的「管理」。
 * 折叠状态按组名存 localStorage：个人偏好，不进 URL、不跟账号走。
 */
.nav-group { margin-top: 2px; }
.nav-group[hidden] { display: none; }

/* 一级项：图标 + 标题 + 右侧 ›，与叶子节点（a）同一套尺寸，读起来是同一层级 */
.nav-t {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 11px; margin: 0; background: none; border: 0;
  border-radius: var(--radius-sm); cursor: pointer; text-align: left;
  font: inherit; font-weight: 500; color: var(--dim);
}
.nav-t:hover { background: var(--bg); color: var(--text); }
.nav-t:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.nav-t-label { flex: 1; min-width: 0; }
/* › 朝右=折起，展开时转 90° 朝下 */
.nav-caret { color: var(--dim-2); font-size: 12px; transition: transform var(--motion-fast) var(--ease); }
.nav-group.open .nav-caret { transform: rotate(90deg); }
/* 组内有当前页时，一级项也标出"这组里有你所在的位置"——否则折起时完全看不出来 */
.nav-group.has-active > .nav-t { color: var(--text); font-weight: 600; }
.nav-group-b { display: none; flex-direction: column; gap: 2px; }
.nav-group.open .nav-group-b { display: flex; }
/**
 * 子项：**带图标**，且图标与一级标题的**文字**对齐（缩进 36px）。
 *
 * 上一版我把子项图标藏了（`.nav-group-b a .ico { display: none }`），想做成"子项只留文字"——
 * 用户一眼看出不对：**子项是功能入口，光有四个字认起来慢**；而且那版还藏漏了一个
 * （医院名册用的是 `<span class="nav-ico">`，不受 `.ico` 规则管），于是"有的有图标有的没有"，
 * 看起来像 bug。现在统一：一级、子项都有图标，子项缩进到一级标题文字下方，
 * 形成"父带图标 → 子在文字列下再带图标"的两级网格。
 * 字号保持 sm：层级靠缩进与字号，不靠"把子项做小到看不清"。
 */
.nav-group-b a { padding-left: 36px; font-size: var(--font-size-sm); }
.nav-group-b a .ico { width: 15px; height: 15px; }

/* ------------------------------ 控件 ------------------------------ */

button {
  font: inherit; cursor: pointer; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--card); color: var(--text);
  padding: 7px 13px; transition: background var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}
button:hover { background: var(--bg); }
/* 底色用 -7 而不是 -6：白字在 -6 上只有 3.43:1（不过 AA），在 -7 上是 4.60:1 ✓ */
button.primary { background: var(--accent-strong); border-color: var(--accent-strong); color: #fff; font-weight: 600; }
button.primary:hover { background: var(--accent-hover); }
/* 破坏性确认（停用账号、降级权限…）：描边+文字用高危色，**不做实心填充**——
   实心红底配白字对比度只有 3.4:1 左右，不过 AA；而描边形态与全站 button.mini.danger 一致 */
button.danger { border-color: var(--high); color: var(--high); }
button.danger:hover { background: var(--high-weak); }
button.ghost { border-color: transparent; background: transparent; color: var(--dim); }
button.ghost:hover { background: var(--bg); color: var(--text); }
button:disabled { opacity: .55; cursor: not-allowed; }
/* 跳转类动作（打开报告）用 <a>：它是导航不是提交，浏览器该给的右键/新标签/复制链接
   都只有链接形态才有。造型与 button 对齐，免得同一条按钮行里两种样子。
   primary 用 -7 同 button.primary：白字在 -6 上不过 AA（见上） */
a.btn {
  display: inline-flex; align-items: center; gap: 4px; font: inherit; cursor: pointer;
  border-radius: var(--radius-sm); border: 1px solid var(--border-strong);
  background: var(--card); color: var(--text); padding: 7px 13px; text-decoration: none;
  transition: background var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}
a.btn:hover { background: var(--bg); }
a.btn.primary { background: var(--accent-strong); border-color: var(--accent-strong); color: #fff; font-weight: 600; }
a.btn.primary:hover { background: var(--accent-hover); }

input, select, textarea {
  font: inherit; background: var(--card); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 7px 10px; outline: none; transition: border-color var(--motion-fast) var(--ease), box-shadow var(--motion-fast) var(--ease);
}
input:focus, select:focus, textarea:focus {
  /* 边框走 -8、光晕用同档的淡色：焦点环的边框与光晕必须同一档，否则看起来是两套绿 */
  border-color: var(--accent-text); box-shadow: 0 0 0 3px rgba(11, 110, 84, .12);
}
textarea { width: 100%; min-height: 110px; font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; resize: vertical; }

.filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.filters input[type=search] { min-width: 210px; }
.note { color: var(--dim-2); font-size: var(--font-size-sm); }

/* 分段切换（监测页内部） */
.seg { display: inline-flex; gap: 2px; background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 3px; }
.seg a {
  padding: 6px 13px; border-radius: var(--border-radius-medium); color: var(--dim); font-size: var(--font-size-table); font-weight: 500;
}
.seg a:hover { color: var(--text); text-decoration: none; }
.seg a.active { background: var(--card); color: var(--text); font-weight: 600; box-shadow: var(--shadow); }

/* ------------------------------ 卡片 ------------------------------ */

.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(146px, 1fr)); gap: 12px; margin-bottom: 16px; }
.card {
  background: var(--card); border: 1px solid transparent; border-radius: var(--radius);
  padding: 14px 16px; box-shadow: var(--shadow-1);
}
.card .k { font-size: var(--font-size-sm); color: var(--dim); margin-bottom: 4px; }
.card .v { font-size: var(--font-size-2xl); font-weight: 700; letter-spacing: -.5px; font-variant-numeric: tabular-nums; }
.card .v.hot { color: var(--high); }
.card .v.good { color: var(--accent-text); }
.card .v.dim { color: var(--dim-2); }
/* 缺口/逾期这类「要盯」的值。介于 hot（红，出事了）与 dim（灰，没数）之间一档 */
.card .v.warn { color: var(--mid); }
.card .hint { font-size: var(--font-size-sm); color: var(--dim-2); margin-top: 2px; }
/* 卡片里的动作入口（业绩卡的「设定目标」）：贴着 hint 往下排，不跟数值抢行 */
.card-act { margin-top: 8px; }

/**
 * 统计条：把三张大卡压成一条。
 *
 * 为什么压：投放分布页的主体是矩阵，而「侧栏 + 页头 + 3 张卡 + 筛选栏 + 两行表头」
 * 之后首屏只剩十行左右数据。KPI 是陪衬，不该吃掉矩阵的纵向空间。
 * 所以卡片变矮（数字与说明同一行）、间距收紧，但**保留"每个数都带基准"这个原则**——
 * 单独一个「2814」没有决策价值，要有分母或一句判断。
 */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; margin-bottom: 20px; }
.stat {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  background: var(--card); border: 1px solid transparent; border-radius: var(--radius);
  padding: 14px 16px; box-shadow: var(--shadow-1); color: inherit; text-decoration: none;
}
a.stat:hover { border-color: transparent; box-shadow: var(--shadow-2); }
.stat .k { font-size: var(--font-size-sm); color: var(--dim); }
/* 只有一个数用强调色（"先看这个"），其余中性——三张卡各用一个色系会变成红绿灯，读者无法判断优先级 */
/* 数就是主角：20px 时和页头 h1 一样大，"先看这个"的层级出不来，抬到 24px */
.stat .v { font-size: var(--font-size-2xl); font-weight: 700; letter-spacing: -.6px; font-variant-numeric: tabular-nums; }
.stat.accent .v { color: var(--accent-text); }
.stat .hint { font-size: var(--font-size-sm); color: var(--dim-2); width: 100%; }

/**
 * 概览「本月业绩」压成的单行摘要。
 *
 * 那四个数（本月已成交 / 未结 / 预计合计 / 距目标）是 `/api/crm/report/forecast` 的
 * 同一批字段，「经营分析 → 业绩」已经用四张卡画过一遍；在概览上再来四张等于把那边重印，
 * 而且没数据时四张卡全是 0、各自还拖一句解释。一行就够，细节点进经营分析。
 * 分隔符用独立的 `<i>` 而不是文本里的 ` · `：窄屏折行时分隔号不会孤零零留在行尾。
 */
.sum-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; font-size: var(--font-size-table); }
.sum-line > span { color: var(--dim); }
.sum-line b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.sum-line > i { color: var(--color-text-4); font-style: normal; }
.sum-line .mini { margin-left: auto; }

/* 只给读屏用的文本（空值、图标含义等） */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/**
 * 列分组表头：把平台列分成「付费投放证据」与「仅收录」两组。
 *
 * 这是全系统最核心的一条区分（付费位 = 已经花过钱、是要打的理由；弱信号 = 只是被收录），
 * 但原来两类列长得一模一样，一屏扫下去分不出哪几列有付费证据。
 * 分组行给出边界，弱信号那组再整体降饱和 + 浅底，形成"主/次"的视觉层级。
 */
table.matrix thead tr.group-row th {
  position: sticky; top: 0; z-index: 3;
  font-size: var(--font-size-sm); font-weight: 600; color: var(--dim);
  background: var(--card-2); border-bottom: 1px solid var(--border);
  text-align: center; padding: 4px 8px;
}
table.matrix thead tr.group-row th.g-paid { color: var(--accent-text); }
table.matrix thead tr.group-row th.g-weak { color: var(--dim-2); }
/* 第二行表头（平台名）跟着往下挪一格 sticky 位置 */
table.matrix thead tr.col-row th { position: sticky; top: var(--matrix-group-h, 26px); z-index: 2; }

/**
 * 覆盖矩阵**不吃通用行内距**（2026-09-23 从实测反推的例外）。
 *
 * 通用规则把 `tbody td` 的内距从 6px 抬到 10px，那是给"阅读型表格"的（一行是一条记录）。
 * 但矩阵是**热力格子**：10 行 × N 列要落在一屏内，行高每加 8px 就多出 80px，
 * 而它的第二行表头还有 `top: 26px` 这个按第一行表头实高算出来的 sticky 偏移——
 * 行高一变，那套吸顶位置就得跟着重算。所以这里把它按原值钉住，改通用规则时不受影响。
 */
table.matrix tbody td { padding: 6px 11px; }
table.matrix thead tr.col-row th { padding: 9px 11px; }

/* 弱信号列的整列降饱和：数字仍可读，但视觉上退到第二层 */
table.matrix td.col-weak, table.matrix th.col-weak { background: var(--card-2); }
table.matrix tr.region-row > td.col-weak { background: #e6ebf0; }
table.matrix td.col-weak .cell-btn { color: var(--dim); }

/**
 * 热力：按当前视图的最大值分四档，给非零格加底色。
 *
 * 为什么这个比"把数字调大"有用：2814 家 × 11 平台的稀疏矩阵里绝大多数格是空的，
 * 原来满屏的「·」和真数据反差很小，想找"哪里有数"得逐格扫。加底色之后
 * 一屏就能看出"江苏 × 家庭医生在线 最深"，不用读数字。
 * 单色深浅（accent 的透明度阶梯）而不是彩虹色——彩虹会把"大"和"类别"混起来。
 */
.cell-btn.heat-1 { background: rgba(15, 157, 120, 0.06); }
.cell-btn.heat-2 { background: rgba(15, 157, 120, 0.13); }
.cell-btn.heat-3 { background: rgba(15, 157, 120, 0.22); }
.cell-btn.heat-4 { background: rgba(15, 157, 120, 0.34); }
/* 弱信号列的热力减半，避免它和付费证据抢注意力 */
table.matrix td.col-weak .cell-btn.heat-1 { background: rgba(15, 157, 120, 0.04); }
table.matrix td.col-weak .cell-btn.heat-2 { background: rgba(15, 157, 120, 0.08); }
table.matrix td.col-weak .cell-btn.heat-3 { background: rgba(15, 157, 120, 0.13); }
table.matrix td.col-weak .cell-btn.heat-4 { background: rgba(15, 157, 120, 0.2); }

/**
 * 「未知地区」行：**它不是地区，是数据缺口**——矩阵把地区信息缺失的记录都归到这一行，
 * 而实测（2026-09-20）市级视图下它是最大的一行（441 家，对比江苏 188 家）。
 *
 * 所以它要有别于真实地区的行：标签走虚线下划线（表示"这不是个名字"）、
 * 加一个「数据缺口」小标、再给个 ⓘ 说明是什么、该补哪里。
 * 用虚线边框而不是把整行涂灰：涂灰会被读成"次要"，而它其实是**要看的那一行**。
 */
table.matrix tr.row-gap > td { border-top-style: dashed; }
table.matrix tr.row-gap .row-link { border-bottom-style: dashed; }
.gap-tag {
  margin-left: 6px; font-size: var(--font-size-sm); font-weight: 400;
  color: var(--mid); border: 1px dashed var(--mid); border-radius: 10px; padding: 0 6px;
}
.gap-i { color: var(--mid); }

/**
 * 列头排序按钮（④）：列头整格可点，箭头指示当前排序列与方向。
 * 做成 button 而不是「th + 点击回调」——规范里"非交互元素挂点击处理器"是明确的反模式，
 * 且 th 不可聚焦，键盘用户根本排不了。
 */
table.matrix .col-sort {
  display: inline-flex; align-items: center; gap: 3px;
  background: none; border: 0; padding: 2px 4px; font: inherit; color: inherit;
  border-radius: 4px; cursor: pointer;
}
table.matrix .col-sort:hover { background: var(--bg); color: var(--accent-text); }
table.matrix .col-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
table.matrix th.sorted .col-sort { color: var(--accent-text); font-weight: 600; }
table.matrix .sort-arrow { font-size: 10px; line-height: 1; }
table.matrix tbody .row-link { color: inherit; text-decoration: none; border-bottom: 1px solid transparent; }
table.matrix tbody .row-link:hover { color: var(--accent-text); border-bottom-color: var(--accent); }
/** 覆盖平台数：行的覆盖广度，比成交/输单标记更轻，不抢注意力 */
.cover-tag {
  margin-left: 6px; font-size: var(--font-size-sm); color: var(--dim-2); font-weight: 400;
  border: 1px solid var(--border); border-radius: 10px; padding: 0 6px;
}
/** 表脚：左边是"读表须知"（口径 + 空格含义），右边是列数说明与操作 */
.table-foot { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.table-foot .foot-main { display: flex; flex-direction: column; gap: 2px; min-width: 260px; }
.table-foot .foot-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.table-foot .foot-acts { display: flex; gap: 8px; }

/* 平台名后的小信息符号：把"弱信号/权重"收成一个可聚焦的小标记，说明放 title */
.plat-i {
  display: inline-block; margin-left: 3px; color: var(--dim-2);
  font-weight: 400; font-size: var(--font-size-sm); cursor: help;
}

/* 筛选栏的分组与「更多筛选」：8 个控件等权平铺时找不到主次 */
.filters .fgroup { display: inline-flex; align-items: center; gap: 8px; }
.filters .fgroup + .fgroup { padding-left: 10px; margin-left: 2px; border-left: 1px solid var(--border); }
.filters button.link { background: none; border: 0; color: var(--dim); font: inherit; padding: 4px 6px; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.filters button.link:hover { color: var(--accent-text); }
.filters details.more { display: inline-block; }
.filters details.more > summary { cursor: pointer; color: var(--dim); font-size: var(--font-size-sm); padding: 6px 4px; list-style: none; }
.filters details.more > summary::-webkit-details-marker { display: none; }
.filters details.more > summary::after { content: " ▾"; }
.filters details.more[open] > summary::after { content: " ▴"; }
.filters details.more > .more-body { display: flex; align-items: center; gap: 8px; padding: 8px 0 2px; flex-wrap: wrap; }

/**
 * 「机构范围」chip：页头里唯一一个"决定你能看到什么"的元素，所以它不能是最弱的那个。
 *
 * 形态取「字段名小字 + 值加粗」而不是一个灰 tag：销售打开页面第一件该确认的事
 * 就是"我这个范围对不对"，而灰 tag 会被当成装饰跳过。未分配时走警示色——
 * 那种情况下「机构线索」是空的，是要处理的事，不是背景信息。
 */
.scope-chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 3px 10px; border-radius: 20px;
  background: var(--accent-weak); border: 1px solid transparent;
}
.scope-chip .k { font-size: var(--font-size-sm); color: var(--dim); }
.scope-chip .v { font-weight: 600; color: var(--accent-text); }
.scope-chip.unset { background: var(--mid-weak); }
.scope-chip.unset .v { color: var(--mid); }

/**
 * 筛选栏的分组标签（范围 / 口径）。
 *
 * 光有竖线分隔不够：控件一换行，竖线就落在行尾、看不出分界（实测 7 个下拉排一行、
 * 「更多筛选 / 重置」折到第二行时，两组的分隔完全失效）。**文字标签在换行后依然成立**，
 * 所以这里以标签为主、竖线为辅。
 */
.filters .flabel {
  font-size: var(--font-size-sm); color: var(--dim-2); font-weight: 600;
  margin-right: -2px; white-space: nowrap;
}
.filters .fgroup { display: inline-flex; align-items: center; gap: 8px; }

/* 可点的卡片：商机页的四块 KPI 用 <a class="card"> —— 点进去就是筛选后的行。
   数字不能点，就只是装饰；这几行只是把链接"长得跟卡片一样"，并给一个明确的悬停反馈。 */
a.card { display: block; color: inherit; text-decoration: none; transition: border-color var(--motion-fast) var(--ease), box-shadow var(--motion-fast) var(--ease); }
a.card:hover { border-color: transparent; box-shadow: var(--shadow-2); }
a.card .k { cursor: pointer; }

/* 容器：**不描边**，靠画布底色差 + 阴影阶梯立起来（2026-09-23 边框饮食）。
   用 border-color 而不是 border: 0——保留那 1px 的布局占位，免得去掉边框后整页元素位移。 */
.panel {
  background: var(--card); border: 1px solid transparent; border-radius: var(--radius);
  margin-bottom: 16px; overflow: hidden; box-shadow: var(--shadow-1);
  transition: box-shadow var(--motion-base) var(--ease);
}
.panel > h2 {
  margin: 0; padding: 14px 18px; font-size: var(--font-size-base); font-weight: 600;
  border-bottom: 1px solid var(--color-border-1); display: flex; align-items: center; gap: 9px;
}
.panel > h2 .sub { font-weight: 400; color: var(--dim-2); font-size: var(--font-size-sm); }
.panel .body { padding: 16px 18px; }
.panel .body.flush { padding: 0; }

/* ══════════════════════ 组件原语（2026-09-23）══════════════════════════════
   说好的"8 个原语"里，**6 个早就有主了**，所以不新造名字：
     容器 = .panel   · 页头 = .page-head      · 指标 = .stats / .stat
     工具条 = .filters · 徽标 = .tag / .badge  · 空态 = .empty
   再立一套 .surface / .toolbar 只会让全站出现两种词汇，下一个人不知道该用哪个。
   这一节做两件事：① 把上面 6 个的"表面处理"收进同一条规则（观感一致才是原语的意义，
   不然改一次要改六处）；② 补上真正缺的两个：.section（分组标题）、.list-row（窄屏行卡片）。
   .section / .list-row 目前**尚未被页面使用**——留给"逐页构图"那一步，先定义不接线。 */
.panel, .stat, .card, details.fold {
  border-color: transparent;
  box-shadow: var(--shadow-1);
}
a.panel:hover, a.stat:hover, a.card:hover, details.fold:hover { box-shadow: var(--shadow-2); }

/* 分组标题：层级靠字重与留白，不靠描一条线（要线的地方才用线） */
.section {
  margin: var(--space-6) 0 var(--space-3); font-size: var(--font-size-base);
  font-weight: 600; letter-spacing: .1px; color: var(--text);
}
.section .sub, .section > .sub { font-weight: 400; color: var(--dim-2); font-size: var(--font-size-sm); }
.section:first-child { margin-top: 0; }

/* 窄屏用行卡片代替横向表格：一条记录一块，字段竖向排 */
.list-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 12px 14px; border-bottom: 1px solid var(--color-border-1);
  transition: background var(--motion-fast) var(--ease);
}
.list-row:last-child { border-bottom: 0; }
.list-row:hover { background: var(--color-fill-1); }
.list-row .grow { flex: 1; min-width: 0; }
.list-row .k { font-weight: 600; }
.list-row .meta { font-size: var(--font-size-sm); color: var(--dim-2); }
@media (max-width: 900px) {
  .list-row { flex-wrap: wrap; align-items: flex-start; }
}

/* 纵向堆叠 + 统一间距。代替遍地 `style="margin-bottom:8px"` 这类内联留白——
   留白要成阶（--space-2/3/4），散在标记里的字面量没法一处调。 */
.stack { display: flex; flex-direction: column; gap: var(--space-2); }

/* 提示块：左侧一道色条，其余是普通表面。
   原先这种块在 app.js 里写成 `<div class="panel" style="border-left:3px solid var(--high)">`，
   三处各写一遍内联样式——同一形态写三遍必然有三处不一样，而且容器内联描边正是
   这一轮要去掉的东西。语义只分两档：`.warn` 需要你去处理，`.tip` 知道一下就行。 */
.callout {
  border-left: 3px solid var(--color-border-3);
  border-radius: var(--border-radius-medium);
  background: var(--card); padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-1); margin-bottom: 16px;
}
.callout.warn { border-left-color: var(--high); }
.callout.tip { border-left-color: var(--mid); }
/* 提示块里要横排（标签 + 一句话 + 说明）时套一层，别在标记里写内联 display:flex */
.callout .callout-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.callout h2 { margin: 0 0 6px; font-size: var(--font-size-base); font-weight: 600; }
.callout p { margin: 0; }

/* ------------------------------ 表格 ------------------------------ */
/* 表头吸顶：长列表滚动时列名不丢，是本系统最常用的表 */

/* max-height 要减掉顶栏：不减的话表格底部会伸到视口外，多出一截要额外滚动 */
.table-wrap { overflow: auto; max-height: calc(100vh - var(--topbar-h) - 250px); }

/**
 * 名册表专用：让「表格 + 它自己的滚动条 + 翻页器」整体落在**一屏之内**。
 *
 * 为什么需要（2026-09-20）：名册一页 100 行，沿用上面那个 250px 时框底会伸到首屏之外——
 * 于是同一屏里同时存在两条滚动条（先滚页面把框底挪上来、再滚框看行），手感和方向感都乱。
 * 250px 是按"页头 + 筛选条"估的，名册页在表格上面还压着 6 张卡片，所以不够用。
 *
 * 关键在 `--fit-above`：把表格**上方**那些东西（页头、卡片、筛选面板、表头说明）
 * 加上下方的翻页器一起算进去，让框底和翻页器始终留在首屏内——任何时刻只面对一条滚动条。
 *
 * 下界 360px 不是凑数：窗口矮的时候（笔记本分屏、投影）算出来只剩 100 多像素、
 * 只能显示 2~3 行，那比"框超出首屏"更没法用。宁可那时让框伸出去，也不能压到读不了。
 */
.table-wrap.fit-screen {
  --fit-above: 452px;
  max-height: max(360px, calc(100vh - var(--topbar-h) - var(--fit-above)));
}
table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--font-size-table); line-height: 1.5; }
thead th {
  position: sticky; top: 0; z-index: 2;
  text-align: left; font-weight: 500; font-size: var(--font-size-sm); color: var(--dim);
  padding: 11px 14px; background: var(--card-2);
  border-bottom: 1px solid var(--color-border-1); white-space: nowrap;
}
/* 行内距 6px → 10px、垂直居中（2026-09-23）：13px 字号是刻意的密度选择（保留），
   但上下只有 6px 时行与行会糊在一起——密度不等于拥挤，用行高换呼吸感，字号不动。
   名册表自带 `#orgRows td` 的钉死行高，ID 选择器优先级更高，不受这条影响。 */
tbody td { padding: 10px 14px; border-bottom: 1px solid var(--color-border-1); vertical-align: middle; }

/* 机构线索：行高钉死、内容不换行
   --------------------------------------------------------------------------
   光压内距和徽标数压不平：实测 79% 的行到了 42px，但仍有 21% 是 54~63px，
   而量下来没有任何单一元素超过 29px —— 是机构名 + 徽标这类行内元素的基线对齐
   把行盒撑高了，换行与否则取决于名字长度。
   与其继续猜，不如钉死：单元格不换行、机构名超出用省略号（title 里是全名）。
   表格每行齐平，扫读时视线不用重新找行——这本身就是"专业感"的一部分。 */
#orgRows td {
  height: 42px;
  white-space: nowrap;
  overflow: hidden;
}
/* 上限 244：加了「改名」按钮后机构列变宽，268 会把表格撑出 21px 横向滚动。
   名字本来就有省略号 + title 兜底，收窄不丢信息。 */
#orgRows td:first-child { max-width: 244px; }
#orgRows td:first-child .org-link {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
#orgRows .badge { vertical-align: bottom; }
#orgRows .plat { vertical-align: bottom; }
tbody tr:hover td { background: var(--color-fill-1); }
tbody tr:last-child td { border-bottom: 0; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.org-name { font-weight: 600; }
/**
 * 0 值降一档。经营分析那几张表里 0 是多数，和真实数字同字号同颜色时，
 * 一屏的 0 会把注意力从「哪格有数」上引开——投放分布主表用 `.z` 处理的是同一件事
 * （那边空格子用淡点，这里是明确的 0）。
 */
.zero { color: var(--dim-2); }

/* 机构名链接：颜色区分出「这里能点」，悬停才加下划线，长列表里不会一片花 */
a.org-link {
  color: inherit;
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
a.org-link:hover { color: var(--accent-text); border-bottom-color: var(--accent-text); }
a.org-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--border-radius-small); }
.truncate { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: bottom; }
.nowrap { white-space: nowrap; }
/**
 * 可点排序的表头（2026-09-18）。它必须是「表头」而不是「按钮」：第一版直接用 `<button>` 的默认外观，
 * 截图里「指数」变成一个带边框的方块、比旁边表头重得多，看着像筛选器。
 * 这里抹掉按钮外观（透明底、无边框、继承字体），可点性靠 hover 变色 + 聚焦环 + 方向箭头表达。
 */
.th-sort {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit;
  display: inline-flex; align-items: center; gap: 3px;
}
.th-sort:hover { color: var(--text); }
.th-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--border-radius-small); }
.th-sort .caret { color: var(--accent-text); font-size: 11px; line-height: 1; }
/** 机构单元格第二行：地区（原来是独立一列，合并后放这儿；仍是可点改的按钮） */
.org-meta { margin-top: 2px; line-height: 1.4; }
/** 付费比与投放徽标同格：它们是同一份数据的两种画法，原来占两列 */
td.plats .paidratio { margin-right: 8px; }
/** 行内展开控件（第 8 项）：取代用户截图里那个"看起来能点"的箭头 */
.row-toggle {
  background: none; border: 0; padding: 0 4px 0 0; margin: 0; cursor: pointer;
  color: var(--dim-2); font: inherit; line-height: 1; vertical-align: 1px;
}
.row-toggle:hover { color: var(--accent-text); }
.row-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--border-radius-small); }
tr.expand-row td { background: var(--bg-soft, rgba(0,0,0,.02)); }
.expand-body { padding: 6px 2px; font-size: var(--font-size-sm); color: var(--dim); line-height: 1.7; }
.expand-body b { color: var(--text); }
.expand-body .sep { margin: 0 6px; color: var(--dim-2); }
tbody tr.clickable { cursor: pointer; }
/**
 * 惰性渲染（2026-09-18 审查后加）：机构线索一次取 400 行 × 10 列，全部铺成 DOM 后
 * 滚动和勾选都会卡。content-visibility:auto 让屏外行跳过布局与绘制；
 * contain-intrinsic-size 给占位高度（≈一行 52px），否则滚动条长度会跳。
 * 只作用于行，不影响列宽计算。
 */
#orgRows tr { content-visibility: auto; contain-intrinsic-size: auto 52px; }
/* 名册页同理：一页 100 行 × 7 列，展开行也是就地插入的（同一套行内展开） */
#entRows tr { content-visibility: auto; contain-intrinsic-size: auto 52px; }

/* ------------------------------ 徽标 ------------------------------ */

.badge {
  display: inline-block; font-size: var(--font-size-sm); line-height: 1.6; padding: 1px 7px;
  border-radius: 20px; border: 1px solid var(--border); color: var(--dim); white-space: nowrap;
}
.badge.paid { background: var(--accent-weak); border-color: transparent; color: var(--accent-text); font-weight: 600; }
/* 重度投放：名下 ≥2 个付费渠道。比「付费位」重一档——2084 家里只有 210 家到这一级，
   而「在多个平台同时买位子」才是真正的投流客户（口径见 docs/2026-09-17-付费位口径复核.md）。
   用 `--accent-strong` 而不是 `--accent-text`：契约里前者才是「绿底白字」的填充色（4.60:1） */
.badge.heavy { background: var(--accent-strong); border-color: transparent; color: #fff; font-weight: 600; }
.badge.weak { background: var(--low-weak); border-color: transparent; color: var(--low); }
.badge.high { background: var(--high-weak); border-color: transparent; color: var(--high); font-weight: 600; }
.badge.mid { background: var(--mid-weak); border-color: transparent; color: var(--mid); font-weight: 600; }
.badge.low { background: var(--low-weak); border-color: transparent; color: var(--low); }
.badge.matched { background: #eef2fb; border-color: transparent; color: #3b5bdb; }
.badge.stalled { background: var(--low-weak); border-color: transparent; color: var(--low); }

/* 在投平台徽标：填色＝付费位，描边＝仅平台收录。
   用徽标而不是「一平台一列」的透视：那 10 列里 73% 的格子是空的、值几乎都是 1，
   真正的信息是「投了哪几个平台」，徽标一次性把这件事说清楚。 */
.plats { line-height: 1.6; }
.plat {
  display: inline-block; font-size: var(--font-size-sm); padding: 1px 6px; margin: 0 2px 1px 0;
  border-radius: var(--border-radius-medium); border: 1px solid var(--border-strong); color: var(--dim); white-space: nowrap;
}
.plat.paid { background: var(--accent-weak); border-color: transparent; color: var(--accent-text); font-weight: 600; }
/* 「+N 收录」：仅被收录的平台折成一个 chip，名字进 tooltip。为的是行高——
   收录徽标一多就换行，把行撑到 55px，一屏少看 5~6 家。 */
.plat.weak { color: var(--dim-2); border-style: dashed; }

/* 图例：默认收起，点 ? 才展开。前一轮按用户要求把口径长文从页面上删了，
   但 权重/弱信号/≥N/徽标填色 这些标记总得有地方说清楚——收进这里，不占版面。 */
.legend-btn {
  width: 24px; height: 24px; padding: 0; border-radius: 50%;
  color: var(--dim); font-weight: 600; flex: none;
}
.legend-btn[aria-expanded='true'] { background: var(--accent-weak); color: var(--accent-text); border-color: transparent; }
.panel.legend { margin-bottom: 16px; }
.legend-list { display: grid; grid-template-columns: 132px 1fr; gap: 8px 16px; margin: 0; font-size: var(--font-size-sm); }
.legend-list dt { color: var(--text); font-weight: 600; }
.legend-list dd { margin: 0; color: var(--dim); }
.legend-list dd b { color: var(--text); }

.score { font-weight: 700; font-size: var(--font-size-lg); font-variant-numeric: tabular-nums; }
.score.high { color: var(--high); }
.score.mid { color: var(--mid); }
.score.low { color: var(--low); }

/* 指数列：数字 + 热力条
   --------------------------------------------------------------------------
   原来「高意向」用红色数字表示，但红色在这个产品里既读作「好」又像「警告」，
   而且一列红数字扫过去看不出强弱差别（100 和 70 都是红的）。
   现在：数字回到中性色（可读、可比较），强弱交给条长，分档交给条的颜色深浅。 */
td.sc { min-width: 62px; }
td.sc .score { color: var(--text); font-size: var(--font-size-base); }
td.sc .heatbar {
  display: block;
  height: 3px;
  margin-top: 4px;
  border-radius: var(--border-radius-small);
  background: var(--bg);
  overflow: hidden;
}
td.sc .heatbar i { display: block; height: 100%; border-radius: var(--border-radius-small); background: var(--accent); }
td.sc.mid .heatbar i { background: var(--mid); }

/**
 * 占比条（经营分析的漏斗）：数字右边挂一条按占比折算的横条。
 *
 * 为什么条长按「当前最大的那个阶段」而不是按占全部的百分比：漏斗里 99.9% 与 0.1%
 * 并存时，按百分比画会让 2,812 和 2 都变成贴边的线，等于没画。归一到最大值之后，
 * 差距才看得见（这正是这张表想说的话）。
 * 行内块而不是 flex：单元格是 `text-align: right`，条跟着百分比一起靠右，
 * 不会飘到单元格左边去。
 */
.share {
  display: inline-block;
  width: 64px; height: 6px; margin-right: 8px;
  border-radius: var(--border-radius-small);
  background: var(--bg);
  overflow: hidden;
  vertical-align: middle;
}
.share i { display: block; height: 100%; border-radius: var(--border-radius-small); background: var(--accent); }
td.sc.low .heatbar i { background: var(--low); }

/* 付费/平台：付费位平台数 / 全部在投平台数。它同时是列表的次级排序依据，
   做成可见的一列，销售不用猜「凭什么这家排前面」。 */
.paidratio { font-variant-numeric: tabular-nums; color: var(--text); font-weight: 600; }
.paidratio .sep { color: var(--dim-2); font-weight: 400; margin: 0 1px; }

/* 行内跟进控件减重：原来 select/input 带边框，视觉重量比数据列还大。
   改成「平时像文字、悬停和聚焦才显形」，让数字回到主体位置。 */
tbody select.stage,
tbody input.owner {
  border-color: transparent;
  background: transparent;
  padding: 4px 6px;
  color: var(--dim);
  font-size: var(--font-size-sm);
  max-width: 100%;
}
tbody select.stage:hover,
tbody input.owner:hover { border-color: var(--border-strong); background: var(--card); }
tbody select.stage:focus,
tbody input.owner:focus { border-color: var(--accent-text); background: var(--card); color: var(--text); }
tbody input.owner { width: 74px; }

/* 骨架屏：先给结构再填数。列表要取 172 行，慢的时候空白会停留可见的一瞬。 */
.sk {
  display: block;
  height: 12px;
  border-radius: var(--border-radius-medium);
  background: linear-gradient(90deg, var(--bg) 25%, #e9edf1 50%, var(--bg) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
}
.sk-title { width: 120px; height: 18px; margin: 4px 0 18px; }
.sk-ctl { display: inline-block; width: 112px; height: 30px; margin-right: 8px; }
.sk-th { height: 10px; width: 60%; }
.sk-td { height: 11px; }
@keyframes shimmer {
  to { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sk { animation: none; }
}

/* 分数构成明细 */
.parts { list-style: none; margin: 6px 0 0; padding: 0; font-size: var(--font-size-sm); }
.parts li { display: flex; justify-content: space-between; padding: 3px 0; color: var(--dim); }
.parts li b { color: var(--text); font-variant-numeric: tabular-nums; }
.parts li.total { border-top: 1px solid var(--border); margin-top: 5px; padding-top: 6px; color: var(--text); font-weight: 600; }

/* --------------------------- 进度/条形 --------------------------- */

.bars { display: flex; flex-direction: column; gap: 7px; }
.bar { display: flex; align-items: center; gap: 9px; font-size: var(--font-size-sm); }
.bar .n { color: var(--dim); min-width: 64px; }
.bar .track { flex: 1; height: 7px; background: var(--bg); border-radius: var(--border-radius-medium); overflow: hidden; }
/* `display:block` 不能省：填充是 `<span>`，而 inline 元素**忽略 width/height**——
   写成 inline 时轨道是一条空的浅灰线、fill 宽度完全不生效（浏览器实测：
   99.7% 与 2% 画出来一模一样）。这个坑只有看图才发现，所以记在这里。 */
.bar .fill { display: block; height: 100%; background: var(--accent); border-radius: var(--border-radius-medium); }
.bar .c { color: var(--dim-2); min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }

/* 名册页把比例条做成链接（点一条即按该来源筛）。条变成链接后有两件事要补：
   全局 `a:hover` 会给整条加下划线——一条横线穿过数字和条形，很脏；
   而条本身没有可悬停的反馈，"能不能点"就没有线索。 */
a.bar { color: inherit; text-decoration: none; }
a.bar:hover { text-decoration: none; }
a.bar:hover .n { color: var(--accent-text); }
a.bar:hover .fill { filter: brightness(.9); }
a.bar:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--border-radius-medium); }

/* ------------------------------ 事件流 ------------------------------ */

.ev { display: flex; gap: 11px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: var(--font-size-table); }
.ev:last-child { border-bottom: 0; }
.ev .t { color: var(--dim-2); font-size: var(--font-size-sm); white-space: nowrap; min-width: 92px; font-variant-numeric: tabular-nums; }
.ev .ty { font-size: var(--font-size-sm); padding: 1px 7px; border-radius: 20px; white-space: nowrap; height: 19px; align-self: flex-start; }
.ty.placement_new { background: var(--accent-weak); color: var(--accent-text); }
.ty.activity_surge { background: var(--mid-weak); color: var(--mid); }
.ty.activity_stall { background: var(--low-weak); color: var(--low); }
.ty.org_matched { background: #eef2fb; color: #3b5bdb; }
.ty.overlap_spotted { background: var(--high-weak); color: var(--high); }
/* 走势类：持续投入的证据，比单次加量更有说服力，所以用主色（与 placement_new 一致的最强档） */
.ty.activity_rising { background: var(--accent-weak); color: var(--accent-text); }

/* ------------------------------ 杂项 ------------------------------ */

.empty { padding: 34px; text-align: center; color: var(--dim-2); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 1080px) { .grid-2 { grid-template-columns: 1fr; } }

.dl { display: grid; grid-template-columns: 88px 1fr; gap: 6px 12px; font-size: var(--font-size-table); }
.dl dt { color: var(--dim-2); }
.dl dd { margin: 0; }

.modal { position: fixed; inset: 0; background: rgba(27, 36, 48, .45); display: flex; align-items: center; justify-content: center; z-index: 50; }
.modal .box { background: var(--card); border-radius: 16px; padding: 24px; width: 384px; box-shadow: var(--shadow-3); }
.modal h3 { margin: 0 0 5px; font-size: var(--font-size-lg); }
.modal p { margin: 0 0 14px; color: var(--dim); font-size: var(--font-size-table); }
.modal input { width: 100%; margin-bottom: 11px; }
.modal .actions { display: flex; gap: 8px; justify-content: flex-end; }
/* 口令弹层里的两个工具按钮（随机生成 / 复制）：单独一行左对齐，不跟「取消/确定」混在一起——
   混在一起时用户会以为「随机生成」也是一种"确定"。 */
.pw-tools { display: flex; gap: 8px; margin: -3px 0 9px; }

/* 表单弹层的字段（联系人姓名/职务/电话；设目标的四个数字）。标签在上、控件在下：
   标签左置时列宽会随最长标签抖动，而这里字段少、竖向本来就富余。 */
.dlg-fields { display: flex; flex-direction: column; gap: 9px; margin-bottom: 4px; }
.dlg-field { display: block; }
.dlg-field > span { display: block; font-size: var(--font-size-sm); color: var(--dim); margin-bottom: 3px; }
.dlg-field input, .dlg-field textarea { width: 100%; }
.dlg-field textarea { resize: vertical; min-height: 62px; }

/* 成员表「活跃」列：上排＝最近跟进，下排＝登录与设备数。
   下排走 note（更小更淡）——它是补充信息，跟上排同权重会让人以为两件事同等重要。 */
.act-main { line-height: 1.35; }
.act-sub { line-height: 1.3; }

/* 团队页成员列表的「操作」列：次要动作平时不显，悬停该行或键盘聚焦进来才出现。
   为什么这么做：四个按钮常显时 6 行就是 20 个按钮，「停用」这种破坏性和「重置密码」
   同权重，一屏全是按钮而真正要看的信息（谁卡住了）被淹掉。
   **必须带 `:focus-within`**——只用 hover 的话键盘用户 Tab 进去什么都看不到
   （`.rename-btn` 那套只处理了单个按钮，这里是整组）。 */
.row-actions { opacity: 0; transition: opacity var(--motion-fast) var(--ease); }
tr:hover .row-actions,
.row-actions:focus-within { opacity: 1; }
/* 触屏与窄屏没有 hover：不让操作彻底消失 */
@media (hover: none) {
  .row-actions { opacity: 1; }
}

/* 成员表的「待处理」摘要行（未配范围 / 7 天没跟进 / 已停用）。
   数字做成可点的筛选链接——不能点的话就只是三个读数，看完还得自己回去找那几行。 */
.sum-row td { background: var(--card-2); padding-top: 6px; padding-bottom: 6px; }
.sumbadge {
  display: inline-block; margin-right: 8px; padding: 1px 8px;
  border: 1px solid var(--border); border-radius: var(--border-radius-large);
  font-size: var(--font-size-sm); color: var(--text); text-decoration: none;
}
.sumbadge b { font-weight: 600; }
.sumbadge:hover { border-color: var(--accent-text); color: var(--accent-text); }
/* 当前生效的筛选：实心一档，让人一眼知道"表里为什么只剩这几行" */
.sumbadge.active { background: var(--accent-weak); border-color: transparent; color: var(--accent-text); font-weight: 600; }
.sumbadge.off { color: var(--dim-2); border-style: dashed; }

/* 已停用账号那一组：计数在按钮上，展开后是正常行 */
.more-row td { background: var(--card-2); }

/* 折叠起来的行。用 class 而不是 `hidden` 属性——作者样式里的 display 会盖掉 UA 的 [hidden] */
.row-hidden { display: none; }

/* 对某些角色"不适用"的格子（不受限的主管、客户运营的名下与范围）：淡一档，
   读的人一眼能分清这是"这一格对他没有含义"而不是"数据缺失" */
td.na { color: var(--dim-2); font-size: var(--font-size-sm); }

/* 可折叠的说明。原生 <details>：键盘可用、零 JS，不自己造开关 */
details.help { display: inline-block; font-size: var(--font-size-sm); color: var(--dim); }
details.help > summary {
  cursor: pointer; color: var(--dim); border-bottom: 1px dotted var(--border-strong);
  display: inline-block; list-style: none;
}
details.help > summary::-webkit-details-marker { display: none; }
details.help > summary::before { content: 'ⓘ '; }
details.help > summary:hover { color: var(--accent-text); border-bottom-color: var(--accent-text); }
details.help[open] > summary { color: var(--accent-text); }
details.help > div {
  margin-top: 6px; padding: 8px 10px; background: var(--bg);
  border-left: 2px solid var(--border-strong); line-height: 1.6; max-width: 760px;
}

/* 角色下拉（成员表「角色」列）。做成下拉而不是"点一下就改"的按钮，见 app.js 的注释。
   保留一圈可见边框：**它是个能改的控件，不是一段文本**——常显的按钮就是为了让人看得见，
   所以这里不能为了"轻"而把边框去掉（改权限的入口藏起来是另一种问题）。 */
select.role-pick {
  padding: 2px 4px; max-width: 112px;
  font-size: var(--font-size-table);
  border-color: var(--border); background: var(--card); cursor: pointer;
}
select.role-pick:hover { border-color: var(--border-strong); }
.msg2 { min-height: 17px; font-size: var(--font-size-sm); color: var(--high); margin-bottom: 8px; }

.back { font-size: var(--font-size-table); color: var(--dim); }
.back:hover { color: var(--text); }

/* ==========================================================================
   投流主表（地区 × 科室 × 平台）
   --------------------------------------------------------------------------
   两个要求决定写法：① 平台列会随接入渠道增加（已 4 个、还会多），必须横向可滚；
   ② 地区/科室是行身份，滚到右边还得看得见 —— 所以前两列做 sticky。
   region-row 是小计行，用浅底色把它和明细行分开，避免把小计当明细读。
   ========================================================================== */

table.matrix td.sticky-l,
table.matrix th.sticky-l {
  position: sticky;
  left: 0;
  background: var(--card);
  z-index: 2;
  white-space: nowrap;
}
table.matrix td.sticky-l2,
table.matrix th.sticky-l2 {
  position: sticky;
  left: 78px;
  background: var(--card);
  z-index: 2;
  box-shadow: 1px 0 0 var(--border);
}
table.matrix thead th.sticky-l,
table.matrix thead th.sticky-l2 {
  background: var(--card-2);
  z-index: 4;
}
table.matrix th.sticky-l { min-width: 78px; }
table.matrix th.sticky-l2 { min-width: 120px; }
/**
 * 矩阵的数据格**居中**。
 *
 * 全局 `.num` 是右对齐（对机构列表、金额表那些"位数不齐、要比较大小"的列是对的，
 * 右对齐能让个位对齐、差值一眼可见）。但矩阵不一样：每格都是 1-2 位的小整数，
 * 右对齐会让数字贴到列右缘，与**居中的表头**错位——扫一列时眼睛要在两个轴上跳。
 * 居中之后"表头—数字"同轴，扫列快得多。
 *
 * 只作用于 `table.matrix`（选择器权重高于全局 `.num`），不影响其它表的数字列。
 */
table.matrix th.num,
table.matrix td.num { text-align: center; }

table.matrix th { white-space: nowrap; }

table.matrix tr.region-row > td {
  /* 小计行原来用 card-2(#fbfcfd) 做底，跟白底几乎看不出差别，扫读时容易把小计当明细读。
     换成明显一档的灰底 + 上边框，三层粒度（地区小计 / 科室 / 总计）才分得开。 */
  background: var(--row-sum);
  font-weight: 600;
  border-top: 1px solid var(--border-strong);
}
table.matrix tr.region-row > td.sticky-l,
table.matrix tr.region-row > td.sticky-l2 {
  background: var(--row-sum);
}
table.matrix td.spec { color: var(--dim); padding-left: 16px; }
table.matrix tr.grand > td {
  background: var(--accent-weak);
  font-weight: 600;
  border-top: 2px solid var(--border-strong);
}
table.matrix tr.grand > td.sticky-l,
table.matrix tr.grand > td.sticky-l2 {
  background: var(--accent-weak);
}
/* 空格子用淡点表示，避免一片 0 把眼睛吸走；列头的 BR / 弱 角标 */
table.matrix .z { color: var(--dim-2); opacity: 0.55; }
table.matrix .br {
  /* 独占一行，不再跟平台名挤成一条长列名——列名一长，列就被撑宽，横向滚动更早出现 */
  display: block;
  margin: 3px 0 0;
  padding: 0;
  background: none;
  color: var(--dim-2);
  font-size: var(--font-size-sm);
  font-weight: 500;
  letter-spacing: 0.2px;
}

/* 主表下钻：可点格子
   --------------------------------------------------------------------------
   颜色语义（2026-09-15 调整）：原先「可点」用主色绿表示，但绿色在这个产品里已经表示
   「付费位/正向」，两种含义打架，扫读时读不出哪个是业务信号。
   现在「可点」只用形状线索表达（等宽数字下的虚线 + 悬停底色 + 焦点环），
   颜色留给业务语义（付费位/弱信号/公立）。
   格子是真 <button>，不是带点击回调的 td —— td 不可聚焦，键盘到不了下钻。 */
table.matrix td.cell { padding: 0; }
button.cell-btn {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: right;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  border-radius: 0;
}
button.cell-btn b {
  font-weight: 600;
  border-bottom: 1px dashed var(--border-strong);
  padding-bottom: 1px;
}
button.cell-btn:hover { background: var(--bg); }
button.cell-btn:hover b { border-bottom-color: var(--accent-text); }
button.cell-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
button.cell-btn.on { background: var(--accent-weak); }
button.cell-btn.on b { border-bottom-style: solid; border-bottom-color: var(--accent-text); }

/* 通用文字按钮（机构名等异步解析入口，没有 href 可给，所以用 button 而不是 a） */
button.link-btn {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  border-bottom: 1px solid transparent;
}
button.link-btn:hover { color: var(--accent-text); border-bottom-color: var(--accent-text); }
button.link-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--border-radius-small); }

/* 投放分布：主表与下钻并排
   --------------------------------------------------------------------------
   原来下钻是插在主表下方的一整块面板：钻完那一格要往下滚，主表还被顶出视野，
   「从这个格子来的」这层关系看不出来。并排之后点格子右侧就出明细，主表原地不动，
   格局与明细可以对着看。窄屏（<1200px）退回上下堆叠。 */
.dist-split { display: flex; gap: 16px; align-items: flex-start; }
.dist-main { flex: 1; min-width: 0; }
.dist-side {
  flex: 0 0 470px;
  max-width: 470px;
  position: sticky;
  /* 吸顶位置要让开顶栏，否则侧栏会钻到顶栏底下（顶栏 z-index 更高，看着就是被吞掉一截） */
  top: calc(var(--topbar-h) + 6px);
  max-height: calc(100vh - var(--topbar-h) - 78px);
  overflow: auto;
}
@media (max-width: 1200px) {
  .dist-split { display: block; }
  .dist-side { position: static; max-height: none; max-width: none; flex: none; }
}

/* 表格下方那行：长尾平台折叠开关 */
.table-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  border-top: 1px solid var(--border);
}
.table-foot .spacer { flex: 1; }
.table-foot button { font-size: var(--font-size-sm); padding: 5px 11px; }

.detail-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 10px;
}
.detail-head .spacer { flex: 1; }
.detail-head b { font-size: var(--font-size-base); }
td.plats a { margin-right: 10px; white-space: nowrap; }

/* 机构类型标签（下钻名单里区分公立/付费/疑似） */
.tag { display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: var(--border-radius-medium); font-size: var(--font-size-sm); font-weight: 500; vertical-align: 1px; }
.tag-public { background: var(--low-weak); color: var(--dim-2); }
/* 「需要动作」那一档：原来在 app.js 里用内联 style 写死了两处（色值一样、写法不同）。
   收成类之后，"未配范围"这类警示标签全站一个样子（2026-09-23）。 */
.tag-high { background: var(--high-weak); color: var(--high); }
/* 经营分析「账号」列的角色标记（主管 / 销售 / 客户运营）。
   中性一档：它是身份说明，不是状态——不与旁边「已停用」的警示色抢注意力。
   用户名与角色同名（admin / admin）时前端不渲染这枚标记，免得读成重复。 */
.tag-role { background: var(--low-weak); color: var(--dim); }
.tag-paid { background: var(--accent-weak); color: var(--accent-text); }
.tag-likely { background: var(--mid-weak); color: var(--mid); }

/**
 * 商机表里的阶段选择器：它同时是"当前阶段"的显示处。
 *
 * 原来阶段另有一列彩色 tag（已成交绿 / 已输单灰），而下拉里显示的**本来就是当前阶段**——
 * 同一行画了两遍。删掉那列之后，把颜色线索移到下拉自己身上：
 * 数据属性 `data-cur` 是渲染时就写好的，所以这是纯 CSS，不用 JS 同步。
 * 商机页（app.js 那张表）用同一个类名，一并生效。
 */
select.opp-stage[data-cur='won'] { color: var(--accent-text); font-weight: 600; }
select.opp-stage[data-cur='lost'] { color: var(--dim-2); }
/* 名册所有制标记（公立/民营）。与上面 pageTag 的「目录条目/主题页」不是同一维度，故另起类名 */
.tag-own-public { background: var(--low-weak); color: var(--dim-2); }
/**
 * 带解释的 hover 提示（配 `title`）。
 * 为什么不只写 title：**看不出来能 hover 等于没写**——虚线下划线 + help 光标是唯一提示，
 * 所以凡是有话要补充的地方都套这个类，而不是悄悄挂个 title。
 */
.hlp { border-bottom: 1px dotted var(--border-strong); cursor: help; }

/* 主表：属于当前账号访问范围的行（范围 = 城市 × 科室，按行上的 data-region/data-spec 匹配） */
.matrix tr.in-scope td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.tag-own-private { background: var(--mid-weak); color: var(--mid); }

/* 机构名单的平台透视列：十来列要放得下，列头竖排短名 + 数字窄列 */
table th.plat-h {
  font-size: var(--font-size-sm);
  font-weight: 600;
  letter-spacing: 0;
  padding-left: 5px;
  padding-right: 5px;
  white-space: nowrap;
  color: var(--muted, #6b7280);
}
table td.num { white-space: nowrap; }

/* 地区格里的城市输入框：常驻可编辑，但平时无边框——看着像文字，悬停/聚焦才显出是输入框。
   起因：只在「缺城市」时才渲染输入框，导致城市里存了省名的行（黑龙江）看着像已填、
   用户反而没法改成真正的市。常驻输入框让任何一行都能直接改。 */
td.regioncell input.city {
  width: 96px;
  border: 1px solid transparent;
  background: transparent;
  padding: 1px 4px;
  border-radius: var(--border-radius-medium);
  font: inherit;
}
td.regioncell input.city:hover { border-color: var(--border-strong); }
td.regioncell input.city:focus { border-color: var(--accent-text); background: #fff; outline: none; }
td.regioncell input.city::placeholder { color: var(--dim); }
td.regioncell .provtag { margin-right: 4px; }

/* ==========================================================================
   自绘下拉（.sel）
   --------------------------------------------------------------------------
   原生 <select> 的弹层由操作系统绘制，CSS 完全触及不到 —— 筛选栏一排原生下拉
   是界面「手工感」的头号来源。这里换成 button + role=listbox 的自绘实现：
   键盘齐备（Enter/Space 展开、上下键移动、Enter 选中、Esc 关闭并把焦点还给触发器），
   aria 齐备（aria-haspopup / aria-expanded / role=listbox / aria-selected）。
   ========================================================================== */
.sel { position: relative; display: inline-block; }
.sel-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 10px 0 12px;
  font-size: var(--font-size-table); color: var(--text);
  background: var(--card); border: 1px solid var(--border-strong); border-radius: 8px;
  cursor: pointer; white-space: nowrap;
}
.sel-trigger:hover { border-color: var(--dim-2); }
.sel-trigger[aria-expanded='true'] { border-color: var(--accent-text); }
.sel-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sel-trigger .ico { color: var(--dim-2); flex: none; }
.sel-menu {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0;
  min-width: 100%; max-height: 320px; overflow: auto;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--border-radius-large);
  box-shadow: var(--shadow-lg); padding: 4px;
}
.sel-opt {
  padding: 7px 10px; border-radius: var(--border-radius-medium); font-size: var(--font-size-table); color: var(--text);
  cursor: pointer; white-space: nowrap;
}
.sel-opt:hover { background: var(--bg); }
.sel-opt[aria-selected='true'] { background: var(--accent-weak); color: var(--accent-text); font-weight: 600; }
.sel-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* 阶段 chip：点一下推进，右侧小箭头展开全部阶段 */
.stage-cell { display: inline-flex; align-items: center; gap: 2px; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  height: 24px; padding: 0 9px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--card);
  font-size: var(--font-size-sm); color: var(--dim); cursor: pointer; white-space: nowrap;
}
.chip:hover:not(:disabled) { border-color: var(--accent-text); color: var(--text); }
.chip:disabled { cursor: default; }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.chip .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.chip-caret {
  height: 24px; width: 20px; padding: 0; border: 0; background: none; color: var(--dim-2);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer; border-radius: var(--border-radius-medium);
}
.chip-caret:hover { background: var(--bg); color: var(--text); }
.chip-caret:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.chip-menu { min-width: 112px; }
.undo {
  border: 0; background: none; color: var(--accent-text); font: inherit; cursor: pointer;
  text-decoration: underline; padding: 0 2px; border-radius: var(--border-radius-small);
}
.undo:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* 长列表的行区分度
   --------------------------------------------------------------------------
   行高钉到 42px 齐平、171 行只靠 hover 区分时，横向扫读很容易串行。
   隔行给一层几乎看不见的底色（不是明显的斑马纹，只是让眼睛有个参照）。 */
#orgRows tr:nth-child(even) td { background: #fcfdfe; }

/* 加载失败：不能只说"失败了"，得给出下一步 */
.errbox { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 30px 16px; }
.errbox p { margin: 0; color: var(--dim); font-size: var(--font-size-table); }
.errbox button { display: inline-flex; align-items: center; gap: 6px; }

/* 侧栏信息块：两个菜单项下面原本一大片空白，把空白换成信息 */

/* 控件高度统一到 32px
   --------------------------------------------------------------------------
   实测原先控件高度是混的：阶段下拉 27px、负责人输入框 29px、按钮 30px 出头。
   高度不齐会让一整排控件看起来"手工拼的"，这是观感标准化里最便宜的一刀。 */
.filters input[type='search'],
.filters button,
/* 固定高度的按钮必须自己居中，不能靠继承的行高：
   body 的 line-height 是 1.6（14px → 22.4px），而 32px 高 − 上下 padding 14px − 边框 2px = 内容盒只有 16px，
   行盒比内容盒高，浏览器把行盒顶端贴到内容盒顶端，文字于是比盒中心低 2.3px（实测盒中心 88、文字中心 90.3）。 */
.page-head button {
  height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1.2;
}
/* 图例按钮是圆：.page-head button 的 height:32px 优先级比 .legend-btn 高（类+类型 > 类），
   会把 24×24 撑成 24×32 的椭圆——所以这里要用同级选择器写回来。 */
.page-head .legend-btn { width: 24px; height: 24px; padding: 0; }
/* 期间选择器（经营分析）与页头按钮同高。
   不写的话它是 38px：input 的 7px 上下内距 + 1.6 行高（14 → 22.4）+ 边框 2px，
   比旁边的 h1 和按钮都高一头，页头看着就不齐。 */
.page-head input[type='month'] {
  height: 32px; width: 150px; padding-top: 0; padding-bottom: 0;
}
.filters input[type='search'] { padding-top: 0; padding-bottom: 0; }
.filters label.note { height: 32px; }
.filters input[type='checkbox'] { height: auto; }

/* ---------------- 地区三级选择面板（省 / 市 / 区） ----------------
   为什么不用 <select> 做三级联动：页面里 enhanceSelects 会把原生 select 整个替换成自绘组件，
   动态改选项非常别扭；而区县有 3407 个，可搜索的列表本来就比下拉好用。
   每点一级即时落库，于是「能到市就到市、不能就到省」是自然结果，不需要额外的确认动作。 */
td.regioncell button.region-btn {
  border: 1px solid transparent;
  background: transparent;
  padding: 2px 6px;
  border-radius: var(--border-radius-medium);
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
td.regioncell button.region-btn:hover { border-color: var(--border-strong); }
td.regioncell button.region-btn.empty { color: var(--dim); }
td.regioncell button.region-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.geo-panel {
  position: fixed;
  z-index: 60;
  width: 620px;
  max-width: calc(100vw - 24px);
  background: #fff;
  border: 1px solid var(--border-strong);
  border-radius: var(--border-radius-large);
  box-shadow: 0 12px 32px rgba(16, 24, 40, .16);
  padding: 10px;
}
/* flex 而非 grid：藏掉一列时其余列自动铺满，不会留下空格子（只填一级的「全国」、海外国家只到市时会藏列） */
.geo-cols { display: flex; gap: 8px; }
.geo-col { flex: 1 1 0; display: flex; flex-direction: column; min-width: 0; }
.geo-head { font-size: var(--font-size-sm); color: var(--dim); margin-bottom: 4px; }
.geo-search {
  border: 1px solid var(--border-strong);
  border-radius: var(--border-radius-medium);
  padding: 4px 7px;
  font: inherit;
  font-size: var(--font-size-sm);
  margin-bottom: 5px;
  width: 100%;
}
.geo-search:focus { outline: none; border-color: var(--accent-text); }
.geo-list { height: 232px; overflow-y: auto; border: 1px solid var(--border-strong); border-radius: var(--border-radius-medium); padding: 3px; }
.geo-item {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--font-size-sm);
  padding: 3px 7px;
  border-radius: var(--border-radius-medium);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.geo-item:hover { background: var(--accent-weak); }
.geo-item.on { background: var(--accent-weak); color: var(--accent-text); font-weight: 600; }
.geo-empty { color: var(--dim); font-size: var(--font-size-sm); padding: 6px 7px; }
.geo-foot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.geo-foot .spacer { flex: 1; }

/* 机构名就地改名
   --------------------------------------------------------------------------
   平时低对比（opacity .35）、行悬停或键盘聚焦时才清晰：186 行里常显就是噪声。
   没有做成完全隐藏，是因为「看不见但可点」的靶子容易误触，也让人发现不了这个功能。
   用文字按钮而不是图标：契约禁的是「手绘字形」，文字不算字形，而且「改名」比铅笔图标更不易误解。 */
td .rename-btn {
  margin-left: 6px;
  height: 20px;
  padding: 0 6px;
  font-size: var(--font-size-sm);
  border-radius: var(--border-radius-medium);
  border-color: transparent;
  background: none;
  color: var(--dim-2);
  opacity: 0.35;
  transition: opacity var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}
#orgRows tr:hover .rename-btn,
td .rename-btn:hover { opacity: 1; }
td .rename-btn:hover { color: var(--accent-text); border-color: var(--accent-text); }
td .rename-btn:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: 1px; }
.rename-wrap { display: inline-flex; align-items: center; gap: 6px; }
.rename-input { width: 240px; height: 28px; font-size: var(--font-size-table); }
.rename-wrap button { height: 28px; padding: 0 10px; font-size: var(--font-size-sm); }

/* 团队页：小号操作按钮 + 阶段分布标签 */
button.mini {
  font: inherit; font-size: var(--font-size-sm); padding: 2px 8px; margin-right: 4px;
  border: 1px solid var(--border-strong); background: none; color: var(--dim);
  border-radius: var(--border-radius-medium); cursor: pointer;
}
button.mini:hover:not(:disabled) { border-color: var(--accent-text); color: var(--accent-text); }
button.mini.danger:hover:not(:disabled) { border-color: var(--low); color: var(--low); }
button.mini:disabled { opacity: .45; cursor: not-allowed; }
/* 「导出记录」是个下载链接，不是按钮，但要跟旁边的小按钮长得一样 */
a.mini {
  font: inherit; font-size: var(--font-size-sm); padding: 2px 8px; margin-right: 4px;
  border: 1px solid var(--border-strong); background: none; color: var(--dim);
  border-radius: var(--border-radius-medium); cursor: pointer; display: inline-block; text-decoration: none;
}
a.mini:hover { border-color: var(--accent-text); color: var(--accent-text); }

/* 数据审计里需要被一眼看见的数字/行：导出、看完整号码 */
.warnnum { color: var(--mid); }
tr.warnrow td { background: var(--mid-weak); }
.stagetag {
  display: inline-block; font-size: var(--font-size-sm); padding: 1px 7px; margin: 0 4px 3px 0;
  border-radius: var(--border-radius-medium); background: var(--accent-weak); color: var(--accent-text); white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   我的工作台（销售视角）
   参考主流 CRM 的 rep home：待办清单 / 跟进时间线 / 行内动作 / 我的漏斗。
   样式尽量复用已有 token 与 .card/.mini，避免再造一套视觉语言。
   --------------------------------------------------------------------------- */

/* 按角色收起的入口：触发抓取与全量导出都是内部的事。
   放 CSS 而不是视图里判 amAdmin()——视图可能在 ME 取回来之前就渲染完了，
   那时条件渲染会把按钮藏到下一次重渲染为止。后端两边也各自校验一遍。

   ⚠️ 选择器必须逐个列出角色名。原来只写 'sales'，加客户角色时**这就是最易漏的一处**：
   漏了它，`body[data-view-role='customer']` 不匹配任何规则，扫描与导出按钮会重新出现
   （即使后端会 403，界面上也等于给客户看了一个点了没用的按钮，还暴露了"有导出"这件事）。 */
body[data-view-role='sales'] #btnScan,
body[data-view-role='sales'] #btnExport,
body[data-view-role='customer'] #btnScan,
body[data-view-role='customer'] #btnExport { display: none; }

/* ------------------------------ 客户门户（外部只读） ------------------------------ */
/* 客户只有一页，所以不做侧栏导航的多 tab 形态，改用上下分段。
   与内部页面的关键差别是**没有任何写操作入口**：没有扫描、没有导出全量、没有阶段/负责人。 */
.pf-hero {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 16px 18px; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); margin-bottom: 16px; box-shadow: var(--shadow);
}
.pf-hero .who { font-size: var(--font-size-lg); font-weight: 650; letter-spacing: -.2px; }
.pf-hero .meta { color: var(--dim); font-size: var(--font-size-sm); }

/* 机会清单：客户最该先看的一段，所以给主色左边框 */
.pf-opp { border-left: 3px solid var(--accent); }
.pf-opp-row { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-top: 1px solid var(--border); }
.pf-opp-row:first-child { border-top: 0; }
.pf-opp-row .plat { font-weight: 600; min-width: 160px; }
.pf-opp-row .n { color: var(--accent-text); font-weight: 600; font-variant-numeric: tabular-nums; }
.pf-opp-row .why { color: var(--dim); font-size: var(--font-size-sm); }

/* 月度一页纸：屏幕上不显示，打印时**只留它**（见下面 @media print 的规则）。
   客户/销售拿这张纸去开会——它必须自带出处、口径与样本天数，否则脱离系统就没人知道
   这些数字是哪天、什么范围的（外流时也能追到来源）。 */
.pf-onepager { display: none; }
.pf-onepager .op-h1 { font-size: 19px; font-weight: 700; letter-spacing: -.2px; }
.pf-onepager .op-sub { color: var(--dim); font-size: var(--font-size-sm); margin-top: 3px; }
.pf-onepager .op-sec { margin-top: 14px; }
.pf-onepager .op-h2 { font-size: 14px; font-weight: 650; margin-bottom: 4px; }
.pf-onepager .op-li { font-size: 12.5px; line-height: 1.65; }
.pf-onepager .op-note { color: var(--dim); font-size: 11.5px; margin-top: 3px; }
.pf-onepager .op-foot { margin-top: 16px; padding-top: 8px; border-top: 1px solid var(--border); color: var(--dim-2); font-size: 11px; line-height: 1.6; }

/* 开号前体检的结果行（团队页 → 客户账号档，「主管」与「客户运营」共用） */
.ob-row { display: flex; gap: 8px; align-items: flex-start; padding: 5px 0; border-top: 1px solid var(--border); font-size: 12.5px; }
.ob-row:first-child { border-top: 0; }
.ob-i { flex: none; width: 14px; font-weight: 700; }
.ob-i.ok { color: var(--accent-text); }
.ob-i.warn { color: var(--mid); }
.ob-i.bad { color: var(--high); }
.ob-l { flex: none; width: 130px; color: var(--dim); }
.ob-d { flex: 1; }

/* 并排对比：我为 0 而竞品 >0 的格子要一眼看出来——那就是差距 */
table.pf-cmp td.gap { background: var(--mid-weak); color: var(--mid); font-weight: 600; }
table.pf-cmp td.mine { font-variant-numeric: tabular-nums; }
.pf-trend { font-size: var(--font-size-sm); border-radius: 999px; padding: 1px 8px; flex: none; }
.pf-trend.rising { color: var(--accent-text); background: var(--accent-weak); }
.pf-trend.falling { color: var(--mid); background: var(--mid-weak); }
.pf-trend.flat { color: var(--dim); background: var(--bg); }
.pf-trend.unknown { color: var(--dim-2); background: var(--bg); }

/* 未配置范围的空态要说得清楚，而不是给一个空白页 */
.pf-unset { max-width: 620px; }

/* 打印 / 另存为 PDF：客户会把这个对比表拿去开会。
   不引 PDF 库（Node 侧要么上 puppeteer 要装 Chromium，要么手写 PDF 难维护），
   改成打印友好的样式，让浏览器自己出 PDF——版式可控、零依赖。
   打印时收掉所有导航与操作入口，只留正文。 */
@media print {
  .topbar, .side, .page-head button, .page-head .spacer, .note#flash { display: none !important; }
  /* 打印时**只留一页纸**：其余面板全收掉（"一页装下"靠这里保证，而不是靠碰运气） */
  #view > *:not(.pf-onepager) { display: none !important; }
  .pf-onepager { display: block !important; }
  @page { margin: 12mm; }
  .shell, .layout { display: block !important; }
  .main { padding: 0 !important; }
  .panel { break-inside: avoid; box-shadow: none; }
  .table-wrap { max-height: none !important; overflow: visible !important; }
  thead th { position: static !important; }
  a { color: inherit; text-decoration: none; }
  /* 打印时把 URL 显示出来，纸质件上也能核对来源 */
  .pf-cmp + * a[href^='http']::after { content: ''; }
}

.wb-list { list-style: none; margin: 0; padding: 0; }
.wb-row { display: flex; align-items: center; gap: 12px; padding: 9px 14px; border-top: 1px solid var(--border); }
.wb-row:first-child { border-top: 0; }
.wb-row:hover { background: var(--row-sum); }
.wb-main { flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px; }
.wb-main .org-link { font-weight: 600; color: var(--text); text-decoration: none; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-main .org-link:hover { color: var(--accent-text); text-decoration: none; }
/* 「为什么排在这里」：待办和清单的区别就在这一枚标记上，所以给它颜色 */
.wb-why { font-size: var(--font-size-sm); color: var(--dim); background: var(--bg); border-radius: 999px; padding: 1px 8px; flex: none; }
.wb-why.overdue { color: #c0392b; background: #fdecea; }
.wb-why.signal { color: var(--accent-text); background: var(--accent-weak); }
/* 由头强度：排序已经按强度来，颜色只是让人扫一眼能分辨「这条更值得先打」 */
.wb-why.signal.lv-low { color: var(--dim); background: var(--bg); }
.wb-why.pool { color: #6b4ff2; background: #f1edff; }
.wb-act { display: flex; align-items: center; gap: 6px; flex: none; }
.wb-act .mini { display: inline-flex; align-items: center; gap: 4px; text-decoration: none; }

/* 动态条目：比普通待办多两行（话术、出处），所以整体改成上下结构。
   上行 = 机构 + 由头 + 建议动作；中行 = 话术（销售打电话照着说的那句）；
   下行 = 出处（雷达原话，可核对）与操作。 */
.wb-row.sig { display: block; padding: 11px 14px; }
.wb-row.sig .wb-main { flex-wrap: wrap; }
.wb-row.sig .wb-act { margin-top: 9px; flex-wrap: wrap; }
.sig-act { font-size: var(--font-size-sm); font-weight: 600; color: var(--accent-text); flex: none; }
/**
 * 话术块：**这是销售唯一会照着念的那句话**，所以给它引用样式（左侧竖线 + 浅底），
 * 让它一眼和"出处/时间"这类辅助信息区分开；右侧挂一个复制按钮——
 * 要粘到微信/短信里，让浏览器替他选中再自己按 Ctrl+C 太别扭。
 */
.sig-script {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 7px; padding: 7px 10px 7px 11px;
  border-left: 2px solid var(--accent); border-radius: 0 6px 6px 0; background: var(--bg);
  font-size: var(--font-size-sm); color: var(--text); line-height: 1.6;
}
.sig-script .txt { flex: 1; min-width: 0; }
.sig-script .copy { flex: none; }
.sig-script b { color: var(--dim-2); font-weight: 500; margin-right: 7px; font-size: var(--font-size-sm); }
/* 时效：排序是按「强度 × 时效」算的，那就得让它看得见——否则"为什么这条排前面"对销售不透明 */
.sig-when { flex: none; }
/* 同机构其它由头的徽章：一个机构一张卡，次要由头降级到这里 */
.sig-extra { font-size: var(--font-size-sm); color: var(--dim); background: var(--card-2); border-radius: 999px; padding: 1px 8px; flex: none; }
.sig-handled { font-size: var(--font-size-sm); color: var(--dim); font-weight: 600; flex: none; }
/**
 * 次要操作收进 hover：一屏 20 条 × 5 个常显按钮 = 上百个按钮，主操作（拨打）被淹没。
 * 团队页的 `.row-actions` 是同一个做法，但它选择器写的是 `tr:hover`，这里得给 li 一条。
 */
.wb-row.sig .row-actions { display: inline-flex; align-items: center; gap: 6px; opacity: 0; transition: opacity var(--motion-fast) var(--ease); }
.wb-row.sig:hover .row-actions,
.wb-row.sig .row-actions:focus-within { opacity: 1; }
@media (hover: none) { .wb-row.sig .row-actions { opacity: 1; } }

/* 本周必打的名单：序号自己渲染，所以关掉 ol 的默认编号——否则屏幕上会出现「1. 1」 */
.mc-list { list-style: none; margin: 0; padding: 0; }
.mc-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 14px; border-top: 1px solid var(--border); }
.mc-row:first-child { border-top: 0; }
.mc-row:hover { background: var(--row-sum); }
.mc-no { flex: none; width: 18px; text-align: right; color: var(--dim); font-variant-numeric: tabular-nums; font-size: var(--font-size-sm); line-height: 1.75; }
.mc-body { flex: 1; min-width: 0; }
.mc-phone { font-variant-numeric: tabular-nums; }
/* 出处是雷达原话（可能带未清洗的 SEO 堆词标题），截断显示，完整内容留给 title 属性 */
.sig-src { margin-top: 4px; font-size: var(--font-size-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 侧栏角标：未读动态数。等宽数字，两位数时宽度不跳 */
.nav-badge {
  margin-left: auto; min-width: 18px; padding: 0 5px;
  border-radius: 999px; background: var(--accent); color: #fff;
  font-size: var(--font-size-sm); font-weight: 600; line-height: 18px; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* 电话点一下就能拨：销售的主战场在电话上 */
.tel { color: var(--text); font-variant-numeric: tabular-nums; }
.tel:hover { color: var(--accent-text); }

/* 掩码电话：点一下才取全号（那一次会记入数据审计）。
   做成本地按钮而不是链接——掩码号拨不通，做成 tel: 链接会让人以为能拨。 */
button.telview {
  font: inherit; font-variant-numeric: tabular-nums; color: var(--dim);
  background: none; border: 0; border-bottom: 1px dashed var(--border-strong);
  padding: 0 0 1px; cursor: pointer;
}
button.telview:hover:not(:disabled) { color: var(--accent-text); border-bottom-color: var(--accent-text); }
button.telview:disabled { opacity: .6; cursor: default; }

/* 电话形态徽标：手机能直接找到人，固话要过前台，客服总机基本见不到决策人。
   颜色只用三档冷热度，不加新色相——这一列的目标是"一眼分出来"，不是好看。 */
.telwrap { display: inline-flex; align-items: center; gap: 5px; }
.pk {
  font-size: var(--font-size-sm); line-height: 1; padding: 3px 5px; border-radius: var(--border-radius-medium);
  border: 1px solid var(--border-strong); color: var(--dim); white-space: nowrap;
}
.pk-mobile { color: var(--accent-text); background: var(--accent-weak); border-color: var(--accent-weak); }
.pk-landline { color: var(--text); }
.pk-hotline, .pk-other { color: var(--dim); opacity: .85; }
/* 人工核过：机器扒的号和人打过的号价值不同，这个标记是给销售的信任信号 */
.pk-verified { color: var(--accent-text); font-size: var(--font-size-sm); font-weight: 600; }
/* 共用号：这个号挂在好几家机构上，打过去是外包客服。用警示色（--mid 是既有的"要注意"色） */
.pk-shared { color: var(--mid); background: var(--mid-weak); border-color: var(--mid-weak); }
/* 改号入口：**常驻可见（淡）**，而不是 hover 才出现。
   一开始写成 opacity:0 + 行 hover 时显形，看着干净，但"人工改号码"是这套电话体系
   唯一的纠错入口——藏起来的纠错入口等于没有（销售根本不知道能改，就只会继续打错号）。
   淡态不抢号码本身的注意力，hover 时加深。 */
.pkedit {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: 0; background: none;
  border: 0; border-radius: var(--border-radius-medium); color: var(--dim); opacity: .45; cursor: pointer;
  transition: opacity var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease);
}
tr:hover .pkedit { opacity: .8; }
.pkedit:hover, .pkedit:focus-visible { opacity: 1; color: var(--accent-text); background: var(--bg); }

/* 跟进记录（谁在什么时候做了什么） */
.tl { list-style: none; margin: 0; padding: 0; }
.tl-row { display: flex; gap: 12px; padding: 8px 14px; border-top: 1px solid var(--border); font-size: var(--font-size-table); }
.tl-row:first-child { border-top: 0; }
.tl-time { color: var(--dim); font-variant-numeric: tabular-nums; flex: none; width: 134px; }
.tl-who { font-weight: 600; flex: none; width: 92px; overflow: hidden; text-overflow: ellipsis; }
.tl-body { min-width: 0; }
.due { font-variant-numeric: tabular-nums; }
.due.overdue { color: #c0392b; }

/* 行内快捷动作浮层（排期 / 记一笔）：就地完成一次跟进，不必进详情页 */
.quick-panel {
  position: absolute; z-index: 60; min-width: 220px;
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--border-radius-large); box-shadow: 0 12px 32px rgba(16, 24, 40, .16); padding: 10px;
}
.quick-hd { font-size: var(--font-size-sm); font-weight: 600; margin-bottom: 7px; display: flex; gap: 6px; align-items: baseline; }
.quick-opts { display: flex; flex-wrap: wrap; gap: 6px; }
.quick-note { width: 258px; min-height: 62px; }
.quick-foot { display: flex; justify-content: flex-end; margin-top: 7px; }

/* ---------------------- 跨系统绑定（客户账号 ↔ medbuild） ---------------------- */

.link-cell { display: flex; flex-direction: column; gap: 3px; align-items: flex-start; }
.link-cell code { font-size: var(--font-size-sm); color: var(--text); background: var(--bg); padding: 1px 5px; border-radius: var(--border-radius-medium); }
.link-acts { display: flex; flex-wrap: wrap; gap: 4px; }
.note.err { color: #c0392b; }

/* 确认区：整行展开在客户账号那一行下面，不弹窗——换页/重渲染都不会留孤儿 */
tr.draft-row > td { background: var(--row-sum); padding: 0; }
.link-draft { padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; }
.link-draft .filters { gap: 8px; }
.lk-grid { display: grid; grid-template-columns: minmax(240px, 1fr) minmax(320px, 1.4fr); gap: 16px; align-items: start; }
.lk-t { font-size: var(--font-size-sm); color: var(--dim-2); font-weight: 600; margin-bottom: 5px; letter-spacing: .2px; }
.lk-dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; font-size: var(--font-size-sm); }
.lk-dl dt { color: var(--dim); }
.lk-dl dd { margin: 0; color: var(--text); }
.link-draft select { width: 100%; }

/* ---------------------- 团队页下钻 + 批量指派 ---------------------- */

/* 卡片可以点进去看名单（原来是死数字）：整块可点，不留"看着像能点其实点了没反应" */
a.card.link { display: block; color: inherit; text-decoration: none; }
a.card.link:hover { border-color: var(--accent-text); text-decoration: none; }
a.card.link .k::after { content: ' →'; color: var(--dim-2); }

/* 阶段标签变成链接时保持原样；数量为 0 的降级成灰字，一眼分清可点与不可点 */
a.stagetag { text-decoration: none; }
a.stagetag:hover { background: var(--card); border: 1px solid var(--accent); color: var(--text); margin: -1px 3px 2px -1px; }
.stagetag.off { background: var(--bg); color: var(--dim-2); }

/* 批量指派的勾选列与选择条 */
.pick-cell { width: 34px; }
.pick-cell input { width: 15px; height: 15px; cursor: pointer; vertical-align: middle; }
#pickBar { margin-top: 8px; padding: 7px 10px; background: var(--accent-weak); border-radius: var(--radius-sm); }
#pickBar b { color: var(--accent-text); }

/* 客户看板：「我的 AI 可见度」（数据来自 geohub，另一台服务器，单独异步取） */
.vis-row { display: flex; flex-wrap: wrap; gap: 20px; }
.vis-item { min-width: 112px; }
.vis-k { font-size: var(--font-size-sm); color: var(--dim-2); }
.vis-v { font-size: var(--font-size-2xl); font-weight: 700; letter-spacing: -.4px; font-variant-numeric: tabular-nums; }
.vis-u { font-size: var(--font-size-sm); font-weight: 500; color: var(--dim-2); margin-left: 2px; }
.vis-bar { display: block; height: 6px; border-radius: 99px; background: var(--bg); overflow: hidden; margin-top: 4px; }
.vis-bar i { display: block; height: 100%; border-radius: 99px; background: var(--accent); }
.vis-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 10px; }

/* 竞品表里的「你填的」标记：把客户在 geohub 填过的竞品与雷达发现的区分开 */
.src-badge {
  display: inline-block; font-size: var(--font-size-sm); padding: 0 6px; border-radius: 999px;
  background: var(--accent-weak); color: var(--accent-text); white-space: nowrap; vertical-align: 1px;
}

/* 数据体检卡（团队页）：一行一项，左边数字、中间"为什么要管"、右边去看 */
.dh-row { display: flex; align-items: flex-start; gap: 14px; padding: 10px 0; border-top: 1px solid var(--border); }
.dh-row:first-of-type { border-top: 0; }
.dh-n { font-size: var(--font-size-2xl); font-weight: 700; font-variant-numeric: tabular-nums; min-width: 54px; text-align: right; letter-spacing: -.4px; }
.dh-n.dim { color: var(--dim-2); font-weight: 600; }
.dh-n.mid { color: #c0392b; }
.dh-n.good { color: var(--accent-text); }
.dh-main { flex: 1; min-width: 0; }
.dh-label { font-weight: 600; margin-bottom: 2px; }
.dh-act { flex: none; padding-top: 3px; }

/* 电话构成条：一次看清名单整体"能不能打到人"。四档用冷热度分色，
   手机（能直接找到人）是唯一给强调色的那一档——它不是"其中一种"，它是目标。 */
.pm { margin: 4px 0 12px; }
.pm-hd { font-weight: 600; margin-bottom: 7px; }
.pm-bar { display: flex; height: 26px; border-radius: var(--radius-sm); overflow: hidden; border: 1px solid var(--border); }
.pm-seg {
  display: flex; align-items: center; justify-content: center; min-width: 0;
  font-size: var(--font-size-sm); font-variant-numeric: tabular-nums; color: var(--dim);
  text-decoration: none; border-right: 1px solid var(--card); background: var(--bg);
}
.pm-seg:last-child { border-right: 0; }
a.pm-seg:hover { filter: brightness(.96); }
.pm-seg.s0 { background: var(--accent-weak); color: var(--accent-text); font-weight: 600; }
.pm-seg.s1 { background: #eef2f6; color: var(--text); }
.pm-seg.s2 { background: #f6f7f9; color: var(--dim); }
.pm-seg.s3 { background: repeating-linear-gradient(45deg, #f0f2f5, #f0f2f5 4px, #e7eaee 4px, #e7eaee 8px); color: var(--dim-2); }
.pm-n { font-size: var(--font-size-sm); }
.pm-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; }
.pm-lg { display: inline-flex; align-items: center; gap: 5px; font-size: var(--font-size-sm); color: var(--dim); }
.pm-lg i { width: 9px; height: 9px; border-radius: var(--border-radius-small); display: inline-block; }
.pm-lg.s0 i { background: var(--accent-weak); border: 1px solid var(--accent); }
.pm-lg.s1 i { background: #eef2f6; border: 1px solid var(--border-strong); }
.pm-lg.s2 i { background: #f6f7f9; border: 1px solid var(--border-strong); }
.pm-lg.s3 i { background: #f0f2f5; border: 1px dashed var(--border-strong); }

/* 业绩报表里的小表（阶段 / 按人 / 来源转化率）：比主表紧凑，不抢视觉 */
.dh-table { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); }
.dh-table th, .dh-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.dh-table th { color: var(--dim-2); font-weight: 600; }
.dh-table td.num, .dh-table th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* 投放分布主表：行上的成交/输单标记。
   成交是机构的属性、不是平台的属性，所以标在行（地区 × 科室）上而不是格子里 ——
   运营看这一列是为了回答"哪类机构好卖"，那是雷达该多扫哪里的输入。 */
.deal-tag { display: inline-flex; gap: 6px; margin-left: 8px; font-size: 11px; vertical-align: 1px; }
.deal-tag b.won { color: var(--accent); font-weight: 600; }
.deal-tag .lost { color: var(--dim-2); }

/* 配范围面板里的「实时库存」与「哪里有货」点选榜。
   库存那一行要能一眼看出"这个范围是不是空的"，所以数字加粗、警示用 warn 色；
   点选榜是多行小按钮，让它独立成行、不要跟输入框挤在同一条 flex 里。 */
.scope-stock { flex-basis: 100%; margin-top: 2px; font-size: var(--font-size-sm); }
.scope-supply { flex-basis: 100%; margin-top: 6px; }
.scope-supply .stock-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 4px; }
.scope-supply .stock-row .note { margin-right: 2px; }
.warn { color: var(--high); }

/* ------------------------- AI 可见度体检报告面板 -------------------------
   这块是"给客户看的那份报告"的出口，所以它要一眼看出三件事：有没有报告、
   分数是多少、报告生成得怎么样了。刻意不用进度条：一次诊断几分钟，
   假进度条（按时间猜）比没有更糟——它会在卡住时表现得像正常。 */

.dx-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.dx-score {
  font-size: 30px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--dim);
}
.dx-score.high { color: var(--accent); }
.dx-score.mid { color: var(--mid); }
.dx-score.low { color: var(--high); }

.dx-run { display: flex; align-items: center; gap: 8px; color: var(--dim); }
/* 转圈：纯装饰，所以 aria-hidden（见 app.js 的 span），并尊重系统的"减少动效" */
.spin {
  width: 13px; height: 13px; flex: 0 0 auto; border-radius: var(--border-radius-circle);
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  animation: dx-spin .8s linear infinite;
}
@keyframes dx-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* 减少动效时不能停成"一个静止的圈"——那看起来像个句号。换成呼吸式明暗 */
  .spin { animation: dx-pulse 1.4s ease-in-out infinite; border-top-color: var(--border-strong); }
  @keyframes dx-pulse { 50% { opacity: .35; } }
}

/* 生成前的如实交代（名字太通用会让分数虚高、缺官网会少一维）。用警示色的描边块，
   不用纯文字 note：这两条是"看报告的人会被误导"的话，值得占点视觉权重。 */
.dx-warn {
  margin-top: 9px; padding: 8px 11px; border-left: 3px solid var(--mid);
  background: var(--mid-weak); border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
  font-size: var(--font-size-sm); color: var(--text); display: grid; gap: 5px;
}
.dx-fail {
  margin-bottom: 9px; padding: 8px 11px; border-left: 3px solid var(--high);
  background: var(--high-weak); border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
  font-size: var(--font-size-sm);
}
.dx-hist { margin-top: 10px; font-size: var(--font-size-sm); }
.dx-hist summary { cursor: pointer; color: var(--dim); }
.dx-hist ul { margin: 6px 0 0; padding-left: 18px; display: grid; gap: 4px; }


/* ------------------------------ 容量与覆盖矩阵 ------------------------------ */

/**
 * 覆盖矩阵（名册页）。与投流矩阵（`table.matrix`）**刻意不同**：那张是"我们发了什么"、
 * 列是平台；这张是"市场多大 vs 我们碰了多少"、列是科室。所以不复用 `table.matrix` 的
 * 列分组与两行 sticky 表头——那套是为 11 个平台列设计的，搬过来只会有多余的复杂度。
 *
 * 底色沿用 accent 透明度阶梯（与 `.cell-btn.heat-1..4` 同一组值）：同一套语义
 * （越深越多）就该是同一个色，不该为这张表再发明一套色阶。
 */
.cov-top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.cov-top .spacer { flex: 1; }
.cov-top .lgd { display: inline-flex; align-items: center; gap: 4px; font-size: var(--font-size-sm); color: var(--dim); }
.cov-top .lgd i { width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border); }
/* 图例的色块与格子用同一组背景值，所以这里也走同样的四个梯度 */
.cov-top .lgd i.c0 { background: var(--card); }
.cov-top .lgd i.c1 { background: rgba(15, 157, 120, 0.06); }
.cov-top .lgd i.c2 { background: rgba(15, 157, 120, 0.13); }
.cov-top .lgd i.c3 { background: rgba(15, 157, 120, 0.22); }

/* 矩阵自身不高：10 行 × 7 列，别继承 `.table-wrap` 那个为长表准备的 max-height */
.cov-wrap { max-height: none; overflow-x: auto; }
table.cov { width: 100%; font-size: var(--font-size-table); }
table.cov th, table.cov td { padding: 5px 8px; border-bottom: 1px solid var(--border); white-space: nowrap; }
table.cov thead th { color: var(--dim); font-weight: 600; background: var(--card-2); }
table.cov th.rowhead, table.cov tfoot th { text-align: left; font-weight: 600; color: var(--text); }
table.cov tbody tr:hover th.rowhead { color: var(--accent-text); }
table.cov tfoot td, table.cov tfoot th { background: var(--card-2); border-bottom: 0; font-variant-numeric: tabular-nums; }
table.cov td.cell { text-align: right; font-variant-numeric: tabular-nums; }
/* 合计列与"没碰过"的格子同色：它是行汇总，不该比明细格更响 */
table.cov td.cnone { background: var(--card); text-align: right; font-variant-numeric: tabular-nums; }
/* 四档底色。c0 不设背景色（留白＝没碰过，那是要看的那一片白），只把数字压暗 */
table.cov td.c1 { background: rgba(15, 157, 120, 0.06); }
table.cov td.c2 { background: rgba(15, 157, 120, 0.13); }
table.cov td.c3 { background: rgba(15, 157, 120, 0.22); }
table.cov td.cell a { color: inherit; text-decoration: none; display: block; }
table.cov td.cell a:hover b { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }
table.cov td.cell a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 3px; }
table.cov td.cell b { font-weight: 600; }
/* 右半边的「池子里」是弱信息：同一个格子里两个数必须能分开读，否则会被当成一个分数 */
table.cov td.cell .u, table.cov tfoot .u { color: var(--dim-2); font-weight: 400; }
table.cov td.c0 b { color: var(--dim-2); font-weight: 400; }

/* ------------------------------ 下钻抽屉（右侧滑出） ------------------------------ */

/**
 * 名册的下钻从「行内展开」改成右侧抽屉（2026-09-22）：七组信息塞不进一个表格行，
 * 而且行内展开会把表格撑开、扫读位置跳。抽屉不动列表，符合"扫一列 → 点一家看细节 → 继续扫"。
 *
 * **不用 `[hidden]` 控制显隐**：作者样式里的 `display` 会盖掉 UA 的 `[hidden]`
 * （本文件里已记过同款踩雷），而这里本来就靠 transform 做位移，索性用类控制。
 * `visibility: hidden` 同时解决两件事——滑出屏外之后不被 Tab 聚焦、也不被读屏读到
 * （只写 transform 的话，隐藏时仍是可聚焦的，键盘用户会 Tab 进一块看不见的面板）。
 */
.ent-drawer {
  position: fixed; top: var(--topbar-h); right: 0; bottom: 0;
  width: min(520px, 92vw);
  display: flex; flex-direction: column;
  background: var(--card); border-left: 1px solid var(--border);
  box-shadow: -8px 0 24px rgba(15, 23, 42, 0.08);
  transform: translateX(100%); visibility: hidden;
  transition: transform var(--motion-base) var(--ease), visibility var(--motion-base);
  z-index: 40;
}
.ent-drawer.open { transform: none; visibility: visible; }
.ent-drawer-hd {
  display: flex; align-items: center; gap: 8px; flex: none;
  padding: 10px 14px; border-bottom: 1px solid var(--border); background: var(--card-2);
}
.ent-drawer-hd .spacer { flex: 1; }
.ent-drawer-t { font-weight: 600; }
.ent-drawer-x {
  background: none; border: 0; font: inherit; color: var(--dim);
  cursor: pointer; padding: 2px 8px; border-radius: 6px; line-height: 1.4;
}
.ent-drawer-x:hover { background: var(--bg); color: var(--text); }
.ent-drawer-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ent-drawer-bd { overflow: auto; padding: 14px; }

.ent-hd { padding-bottom: 10px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.ent-hd-n { font-size: var(--font-size-lg); font-weight: 600; line-height: 1.4; }
.ent-hd-m { color: var(--dim); font-size: var(--font-size-sm); margin-top: 3px; }
.ent-sec { padding: 10px 0; border-bottom: 1px dashed var(--border); }
.ent-sec:last-child { border-bottom: 0; }
.ent-sec-t { margin: 0 0 6px; font-size: var(--font-size-sm); font-weight: 600; color: var(--dim); }
/* 抽屉比页面窄，`dl` 的 88px 标签列在这里偏宽，压到 76px 让值有更多位置 */
.ent-drawer .dl { grid-template-columns: 76px 1fr; }
.ent-drawer .dl dd { word-break: break-word; }
.ent-drawer .tag { margin-right: 0; }

/* 动效敏感的用户：只去掉位移过渡，显隐仍然要生效（不能整块拿掉 transition） */
@media (prefers-reduced-motion: reduce) {
  .ent-drawer { transition: none; }
}

/* ══════════════════════ 收尾：聚焦环兜底（2026-09-23）══════════════════════
   边框饮食之后，能"框住"元素的东西只剩三样：输入框描边、表行分隔线、聚焦环。
   所以聚焦环必须每个可交互元素都有——它是键盘用户唯一能看见自己位置的东西。

   文件里已有 21 条各自写的 `:focus-visible`（颜色一致、偏移不同，偏移是有意的）。
   下面这条补的是**没有单独立规则**的那部分（普通 button、a，以及以后新增的控件）；
   已写规则的元素由更具体的选择器接管（`.chip:focus-visible` 之类优先级更高），不受影响。

   表单控件排除在外：它的焦点提示是"描边 + 光晕"（见 input:focus），
   再叠一圈 outline 会变成两套焦点样式。 */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: none; }

/* 关于动效：本轮把 12 处散落的时长统一到 --motion-fast / --motion-base，**没有新增动画**，
   所以上面这些既有的 prefers-reduced-motion 仍然覆盖全部动效，不另加全局兜底——
   全局压时长会与抽屉那条"显隐仍要生效"的用意冲突。 */
