/* ui.css — 全站组件层（ink P12 · 界面改造第一刀）
   ────────────────────────────────────────────────────────────────────────
   改造前的实测：「状态标签」这一个东西，在 37 个房间的 CSS 里各写了一遍；
   按钮 36 遍、卡片 32 遍、空状态 27 遍、表格 17 遍、弹层 10 遍。
   于是同一个概念在不同房间是不同的圆角、不同的字号、不同的绿。

   这里放的是那 12 个被重造最多次的东西的唯一实现。房间只管排列组合，
   不再自己画。所有值取自 tokens.css，**这个文件里不出现一个 #hex**。

   配套：
     /static/tokens.css   变量（间距 / 字号 / 圆角 / 语义色 / 密度）
     /static/ui.js        行为（表格排序、抽屉开合、筛选折叠、accent 字色）
     /ui-kit              预览页：12 个组件 × 明暗 × 中英长短文本

   命名：一律 `u-` 前缀，房间自己的类名不许用这个前缀。
*/

/* ══════════════════════════════════════════════════════════════════════
   0 · [hidden] 必须真的隐藏
   下面每个组件都自己写了 display（flex / inline-flex / grid），**作者样式的
   display 会盖掉 `[hidden]` 那条来自浏览器默认表的 display:none**。
   后果：房间里写 <button class="u-btn" hidden> 按钮照样显示 —— 不报错、不 404，
   只是一个本该藏起来的操作一直摆在那儿（账房那一枝实测踩到）。
   这条把它兜回来。**新增任何设 display 的 .u-* 类，都要同时加进这张名单**，
   tests/test_ui_components.py 会盯着。
   ══════════════════════════════════════════════════════════════════════ */
.u-btn[hidden], .u-chip[hidden], .u-dot[hidden], .u-chips[hidden], .u-num[hidden],
.u-card[hidden], .u-cards[hidden], .u-kpi[hidden], .u-cell2[hidden], .u-empty[hidden],
.u-alert[hidden], .u-toolbar[hidden], .u-search[hidden], .u-filterbar[hidden],
.u-frow[hidden], .u-pill[hidden], .u-board[hidden], .u-col[hidden], .u-colhead[hidden],
.u-collist[hidden], .u-colsearch[hidden], .u-bcard[hidden], .u-panel[hidden], .u-scrim[hidden],
.u-form[hidden], .u-field[hidden], .u-hint[hidden], .u-table[hidden],
.u-tablewrap[hidden], .u-sec[hidden],
/* 13–16 节（2026-08-24 抽项目单那套形状进来的四件） */
.u-pills[hidden], .u-sumbar[hidden], .u-metric[hidden], .u-colfold[hidden] { display: none; }

/* ══════════════════════════════════════════════════════════════════════
   1 · .u-btn — 按钮
   variant: primary / secondary / ghost / danger    size: sm / md（默认 md）

   2026-08-12 改造（Asher 选的「分量」方向）。改之前的毛病，量出来是这三条：
   ① 默认按钮底色 --surface-2 铺在 --surface 上，对比 1.14:1，边框还是
      transparent —— 按钮在页面上看着像一段文字，不像能按的东西。
   ② 悬停只换底色，按下 translateY(.5px)：半个像素，等于没有反馈。
   ③ 项目单工具栏 11 个控件同一个视觉重量，主操作和次要操作分不出轻重，
      整排读起来是一堵墙。
   改法是把权重拉成三档 —— 主操作实心＋内高光＋投影（跳出来）／次要只剩边／
   弱化只剩字 —— 并给悬停 1px 抬升、按下 .985 回压，让「按下去了」看得见。
   ══════════════════════════════════════════════════════════════════════ */
.u-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: 0 var(--sp-3); height: 32px;
  border: 1px solid transparent; border-radius: var(--r-md);
  background: var(--surface-3); color: var(--text);
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-med);
  line-height: 1; white-space: nowrap; cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease), box-shadow var(--dur-base) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.u-btn:hover { background: var(--surface-hover); transform: translateY(-1px); box-shadow: var(--el-2); }
.u-btn:active { transform: translateY(0) scale(.985); box-shadow: none; }
.u-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.u-btn:disabled, .u-btn[aria-disabled="true"] {
  opacity: .45; cursor: not-allowed; transform: none; box-shadow: none;
}
.u-btn:disabled:hover, .u-btn[aria-disabled="true"]:hover {
  background: var(--surface-3); transform: none; box-shadow: none;
}
.u-btn .ic { width: 15px; height: 15px; flex: none; }

/* 主操作：实心 accent ＋ 顶部内高光 ＋ 更深的投影。一屏只该有一个。 */
.u-btn.primary {
  background: var(--accent); color: var(--on-accent); border-color: transparent;
  font-weight: var(--fw-bold); box-shadow: var(--el-2), var(--hi-inset);
}
.u-btn.primary:hover {
  background: var(--accent); filter: brightness(1.06);
  box-shadow: var(--el-3), var(--hi-inset);
}
.u-btn.primary:active { filter: none; box-shadow: var(--hi-inset); }
/* 次要：只剩一圈边 */
.u-btn.secondary { background: transparent; border-color: var(--border-strong); color: var(--text); }
.u-btn.secondary:hover { background: var(--surface-3); border-color: var(--text-faint); }
/* 弱化：只剩字。悬停染主色，但用 --accent-text —— 白天模式下 --accent 当字
   全部主题都不到 4.5:1（品牌金只有 1.84:1）。不抬升：它本来就该最轻。 */
.u-btn.ghost { background: transparent; color: var(--text-dim); }
.u-btn.ghost:hover {
  background: var(--accent-tint); color: var(--accent-text);
  transform: none; box-shadow: none;
}
/* 危险：平时就是暗底，不是一圈红边 —— 删除键该在一排按钮里自己站出来。
   悬停只加深边和抬升，**不填实心红**：白字压在 --danger-solid 上只有
   3.91:1，过不了 AA；明色档更糟（填色一深，--danger-fg 就掉到 3.5 以下）。 */
.u-btn.danger { background: var(--danger-bg); border-color: transparent; color: var(--danger-fg); }
.u-btn.danger:hover { background: var(--danger-bg); border-color: var(--danger-solid); }

.u-btn.sm { height: 26px; padding: 0 var(--sp-2); font-size: var(--fs-xs); }
.u-btn.sm .ic { width: 13px; height: 13px; }
.u-btn.icon { width: 32px; padding: 0; }
.u-btn.sm.icon { width: 26px; }

/* ══════════════════════════════════════════════════════════════════════
   2 · .u-chip — 状态标签（替掉 37 份各写各的）
   tone: ok / warn / danger / info / neutral（默认 neutral）  size: sm / md
   solid 变体 = 实心，只给「必须一眼看到」的那一个状态用，别一排全实心。
   ══════════════════════════════════════════════════════════════════════ */
.u-chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  max-width: 100%; padding: 2px var(--sp-2);
  border: 1px solid var(--neutral-bd); border-radius: var(--r-sm);
  background: var(--neutral-bg); color: var(--neutral-fg);
  font-size: var(--fs-xs); font-weight: var(--fw-med); line-height: 1.5;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.u-chip.ok     { background: var(--ok-bg);     border-color: var(--ok-bd);     color: var(--ok-fg); }
.u-chip.warn   { background: var(--warn-bg);   border-color: var(--warn-bd);   color: var(--warn-fg); }
.u-chip.danger { background: var(--danger-bg); border-color: var(--danger-bd); color: var(--danger-fg); }
.u-chip.info   { background: var(--info-bg);   border-color: var(--info-bd);   color: var(--info-fg); }
.u-chip.sm { padding: 1px var(--sp-1); font-size: var(--fs-xs); transform: scale(.94); transform-origin: left center; }
.u-chip.solid { color: var(--on-accent); border-color: transparent; }
.u-chip.solid.ok     { background: var(--ok-solid); }
.u-chip.solid.warn   { background: var(--warn-solid); }
.u-chip.solid.danger { background: var(--danger-solid); }
.u-chip.solid.info   { background: var(--info-solid); }
.u-chip.solid.neutral{ background: var(--neutral-solid); }
/* 可点的 chip（展开明细 / 去掉一个筛选）——只有可点的才给手型和 hover */
.u-chip[role="button"], button.u-chip { cursor: pointer; font-family: inherit; }
.u-chip[role="button"]:hover, button.u-chip:hover { filter: brightness(1.12); }
.u-chip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* 状态点：不占宽度地表达状态，看板列头、表格行首用 */
.u-dot { display: inline-block; width: 7px; height: 7px; border-radius: var(--r-full);
  background: var(--neutral-solid); flex: none; }
.u-dot.ok { background: var(--ok-solid); }
.u-dot.warn { background: var(--warn-solid); }
.u-dot.danger { background: var(--danger-solid); }
.u-dot.info { background: var(--info-solid); }
/* 一行放不下的 chip 折成「+3」 */
.u-chips { display: flex; align-items: center; gap: var(--sp-1); min-width: 0; flex-wrap: wrap; }
.u-chips.nowrap { flex-wrap: nowrap; overflow: hidden; }
.u-more { font-size: var(--fs-xs); color: var(--text-faint); flex: none; }

/* ══════════════════════════════════════════════════════════════════════
   3 · .u-num — 数字件（金额 / 百分比 / 倍数）
   等宽 + tabular 绑在一起：只写 --font-num 不写 tabular-nums，列还是对不齐。
   pos/neg 只在**涨跌有含义**时加；「0 次」这种没有含义的不许上色。
   ══════════════════════════════════════════════════════════════════════ */
.u-num {
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1; color: var(--text); white-space: nowrap;
}
.u-num.pos { color: var(--ok-fg); }
.u-num.neg { color: var(--danger-fg); }
.u-num.dim { color: var(--text-dim); }
.u-num.faint { color: var(--text-faint); }
.u-num.big { font-size: var(--fs-2xl); font-weight: var(--fw-bold); line-height: var(--lh-tight); }
.u-num.lg { font-size: var(--fs-lg); font-weight: var(--fw-med); }
/* 单位（¥ / % / 倍）比数字轻一档，数字才是主角 */
.u-num .unit { color: var(--text-faint); font-weight: var(--fw-normal); }

/* ══════════════════════════════════════════════════════════════════════
   4 · .u-card — 容器卡（一档 padding，别开变体）
   ══════════════════════════════════════════════════════════════════════ */
.u-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-4);
}
.u-card > header {
  display: flex; align-items: center; gap: var(--sp-2);
  margin: 0 0 var(--sp-3); font-size: var(--fs-sm); font-weight: var(--fw-med); color: var(--text);
}
.u-cards { display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* ══════════════════════════════════════════════════════════════════════
   5 · .u-kpi — 指标卡（强制同结构）
   四个位，顺序固定：标签 → 主数 → 一行辅助 → 可选一条对比。
   改造前账房那四张卡各长各样（第四张挤成一团文字），就是缺这个约束。
   ══════════════════════════════════════════════════════════════════════ */
.u-kpi {
  display: flex; flex-direction: column; gap: var(--sp-2);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-4);
}
.u-kpi > .k-label {
  font-size: var(--fs-xs); font-weight: var(--fw-med); color: var(--text-dim);
  letter-spacing: .04em; display: flex; align-items: center; gap: var(--sp-1);
}
.u-kpi > .k-main { font-size: var(--fs-2xl); font-weight: var(--fw-bold);
  line-height: var(--lh-tight); color: var(--text);
  font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.u-kpi > .k-sub { font-size: var(--fs-xs); color: var(--text-dim);
  line-height: var(--lh-loose); display: flex; flex-direction: column; gap: 2px; }
.u-kpi > .k-sub b { color: var(--text); font-weight: var(--fw-med);
  font-family: var(--font-num); font-variant-numeric: tabular-nums; }
/* 对比条：「本月 ¥1,830，上月 ¥1,020 ＝ 1.79 倍 / +¥810（涨）」——算式本身就是内容，
   给它独立底色，它不是脚注。Asher 不做心算，这一条不许被「简洁」删掉。 */
.u-kpi > .k-cmp {
  margin-top: auto; padding: var(--sp-2) var(--sp-2);
  border-radius: var(--r-sm); background: var(--surface-2);
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs); color: var(--text-dim); line-height: var(--lh-normal);
}
.u-kpi > .k-cmp .pos { color: var(--ok-fg); }
.u-kpi > .k-cmp .neg { color: var(--danger-fg); }

/* ══════════════════════════════════════════════════════════════════════
   6 · .u-table — 索引表（替掉 17 份）
   密度三档：加 .compact / .tall，不加就是 normal。
   数字列加 .num：右对齐 + 等宽 + tabular，一处搞定。
   ══════════════════════════════════════════════════════════════════════ */
.u-table { width: 100%; border-collapse: separate; border-spacing: 0;
  --u-row: var(--row-normal); }
.u-table.compact { --u-row: var(--row-compact); }
.u-table.tall { --u-row: var(--row-tall); }
.u-table th, .u-table td {
  padding: 0 var(--sp-3); height: var(--u-row);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm); color: var(--text); text-align: left; vertical-align: middle;
}
.u-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface); color: var(--text-dim);
  font-size: var(--fs-xs); font-weight: var(--fw-med); letter-spacing: .04em;
  border-bottom: 1px solid var(--border-strong); white-space: nowrap;
}
.u-table th.num, .u-table td.num {
  text-align: right; font-family: var(--font-num); font-variant-numeric: tabular-nums;
}
.u-table tbody tr { transition: background var(--dur-fast) var(--ease); }
.u-table tbody tr:hover { background: var(--surface-2); }
.u-table tbody tr.on { background: var(--surface-3); }
.u-table.zebra tbody tr:nth-child(even) { background: var(--surface-2); }
.u-table.zebra tbody tr:nth-child(even):hover { background: var(--surface-3); }
.u-table tbody tr.clickable { cursor: pointer; }
.u-table tbody tr:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.u-table tfoot td { font-weight: var(--fw-bold);
  border-top: 1px solid var(--border-strong); border-bottom: 0; }
/* 一格两层：主行 + 次行。**不许把行高撑起来**——次行用 --fs-xs，两层刚好塞进 tall。 */
.u-cell2 { display: flex; flex-direction: column; justify-content: center; gap: 1px; min-width: 0; }
.u-cell2 > .a { font-size: var(--fs-sm); color: var(--text); line-height: var(--lh-tight);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.u-cell2 > .b { font-size: var(--fs-xs); color: var(--text-faint); line-height: var(--lh-tight);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 空值：一片 `—` 是噪音。统一成极淡的短横，眼睛自动跳过它 */
.u-nil { color: var(--text-faint); opacity: .55; }
/* 可排序表头 */
.u-table th[data-sort] { cursor: pointer; user-select: none; }
.u-table th[data-sort]:hover { color: var(--text); }
.u-table th[data-sort]::after { content: ""; display: inline-block; width: 0; height: 0;
  margin-left: var(--sp-1); vertical-align: middle; opacity: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 4px solid currentColor; }
.u-table th[data-sort][aria-sort]::after { opacity: .8; }
.u-table th[aria-sort="ascending"]::after { border-top: 0; border-bottom: 4px solid currentColor; }
/* 表格外壳：表头钉住 + 内容撑满可用高度（改造前底部留 40% 空白） */
.u-tablewrap { flex: 1; min-height: 0; overflow: auto; }

/* ══════════════════════════════════════════════════════════════════════
   7 · .u-empty — 空状态（替掉 27 处只写一个「空」字的）
   图标 + 一句话 + 一个动作。没有动作可给就只留前两样。
   ══════════════════════════════════════════════════════════════════════ */
.u-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-2); padding: var(--sp-7) var(--sp-4); text-align: center;
  color: var(--text-faint);
}
.u-empty .ic { width: 26px; height: 26px; opacity: .5; }
.u-empty > .t { font-size: var(--fs-sm); color: var(--text-dim); }
.u-empty > .d { font-size: var(--fs-xs); color: var(--text-faint); max-width: 34ch;
  line-height: var(--lh-normal); }
.u-empty.inline { padding: var(--sp-5) var(--sp-3); }

/* ══════════════════════════════════════════════════════════════════════
   8 · .u-alert — 告警条（替掉当组件用的 ⚠️ emoji）
   tone: info / warn / danger
   ══════════════════════════════════════════════════════════════════════ */
.u-alert {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md);
  border: 1px solid var(--info-bd); background: var(--info-bg); color: var(--info-fg);
  font-size: var(--fs-xs); line-height: var(--lh-normal);
}
.u-alert.warn   { border-color: var(--warn-bd);   background: var(--warn-bg);   color: var(--warn-fg); }
.u-alert.danger { border-color: var(--danger-bd); background: var(--danger-bg); color: var(--danger-fg); }
.u-alert .ic { width: 15px; height: 15px; flex: none; margin-top: 1px; }
.u-alert b { font-weight: var(--fw-bold); }
.u-alert a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* ══════════════════════════════════════════════════════════════════════
   9 · .u-toolbar — 房间头（全站固定顺序）
   标题+计数 → 视图切换 → 筛选 → 搜索 → 次操作 → 主操作
   改造前每个房间的头部位置和顺序都不一样，人每进一个房间都要重新找按钮。
   ══════════════════════════════════════════════════════════════════════ */
.u-toolbar {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border);
  background: var(--surface); flex: none;
}
.u-toolbar > .t-title { font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--text);
  display: flex; align-items: baseline; gap: var(--sp-2); }
.u-toolbar > .t-title > .n { font-size: var(--fs-xs); font-weight: var(--fw-normal);
  color: var(--text-faint); font-family: var(--font-num); font-variant-numeric: tabular-nums; }
/* 折行：账房有八个页签，一行放不下。不折的话第八个直接被挤没，
   而「被挤没」在页面上不报错、不留痕，只能靠眼睛发现。 */
.u-toolbar > .t-views { position: relative; display: flex; flex-wrap: wrap; gap: 2px; padding: 2px;
  border-radius: var(--r-md); background: var(--surface-2); }
/* 当前项底下那块高亮抽成容器上的一个滑块（坐标由 ui.js 的 syncSegs 给）。
   .has-ind 是 JS 量到位置后才挂上的：没挂上时走 button.on 的兜底底色，
   所以 JS 没跑起来 / 还没量完的那一帧，当前项也不会没有高亮。 */
.u-toolbar > .t-views.has-ind::before {
  content: ""; position: absolute; left: 0; top: 0;
  width: var(--seg-w); height: var(--seg-h);
  transform: translate(var(--seg-x), var(--seg-y));
  background: var(--surface); border-radius: var(--r-sm); box-shadow: var(--el-1);
  transition: transform var(--dur-base) var(--ease), width var(--dur-base) var(--ease),
              height var(--dur-base) var(--ease);
  pointer-events: none;
}
.u-toolbar > .t-views > button {
  position: relative; z-index: 1;
  padding: 0 var(--sp-3); height: 26px; border: 0; border-radius: var(--r-sm);
  background: none; color: var(--text-dim); font-family: inherit; font-size: var(--fs-xs);
  cursor: pointer; transition: color var(--dur-fast) var(--ease); }
.u-toolbar > .t-views > button:hover { color: var(--text); }
.u-toolbar > .t-views > button.on { background: var(--surface); color: var(--text);
  font-weight: var(--fw-med); box-shadow: var(--el-1); }
/* 滑块接管之后当前项自己不再画底 —— 否则两块底叠在一起，滑动时能看到残影 */
.u-toolbar > .t-views.has-ind > button.on { background: none; box-shadow: none; }
.u-toolbar > .t-views > button:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* 搜索、次操作、主操作靠右；.t-gap 之后的全部推到右边 */
.u-toolbar > .t-gap { margin-left: auto; }
.u-toolbar > .t-sum { margin-left: auto; display: flex; gap: var(--sp-4);
  font-size: var(--fs-xs); color: var(--text-dim); }
.u-toolbar > .t-sum b { color: var(--text); font-family: var(--font-num);
  font-variant-numeric: tabular-nums; font-weight: var(--fw-med); }

/* 搜索框：全站一个样子 */
.u-search { position: relative; display: inline-flex; align-items: center; }
.u-search > .ic { position: absolute; left: var(--sp-2); width: 14px; height: 14px;
  color: var(--text-faint); pointer-events: none; }
.u-search > input {
  height: 32px; width: 220px; max-width: 46vw;
  padding: 0 var(--sp-3) 0 calc(var(--sp-2) * 2 + 14px);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--text);
  font-family: inherit; font-size: var(--fs-sm); outline: none;
}
.u-search > input::placeholder { color: var(--text-faint); }
.u-search > input:hover { border-color: var(--border-strong); }
.u-search > input:focus { border-color: var(--accent); background: var(--surface); }
.u-search > input:focus-visible { box-shadow: var(--focus-ring); }

/* ══════════════════════════════════════════════════════════════════════
   10 · .u-filterbar — 两级筛选
   主筛（一行，实心选中）／ 次筛（描边，视觉更轻）。
   改造前两排 pill 视觉重量一样，分不出哪个是主轴。
   ══════════════════════════════════════════════════════════════════════ */
.u-filterbar { display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4); border-bottom: 1px solid var(--border); flex: none; }
.u-frow { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
.u-frow > .lab { font-size: var(--fs-xs); color: var(--text-faint); flex: none; }
.u-pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  height: 26px; padding: 0 var(--sp-3);
  border: 1px solid transparent; border-radius: var(--r-full);
  background: var(--surface-2); color: var(--text-dim);
  font-family: inherit; font-size: var(--fs-xs); cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease), box-shadow var(--dur-base) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.u-pill:hover { background: var(--surface-hover); color: var(--text); transform: translateY(-1px); }
.u-pill:active { transform: translateY(0) scale(.985); }
.u-pill:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.u-pill > .n { color: var(--text-faint); font-family: var(--font-num);
  font-variant-numeric: tabular-nums; }
.u-pill.on { background: var(--accent); color: var(--on-accent); font-weight: var(--fw-med); }
.u-pill.on > .n { color: inherit; opacity: .75; }
/* 次级：只描边，不填色 —— 一眼看出它不是主轴 */
.u-frow.sub .u-pill { background: transparent; border-color: var(--border); height: 24px; }
.u-frow.sub .u-pill:hover { border-color: var(--border-strong); background: var(--surface-2); }
.u-frow.sub .u-pill.on { background: var(--surface-3); border-color: var(--border-strong);
  color: var(--text); }
/* 超出一行折起来。
   「更多 ▾」必须**绝对定位钉在行首那一行的右端**：它是 appendChild 进去的最后一个
   flex 项，跟着流排就落在被 max-height 裁掉的第二行里 —— 于是折起来之后人永远
   点不开，而且看不出有东西被折了（客户房间实测踩到）。 */
.u-frow { position: relative; padding-right: 60px; }
.u-frow.folded { max-height: 26px; overflow: hidden; }
.u-fold { position: absolute; right: 0; top: 0; height: 26px;
  border: 0; background: none; color: var(--text-faint); cursor: pointer;
  font-family: inherit; font-size: var(--fs-xs); padding: 0 var(--sp-1); }
.u-fold:hover { color: var(--text); }
.u-fold:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ══════════════════════════════════════════════════════════════════════
   11 · .u-board — 看板
   列自适应铺满宽度、竖向占满视口；列内搜索收进列头菜单，不是 N 个并排输入框。
   改造前：五列固定宽 → 右边被裁掉半列，下面 40% 视口全空。
   ══════════════════════════════════════════════════════════════════════ */
.u-board {
  flex: 1; min-height: 0; display: grid; gap: var(--sp-3);
  /* --board-col-min（默认 220px，在 tokens.css）是「一张卡还读得动」的下限。
     列数少时 1fr 把宽度分完（不留空地），多到挤不下才横向滚 —— 改造前是列宽
     写死，于是既留了空地又切掉了第五列。
     轴上状态特别多的房间（项目单的交付轴 10 档、收款轴 12 档）可以在房间根上
     调低这一档换「一屏看全」，但别调到卡片读不动。 */
  grid-auto-flow: column; grid-auto-columns: minmax(var(--board-col-min), 1fr);
  overflow-x: auto; overflow-y: hidden; padding: var(--sp-3) var(--sp-4) var(--sp-4);
  align-items: stretch;
}
.u-col { display: flex; flex-direction: column; min-height: 0; min-width: 0;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-lg); }
.u-colhead { display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border); flex: none; }
.u-colhead > .name { font-size: var(--fs-sm); font-weight: var(--fw-med); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.u-colhead > .n { font-size: var(--fs-xs); color: var(--text-faint);
  font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.u-colhead > .sum { margin-left: auto; font-size: var(--fs-xs); color: var(--text-dim);
  font-family: var(--font-num); font-variant-numeric: tabular-nums; }
/* 列内搜索：默认收成一个放大镜，点开才展开成输入框 */
.u-colsearch { flex: none; padding: 0 var(--sp-2) var(--sp-2); display: none; }
.u-col.searching .u-colsearch { display: block; }
.u-colsearch input { width: 100%; height: 26px; padding: 0 var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text);
  font-family: inherit; font-size: var(--fs-xs); outline: none; }
.u-colsearch input:focus { border-color: var(--accent); }
.u-col.filtered .u-colhead > .name::after { content: "•"; margin-left: var(--sp-1);
  color: var(--accent); }
.u-collist { flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--sp-2); display: flex; flex-direction: column; gap: var(--sp-2); }
.u-collist::-webkit-scrollbar { width: 6px; }
.u-collist::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }
.u-collist::-webkit-scrollbar-track { background: transparent; }
.u-col.drop .u-collist { outline: 1px dashed var(--accent); outline-offset: -3px; border-radius: var(--r-md); }

/* 看板卡：三级层次。标题最重 → 元信息次 → 金额独立右位 → 状态最轻 */
.u-bcard { background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: var(--sp-3); cursor: pointer;
  display: flex; flex-direction: column; gap: var(--sp-2);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
.u-bcard:hover { border-color: var(--border-strong); box-shadow: var(--el-1); }
.u-bcard:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.u-bcard.dragging { opacity: .45; }
.u-bcard > .b-top { display: flex; align-items: baseline; gap: var(--sp-2); }
.u-bcard > .b-top > .code { font-family: var(--font-num); font-size: var(--fs-xs);
  color: var(--text-faint); flex: none; }
.u-bcard > .b-top > .when { margin-left: auto; font-family: var(--font-num);
  font-size: var(--fs-xs); color: var(--text-dim); flex: none; }
.u-bcard > .b-title { font-size: var(--fs-md); font-weight: var(--fw-med); color: var(--text);
  line-height: var(--lh-tight); overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.u-bcard > .b-meta { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  font-size: var(--fs-xs); color: var(--text-dim); min-width: 0; }
.u-bcard > .b-meta .ic { width: 12px; height: 12px; opacity: .7; }
.u-bcard > .b-money { display: flex; align-items: baseline; gap: var(--sp-2); }
.u-bcard > .b-money > .m2 { font-size: var(--fs-xs); color: var(--text-faint); }

/* ══════════════════════════════════════════════════════════════════════
   12 · .u-panel — 抽屉 / 弹层（替掉 10 份）
   .drawer = 右侧抽屉（Asher 要一边看 AI 面板一边对照，所以默认抽屉不是全屏）
   .modal  = 居中弹层

   ⚠️ 抽屉是 `position:absolute; top:0; bottom:0`，**贴的是 host 的四边**。
   host 直接给房间根的话，抽屉会从房间顶端起画、盖住 .u-toolbar。
   正确做法：房间根下面单开一个「舞台」div（工具条之下、内容之上那一层）当 host，
   `UI.panel({host: stage})`。看板房间都会需要这一手。
   不要遮罩就传 `{scrim: false}`（别再自己去摘遮罩节点，那依赖插入顺序）。
   ══════════════════════════════════════════════════════════════════════ */
.u-scrim { position: absolute; inset: 0; z-index: 55; background: var(--overlay);
  opacity: 0; animation: u-fade var(--dur-base) var(--ease) forwards; }
.u-panel {
  position: absolute; z-index: 60; background: var(--surface);
  display: flex; flex-direction: column; min-height: 0;
}
.u-panel.drawer { right: 0; top: 0; bottom: 0; width: min(560px, 96vw);
  border-left: 1px solid var(--border); box-shadow: var(--el-3);
  animation: u-slide var(--dur-base) var(--ease); }
.u-panel.modal { left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(560px, 92vw); max-height: 86%; border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--el-3);
  animation: u-pop var(--dur-base) var(--ease); }
.u-panel > header { display: flex; align-items: center; gap: var(--sp-2); flex: none;
  padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border); }
.u-panel > header > h3 { margin: 0; font-size: var(--fs-xl); font-weight: var(--fw-bold);
  color: var(--text); }
.u-panel > header > .sub { font-size: var(--fs-xs); color: var(--text-faint); }
.u-panel > header > .x { margin-left: auto; }
.u-panel > .body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--sp-4) var(--sp-5); }
.u-panel > footer { flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--border); }
.u-panel > footer > .right { margin-left: auto; display: flex; gap: var(--sp-2); }
@keyframes u-fade { to { opacity: 1; } }
@keyframes u-slide { from { transform: translateX(18px); opacity: .6; } }
@keyframes u-pop { from { transform: translate(-50%, -48%) scale(.98); opacity: .5; } }

/* 表单栅格：抽屉里的「标签 : 控件」两列，全站一个宽度 */
.u-form { display: grid; grid-template-columns: 108px 1fr; gap: var(--sp-2) var(--sp-3);
  align-items: center; }
.u-form > label { font-size: var(--fs-xs); color: var(--text-faint); text-align: right; }
.u-form > .full { grid-column: 1 / -1; }
/* 🔴 边不许是 transparent。2026-08-17 Asher 在公开表单上报「什么都看不见」——
   输入框只有 hover / focus 才现边，在手机上根本没有 hover，于是一个该填字的框
   跟卡片底融成一片，人不知道那里能点。后台密集表格里「无边」看着干净，
   代价是**没有指针的设备上这个控件等于不存在**（同 ui.md §28 那条的同一个病根）。
   常态就给一道 --border，focus 再换成 accent。 */
.u-field { width: 100%; height: 32px; padding: 0 var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--text);
  font-family: inherit; font-size: var(--fs-sm); outline: none; }
textarea.u-field { height: auto; min-height: 64px; padding: var(--sp-2);
  line-height: var(--lh-normal); resize: vertical; }
.u-field:hover { border-color: var(--border-strong); }
.u-field:focus { border-color: var(--accent); background: var(--surface); }
.u-field:focus-visible { box-shadow: var(--focus-ring); }
.u-field.num { text-align: right; font-family: var(--font-num);
  font-variant-numeric: tabular-nums; }

/* ── 分节标题：三段表格连成一片时用它断开（账房总览的病）─────────────── */
.u-sec { margin-top: var(--sp-6); }
.u-sec > h4 { display: flex; align-items: center; gap: var(--sp-2);
  margin: 0 0 var(--sp-2); font-size: var(--fs-sm); font-weight: var(--fw-med);
  color: var(--text-dim); }
.u-sec:first-child { margin-top: 0; }
/* ⓘ：解释性长句收进这里，hover 展开，不占数据层 */
.u-hint { position: relative; display: inline-flex; width: 15px; height: 15px;
  align-items: center; justify-content: center; flex: none;
  border: 1px solid var(--border-strong); border-radius: var(--r-full);
  background: none; color: var(--text-faint); cursor: help;
  font-family: inherit; font-size: 10px; line-height: 1; }
.u-hint:hover, .u-hint:focus-visible { color: var(--text); border-color: var(--accent); outline: none; }
.u-hint > .tip {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 40;
  display: none; width: max-content; max-width: 46ch;
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--r-md); background: var(--surface-3); color: var(--text-dim);
  box-shadow: var(--el-2); font-size: var(--fs-xs); line-height: var(--lh-normal);
  text-align: left; white-space: normal; cursor: auto;
}
.u-hint:hover > .tip, .u-hint:focus-within > .tip, .u-hint:focus > .tip { display: block; }

/* ══════════════════════════════════════════════════════════════════════
   13 · 房间头补件 —— `.u-toolbar > .lab` / `.u-pills` / `.t-views.solid`

   2026-08-24 Asher 拿日蚀墨的项目单跟 Helena House 的送货台/生产对比：
   「Eclipse 的项目单的结构要明显好于 helena house 的 UI……比如视觉上有全部的
   填充，然后有这种搜索的功能」。那一屏好在哪，拆出来是四件（13–16 节）。

   第一件是房间头。骨架 `.u-toolbar` 早就有了，但「一排胶囊 + 前面一个字」
   和「当前那颗实心填满」这两个形状是**每间自己拼的**：
     projects.css  `.pj-pills` + `#pj-root .u-toolbar > .lab`
     enquiries.css `.enq-pills`（一模一样的四条声明，字都没差）
   收进来一份。

   ⚠️ `.t-views` 默认那一档（浅色滑块的分段控件）**一个像素不动** ——
   11 个房间在用。要「品牌色填满」的那一档是新加的 `.solid`，opt-in。
   两档各有各的场合：分段控件说「同一份数据的两种看法」，
   实心胶囊说「我现在站在哪一档」。
   ══════════════════════════════════════════════════════════════════════ */
/* 筛选轴前面那个字（「分组」「排序」「口径」）。轴在某个视图下不适用时给它
   加 hidden —— 所以要补 [hidden]，本条自己不设 display，加了也不会互相打架。 */
.u-toolbar > .lab { font-size: var(--fs-xs); color: var(--text-faint); flex: none; }
.u-toolbar > .lab[hidden] { display: none; }

/* 一组胶囊（分组轴 / 排序轴 / 口径）。跟 `.u-frow` 的区别：这个住在房间头里、
   不折行折叠，`.u-frow` 是筛选条那一整块。 */
.u-pills { display: flex; align-items: center; gap: var(--sp-1);
  flex-wrap: wrap; min-width: 0; }

/* 实心选中的视图页签。滑块坐标仍由 ui.js 的 syncSegs 给，只换填充和圆角。 */
.u-toolbar > .t-views.solid { border-radius: var(--r-full); }
.u-toolbar > .t-views.solid > button { border-radius: var(--r-full); }
.u-toolbar > .t-views.solid.has-ind::before {
  background: var(--accent); border-radius: var(--r-full); box-shadow: var(--hi-inset);
}
.u-toolbar > .t-views.solid > button.on { color: var(--on-accent); font-weight: var(--fw-med); }
/* 滑块还没量出来的那一帧（JS 没跑、或刚整块重画还没到下一帧）：当前那颗自己
   先把底填上。不补这条，切过去的瞬间当前项是没有底的，看着像点空了。 */
.u-toolbar > .t-views.solid:not(.has-ind) > button.on {
  background: var(--accent); box-shadow: var(--hi-inset);
}
/* 实心页签上那颗计数：.75 的近黑压在品牌绿上算出来是 4.09，差 AA 一点点。
   抬到 .85 是 4.72 —— 还是比标题轻，但读得出来。 */
.u-toolbar > .t-views.solid > button.on > .n { color: inherit; opacity: .85; }

/* ══════════════════════════════════════════════════════════════════════
   14 · .u-sumbar / .u-metric — 汇总条
   一行「名字 + 已经算好的数」，回答「现在是什么局面」。它不是控件，
   所以跟操作区分两条摆，别混进按钮那一排。

   🔴 **百分比旁边必须写着分子分母。** 这不是排版偏好，是 Asher 的硬约束：
   他要知道数值，但不做心算。「毛利率 41.2%」是半个答案，
   「毛利率 41.2%（¥2,778 / ¥6,736）」才是完整的一句。
   构件 `UI.metric({num, den})` 把这件事做成**唯一出口** —— 房间自己
   toFixed 一个光秃秃的率贴上来，就没有人替它补括号了。

   改造前：projects.css / enquiries.css / production.css **三份**
   `.xx-sumbar { padding-top: 0 }` + `.t-sum { margin-left: 0; flex-wrap: wrap }`
   + 各自一套 `b.pos / b.bad / .warn b` 的上色，还有 `.enq-den` 这个只有
   询价台有、别处想用只能再抄一遍的分母件。
   ══════════════════════════════════════════════════════════════════════ */
/* 头上还紧跟着一条汇总条时，头自己把下边线和下内边距收掉 —— 两条中间夹一道线
   会把「局面」和「操作」割成两块无关的东西，它们说的是同一屏。 */
.u-toolbar.tight { border-bottom: 0; padding-bottom: var(--sp-2); }
.u-sumbar {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  padding: 0 var(--sp-4) var(--sp-3);
  border-bottom: 1px solid var(--border); background: var(--surface); flex: none;
  font-size: var(--fs-xs); color: var(--text-dim);
}
.u-metric { display: inline-flex; align-items: baseline; gap: var(--sp-1); min-width: 0; }
.u-metric > .lab { color: var(--text-dim); }
.u-metric > b {
  color: var(--text); font-weight: var(--fw-med);
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
}
/* 分子分母。它是**内容**不是脚注 —— 只比主数轻一档，不许淡到读不出来。
   负 margin 是抵掉容器那一档 gap：括号要贴着数字（「41.2%（¥2,778 / ¥6,736）」），
   中间空一格就读成两件事了。 */
.u-metric > .den {
  margin-left: calc(-1 * var(--sp-1));
  /* 🔴 用 --text-dim，不用 --text-faint。上面那句注释写的就是「不许淡到读不出来」，
     可代码原来用的是全站最淡那一档：暗色下 --text-faint 压在面板底上只有 3.88，
     不过 AA（要 4.5）；--text-dim 是 6.20。2026-08-24 人验闸在暗色模式实测出来的 ——
     而分子分母正是这条汇总条存在的理由（率旁边必须有绝对数）。
     （这里不写具体十六进制值：组件层不许出现写死色值，注释里也不行，
       test_ui_components 那条闸连注释一起扫。） */
  font-style: normal; color: var(--text-dim);
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
}
.u-metric.pos > b { color: var(--ok-fg); }
.u-metric.neg > b { color: var(--danger-fg); }
.u-metric.warn > b, .u-metric.warn > .lab { color: var(--warn-fg); }
.u-metric.dim > b { color: var(--text-dim); }
.u-metric > .u-hint { align-self: center; }

/* ══════════════════════════════════════════════════════════════════════
   15 · 列头补件 —— 折叠钮 / 命中数 / 合计的「命中 / 全列」

   `.u-colhead` 有了名字、计数、合计三个位，`UI.colSearch` 有了放大镜开合，
   但**列头收起来的那颗折叠钮、搜完之后「命中 N / 全列 M」的样子**
   还在 projects.css 里（`.pj-fold` / `.n.hit` / `.sum .of` / `.u-col.folded`）。
   收进来 —— 别的房间要同一件事只能再抄一遍 projects.css。

   命中数用 `--accent-text` 不用 `--accent`：白天模式下七个主题色当**文字**
   全部不到 4.5:1（品牌金只有 1.84:1），`--accent` 只安全在填充上。
   ══════════════════════════════════════════════════════════════════════ */
.u-colfold {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; padding: 0; border: 0; border-radius: var(--r-sm);
  background: none; color: var(--text-faint); cursor: pointer;
  transition: transform var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.u-colfold:hover { color: var(--text); }
.u-colfold:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.u-colfold .ic { width: 12px; height: 12px; }
/* 收起来只剩列头，计数和合计还在 —— 收起不等于看不见这一列有多少。 */
.u-col.folded { align-self: start; }
.u-col.folded .u-collist, .u-col.folded .u-colsearch { display: none; }
.u-col.folded .u-colhead { border-bottom: 0; }
.u-col.folded .u-colfold { transform: rotate(-90deg); }
/* 搜完之后：命中数点亮，合计变成「命中额 / 全列额」。
   两个数一起印是刻意的 —— 只换成命中数，人就分不清「这一列有多少」
   和「搜出来几个」，而那是两个不同的问题。 */
.u-colhead > .n.hit { color: var(--accent-text); font-weight: var(--fw-med); }
.u-colhead > .sum .of { color: var(--text-faint); }
/* 空列的空状态不该占半列高 */
.u-collist .u-empty { padding: var(--sp-5) var(--sp-3); }
/* ⚠️ 列头一行放不下时，**该被挤的是列名，不是数**。
   不补这三条：搜完之后计数变成「1 / 2」、合计变成「¥800 / ¥3,432」，
   列头当场折成两行，整根列的头比别的列高一截（2026-08-24 在 /ui-kit 上实拍到）。
   production.css 早就自己补过同一条（`#pr-root .u-colhead > .n, … .sum`）——
   一个房间已经独立发现过的毛病，收进共用层，别等第二个房间再撞一次。
   `.name` 的 min-width:0 是配套的：flex 项默认 min-width:auto，不置零它压根不肯缩，
   于是 overflow:hidden + ellipsis 那三条永远轮不到生效。 */
.u-colhead > .name { min-width: 0; }
.u-colhead > .n, .u-colhead > .sum { flex: none; white-space: nowrap; }

/* ══════════════════════════════════════════════════════════════════════
   16 · .u-bcard 补位 —— 从四层补到八层

   原来只有四个位：.b-top（编号+日期）/ .b-title / .b-meta / .b-money。
   项目单那张卡实际有六层，多出来的三层一直住在 projects.css / enquiries.css：
     · `.b-meta` 里那串「客户 · 学校 · 课程」的中点分隔（两份，一字不差）
     · 日期后面「已交 1/2」那句注脚（`.when i`）
     · 「缺 N 项」点开的明细（`.pj-gaps`）、需求原话（`.enq-brief`）
   加上列内搜索筛掉一张卡要用的 `.off`、拖得动的手型。

   🔴 **老四个位一个字节没动** —— 6 个房间（projects / enquiries / delivery /
   invoices / production / quotes）在用它们。下面全是新增。
   ⚠️ 中点分隔做成 opt-in 的 `.b-meta.dot` 而不是默认：production 的 `.b-meta`
   里已经有两个不带分隔的 `<span>`，quotes 那份自己打了一个「·」——
   做成默认就是给它们凭空添一排点。
   ══════════════════════════════════════════════════════════════════════ */
.u-bcard[draggable="true"] { cursor: grab; }
.u-bcard.dragging { cursor: grabbing; }
/* 被列内搜索筛掉的卡（`UI.colFilter` 挂的）。不用 hidden 属性是因为要跟
   `.dragging` 这类状态类走同一套，房间读 `.off` 就知道它现在不算数。 */
.u-bcard.off { display: none; }
/* 客户 · 渠道 / 学校：中点分隔，每项可以自带一个小图标或小图 */
.u-bcard > .b-meta.dot > span {
  display: inline-flex; align-items: center; gap: var(--sp-1); min-width: 0;
}
.u-bcard > .b-meta.dot > span + span::before {
  content: "·"; margin-right: var(--sp-1); color: var(--text-faint);
}
.u-bcard > .b-meta img {
  width: 13px; height: 13px; flex: none; object-fit: contain; border-radius: var(--r-sm);
}
/* 日期后面那句「已交 1/2」是日期的注脚，不是第二个数 */
.u-bcard > .b-top > .when i { font-style: normal; color: var(--text-faint); }
/* 型号 / 课程 / 规格：单独一行，等宽（它是要被人逐字符比对的那种字符串） */
/* 🔴 不能是 flex：`text-overflow: ellipsis` **在 flex 容器上根本不生效**，
   文字只会被硬切掉，看不出后面还有内容。2026-08-24 人验闸在订单房间实测：
   「1set Belle Rattan Hampton Bed in Queen size…」（925px 内容塞进 246px）
   被拦腰切成「…Hampton Bed in Q」，一个省略号都没有。
   规格行本来就是一行纯文本，block 就够；真要放图标另开一个 flex 子元素。 */
.u-bcard > .b-spec {
  display: block; min-width: 0;
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs); color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 客户原话 / 备注：两行截断。它比 .b-meta 还轻一档 —— 是背景，不是数据 */
.u-bcard > .b-note {
  font-size: var(--fs-xs); color: var(--text-faint); line-height: var(--lh-normal);
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
/* 「缺 N 项」点开的明细：默认收起来，**明细一项不少**（收起来不是删掉） */
.u-bcard > .b-more {
  display: flex; flex-wrap: wrap; gap: var(--sp-1);
  padding-top: var(--sp-2); border-top: 1px dashed var(--border);
}
/* ⚠️ 上面三条都写死了 display，`hidden` 属性对它们就失效了（[hidden]{display:none}
   来自浏览器默认表，特异性比作者样式低）。顶部那张名单只管 `.u-*` 开头的类，
   这三个是 `.u-bcard` 的子位，得在这里各补一条。 */
.u-bcard > .b-spec[hidden], .u-bcard > .b-note[hidden], .u-bcard > .b-more[hidden] { display: none; }
/* 第二排状态标签贴着第一排 —— .u-bcard 的 gap 是 sp-2，两排 chip 之间收一档，
   看着才是「同一组标签换了行」，不是「两块无关的东西」 */
.u-bcard > .u-chips + .u-chips { margin-top: calc(-1 * var(--sp-1)); }

/* ── 窄屏 ───────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .u-toolbar { padding: var(--sp-2) var(--sp-3); }
  .u-toolbar > .t-sum { width: 100%; margin-left: 0; }
  .u-sumbar { padding: 0 var(--sp-3) var(--sp-2); gap: var(--sp-1) var(--sp-3); }
  .u-search > input { width: 100%; max-width: none; }
  .u-board { grid-auto-columns: minmax(220px, 78vw); }
  .u-panel.drawer { width: 100vw; border-left: 0; }
  .u-form { grid-template-columns: 1fr; }
  .u-form > label { text-align: left; }
}

/* ══════════════════════════════════════════════════════════════════════
   触控档 · 手指点得到的尺寸
   2026-08-12 设计评审量出来：全站没有一处交互目标到 44px —— .u-btn 32、
   .u-search 输入框 32、.u-pill 26、视图切换 26。手机上那一排 26px 的筛选
   胶囊，一指头下去经常点到隔壁。三处房间自己写过 (pointer: coarse) 抬到
   44，但那三处一个都不在 ink 的房间里，等于没抬。
   放在共用层，一处改全站跟着。悬停类效果在触控设备上也一并关掉 ——
   手指没有 hover，留着只会变成「点一下卡在悬停态」。
   ══════════════════════════════════════════════════════════════════════ */
@media (pointer: coarse) {
  .u-btn { min-height: 44px; padding: 0 var(--sp-4); }
  .u-btn.sm { min-height: 40px; padding: 0 var(--sp-3); }
  .u-btn.icon { width: 44px; }
  .u-btn.sm.icon { width: 40px; }
  .u-pill { min-height: 40px; padding: 0 var(--sp-4); }
  .u-frow.sub .u-pill { min-height: 36px; }
  .u-toolbar > .t-views > button { min-height: 40px; padding: 0 var(--sp-4); }
  .u-search > input { height: 44px; }
  .u-field { height: 44px; }
  .u-fold { height: 40px; }
  /* 2026-08-24 补进来的两种。它们本来就在这一档之外，于是**五个房间各自补了一遍**：
     `.u-hint` 在 aftersale / punch / products 抬到 32、在 channels / supplies /
     warehouse 抬到 24；`button.u-chip` 在 channels 抬到 32、ledger / punch 抬到 36。
     六份补丁的存在本身就是这一档漏了它们的证据。基线取各家里最小的那一档
     （chip 32 / hint 32），房间那几条特异性更高、想再抬照样赢，谁都不用改。
     ⚠️ 只抬**可点的** chip —— 一排纯状态标签跟着长高，卡片就散了。 */
  button.u-chip, .u-chip[role="button"] { min-height: 32px; padding: 2px var(--sp-3); }
  .u-hint { width: 32px; height: 32px; font-size: var(--fs-sm); }
  .u-colfold { width: 32px; height: 32px; }
  .u-colfold .ic { width: 15px; height: 15px; }
  /* 手指没有悬停：抬升和提亮只在真有指针的设备上给 */
  .u-btn:hover, .u-pill:hover { transform: none; box-shadow: none; }
  .u-btn.primary:hover { box-shadow: var(--el-2), var(--hi-inset); filter: none; }
}

/* ── 地图底图滤镜 ─────────────────────────────────────────────────────
   [2026-08-28] CARTO 底图开始强制 API key（瓦片上直接打「API KEY REQUIRED」水印），
   全站换 OSM 官方瓦片（无 key、无账号依赖）。原来 light_all/dark_all 的灰底/深底
   质感用滤镜复刻：亮=去色压灰；暗=反相+转色相（水域仍偏蓝）+去色。
   滤镜只挂 tile-pane —— 标记、连线、弹层在别的 pane，不受影响。 */
/* [2026-08-28 下午] 全灰 → 低饱和：水域蓝 / 绿地绿 / 主干道橙留一点本色当方位参照
   （OSM 的配色本来就是语义色），商业区杂色仍被压住。Asher 点的。 */
html[data-mode="light"] .leaflet-tile-pane {
  filter: grayscale(.55) saturate(.7) contrast(.95) brightness(1.04);
}
/* [2026-08-28 夜] 夜间主力已改 mapkit.js 的 canvas 逐像素调色（水夜蓝/绿地深绿/其余石墨，
   Asher：「树荫至少得是绿色的，海至少得是蓝的」）——那种层给容器挂 mk-night，跳过这条。
   这条石墨滤镜只兜底还没接 MapKit.tiles() 的旧房间（delivery/orders 自建瓦片）。 */
html:not([data-mode="light"]) .leaflet-container:not(.mk-night) .leaflet-tile-pane {
  filter: grayscale(1) invert(1) sepia(.32) hue-rotate(180deg) saturate(1.35) brightness(.85) contrast(1.15);
}

/* ── 地图角上的地名搜索 + 周边配套（mapkit.placeSearch）──────────────────
   业务搜索在页面工具条上；这个框只搜世界（地名/地址），加五颗周边配套按钮。 */
.mk-search { width: 252px; padding: var(--sp-2); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r-md); }
.mk-search .mk-row1 { display: flex; gap: var(--sp-1); }
.mk-search input { flex: 1; min-width: 0; font-size: var(--fs-sm); color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: var(--sp-1) var(--sp-2); }
.mk-search input:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.mk-x { flex: none; border: 1px solid var(--border); background: var(--surface-2);
  color: var(--text-dim); border-radius: var(--r-sm); cursor: pointer;
  padding: 0 var(--sp-2); font-size: var(--fs-sm); }
.mk-list { max-height: 190px; overflow-y: auto; }
.mk-item { padding: var(--sp-1) var(--sp-2); font-size: var(--fs-xs); color: var(--text);
  cursor: pointer; border-radius: var(--r-sm); }
.mk-item:hover { background: var(--surface-3); }
.mk-hint { padding: var(--sp-1) var(--sp-2); font-size: var(--fs-xs); color: var(--text-faint); }
.mk-pois { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-1); }
.mk-pois button { font-size: var(--fs-xs); color: var(--text-dim);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: var(--sp-1) var(--sp-2); cursor: pointer; }
.mk-pois button.on { color: var(--accent); border-color: var(--accent); }
.mk-pois button:disabled { opacity: .5; cursor: default; }

/* 手动落点区（mk-search 里的第三段）：落点按钮 + 侧面清单 */
.mk-pinbar { margin-top: var(--sp-2); padding-top: var(--sp-2);
  border-top: 1px solid var(--border); }
.mk-drop { display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-xs); color: var(--text-dim); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: var(--sp-1) var(--sp-2); cursor: pointer; }
.mk-drop.on { color: var(--accent); border-color: var(--accent); }
.mk-pins { max-height: 170px; overflow-y: auto; margin-top: var(--sp-1); }
.mk-pin { display: flex; align-items: center; gap: var(--sp-1); }
.mk-pin-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; padding: var(--sp-1) var(--sp-2);
  font-size: var(--fs-xs); color: var(--text); cursor: pointer;
  border-radius: var(--r-sm); }
.mk-pin-t:hover { background: var(--surface-3); }
.mk-pin-x { flex: none; border: 0; background: none; color: var(--text-faint);
  cursor: pointer; font-size: var(--fs-xs); padding: var(--sp-1); }
.mk-pin-x:hover { color: var(--danger-fg); }

/* 常驻地点区：分类芯片（点一下整类显隐）+ 清单。颜色点是数据色，不走主题 token */
.mk-resbar { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
.mk-resbar:empty { display: none; }
.mk-chip { display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-xs); color: var(--text-faint); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: var(--sp-1) var(--sp-2); cursor: pointer; }
.mk-chip.on { color: var(--text); border-color: var(--border-strong); }
.mk-chip:not(.on) .mk-dot { opacity: .25; }
.mk-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; }
.mk-res { max-height: 170px; overflow-y: auto; margin-top: var(--sp-1); }
.mk-res .mk-pin { padding-left: var(--sp-1); }
.mk-pin-up { flex: none; border: 1px solid var(--border); background: var(--surface-2);
  color: var(--text-dim); border-radius: var(--r-sm); cursor: pointer;
  font-size: var(--fs-xs); padding: 0 var(--sp-1); }
.mk-pin-up:hover { color: var(--accent); border-color: var(--accent); }
