Jelajahi Sumber

feat(v15): 字号收敛到 5 档(11/13 → 12/14)+ 工作台底行 = 机会卡 + 时间线 + 卡片抽屉

- 字号:全站 11px 清零(634→0),13px 清零(99→0),只留 12/14/16/20/32;v12 字号档由 7 档降到 5 档
- 工作台底行:左 2 张「安静」机会卡(playCard:是什么 / 三个数 / 快捷决策),右「最近动过」时间线;C 卡改「我的环节」
- 点卡开抽屉:kf-drawer + 关联成果的 reportCard(与报告中心同一套卡);决策走 app 的 status-change(带操作人校验)
- 入场动效兜底:1.2s 后把没进过 IntersectionObserver 的 pending 块放行(之前会把内容停在 opacity:0)
- 报告中心类型卡:阶段条独占一行,最多 N 份 右上角,来源行贴底
FmodeCode 4 hari lalu
induk
melakukan
41db7acdcf

+ 4 - 1
scripts/verify-v15.mjs

@@ -271,6 +271,8 @@ const main = async () => {
   await page.evaluate(() => localStorage.setItem('kf-board:me', JSON.stringify('赵磊')));
   await mount('/');
   const nobodyProbe = await page.evaluate(async () => {
+    /* 每次刷新都会重建 kf-app;离屏的确定性实例先在页面里摘掉(页面重新渲染时也会清掉) */
+    document.getElementById('v15-det')?.remove();
     const main = document.querySelector('kf-app').shadowRoot.querySelector('main#view');
     const tpl = document.createElement('template');
     tpl.innerHTML = '<view-workbench id="v15-probe"></view-workbench>';
@@ -366,7 +368,8 @@ const main = async () => {
     });
     /* 横向溢出:任一可见元素右边超出视口 */
     let over = [];
-    walk(vr, (el) => { const r = el.getBoundingClientRect(); if (r.width > 0 && r.right > document.documentElement.clientWidth + 1) over.push(elLabel(el) + '@' + Math.round(r.right)); });
+    /* 只算「看得见」的:display/visibility/opacity 链要正常 —— 关着的抽屉/弹层还在 DOM 里(在视口右外侧),不算溢出 */
+    walk(vr, (el) => { const r = el.getBoundingClientRect(); if (r.width > 0 && r.right > document.documentElement.clientWidth + 1 && shownByStyle(el)) over.push(elLabel(el) + '@' + Math.round(r.right)); });
     return { scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth,
       A: a ? R(a) : null, B: b ? R(b) : null,
       /* 堆叠 = B 在垂直方向完全位于 A 下方且与 A 左边缘对齐(同一列里上下排)。

+ 2 - 2
src/components/kf-badge.js

@@ -42,7 +42,7 @@ export class KfBadge extends LitElement {
     .b {
       display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px;
       text-box-trim: trim-both; text-box-edge: cap alphabetic;
-      border-radius: var(--r-sm); font: 600 11px/1 var(--kf-font); letter-spacing: -0.01em;
+      border-radius: var(--r-sm); font: 600 12px/1 var(--kf-font); letter-spacing: -0.01em;
       border: 1px solid var(--kf-line-strong); color: var(--kf-ink-2); background: var(--kf-surface-btn);
       white-space: nowrap; font-variant-numeric: tabular-nums;
     }
@@ -57,7 +57,7 @@ export class KfBadge extends LitElement {
     :host([kind="status"][tone="neutral"]) .b, .b.neutral { color: var(--kf-ink-2); }
     :host([kind="evidence"]) .b { color: var(--kf-ink-2); }
     /* 阶段 S1–S5:实色底白字,和报告卡片的阶段色一致 */
-    :host([kind="stage"]) .b { color: #fff; font-family: var(--kf-font-num); font-size: 11px; letter-spacing: 0.02em; border-color: transparent; }
+    :host([kind="stage"]) .b { color: #fff; font-family: var(--kf-font-num); font-size: 12px; letter-spacing: 0.02em; border-color: transparent; }
     :host([kind="stage"][value="S1"]) .b { background: var(--kf-s1); } :host([kind="stage"][value="S2"]) .b { background: var(--kf-s2); }
     :host([kind="stage"][value="S3"]) .b { background: var(--kf-s3); } :host([kind="stage"][value="S4"]) .b { background: var(--kf-s4); }
     :host([kind="stage"][value="S5"]) .b { background: var(--kf-s5); }

+ 3 - 3
src/components/kf-drawer.js

@@ -107,9 +107,9 @@ export class KfDrawer extends LitElement {
     :host([open]) .panel { transform: none; opacity: 1; pointer-events: auto; }
     .head { display: flex; gap: 12px; align-items: flex-start; padding: 18px 20px 14px; border-bottom: 1px solid var(--kf-line); }
     .head .k { flex: 1; min-width: 0; }
-    .kicker { font-size: 11px; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-weight: 600; }
+    .kicker { font-size: 12px; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-weight: 600; }
     h2 { margin: 4px 0 0; font-size: 20px; line-height: 1.35; color: var(--kf-ink); font-weight: 600; }
-    .sub { margin-top: 4px; color: var(--kf-ink-3); font-size: 13px; }
+    .sub { margin-top: 4px; color: var(--kf-ink-3); font-size: 14px; }
     .x { width: 32px; height: 32px; flex: none; border-radius: var(--r-md); display: grid; place-items: center;
       cursor: pointer; color: var(--kf-ink); }
     .body { flex: 1; overflow: auto; padding: 16px 20px 24px; overscroll-behavior: contain; }
@@ -119,7 +119,7 @@ export class KfDrawer extends LitElement {
       .head { padding: 18px 14px 14px; padding-top: calc(14px + env(safe-area-inset-top, 0px)); }
       .x { width: 44px; height: 44px; }
       .body { padding: 16px 16px calc(24px + env(safe-area-inset-bottom, 0px)); -webkit-overflow-scrolling: touch; }
-      .sub { font-size: 13px; }
+      .sub { font-size: 14px; }
     }
   `;
 

+ 2 - 2
src/components/kf-empty.js

@@ -40,8 +40,8 @@ export class KfEmpty extends LitElement {
     .ico svg { width: 24px; height: 24px; stroke: var(--kf-ink-3); }
     :host([kind="error"]) .ico svg { stroke: var(--kf-danger); }
     .t { color: var(--kf-ink); font-weight: 500; font-size: 14px; line-height: 1.4; letter-spacing: 0; }
-    .h { color: var(--kf-ink-3); font-size: 13px; max-width: 46ch; line-height: 1.6; }
-    .e .kf-btn { margin-top: 8px; height: 28px; font-size: 13px; }
+    .h { color: var(--kf-ink-3); font-size: 14px; max-width: 46ch; line-height: 1.6; }
+    .e .kf-btn { margin-top: 8px; height: 28px; font-size: 14px; }
     /* 加载骨架:与空态同高,数据回来不跳 */
     .e.load { justify-items: stretch; justify-content: stretch; gap: 12px; padding: 24px; }
     .bar { width: 100%; height: 12px; }

+ 5 - 5
src/components/kf-evidence-chain.js

@@ -33,17 +33,17 @@ export class KfEvidenceChain extends LitElement {
     :host { display: block; }
     .seg { padding: 12px 0; border-top: 1px solid var(--kf-line); }
     .seg:first-child { border-top: 0; padding-top: 0; }
-    .seg h4 { margin: 0 0 8px; font-size: 11px; line-height: 1.4; color: var(--kf-ink-3); font-weight: 600; letter-spacing: var(--ls-cn-small); display: flex; gap: 8px; align-items: center; }
+    .seg h4 { margin: 0 0 8px; font-size: 12px; line-height: 1.4; color: var(--kf-ink-3); font-weight: 600; letter-spacing: var(--ls-cn-small); display: flex; gap: 8px; align-items: center; }
     .seg h4 .cnt { background: rgba(20,36,70,0.08); border-radius: var(--r-sm); padding: 1px 7px; color: var(--kf-ink-2); font-variant-numeric: tabular-nums; }
     ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
     li { display: grid; gap: 2px; }
-    button.link { all: unset; cursor: pointer; color: var(--kf-ink); font-weight: 600; font-size: 13px; border-bottom: 1px dashed var(--kf-line-strong); align-self: start; }
+    button.link { all: unset; cursor: pointer; color: var(--kf-ink); font-weight: 600; font-size: 14px; border-bottom: 1px dashed var(--kf-line-strong); align-self: start; }
     button.link:hover { color: var(--kf-accent); border-bottom-color: var(--kf-accent); }
-    .sub { font-size: 11px; color: var(--kf-ink-3); }
+    .sub { font-size: 12px; color: var(--kf-ink-3); }
     .sub b { color: var(--kf-ink-2); font-weight: 600; }
-    .empty { font-size: 13px; color: var(--kf-ink-3); }
+    .empty { font-size: 14px; color: var(--kf-ink-3); }
     .warn { color: var(--kf-warn-text); }
-    .miss { color: var(--kf-warn-text); font-size: 11px; }
+    .miss { color: var(--kf-warn-text); font-size: 12px; }
   `;
 
   #emit(name, detail) {

+ 18 - 17
src/components/kf-opportunity-card.js

@@ -60,42 +60,43 @@ export class KfOpportunityCard extends LitElement {
       word-break: keep-all; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-width: 0; }
     /* 行盒修剪 + -webkit-box 行数截断会把行盒压成 ~1 行高(实测 11.6px);按两行高度收(2 × 1.35em) */
     .title { min-height: calc(2.7em + 0.25em); padding-block: 0.125em; }
-    .one { margin: 0; font-size: 13px; line-height: 1.6; letter-spacing: var(--ls-cn-body); color: var(--kf-ink-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: calc(2 * 1.6em); }
+    .one { margin: 0; font-size: 14px; line-height: 1.6; letter-spacing: var(--ls-cn-body); color: var(--kf-ink-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: calc(2 * 1.6em); }
     /* 卡面余量摊给无描述卡(v14 定稿):.one 不出文字但保留等高空块(aria-hidden,无文字节点,读屏与搜索都看不见「未填写」)。
        实测约束——余量 53px,只在单缝做 margin:auto 那缝必 >24px;平分到多缝则 .dec/.acts 整体上移、与邻卡底边错位 53px。
        保留等高空块是唯一能同时满足「不显示文字 / 卡内无大缝 / 底边逐 px 对齐」的做法。 */
     .one-empty { min-height: calc(2 * 1.6em); }
     .nums { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; padding: 10px 0 8px; border-top: 1px solid var(--kf-line); border-bottom: 1px solid var(--kf-line); }
     .n { min-width: 0; }
-    /* 三个数值(1.2 单位分层):大数字 = Inter 数字 + 行盒修剪;单位 11px 灰,留 0.12em */
+    /* 三个数值(1.2 单位分层):大数字 = Inter 数字 + 行盒修剪;单位 12px 灰,留 0.12em */
     .n .v { display: flex; align-items: baseline; font-size: 20px; font-weight: 600; color: var(--kf-ink); line-height: 1; font-variant-numeric: tabular-nums;
       text-box-trim: trim-both; text-box-edge: cap alphabetic; }
-    .n .v small { font-size: 11px; color: var(--kf-ink-3); font-weight: 500; margin-left: 0.12em; letter-spacing: 0; }
-    .n .l { font-size: 11px; color: var(--kf-ink-3); margin-top: 4px; letter-spacing: var(--ls-cn-small); }
+    .n .v small { font-size: 12px; color: var(--kf-ink-3); font-weight: 500; margin-left: 0.12em; letter-spacing: 0; }
+    .n .l { font-size: 12px; color: var(--kf-ink-3); margin-top: 4px; letter-spacing: var(--ls-cn-small); }
     .n.score .v { color: var(--kf-accent-ink); }
     .n.score .v.none { color: var(--kf-ink-3); text-shadow: none; font-weight: 600; font-size: 16px; }
     /* 决策行 = 状态 + 一句理由。理由最多两行,理由一长卡就被撑高、同排卡的分隔线跟着错位,
        所以这里定高两行(理由两行截断);「谁 · 何时」在标题上浮出全文,不靠这一行承载。 */
-    .dec { font-size: 13px; color: var(--kf-ink-2); margin-top: auto; line-height: 1.6; min-height: calc(2 * 1.6em); max-height: calc(2 * 1.6em); overflow: hidden;
+    /* v15:卡面不再被拉伸(fill-to 只用于需要等高铺满的调用方),这里不从 auto 取水平余量 */
+    .dec { font-size: 14px; color: var(--kf-ink-2); margin-top: 4px; line-height: 1.6; min-height: calc(2 * 1.6em); max-height: calc(2 * 1.6em); overflow: hidden;
       display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
     .dec .kf-muted { display: inline; }
     .dec b { color: var(--kf-ink); font-weight: 600; }
-    .src { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding-top: 12px; border-top: 1px solid var(--kf-line); font-size: 11px; line-height: 20px; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+    .src { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding-top: 12px; border-top: 1px solid var(--kf-line); font-size: 12px; line-height: 20px; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
     .src .who { color: var(--kf-ink); font-weight: 600; }
     .src .agent { color: var(--kf-accent-ink); font-weight: 500; }
     .src .miss { color: var(--kf-warn-text); }
     .acts { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
     .acts .sp { flex: 1; }
-    a.report { font-size: 11px; color: var(--kf-accent); text-decoration: none; border-bottom: 1px dashed rgba(91, 91, 214, 0.40); }
+    a.report { font-size: 12px; color: var(--kf-accent); text-decoration: none; border-bottom: 1px dashed rgba(91, 91, 214, 0.40); }
     a.report:hover { border-bottom-style: solid; }
     .st { display: inline-flex; border: 1px solid var(--kf-line-strong); border-radius: var(--r-sm); overflow: hidden; background: var(--kf-surface); }
-    .st button { border: 0; background: transparent; font: 500 11px/1 var(--kf-font); color: var(--kf-ink-2); padding: 0 10px; height: 26px; cursor: pointer; transition: background var(--kf-dur) var(--kf-ease); }
+    .st button { border: 0; background: transparent; font: 500 12px/1 var(--kf-font); color: var(--kf-ink-2); padding: 0 10px; height: 26px; cursor: pointer; transition: background var(--kf-dur) var(--kf-ease); }
     .st button:hover { background: var(--kf-surface-btn); }
     .st button[aria-pressed="true"]:hover { background: var(--kf-ink); }
     .st button[aria-pressed="true"] { background: var(--kf-ink); color: #fff; }
     .st button[aria-pressed="true"].ok { background: #0f5232; }
     .st button[aria-pressed="true"].no { background: #8a1a14; }
-    .sample { font-size: 11px; line-height: 1; color: #5c4300; background: rgba(255,236,170,0.55); border: 1px solid rgba(170,130,0,0.35); border-radius: var(--r-sm); padding: 1px 6px; }
+    .sample { font-size: 12px; line-height: 1; color: #5c4300; background: rgba(255,236,170,0.55); border: 1px solid rgba(170,130,0,0.35); border-radius: var(--r-sm); padding: 1px 6px; }
     :host([compact]) .title { font-size: 14px; }
     :host([compact]) .nums .v { font-size: 16px; }
     /* 手机(任务书 1.3 / 1.5 / 2.4):标题 17/600、一句话 15/1.55、大数字 24/700、按钮 44 触摸高 */
@@ -105,22 +106,22 @@ export class KfOpportunityCard extends LitElement {
       .one { font-size: 14px; line-height: 1.55; }
       .nums { padding: 12px 0 10px; }
       .n .v { font-size: 20px; font-weight: 600; }
-      .n .l { font-size: 11px; }
+      .n .l { font-size: 12px; }
       .n.score .v.none { font-size: 20px; }
       /* 来源 = 一行图标四要素;手机上「谁 · Agent」一行、「方式 · 时间」一行,不折成一串 */
-      .src { font-size: 13px; line-height: 20px; gap: 2px 8px; }
+      .src { font-size: 14px; line-height: 20px; gap: 2px 8px; }
       .src { display: flex; flex-direction: column; align-items: flex-start; }
       .src .l1, .src .l2 { display: flex; gap: 6px; flex-wrap: wrap; }
       .src .l2 { color: var(--kf-ink-2); }
       .src .who, .src .agent { white-space: nowrap; }
-      .dec { font-size: 13px; }
+      .dec { font-size: 14px; }
       .acts { gap: 8px; }
       .st { height: 44px; }
-      .st button { height: 44px; padding: 0 14px; font-size: 13px; }
+      .st button { height: 44px; padding: 0 14px; font-size: 14px; }
       .st button:active { background: var(--kf-surface-btn); }
-      .acts .kf-btn { min-height: 44px; padding: 0 14px; font-size: 13px; }
-      a.report { min-height: 44px; display: inline-flex; align-items: center; font-size: 13px; }
-      .sample { font-size: 11px; padding: 3px 8px; }
+      .acts .kf-btn { min-height: 44px; padding: 0 14px; font-size: 14px; }
+      a.report { min-height: 44px; display: inline-flex; align-items: center; font-size: 14px; }
+      .sample { font-size: 12px; padding: 3px 8px; }
     }
   `;
 
@@ -159,7 +160,7 @@ export class KfOpportunityCard extends LitElement {
     const dec = o.decision;
     const reportHref = o.reportArtifact ? `${this.base || ''}/report/${encodeURIComponent(o.reportArtifact.id)}` : '';
     return html`
-      <kf-glass-card lift surface="2" tone=${o.status === 'rejected' ? 'danger' : o.status === 'approved' ? 'ok' : ''} @card-activate=${this.#open}>
+      <kf-glass-card lift fill-to surface="2" tone=${o.status === 'rejected' ? 'danger' : o.status === 'approved' ? 'ok' : ''} @card-activate=${this.#open}>
         <div class="wrap">
           <div class="top">
             ${o.priority ? html`<kf-badge kind="priority" .value=${o.priority}></kf-badge>` : ''}

+ 2 - 2
src/components/kf-pipeline.js

@@ -28,9 +28,9 @@ export class KfPipeline extends LitElement {
     .p { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px; }
     .step { display: flex; align-items: baseline; gap: 6px; }
     .v { font-size: 20px; font-weight: 600; line-height: 1.1; color: var(--kf-ink); font-variant-numeric: tabular-nums; font-family: var(--kf-font-num); }
-    .l { font-size: 11px; color: var(--kf-ink-3); }
+    .l { font-size: 12px; color: var(--kf-ink-3); }
     .arr { display: inline-flex; color: var(--kf-ink-3); }
-    .rate { margin-left: auto; font-size: 11px; line-height: 1; color: var(--kf-ink-3); border: 1px solid var(--kf-line-strong); border-radius: var(--r-sm); padding: 4px 12px; background: var(--kf-surface-btn); }
+    .rate { margin-left: auto; font-size: 12px; line-height: 1; color: var(--kf-ink-3); border: 1px solid var(--kf-line-strong); border-radius: var(--r-sm); padding: 4px 12px; background: var(--kf-surface-btn); }
     .rate b { color: var(--kf-accent-ink); font-variant-numeric: tabular-nums; font-family: var(--kf-font-num); }
   `;
 

+ 7 - 7
src/components/kf-score-panel.js

@@ -43,27 +43,27 @@ export class KfScorePanel extends LitElement {
     .avgwrap { display: flex; align-items: center; gap: 12px; }
     .avg { font-size: 32px; font-weight: 600; color: var(--kf-ink); line-height: 1; font-variant-numeric: tabular-nums; text-box-trim: trim-both; text-box-edge: cap alphabetic;
       font-family: var(--kf-font-num); letter-spacing: var(--ls-cn-display); display: block; height: max-content; }   /* v11b:flex 容器不修剪 */
-    .avg small { font: 500 11px var(--kf-font); color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
-    .meta { display: grid; gap: 2px; font-size: 13px; color: var(--kf-ink-3); }
+    .avg small { font: 500 12px var(--kf-font); color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
+    .meta { display: grid; gap: 2px; font-size: 14px; color: var(--kf-ink-3); }
     .meta b { color: var(--kf-ink); font-weight: 600; font-variant-numeric: tabular-nums; }
     .none { color: var(--kf-ink-3); font-size: 14px; font-weight: 600; }
     ul { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 6px; }
     li { display: grid; grid-template-columns: 88px 48px 1fr; gap: 8px; align-items: baseline; padding: 8px 12px; border-radius: var(--r-sm); background: var(--kf-surface-btn); border: 1px solid var(--kf-line); }
     /* Agent 评分:6% 染色 + 1px 同色半透明描边(不是整块紫底) */
     li.agent { background: color-mix(in srgb, var(--kf-accent) 6%, var(--kf-surface)); border-color: color-mix(in srgb, var(--kf-accent) 18%, transparent); }
-    .who { color: var(--kf-ink); font-weight: 600; font-size: 13px; }
+    .who { color: var(--kf-ink); font-weight: 600; font-size: 14px; }
     .s { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--kf-accent-ink); font-size: 14px; }
     /* 行内只用 kf-stat 的数字槽(数字滚动 + 单位分层都复用它),不渲染标签 / 注脚 */
     .s kf-stat { display: inline-block; }
-    .c .t { color: var(--kf-ink-3); font-size: 11px; margin-left: 6px; }
+    .c .t { color: var(--kf-ink-3); font-size: 12px; margin-left: 6px; }
     form { margin-top: 16px; padding: 12px; border-radius: var(--r-sm); border: 1px dashed var(--kf-line-strong); display: grid; gap: 8px; }
     .row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
     input[type="number"], input[type="text"] { height: 32px; border-radius: var(--r-sm); border: 1px solid var(--kf-line-strong); background: var(--kf-surface); transition: var(--kf-t); }
     input:focus { outline: none; border-color: var(--kf-focus); box-shadow: 0 0 0 2px rgba(91, 91, 214, 0.22); background: #fff; }
     input[type="number"] { width: 88px; padding: 0 8px; font: 600 14px var(--kf-font-num); font-variant-numeric: tabular-nums; }
-    input[type="text"] { flex: 1; min-width: 160px; padding: 0 12px; font: 13px var(--kf-font); }
-    .err { color: var(--kf-danger); font-size: 11px; }
-    .hint { font-size: 11px; color: var(--kf-ink-3); }
+    input[type="text"] { flex: 1; min-width: 160px; padding: 0 12px; font: 14px var(--kf-font); }
+    .err { color: var(--kf-danger); font-size: 12px; }
+    .hint { font-size: 12px; color: var(--kf-ink-3); }
   `;
 
   #submit(e) {

+ 4 - 4
src/components/kf-stat.js

@@ -45,14 +45,14 @@ export class KfStat extends LitElement {
       font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
     }
     /* 单位分层(v11 1.2):小一号、灰一档、留 0.12em */
-    .u { margin-left: 1.5px; font-size: 11px; color: var(--kf-ink-3); font-weight: 500; letter-spacing: 0; }
+    .u { margin-left: 1.5px; font-size: 12px; color: var(--kf-ink-3); font-weight: 500; letter-spacing: 0; }
     /* 标签 11 / 500 / +0.02em(12–13px 全中文小标题只加 0.02em 提清晰度,v11 1.5) */
-    .l { margin-top: 4px; font-size: 11px; line-height: 1.4; color: var(--kf-ink-3); font-weight: 500; letter-spacing: var(--ls-cn-small); text-box-trim: trim-both; text-box-edge: cap alphabetic; }
+    .l { margin-top: 4px; font-size: 12px; line-height: 1.4; color: var(--kf-ink-3); font-weight: 500; letter-spacing: var(--ls-cn-small); text-box-trim: trim-both; text-box-edge: cap alphabetic; }
     /* 行内模式(inline):只给数字槽用,去掉 24px 内边距与标签 / 注脚 */
     .stat[data-inline="1"] { padding: 0; }
     .stat[data-inline="1"] .v, .stat[data-inline="1"] .num { font-size: inherit; font-weight: inherit; color: inherit; letter-spacing: 0; height: auto; }
     .stat[data-inline="1"] .num .dg, .stat[data-inline="1"] .num .dg i { height: auto; }
-    .n { margin-top: 4px; font-size: 13px; line-height: 1.5; color: var(--kf-ink-2); }
+    .n { margin-top: 4px; font-size: 14px; line-height: 1.5; color: var(--kf-ink-2); }
     /* 数字槽:每位数一个 1em 高的窗口;只有 .roll 的槽会播放 -1em 位移 */
     .num { display: inline-flex; align-items: baseline; }
     .dg { display: inline-block; height: 1em; line-height: 1; font-variant-numeric: tabular-nums; }
@@ -68,7 +68,7 @@ export class KfStat extends LitElement {
     :host([tone="b"]) .num { color: var(--kf-b); }
     :host([tone="ok"]) .num { color: var(--kf-ok); }
     :host([tone="warn"]) .num { color: var(--kf-warn); }
-    .t { font-size: 11px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); margin-left: 4px; }
+    .t { font-size: 12px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); margin-left: 4px; }
   `;
 
   /** 上一次的数值(数字滚动只滚「变了的那几位」用):实例字段,不触发重渲染 */

+ 4 - 4
src/components/kf-table.js

@@ -55,9 +55,9 @@ export class KfTable extends LitElement {
       background: var(--kf-surface);
       border: 1px solid var(--kf-line-soft);
       box-shadow: none; }
-    table { width: 100%; border-collapse: collapse; font-size: 13px; line-height: 1.5; }
-    caption { text-align: left; padding: 10px 12px 4px; color: var(--kf-ink-3); font-size: 11px; }
-    th { color: var(--kf-ink-3); font-weight: 500; font-size: 11px; letter-spacing: var(--ls-cn-small);
+    table { width: 100%; border-collapse: collapse; font-size: 14px; line-height: 1.5; }
+    caption { text-align: left; padding: 10px 12px 4px; color: var(--kf-ink-3); font-size: 12px; }
+    th { color: var(--kf-ink-3); font-weight: 500; font-size: 12px; letter-spacing: var(--ls-cn-small);
       text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--kf-line); white-space: nowrap; }
     td { padding: 8px 12px; border-bottom: 1px solid var(--kf-line); color: var(--kf-ink-2); vertical-align: middle; font-variant-numeric: tabular-nums; }
     :host([dense]) td { padding: 8px 10px; }
@@ -68,7 +68,7 @@ export class KfTable extends LitElement {
     tr.hl td { background: rgba(255, 214, 102, 0.26); }
     .r { text-align: right; font-variant-numeric: tabular-nums; }
     .c { text-align: center; }
-    .mono { font-family: var(--kf-font-num); font-size: 11px; }
+    .mono { font-family: var(--kf-font-num); font-size: 12px; }
     .k { color: var(--kf-ink); font-weight: 600; }
   `;
 

+ 2 - 2
src/components/kf-viz.js

@@ -22,12 +22,12 @@ const VIZ_CSS = css`
   .ringwrap { position: relative; display: inline-grid; place-items: center; width: var(--ring, 44px); height: var(--ring, 44px); }
   .ring { display: block; width: 100%; height: 100%; transform: rotate(-90deg); }
   /* 数值居中:读数属于旁边文字/环内数字,环本身 aria-hidden */
-  .ringtxt { position: absolute; inset: 0; display: grid; place-items: center; font: 600 13px/1 var(--kf-font-num); color: var(--kf-ink); font-variant-numeric: tabular-nums; }
+  .ringtxt { position: absolute; inset: 0; display: grid; place-items: center; font: 600 14px/1 var(--kf-font-num); color: var(--kf-ink); font-variant-numeric: tabular-nums; }
   .ring circle { fill: none; stroke-width: 3; }
   .ring .track { stroke: rgba(16, 24, 40, 0.08); }
   .ring .fill { stroke: var(--kf-accent); stroke-linecap: round; stroke-dasharray: 100; stroke-dashoffset: calc(100 - var(--pct, 0));
     transition: stroke-dashoffset 600ms cubic-bezier(0.4, 0, 0.2, 1); }
-  .bars { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; width: 100%; font-size: 13px; line-height: 1; }
+  .bars { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; width: 100%; font-size: 14px; line-height: 1; }
   .brow { grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto auto; }
   .brow .kf-label { color: var(--kf-ink-3); }
   .brow .n { color: var(--kf-ink); }

+ 25 - 12
src/layout/kf-app.js

@@ -84,6 +84,19 @@ export class KfApp extends SignalWatcher(LitElement) {
     this.#io = new IntersectionObserver((entries) => {
       for (const e of entries) if (e.isIntersecting) { e.target.removeAttribute('data-enter'); this.#io.unobserve(e.target); }
     }, { rootMargin: '0px 0px -8% 0px' });
+    /* v15 兜底:入场只该是「一次性错峰」,不能因为观察器漏掉(块在视口外又从未进入、或挂载时机错过)
+       让内容永远停在 opacity:0。每次 new 一个视图后 1.2s 扫一遍视图的 shadow,把 pending 全部放行。 */
+    const release = () => {
+      const view = this.shadowRoot?.querySelector('main#view')?.firstElementChild;
+      const scan = (root) => {
+        for (const el of root.querySelectorAll('[data-enter="pending"]')) { el.removeAttribute('data-enter'); this.#io?.unobserve(el); }
+        for (const el of root.querySelectorAll('*')) if (el.shadowRoot) scan(el.shadowRoot);
+      };
+      if (view?.shadowRoot) scan(view.shadowRoot);
+    };
+    window.__kfReleaseEnter = release;
+    const origRender = this.updated.bind(this);
+    this.updated = () => { origRender(); clearTimeout(this.__relT); this.__relT = setTimeout(release, 1200); };
   }
 
   #io = null;
@@ -455,16 +468,16 @@ export class KfApp extends SignalWatcher(LitElement) {
     /* 抽屉内部排版:段落之间用发丝线分隔,不再是卡片套卡片 */
     .d-sec { padding: 16px 0; border-top: 1px solid var(--kf-line); }
     .d-sec:first-child { border-top: 0; padding-top: 0; }
-    .d-sec h4 { margin: 0 0 8px; font-size: 11px; line-height: 1.5; color: var(--kf-ink-3); font-weight: 600; letter-spacing: var(--ls-cn-small); }
+    .d-sec h4 { margin: 0 0 8px; font-size: 12px; line-height: 1.5; color: var(--kf-ink-3); font-weight: 600; letter-spacing: var(--ls-cn-small); }
     .one { font-size: 14px; line-height: var(--lh-body); letter-spacing: var(--ls-cn-body); max-width: 48em; color: var(--kf-ink-2); }
     .chips { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
     .st-row { display: flex; gap: 8px; flex-wrap: wrap; }
-    .dec { margin-top: 10px; font-size: 13px; color: var(--kf-ink-2); }
-    .log { margin: 8px 0 0; padding-left: 18px; font-size: 13px; color: var(--kf-ink-2); display: grid; gap: 4px; }
-    .src { font-size: 13px; color: var(--kf-ink-2); }
-    .src.miss, .miss { color: var(--kf-warn-text); font-size: 13px; }
+    .dec { margin-top: 10px; font-size: 14px; color: var(--kf-ink-2); }
+    .log { margin: 8px 0 0; padding-left: 18px; font-size: 14px; color: var(--kf-ink-2); display: grid; gap: 4px; }
+    .src { font-size: 14px; color: var(--kf-ink-2); }
+    .src.miss, .miss { color: var(--kf-warn-text); font-size: 14px; }
     .links { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
-    .links li { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; font-size: 13px; line-height: 1.6; }
+    .links li { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; font-size: 14px; line-height: 1.6; }
     .lnk { all: unset; cursor: pointer; color: var(--kf-ink); font-weight: 600; border-bottom: 1px dashed var(--kf-line-strong); }
     .lnk:focus-visible { outline: 2px solid var(--kf-focus); outline-offset: 2px; }
     .lnk:focus { outline: 2px solid var(--kf-focus); outline-offset: 2px; }
@@ -472,15 +485,15 @@ export class KfApp extends SignalWatcher(LitElement) {
     .kv { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; }
     .kv > div { display: grid; gap: 2px; padding-right: 24px; border-right: 1px solid var(--kf-line); }
     .kv b { font-size: 20px; line-height: 1.1; font-weight: 600; color: var(--kf-ink); font-variant-numeric: tabular-nums; font-family: var(--kf-font-num); }
-    .kv span { font-size: 11px; line-height: 1.5; color: var(--kf-ink-3); }
+    .kv span { font-size: 12px; line-height: 1.5; color: var(--kf-ink-3); }
     .kv .kf-btn { margin-left: auto; }
-    .prov { display: flex; flex-wrap: wrap; gap: 4px; align-items: baseline; font-size: 11px; color: var(--kf-ink-3); }
+    .prov { display: flex; flex-wrap: wrap; gap: 4px; align-items: baseline; font-size: 12px; color: var(--kf-ink-3); }
     .prov .plat { color: var(--kf-ink); font-weight: 600; }
-    .prov { display: flex; flex-wrap: wrap; gap: 4px; align-items: baseline; font-size: 11px; color: var(--kf-ink-3); }
+    .prov { display: flex; flex-wrap: wrap; gap: 4px; align-items: baseline; font-size: 12px; color: var(--kf-ink-3); }
     .prov .plat { color: var(--kf-ink); font-weight: 600; }
     .quotes { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
     .quotes li { padding: 12px 16px; border-radius: var(--r-md); background: rgba(255,255,255,0.45); border: 1px solid var(--kf-line); }
-    .quotes p { margin: 0 0 4px; font-size: 13px; line-height: var(--lh-body); max-width: 48em; color: var(--kf-ink); }
+    .quotes p { margin: 0 0 4px; font-size: 14px; line-height: var(--lh-body); max-width: 48em; color: var(--kf-ink); }
     /* ===== 断点(任务书第 1 节,全站统一) ===== */
     /* 平板:导航收窄,仍然在左侧 */
     @media (min-width: 768px) and (max-width: 1023px) {
@@ -495,8 +508,8 @@ export class KfApp extends SignalWatcher(LitElement) {
       /* 手机上所有文字落在玻璃上都要看得清:原 14px 正文升到 15,行高 1.55 */
       .view { font-size: 14px; line-height: 1.55; }
       .one { font-size: 14px; line-height: 1.55; }
-      .prov, .links li, .src, .dec { font-size: 13px; }
-      .d-sec h4 { font-size: 11px; }
+      .prov, .links li, .src, .dec { font-size: 14px; }
+      .d-sec h4 { font-size: 12px; }
       .quotes li, .d-sec, .links li { min-height: 0; }
       .st-row .kf-btn { min-height: 44px; padding: 0 18px; }
       .lnk { display: inline-flex; align-items: center; min-height: 44px; }

+ 6 - 6
src/layout/kf-nav.js

@@ -53,7 +53,7 @@ export class KfNav extends LitElement {
     .mark { width: 40px; height: 40px; border-radius: var(--r-md); display: grid; place-items: center; color: #fff; font-weight: 600; font-size: 20px;
       background: var(--kf-accent); }
     .name { font-size: 14px; font-weight: 600; color: var(--kf-ink); letter-spacing: -0.01em; }
-    .sub { font-size: 11px; color: var(--kf-ink-3); }
+    .sub { font-size: 12px; color: var(--kf-ink-3); }
     nav { display: grid; gap: 4px; }
     a { display: flex; align-items: center; gap: 12px; height: 40px; padding: 0 12px; border-radius: var(--r-md); color: var(--kf-ink-2);
       text-decoration: none; font-size: 14px; font-weight: 500; position: relative; }
@@ -63,12 +63,12 @@ export class KfNav extends LitElement {
     a[aria-current="page"]::before { content: ""; position: absolute; left: 0; top: 11px; bottom: 11px; width: 3px; border-radius: 0 var(--r-sm) var(--r-sm) 0; background: var(--kf-accent); }
     svg.i { width: 18px; height: 18px; }
     .lbl { flex: 1; min-width: 0; }
-    .en { display: block; font-size: 11px; color: var(--kf-ink-3); letter-spacing: 0.03em; font-weight: 500; }
+    .en { display: block; font-size: 12px; color: var(--kf-ink-3); letter-spacing: 0.03em; font-weight: 500; }
     .short { display: none; }
     .badge { min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--r-sm); display: grid; place-items: center;
-      font-size: 11px; font-weight: 500; letter-spacing: 0.02em; background: var(--kf-fill); color: var(--kf-ink-2); font-variant-numeric: tabular-nums; }
+      font-size: 12px; font-weight: 500; letter-spacing: 0.02em; background: var(--kf-fill); color: var(--kf-ink-2); font-variant-numeric: tabular-nums; }
     .badge.danger { background-color: var(--kf-s); color: #fff; }
-    .foot { margin-top: auto; padding: 12px 10px 0; border-top: 1px solid var(--kf-line); font-size: 11px; color: var(--kf-ink-3); display: flex; align-items: center; gap: 8px; }
+    .foot { margin-top: auto; padding: 12px 10px 0; border-top: 1px solid var(--kf-line); font-size: 12px; color: var(--kf-ink-3); display: flex; align-items: center; gap: 8px; }
     /* 状态点:实色圆点 + 细描边(v11:原来三种颜色的 3px 发光环是「装饰性阴影」) */
     .live { width: 8px; height: 8px; border-radius: 50%; background: #2e7d5b; outline: 3px solid rgba(46,125,91,0.12); flex: none; }
     .live.err { background: var(--kf-s); outline-color: rgba(179,38,30,0.12); }
@@ -96,7 +96,7 @@ export class KfNav extends LitElement {
       .tabbar .ico { position: relative; width: 24px; height: 24px; display: grid; place-items: center; }
       .tabbar svg.i { width: 22px; height: 22px; stroke-width: 2.1; }
       .tabbar .lbl, .tabbar .en { display: none; }
-      .tabbar .short { display: block; font-size: 11px; line-height: 12px; font-weight: 600; letter-spacing: 0.02em; white-space: nowrap; }
+      .tabbar .short { display: block; font-size: 12px; line-height: 12px; font-weight: 600; letter-spacing: 0.02em; white-space: nowrap; }
       .tabbar a[aria-current="page"] { color: var(--kf-accent-ink); background: none; box-shadow: none; }
       .tabbar .ico .sl { display: none; stroke-width: 2.6; }
       .tabbar a[aria-current="page"] .ico .ln { display: none; }
@@ -106,7 +106,7 @@ export class KfNav extends LitElement {
       .tabbar a[aria-current="page"]::after { content: ""; position: absolute; top: -6px; left: 50%; width: 20px; height: 2px; margin-left: -10px;
         border-radius: 0 0 2px 2px; background: var(--kf-accent); }
       /* 移动 Tab 角标:不绝对定位出头(v10 实测悬在栏外 20px 处,形成一条悬空的小色块) */
-      .tabbar .badge { position: static; margin: -4px 0 0 20px; min-width: 16px; height: 16px; padding: 0 4px; font-size: 11px; border-radius: var(--r-sm); }
+      .tabbar .badge { position: static; margin: -4px 0 0 20px; min-width: 16px; height: 16px; padding: 0 4px; font-size: 12px; border-radius: var(--r-sm); }
     }
     @media (min-width: 768px) { .tabbar { display: none; } }
   `;

+ 8 - 8
src/layout/kf-topbar.js

@@ -41,8 +41,8 @@ export class KfTopbar extends LitElement {
     .bar { display: flex; align-items: center; gap: 14px; padding: 12px 28px; background: var(--kf-surface); border: 1px solid var(--kf-line-soft); border-top: 0; border-radius: 0 0 var(--r-md) var(--r-md); }
     .t { min-width: 0; flex: 1; }
     h1 { margin: 0; font-size: 20px; font-weight: 600; color: var(--kf-ink); letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-box-trim: trim-both; text-box-edge: cap alphabetic; }
-    .s { font-size: 11px; color: var(--kf-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-    .sync { font-size: 11px; color: var(--kf-ink-3); display: flex; align-items: center; gap: 6px; }
+    .s { font-size: 12px; color: var(--kf-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+    .sync { font-size: 12px; color: var(--kf-ink-3); display: flex; align-items: center; gap: 6px; }
     /* 视图可以把「页面级切换」放进顶栏(typescript 类型页的类型 tab):省掉一整行,首屏直接是内容 */
     .acts { display: none; align-items: center; min-width: 0; flex: 1 1 auto; justify-content: flex-end; }
     .acts:has(slot[name="actions"]:not(:empty)) { display: flex; }
@@ -50,11 +50,11 @@ export class KfTopbar extends LitElement {
     .sync.warn .dot { background: #b27a00; }
     .sync.err .dot { background: var(--kf-s); }
     .me { display: flex; align-items: center; gap: 8px; padding: 2px 2px 2px 12px; border-radius: var(--r-sm); border: 1px solid var(--kf-line-soft); background: var(--kf-surface-btn); }
-    .me label { font-size: 11px; color: var(--kf-ink-3); }
-    select { height: 28px; border-radius: var(--r-sm); border: 0; background: transparent; font: 600 13px var(--kf-font); color: var(--kf-ink); padding: 0 8px 0 2px; cursor: pointer; }
-    .who { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-size: 11px; font-weight: 600; color: #fff;
+    .me label { font-size: 12px; color: var(--kf-ink-3); }
+    select { height: 28px; border-radius: var(--r-sm); border: 0; background: transparent; font: 600 14px var(--kf-font); color: var(--kf-ink); padding: 0 8px 0 2px; cursor: pointer; }
+    .who { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-size: 12px; font-weight: 600; color: #fff;
       background: linear-gradient(135deg, #2457d6, #5a3fd6); }
-    button.refresh { height: 32px; padding: 0 12px; border-radius: var(--r-sm); font: 500 13px var(--kf-font); color: var(--kf-ink); cursor: pointer; }
+    button.refresh { height: 32px; padding: 0 12px; border-radius: var(--r-sm); font: 500 14px var(--kf-font); color: var(--kf-ink); cursor: pointer; }
     @media (max-width: 1023px) { .bar { padding: 10px 20px; } .sync { display: none; } }
     /* 手机:状态栏让开(safe-area-top),标题 17/600,一行装下「我是 + 刷新」 */
     @media (max-width: 767px) {
@@ -62,10 +62,10 @@ export class KfTopbar extends LitElement {
       .bar { padding: calc(8px + env(safe-area-inset-top, 0px)) 16px 8px; gap: 8px; border-radius: 0 0 20px 20px; }
       h1 { font-size: 16px; font-weight: 600; }
       .s { display: none; }
-      :host([data-route="ledger"]) .s { display: block; font-size: 11px; }
+      :host([data-route="ledger"]) .s { display: block; font-size: 12px; }
       .me { padding: 1px 1px 1px 10px; min-height: 44px; }
       .me label { display: none; }
-      .who { width: 32px; height: 32px; font-size: 13px; margin-left: -8px; }
+      .who { width: 32px; height: 32px; font-size: 14px; margin-left: -8px; }
       select { height: 44px; min-width: 44px; font-size: 14px; font-weight: 600; max-width: 128px; }
       .me:active { background: var(--kf-surface-btn); }
       button.refresh { height: 44px; min-width: 44px; padding: 0 14px; font-size: 14px; border-radius: var(--r-sm); }

+ 8 - 8
src/pages/card-toolbar.js

@@ -38,22 +38,22 @@ export const TOOLBAR_CSS = css`
     -webkit-backdrop-filter: blur(var(--kf-blur-top)) saturate(var(--kf-sat-top)); backdrop-filter: blur(var(--kf-blur-top)) saturate(var(--kf-sat-top));
     box-shadow: var(--kf-shadow-pop); }
   .dd .opt { all: unset; box-sizing: border-box; display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--r-sm);
-    font: 500 13px var(--kf-font); color: var(--kf-ink); cursor: pointer; }
+    font: 500 14px var(--kf-font); color: var(--kf-ink); cursor: pointer; }
   .dd .opt:hover { background: rgba(91, 91, 214, 0.10); }
   .dd .opt[aria-checked="true"] { background: rgba(24, 24, 27, 0.06); font-weight: 600; }
-  .dd .opt .n { margin-left: auto; font: 500 11px var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+  .dd .opt .n { margin-left: auto; font: 500 12px var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
   .dd .opt .sw { width: 8px; height: 8px; border-radius: 2px; background: var(--kf-line-strong); }
   .sw.t-blue { background: var(--kf-s1); } .sw.t-violet { background: var(--kf-s2); } .sw.t-teal { background: var(--kf-s3); }
   .sw.t-amber { background: var(--kf-s4); } .sw.t-rose { background: var(--kf-s5); }
   .dd .opt .ck { width: 14px; height: 14px; border-radius: 4px; border: 1.5px solid var(--kf-line-strong); flex: none; }
   .dd .opt[aria-checked="true"] .ck { background: var(--gray-12); border-color: var(--gray-12); }
   .seg2 { display: inline-flex; border: 1px solid var(--kf-line-strong); border-radius: var(--r-sm); overflow: hidden; background: rgba(255,255,255,0.5); }
-  .seg2 button { all: unset; cursor: pointer; padding: 0 10px; height: 26px; display: inline-flex; align-items: center; gap: 4px; font: 500 11px/1 var(--kf-font); color: var(--kf-ink-2); }
+  .seg2 button { all: unset; cursor: pointer; padding: 0 10px; height: 26px; display: inline-flex; align-items: center; gap: 4px; font: 500 12px/1 var(--kf-font); color: var(--kf-ink-2); }
   .seg2 button[aria-pressed="true"] { background: var(--kf-ink); color: #fff; }
   .sortb { display: inline-flex; align-items: center; gap: 2px; padding: 2px 2px 2px 8px; border-radius: var(--r-sm); border: 1px solid var(--kf-line-strong); background: rgba(255,255,255,0.5); color: var(--kf-ink-3); }
-  .sortb button { all: unset; cursor: pointer; padding: 0 8px; height: 22px; border-radius: var(--r-sm); font: 500 11px/1 var(--kf-font); color: var(--kf-ink-2); }
+  .sortb button { all: unset; cursor: pointer; padding: 0 8px; height: 22px; border-radius: var(--r-sm); font: 500 12px/1 var(--kf-font); color: var(--kf-ink-2); }
   .sortb button[aria-pressed="true"] { background: rgba(24, 24, 27, 0.07); color: var(--kf-ink); font-weight: 600; }
-  .tb .cnt { font: 500 11px var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+  .tb .cnt { font: 500 12px var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
   .tb .cnt b { color: var(--kf-ink); font-weight: 600; }
 
   /* ===== 手机(任务书 1.2):6 个下拉 + 4 个开关收成一个「筛选」按钮 + 底部弹层 ===== */
@@ -71,7 +71,7 @@ export const TOOLBAR_CSS = css`
       -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); transition: transform 90ms var(--kf-ease); }
     .fbtn:active { transform: scale(0.985); background: var(--kf-surface-btn); }
     .fbtn.on { border-color: var(--kf-accent); box-shadow: inset 0 0 0 1px var(--kf-accent); }
-    .fbtn .nb { min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px; display: grid; place-items: center; font: 500 11px var(--kf-font-num); color: #fff; background: var(--kf-accent); font-variant-numeric: tabular-nums; }
+    .fbtn .nb { min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px; display: grid; place-items: center; font: 500 12px var(--kf-font-num); color: #fff; background: var(--kf-accent); font-variant-numeric: tabular-nums; }
     .seg2.m { display: inline-flex; margin-left: auto; height: 44px; border-radius: var(--r-md); flex: none; }
     .seg2.m button { height: 44px; min-width: 44px; padding: 0 10px; }
     .seg2.m button svg { width: 18px; height: 18px; }
@@ -81,7 +81,7 @@ export const TOOLBAR_CSS = css`
     .mor > summary .ch { margin-left: auto; color: var(--kf-ink-3); transition: transform var(--kf-dur) var(--kf-ease); }
     .mor[open] > summary .ch { transform: rotate(180deg); }
     .mor .mv { display: flex; align-items: center; gap: 8px; padding: 0 8px 8px; flex-wrap: wrap; }
-    .mor .mv .mv-l { font-size: 11px; color: var(--kf-ink-3); width: 40px; flex: none; }
+    .mor .mv .mv-l { font-size: 12px; color: var(--kf-ink-3); width: 40px; flex: none; }
     .mchips .kf-chip { border: 0; box-shadow: none; }
     .mchips .kf-chip.on { background: var(--kf-ink); color: #fff; }
     .mchips .kf-chip .n { opacity: 0.85; }
@@ -156,7 +156,7 @@ export function cardToolbar({ q, counts = {}, whoList = [], shown, all, me, extr
           return html`<details class="dd ${n ? 'has' : ''}" data-dim=${d.key} @click=${(ev) => {
             const o = ev.target.closest('details'); if (o && o.open) for (const x of o.parentElement.querySelectorAll('details[open]')) if (x !== o) x.open = false;
           }}>
-            <summary>${d.label}${n ? html` <span class="kf-chip on" style="all:unset;font: 500 11px var(--kf-font-num)">·${n}</span>` : ''}${icon('chev', 12)}</summary>
+            <summary>${d.label}${n ? html` <span class="kf-chip on" style="all:unset;font: 500 12px var(--kf-font-num)">·${n}</span>` : ''}${icon('chev', 12)}</summary>
             <div class="panel kf-glass is-overlay" role="group">
               ${values.map((o) => {
                 const on = d.key === 'who' ? q.who === o.v : sel(d.key).includes(o.v);

+ 2 - 2
src/pages/filter-sheet.js

@@ -155,8 +155,8 @@ export class KfFilterSheet extends LitElement {
     .grow-row:active { background: rgba(18, 32, 64, 0.05); }
     .gl { font-size: 14px; font-weight: 600; flex: none; }
     .g.has .gl { color: var(--kf-accent-ink); }
-    .gn { min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--r-md); display: grid; place-items: center; font: 500 11px var(--kf-font-num); color: #fff; background: var(--kf-accent); font-variant-numeric: tabular-nums; }
-    .gv { flex: 1; min-width: 0; font-size: 13px; color: var(--kf-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
+    .gn { min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--r-md); display: grid; place-items: center; font: 500 12px var(--kf-font-num); color: #fff; background: var(--kf-accent); font-variant-numeric: tabular-nums; }
+    .gv { flex: 1; min-width: 0; font-size: 14px; color: var(--kf-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
     .gc { margin-left: auto; display: grid; place-items: center; color: var(--kf-ink-3); transition: transform var(--kf-dur) var(--kf-ease); }
     .gc.up { transform: rotate(180deg); }
     .opts { display: grid; gap: 2px; padding: 0 0 8px; animation: kf-enter 160ms var(--kf-ease) both; }

+ 77 - 16
src/pages/report-card.js

@@ -50,7 +50,7 @@ export const CARD_CSS = css`
   @media (max-width: 767px) { .rc .top .badge.type, .rc .top .full { display: none; } }
   .rc .top .fresh:has(~ .full) { order: 9; }
   .rc .badge { display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px; border-radius: var(--r-sm);
-    font: 600 11px/1 var(--kf-font); letter-spacing: -0.01em; white-space: nowrap; border: 1px solid transparent; }
+    font: 600 12px/1 var(--kf-font); letter-spacing: -0.01em; white-space: nowrap; border: 1px solid transparent; }
   /* 阶段色 S1–S5:白字压实色,逐色验证过 ≥ 4.5:1 */
   .rc .badge.stage { color: #fff; }
   .rc .badge.stage.t-blue { background: var(--kf-s1); } .rc .badge.stage.t-violet { background: var(--kf-s2); }
@@ -61,12 +61,12 @@ export const CARD_CSS = css`
   /* 优先级:阶段块右上角一枚 14px 小角标(实底 + 白字,颜色仍按 S/A/B) */
   .rc .tagstage { position: relative; display: inline-flex; }
   .rc .pri { position: absolute; top: -5px; right: -5px; width: 14px; height: 14px; border-radius: var(--r-sm); display: grid; place-items: center;
-    font: 500 11px/1 var(--kf-font); color: #fff; letter-spacing: 0; border: 1px solid rgba(255,255,255,0.9); }
+    font: 500 12px/1 var(--kf-font); color: #fff; letter-spacing: 0; border: 1px solid rgba(255,255,255,0.9); }
   .rc .pri.p-S { background: var(--kf-s); } .rc .pri.p-A { background: var(--kf-a); } .rc .pri.p-B { background: var(--kf-b); }
   .rc .badge.status { background: rgba(255,255,255,0.5); border-color: var(--kf-line-strong); color: var(--kf-ink-2); }
   .rc .badge.status.s-approved { color: var(--kf-ok-text); } .rc .badge.status.s-rejected { color: var(--kf-danger-text); }
   .rc .badge.status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
-  .rc .fresh { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; font: 600 11px/1 var(--kf-font-num); letter-spacing: 0.04em; color: var(--kf-ok-text); font-variant-numeric: tabular-nums; }
+  .rc .fresh { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; font: 600 12px/1 var(--kf-font-num); letter-spacing: 0.04em; color: var(--kf-ok-text); font-variant-numeric: tabular-nums; }
   /* 全文状态:细描边小圆,不再是第四个实底徽标(有无全文靠线色深浅区分) */
   .rc .full { margin-left: auto; display: inline-flex; width: 22px; height: 22px; align-items: center; justify-content: center;
     border-radius: 50%; color: var(--kf-ink-2); border: 1px solid var(--kf-line-strong); background: transparent; }
@@ -79,7 +79,7 @@ export const CARD_CSS = css`
      这里让标题行盒按 2 行高度收(2 × 1.35em),超过两行由 line-clamp 截掉 */
   /* v11b:标题不再写死两行高(单行标题下方会多出一条 27px 的空行,看起来像卡片被撑高) */
   .rc h3 { padding-block: 0.125em; }
-  .rc .lead { font-size: 13px; color: var(--kf-ink-2); line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
+  .rc .lead { font-size: 14px; color: var(--kf-ink-2); line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
   /* hover / 键盘聚焦揭示(v11 5.4):0fr → 1fr 是唯一正确的 auto-height 动画;禁用 max-height:999px 这类写法。
      不遮挡:展开只是把卡片撑高(栅格自动排版,不会盖住下面的卡)。
      progressive disclosure 用 <details>:键盘 / 读屏 / 触屏都能开,CSS 只管高度过渡;
@@ -88,7 +88,7 @@ export const CARD_CSS = css`
   .rc .detail { display: grid; grid-template-rows: 0fr; row-gap: 0; column-gap: 0; opacity: 0; margin: 0; transition: grid-template-rows 220ms cubic-bezier(0.4, 0, 0.2, 1), opacity 180ms cubic-bezier(0.4, 0, 0.2, 1); }
   .rc .detail > .detail-in { overflow: hidden; min-height: 0; }
   .rc .detail[open] { grid-template-rows: 1fr; opacity: 1; }
-  /* 触发点是一条 11px 的「原文」小字:没原文就只留图标,不写「暂无」占一行 */
+  /* 触发点是一条 12px 的「原文」小字:没原文就只留图标,不写「暂无」占一行 */
   .rc .detail > summary { height: 20px; box-sizing: border-box; overflow: hidden; }
   .rc .detail summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; font-size: 12px; line-height: 1.4; color: var(--kf-ink-3);
     letter-spacing: var(--ls-cn-small); width: max-content; min-height: 20px; }
@@ -101,14 +101,14 @@ export const CARD_CSS = css`
   .rc .m > div { min-width: 0; padding: 0 12px; border-left: 1px solid var(--kf-line); display: grid; justify-items: start; }
   .rc .m > div.score-cell { grid-template-columns: auto auto; align-items: center; gap: 2px 8px; justify-items: start; }
   .rc .m > div:first-child { border-left: 0; padding-left: 0; }
-  .rc .m .k { display: flex; align-items: center; gap: 4px; min-height: 16px; font-size: 11px; line-height: 1.3; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); }
+  .rc .m .k { display: flex; align-items: center; gap: 4px; min-height: 16px; font-size: 12px; line-height: 1.3; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); }
   .rc .m .k svg { transform: translateY(-0.5px); }   /* 1.6 基线补偿:图标随中文视觉重心下移 */
   .rc .m .k span { line-height: 1; }
   /* 数值:Inter 数字 + 行盒修剪;单位小一号灰一档(1.2),与数值同一基线 */
   .rc .m .v { display: block; height: max-content; margin-top: 2px;   /* v11b:flex 容器不参与 text-box 修剪 */ font-family: var(--kf-font-num); font-size: 16px; font-weight: 600; line-height: 1;
     color: var(--kf-ink); font-variant-numeric: tabular-nums; text-box-trim: trim-both; text-box-edge: cap alphabetic; }
-  .rc .m .v small { font-family: var(--kf-font); font-size: 11px; font-weight: 500; color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
-  .rc .m .v span.u { margin-left: 0.12em; font: 500 11px var(--kf-font); color: var(--kf-ink-3); }
+  .rc .m .v small { font-family: var(--kf-font); font-size: 12px; font-weight: 500; color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
+  .rc .m .v span.u { margin-left: 0.12em; font: 500 12px var(--kf-font); color: var(--kf-ink-3); }
   .rc .m .v.dim { color: var(--kf-ink-3); font-weight: 600; }
   .rc .seg { display: inline-flex; gap: 2px; margin-top: 4px; }
   .rc .seg i { width: 14px; height: 4px; border-radius: 2px; background: rgba(18,32,64,0.12); }
@@ -119,12 +119,12 @@ export const CARD_CSS = css`
   .rc .thumb { display: grid; grid-template-columns: repeat(7, 1fr); grid-template-rows: 1fr auto; gap: 4px 4px; align-items: end; height: 48px; padding: 8px 8px 6px;
     border-radius: var(--r-sm); background: linear-gradient(180deg, rgba(255,255,255,0.5), rgba(255,255,255,0.2)); border: 1px solid var(--kf-line); }
   .rc .thumb i { display: block; border-radius: 2px 2px 0 0; background: color-mix(in oklab, var(--kf-ink) 18%, transparent); min-height: 4px; }
-  .rc .thumb .cap { grid-column: 1 / -1; display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--kf-ink-3); }
+  .rc .thumb .cap { grid-column: 1 / -1; display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--kf-ink-3); }
   .rc .thumb .dots { display: flex; gap: 4px; }
   .rc .thumb .dots b { width: 4px; height: 4px; border-radius: 50%; background: var(--kf-s3); }
   .rc .thumb img { grid-column: span 7; width: 100%; height: 100%; object-fit: cover; border-radius: 6px; }
   /* 引文块:不画彩色左边栏(反例 #2),用 4% 染色底 + 1px 描边表达「这是一段原文」 */
-  .rc .q { font-size: 11px; color: var(--kf-ink-2); line-height: 1.6; display: flex; gap: 8px; padding: 8px 10px; border-radius: var(--r-sm);
+  .rc .q { font-size: 12px; color: var(--kf-ink-2); line-height: 1.6; display: flex; gap: 8px; padding: 8px 10px; border-radius: var(--r-sm);
     background: color-mix(in srgb, var(--kf-ink) 4%, transparent); border: 1px solid color-mix(in srgb, var(--kf-ink) 8%, transparent); }
   .rc .q span { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
   .rc .src { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 1.4; color: var(--kf-ink-2); margin-top: auto; padding-top: 12px; border-top: 1px solid var(--kf-line); min-width: 0; flex-wrap: wrap; font-variant-numeric: tabular-nums; }
@@ -145,14 +145,14 @@ export const CARD_CSS = css`
     .rc { gap: 12px; }
     .rc h3 { font-size: 16px; font-weight: 600; line-height: 1.4; }
     .rc .lead { font-size: 14px; line-height: 1.55; }
-    .rc .m .k { font-size: 11px; }
+    .rc .m .k { font-size: 12px; }
     .rc .m .v { font-size: 16px; }
-    /* 评分格:单位 /3 不上 11px 底(列窄,标签会和环挤成两行),标签独占一行、环占满第二行 */
+    /* 评分格:单位 /3 不降到小一档(12px)(列窄,标签会和环挤成两行),标签独占一行、环占满第二行 */
     .rc .m > div.score-cell { grid-template-columns: minmax(0, 1fr); }
     .rc .m > div.score-cell .ring { justify-self: start; }
-    .rc .q { font-size: 13px; line-height: 1.55; }
-    .rc .badge { height: 24px; padding: 0 8px; font-size: 11px; border-radius: var(--r-sm); }
-    .rc .src { display: flex; flex-direction: column; align-items: stretch; gap: 4px; font-size: 13px; line-height: 1.4; padding-top: 12px; }
+    .rc .q { font-size: 14px; line-height: 1.55; }
+    .rc .badge { height: 24px; padding: 0 8px; font-size: 12px; border-radius: var(--r-sm); }
+    .rc .src { display: flex; flex-direction: column; align-items: stretch; gap: 4px; font-size: 12px; line-height: 1.4; padding-top: 12px; }
     .rc .src .l1 { display: flex; align-items: center; gap: 6px; min-width: 0; }
     .rc .src .l2 { display: flex; align-items: center; gap: 6px; min-width: 0; color: var(--kf-ink-2); }
     .rc .src .l1 .dot { display: none; }
@@ -163,7 +163,7 @@ export const CARD_CSS = css`
   }
   /* 紧凑列表:一行一条,数字列等宽对齐 */
   .row { display: grid; grid-template-columns: 48px minmax(0, 1.5fr) 96px 88px minmax(0, 1fr) 80px; gap: 12px; align-items: center;
-    padding: 8px 12px; border-radius: var(--r-sm); color: var(--kf-ink-2); font-size: 13px; line-height: 1.5; cursor: pointer;
+    padding: 8px 12px; border-radius: var(--r-sm); color: var(--kf-ink-2); font-size: 12px; line-height: 1.5; cursor: pointer;
     transition: background var(--kf-dur) var(--kf-ease); }
   .row + .row { border-top: 1px solid var(--kf-line); }
   .row:hover { background: rgba(255,255,255,0.5); }
@@ -180,6 +180,28 @@ export const CARD_CSS = css`
   .cfoot .cfl { font-variant-numeric: tabular-nums; }
   .cfoot .cfa { display: inline-flex; gap: 8px; }
   .cfoot .kf-btn { height: 28px; padding: 0 10px; font-size: 12px; }
+
+  /* v15:工作台的机会卡(.rc.play)—— 比类型页的成果卡矮一档,只讲「是什么 + 三个数 + 状态」 */
+  .rc.play { display: flex; flex-direction: column; gap: 10px; height: 100%; min-width: 0; }
+  .rc.play h3 { font-size: 16px; }
+  .rc.play .lead { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: calc(2 * 1.6em); }
+  .rc.play .top .right { margin-left: auto; }
+  .rc.play .opp-st { font-size: 12px; line-height: 20px; padding: 0 8px; border-radius: var(--r-sm); border: 1px solid var(--kf-line-soft); color: var(--kf-ink-2); white-space: nowrap; }
+  .rc.play .opp-st.st-approved { color: var(--kf-ok-text); border-color: rgba(21,92,58,0.35); background: rgba(21,92,58,0.06); }
+  .rc.play .opp-st.st-rejected { color: var(--kf-danger-text); border-color: rgba(154,31,24,0.35); background: rgba(154,31,24,0.06); }
+  .rc.play .play-nums { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); padding: 8px 0; border-top: 1px solid var(--kf-line); border-bottom: 1px solid var(--kf-line); }
+  .rc.play .play-nums > div { min-width: 0; padding: 0 10px; border-left: 1px solid var(--kf-line); }
+  .rc.play .play-nums > div:first-child { border-left: 0; padding-left: 0; }
+  .rc.play .play-nums b { display: block; font-size: 20px; font-weight: 600; line-height: 1.1; color: var(--kf-ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
+  .rc.play .play-nums span { display: block; margin-top: 2px; font-size: 12px; color: var(--kf-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+  .rc.play .play-acts { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
+  .rc.play .seg { display: inline-flex; border: 1px solid var(--kf-line-soft); border-radius: var(--r-sm); overflow: hidden; }
+  .rc.play .seg button { border: 0; background: transparent; font: 500 12px/1 var(--kf-font); color: var(--kf-ink-2); padding: 0 10px; height: 26px; cursor: pointer; }
+  .rc.play .seg button + button { border-left: 1px solid var(--kf-line-soft); }
+  .rc.play .seg button:hover { background: var(--kf-surface-btn); }
+  .rc.play .seg button[aria-pressed="true"] { background: var(--kf-ink); color: #fff; }
+  .rc.play .sp { flex: 1; }
+  .rc.play .more { font-size: 12px; color: var(--kf-ink-3); }
 `;
 
 /** 证据档位的文字(跟在数字后面当单位,1.2):2 → 「中」 */
@@ -254,6 +276,45 @@ export function sourceRow(e, { nowrap = false } = {}) {
   </div>`;
 }
 
+/** 机会卡状态 → 中文标签(与卡池 / 台账同一个枚举) */
+const OPP_STATUS_LABEL = { candidate: '候选', approved: '已立项', rejected: '已否决' };
+
+/**
+ * 工作台用的「安静」机会卡(v15):和卡池那张卡同源(同一个 normalized opportunity),
+ * 只留「这张卡是什么 + 关键数字 + 快捷决策」三块,去掉价值分 / 引用数 / 决策日志 —— 详情在抽屉里看。
+ *   opp       机会卡对象(core normalizeOpportunity 的产物)
+ *   status    当前状态(渲染方持有;点击只回 onStatus,由页面去写)
+ *   onStatus  (status) => void
+ */
+export function playCard({ opp: o, status, onStatus }) {
+  const s = o.stats || { avg: null, n: 0 };
+  const st = status || o.status;
+  return html`
+    <div class="rc play" data-opp=${o.id}>
+      <div class="top">
+        ${o.priority ? html`<span class="badge prio">${o.priority}</span>` : ''}
+        ${o.category ? html`<span class="badge cat">${o.category}</span>` : ''}
+        ${o.stageObj ? html`<span class="badge stage s-${o.stageObj.code.toLowerCase()}">${o.stageObj.code}</span>` : ''}
+        <span class="right"><span class="opp-st st-${st}">${OPP_STATUS_LABEL[st] || st}</span></span>
+      </div>
+      <h3 title=${o.title}>${o.title}</h3>
+      ${o.oneLiner ? html`<p class="lead">${o.oneLiner}</p>` : ''}
+      <div class="play-nums">
+        <div><b>${s.avg == null ? '—' : Math.round(s.avg)}</b><span>${s.n ? `分 · ${s.n} 人` : '待评分'}</span></div>
+        <div><b>${o.evidenceLabel}</b><span>证据</span></div>
+        <div><b>${o.quoteCount}</b><span>条引用</span></div>
+      </div>
+      <div class="play-acts" data-no-activate>
+        <div class="seg" role="group" aria-label="改状态">
+          ${['candidate', 'approved', 'rejected'].map((k) => html`<button type="button" ?aria-pressed=${st === k}
+            @click=${(e) => { e.preventDefault(); e.stopPropagation(); onStatus?.(k); }}>${OPP_STATUS_LABEL[k]}</button>`)}
+        </div>
+        <span class="sp"></span>
+        <span class="more">详情 →</span>
+      </div>
+    </div>`;
+}
+
 /**
  * @param {object} p
  *   e         core entry(entryOfArtifact / entryOfOpportunity / entryOfSource)

+ 14 - 14
src/pages/view-ledger.js

@@ -72,10 +72,10 @@ export class ViewLedger extends LitElement {
     .filters { display: block; padding: 12px 16px 16px; }
     .flist { display: grid; gap: 8px; }
     .frow { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
-    .frow .lbl { width: 56px; font-size: 11px; color: var(--kf-ink-3); flex: none; }
+    .frow .lbl { width: 56px; font-size: 12px; color: var(--kf-ink-3); flex: none; }
     .chips { display: flex; gap: 6px; flex-wrap: wrap; flex: 1; }
     .kf-chip[data-zero="1"] { opacity: 0.42; }
-    .sum { display: flex; gap: 16px; align-items: baseline; font-size: 13px; color: var(--kf-ink-3); }
+    .sum { display: flex; gap: 16px; align-items: baseline; font-size: 14px; color: var(--kf-ink-3); }
     .sum { font-family: var(--kf-font-num); font-variant-numeric: tabular-nums; }
     .sum b { color: var(--kf-ink); font-size: 14px; font-family: var(--kf-font-num); font-variant-numeric: tabular-nums; }
     .ledger-head { display: flex; align-items: flex-end; gap: 16px; padding: 4px 0 16px; }
@@ -83,17 +83,17 @@ export class ViewLedger extends LitElement {
     .skel-row + .skel-row { border-top: 1px solid var(--kf-line); }
     .skel-row.head { padding: 8px; border-bottom: 1px solid var(--kf-line); }
     .ledger-head h2 { margin: 0; font-size: 20px; line-height: 1.3; font-weight: 600; color: var(--kf-ink); letter-spacing: -0.01em; }
-    .ledger-head p { margin: 4px 0 0; font-size: 13px; line-height: 1.6; color: var(--kf-ink-3); }
-    .ledger-head .sum { margin-left: auto; display: flex; gap: 16px; align-items: baseline; font-size: 13px; color: var(--kf-ink-3); }
+    .ledger-head p { margin: 4px 0 0; font-size: 14px; line-height: 1.6; color: var(--kf-ink-3); }
+    .ledger-head .sum { margin-left: auto; display: flex; gap: 16px; align-items: baseline; font-size: 14px; color: var(--kf-ink-3); }
     .bar2 { display: flex; align-items: center; gap: 12px; margin: 16px 0 12px; min-height: 24px; }
     tbody tr { cursor: pointer; }
-    .focus-note { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--kf-warn-text); margin: 0; }
+    .focus-note { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--kf-warn-text); margin: 0; }
     .bar2 .clear { height: 28px; }
-    .bar2 .hint { margin-left: auto; font-size: 11px; color: var(--kf-ink-3); }
+    .bar2 .hint { margin-left: auto; font-size: 12px; color: var(--kf-ink-3); }
     .bar2 kf-heat { align-self: center; }
     /* 阶段 × 层 热力:浮在筛选卡上方的独立小块,不和标题抢行 */
     .heatx { display: inline-grid; justify-items: start; gap: 2px; margin: 0 0 12px; padding: 10px 14px; border-radius: 10px; background: rgba(255,255,255,0.55); box-shadow: var(--elev-1); }
-    .hx-title { font-size: 11px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); margin-bottom: 6px; }
+    .hx-title { font-size: 12px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); margin-bottom: 6px; }
     .hx-row { display: flex; align-items: center; gap: 2px; }
     .hx-row kf-heat { flex: none; }
     .hx-row .hx-n { margin-left: 4px; }
@@ -101,21 +101,21 @@ export class ViewLedger extends LitElement {
     .hx-legend { display: flex; gap: 2px; font-size: 10px; color: var(--kf-ink-3); line-height: 12px; padding-left: 24px; }
     /* 四个层名竖排:每格 12px,两个字上下叠(行高 12px,和热力格同高) */
     .hx-legend span { display: block; box-sizing: content-box; width: 12px; flex: none; text-align: center; line-height: 12px; word-break: break-all; }
-    .hx-s { font: 600 11px/1 var(--kf-font-num); color: var(--kf-ink-3); }
-    .hx-n { font: 600 11px/1 var(--kf-font-num); color: var(--kf-ink); text-align: right; font-variant-numeric: tabular-nums; }
+    .hx-s { font: 600 12px/1 var(--kf-font-num); color: var(--kf-ink-3); }
+    .hx-n { font: 600 12px/1 var(--kf-font-num); color: var(--kf-ink); text-align: right; font-variant-numeric: tabular-nums; }
     .ledger-head .lt h3 { font-size: 16px; line-height: var(--lh-title); font-weight: 600; color: var(--kf-ink); letter-spacing: var(--ls-cn-display); }
     /* ---- 手机:台账不再横滚 9 列表格,改「一条一张小玻璃卡」(名称 / 类型·阶段·人·层 / 优先级·状态·时间) ---- */
     .mledger { display: none; }
     @media (max-width: 767px) {
       .ledger-head { flex-wrap: wrap; gap: 4px; padding: 0 0 12px; }
       .ledger-head h2 { font-size: 20px; font-weight: 600; }
-      .ledger-head p { font-size: 13px; color: var(--kf-ink-2); }
-      .ledger-head .sum { margin-left: 0; width: 100%; font-size: 13px; }
+      .ledger-head p { font-size: 14px; color: var(--kf-ink-2); }
+      .ledger-head .sum { margin-left: 0; width: 100%; font-size: 14px; }
       .filters { padding: 12px; border-radius: var(--r-md); }
       .frow { gap: 8px; }
-      .frow .lbl { width: 44px; font-size: 13px; }
+      .frow .lbl { width: 44px; font-size: 14px; }
       .chips { gap: 8px; }
-      .kf-chip { height: 36px; padding: 0 12px; font-size: 13px; }
+      .kf-chip { height: 36px; padding: 0 12px; font-size: 14px; }
       kf-table { display: none; }
       .mledger { display: grid; gap: 8px; }
       .mledger li { display: grid; gap: 6px; padding: 12px 14px; min-height: 64px; border-radius: var(--r-md); cursor: pointer;
@@ -124,7 +124,7 @@ export class ViewLedger extends LitElement {
         -webkit-backdrop-filter: blur(24px) saturate(180%); backdrop-filter: blur(24px) saturate(180%); box-shadow: var(--kf-shadow); }
       .mledger li:active { transform: scale(0.985); transition: transform 80ms cubic-bezier(0.4, 0, 0.2, 1); }
       .mledger .t { font-size: 14px; font-weight: 600; line-height: 1.4; color: var(--kf-ink); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
-      .mledger .m { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 13px; color: var(--kf-ink-2); font-variant-numeric: tabular-nums; }
+      .mledger .m { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 14px; color: var(--kf-ink-2); font-variant-numeric: tabular-nums; }
       .mledger .m .k { color: var(--kf-ink-3); }
       .mledger li.hl { outline: 2px solid var(--kf-accent); outline-offset: 2px; }
     }

+ 9 - 9
src/pages/view-members.js

@@ -33,24 +33,24 @@ export class ViewMembers extends LitElement {
     .gcard { padding: var(--kf-space-card); margin: 0 0 var(--kf-space-card-gap); }
     .gh { display: flex; align-items: baseline; gap: 8px; margin-bottom: var(--kf-space-in); }
     .gh h3 { margin: 0; font-size: 16px; line-height: var(--lh-title); font-weight: 600; color: var(--kf-ink); letter-spacing: var(--ls-cn-display); }
-    .num .u { margin-left: 2px; font-size: 11px; color: var(--kf-ink-3); font-weight: 400; }
-    .gh .n { margin-left: auto; font-size: 11px; color: var(--kf-ink-3); letter-spacing: var(--ls-cn-small); }
+    .num .u { margin-left: 2px; font-size: 12px; color: var(--kf-ink-3); font-weight: 400; }
+    .gh .n { margin-left: auto; font-size: 12px; color: var(--kf-ink-3); letter-spacing: var(--ls-cn-small); }
     .list { list-style: none; margin: 0; padding: 0; display: grid; }
     .list li { display: grid; grid-template-columns: 40px minmax(96px, 1fr) 180px 80px 72px 72px 112px 80px; gap: 12px; align-items: center;
-      padding: 8px 12px; border-radius: var(--r-sm); font-size: 13px; line-height: 1.5; color: var(--kf-ink-2);
+      padding: 8px 12px; border-radius: var(--r-sm); font-size: 14px; line-height: 1.5; color: var(--kf-ink-2);
       font-variant-numeric: tabular-nums; transition: background var(--kf-dur) var(--kf-ease); }
     .list li + li { border-top: 1px solid var(--kf-line); }
     .list li:not(.head):hover { background: var(--kf-surface-btn); }
-    .list li.head { box-shadow: none; color: var(--kf-ink-3); font-size: 11px; letter-spacing: var(--ls-cn-small); font-weight: 600; padding: 0 12px 8px; border-bottom: 1px solid var(--kf-line); border-radius: 0; }
-    .av { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-weight: 600; font-size: 11px; color: #fff; background: linear-gradient(135deg, var(--kf-s1), var(--kf-s2)); }
+    .list li.head { box-shadow: none; color: var(--kf-ink-3); font-size: 12px; letter-spacing: var(--ls-cn-small); font-weight: 600; padding: 0 12px 8px; border-bottom: 1px solid var(--kf-line); border-radius: 0; }
+    .av { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-weight: 600; font-size: 12px; color: #fff; background: linear-gradient(135deg, var(--kf-s1), var(--kf-s2)); }
     .nm { font-weight: 600; color: var(--kf-ink); }
     .nm a { color: inherit; text-decoration: none; }
     .nm a:hover { color: var(--kf-accent); }
-    .me-tag { display: inline-block; font-size: 11px; line-height: 16px; color: var(--kf-accent-ink); border: 1px solid rgba(91, 91, 214, 0.30); border-radius: var(--r-sm); padding: 0 4px; margin-left: 6px; }
+    .me-tag { display: inline-block; font-size: 12px; line-height: 16px; color: var(--kf-accent-ink); border: 1px solid rgba(91, 91, 214, 0.30); border-radius: var(--r-sm); padding: 0 4px; margin-left: 6px; }
     .stuck { display: inline-flex; align-items: center; gap: 4px; color: var(--kf-s); font-weight: 600; }
     .ok { color: var(--kf-ok-text); }
     kf-bars { display: block; width: 100%; color: var(--kf-accent); }
-    .cmp-n { display: block; margin-top: 4px; font-size: 11px; font-weight: 500; color: var(--kf-ink-3); text-align: right; }
+    .cmp-n { display: block; margin-top: 4px; font-size: 12px; font-weight: 500; color: var(--kf-ink-3); text-align: right; }
     @media (max-width: 1000px) { .list li { grid-template-columns: 40px 1fr 1fr; } .list li > :nth-child(n+4) { display: none; } .list li.head { display: none; } }
     /* 手机:一行一个人 —— 头像 + 名字/阶段 + 状态;姓名为 44px 触摸行;整卡内边距 16 */
     @media (max-width: 767px) {
@@ -60,8 +60,8 @@ export class ViewMembers extends LitElement {
       .list li { grid-template-columns: 36px minmax(0, 1fr) auto; grid-template-areas: "a n s" "a t s"; column-gap: 10px; row-gap: 2px; padding: 8px 16px; min-height: 60px; font-size: 14px; box-sizing: border-box; }
       .list li > :nth-child(1) { grid-area: a; }
       .list li > :nth-child(2) { grid-area: n; }
-      .list li > :nth-child(3) { grid-area: t; font-size: 13px; color: var(--kf-ink-3); }
-      .list li > :nth-child(8) { grid-area: s; font-size: 13px; justify-self: end; }
+      .list li > :nth-child(3) { grid-area: t; font-size: 14px; color: var(--kf-ink-3); }
+      .list li > :nth-child(8) { grid-area: s; font-size: 14px; justify-self: end; }
       .nm a { display: inline-flex; align-items: center; min-height: 24px; }
     }
   `];

+ 3 - 3
src/pages/view-pool.js

@@ -66,7 +66,7 @@ export class ViewPool extends LitElement {
     .chips { display: flex; gap: 6px; flex-wrap: wrap; flex: 1; align-items: center; }
     /* 筛选 chip:28px / 12px 字 / r-sm;未选中透明底 + 1px 描边 + 次级字色;选中实底白字(共享表 .kf-chip[aria-pressed]) */
     .filters .kf-chip { height: 28px; padding: 0 10px; font-size: 12px; font-weight: 500; gap: 6px; }
-    .filters .kf-chip .n { font-size: 11px; color: var(--kf-ink-3); }
+    .filters .kf-chip .n { font-size: 12px; color: var(--kf-ink-3); }
     .filters .kf-chip[aria-pressed="true"] .n { color: rgba(255, 255, 255, 0.72); }
     .kf-chip[data-zero="1"] { opacity: 0.42; }
     /* 排序 = 单选段(v13 一):一整块(外层 1px 描边 + r-sm),内部 1px 分隔线,按钮各自不带圆角;
@@ -101,7 +101,7 @@ export class ViewPool extends LitElement {
     .sfield input[type="search"] { margin-left: 0; }
     .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 16px; }
     .cards kf-opportunity-card.is-focus { outline: 2px solid var(--kf-accent); outline-offset: 3px; border-radius: var(--r-md); }
-    .count { font-size: 13px; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+    .count { font-size: 14px; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
     .count b { color: var(--kf-ink); font-variant-numeric: tabular-nums; }
     .reset { border: 0; background: transparent; color: var(--kf-accent-ink); cursor: pointer; font: 500 12px var(--kf-font); text-decoration: underline; text-underline-offset: 3px; padding: 4px 2px; border-radius: var(--r-sm); }
     .reset:hover { background: rgba(91, 91, 214, 0.08); text-decoration-color: transparent; }
@@ -116,7 +116,7 @@ export class ViewPool extends LitElement {
       .pbar-more { grid-area: s; border-radius: var(--r-sm); background: var(--kf-surface-btn); }
       .pbar-more > summary { display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 12px; font: 600 14px var(--kf-font); color: var(--kf-ink-2); cursor: pointer; }
       .pbar-sorts { display: flex; gap: 0; padding: 0 8px 8px; }
-      .pbar-sorts button { appearance: none; -webkit-appearance: none; border: 1px solid var(--kf-line-soft); border-left-width: 0; margin: 0; box-sizing: border-box; flex: 1; height: 44px; display: grid; place-items: center; border-radius: 0; font: 500 13px var(--kf-font); color: var(--kf-ink-2); background: var(--kf-surface); cursor: pointer; }
+      .pbar-sorts button { appearance: none; -webkit-appearance: none; border: 1px solid var(--kf-line-soft); border-left-width: 0; margin: 0; box-sizing: border-box; flex: 1; height: 44px; display: grid; place-items: center; border-radius: 0; font: 500 14px var(--kf-font); color: var(--kf-ink-2); background: var(--kf-surface); cursor: pointer; }
       .pbar-sorts button:first-child { border-left-width: 1px; border-top-left-radius: var(--r-sm); border-bottom-left-radius: var(--r-sm); }
       .pbar-sorts button:last-child { border-top-right-radius: var(--r-sm); border-bottom-right-radius: var(--r-sm); }
       .pbar-sorts button[aria-pressed="true"] { background: rgba(0, 0, 0, 0.06); color: var(--kf-ink); border-color: rgba(0, 0, 0, 0.10); }

+ 17 - 17
src/pages/view-report-type.js

@@ -49,13 +49,13 @@ export class ViewReportType extends LitElement {
     .tabs::-webkit-scrollbar { display: none; }
     /* 类型切换 = 点缀层 chip(.55 + blur 12);当前项反白实底 */
     .tabs a { display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 12px; border-radius: var(--r-sm); text-decoration: none;
-      font-size: 13px; line-height: 1; font-weight: 500; color: var(--kf-ink-2); border: 1px solid transparent;
+      font-size: 14px; line-height: 1; font-weight: 500; color: var(--kf-ink-2); border: 1px solid transparent;
       background: var(--kf-surface);
       box-shadow: var(--kf-shadow-chip); transition: var(--kf-t); }
     .tabs a:hover { background: var(--kf-surface-btn); }
     .tabs a.back { width: 32px; padding: 0; justify-content: center; }
     .tabs a[aria-current="page"] { background: var(--gray-12); color: #fff; border-color: var(--gray-12); box-shadow: var(--shadow-1); }
-    .tabs a .n { font: 500 11px/1 var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+    .tabs a .n { font: 500 12px/1 var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
     .tabs a[aria-current="page"] .n { color: rgba(255,255,255,0.8); }
     .head { display: flex; align-items: center; gap: 12px; padding: 0; flex-wrap: wrap; margin-top: var(--kf-space-in); }
     .head .ico { width: 40px; height: 40px; flex: none; border-radius: var(--r-md); display: grid; place-items: center;
@@ -71,12 +71,12 @@ export class ViewReportType extends LitElement {
       display: block; height: max-content; text-box-trim: trim-both; text-box-edge: cap alphabetic; }   /* v11b:flex 容器不修剪,见工作台同处注释 */
     /* 分母(/5、/10)只是说明,放小一号灰色,不和大数字抢;数字用 Inter(拉丁 / 数字字体栈) */
     .kpi b { font-family: var(--kf-font-num); }
-    .kpi b small { font: 500 13px var(--kf-font); color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
-    .kpi .lbl { font: 500 11px var(--kf-font); letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); margin-top: 4px; }
-    .brow2 small { font: 500 11px var(--kf-font); color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
+    .kpi b small { font: 500 14px var(--kf-font); color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
+    .kpi .lbl { font: 500 12px var(--kf-font); letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); margin-top: 4px; }
+    .brow2 small { font: 500 12px var(--kf-font); color: var(--kf-ink-3); margin-left: 0.12em; letter-spacing: 0; }
     .kpi kf-spark { color: var(--kf-accent); }
     .kpi { grid-template-rows: auto auto auto; row-gap: 4px; position: relative; }
-    /* 数值行:数字 + 单位 + sparkline(真正在同一 baseline 上排;单位缩到 11px 当小字) */
+    /* 数值行:数字 + 单位 + sparkline(真正在同一 baseline 上排;单位缩到 12px 当小字) */
     .kpi { grid-template-areas: "b b" "l t"; grid-template-columns: auto minmax(24px, 1fr); column-gap: 8px; }
     .brow2 { grid-area: b; display: flex; align-items: center; gap: 8px; min-width: 0; }
     .brow2 b { font-size: 32px; line-height: 1; letter-spacing: var(--ls-cn-display); color: var(--gray-12); font-weight: 600; display: block; height: max-content;
@@ -92,16 +92,16 @@ export class ViewReportType extends LitElement {
     .cards > kf-glass-card { cursor: default; }
     .listwrap { overflow: hidden; }
     .listhead { display: grid; grid-template-columns: 48px minmax(0, 1.5fr) 96px 88px minmax(0, 1fr) 80px; gap: 12px; padding: 8px 12px;
-      font-size: 11px; line-height: 1.3; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-weight: 500; border-bottom: 1px solid var(--kf-line); }
+      font-size: 12px; line-height: 1.3; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-weight: 500; border-bottom: 1px solid var(--kf-line); }
     .pager { display: flex; align-items: center; justify-content: center; gap: 4px; margin: 24px 0 8px; flex-wrap: wrap; }
     .pager button { all: unset; cursor: pointer; min-width: 32px; height: 32px; padding: 0 8px; display: grid; place-items: center; border-radius: var(--r-sm);
       border: 1px solid transparent;
       background: var(--kf-surface);
-      font: 600 13px var(--kf-font-num); color: var(--kf-ink-2); font-variant-numeric: tabular-nums; transition: var(--kf-t); }
+      font: 600 14px var(--kf-font-num); color: var(--kf-ink-2); font-variant-numeric: tabular-nums; transition: var(--kf-t); }
     .pager button:hover { background: var(--kf-surface-btn); }
     .pager button[aria-current="page"] { background: var(--kf-ink); color: #fff; border-color: var(--kf-ink); }
     .pager button[disabled] { opacity: 0.35; cursor: default; }
-    .pager .sz { margin-left: 8px; font: 500 11px var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+    .pager .sz { margin-left: 8px; font: 500 12px var(--kf-font-num); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
     @media (max-width: 1100px) { .kpis { flex-wrap: wrap; } .kpi { min-width: 88px; } }
     @media (min-width: 768px) and (max-width: 1023px) { .cards { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } }
 
@@ -110,8 +110,8 @@ export class ViewReportType extends LitElement {
       /* 类型 tabs:横向可滑,两端渐隐,滚动条隐藏;高亮项滚到正中 */
       /* 手机:类型切换仍在顶栏里横向可滑(隐藏滚动条);触摸高 44 */
       .tabs { max-width: none; -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity; gap: 6px; }
-      .tabs a { flex: none; height: 32px; padding: 0 10px; font-size: 13px; min-width: 44px; scroll-snap-align: center; }
-      .tabs a .n { font-size: 11px; }
+      .tabs a { flex: none; height: 32px; padding: 0 10px; font-size: 14px; min-width: 44px; scroll-snap-align: center; }
+      .tabs a .n { font-size: 12px; }
 
       /* 标题区:图标 36 + 标题 17/600,四个数据并成一行紧凑条(不再四个 104px 宽卡片各带一个图标) */
       .head { flex-wrap: wrap; gap: 8px 12px; padding: 0 0 16px; }
@@ -124,9 +124,9 @@ export class ViewReportType extends LitElement {
       .kpi:first-child { border-left: 0; }
       .kpi .ic { display: none; }
       .kpi b { font-size: 20px; font-weight: 600; line-height: 1.1; letter-spacing: -0.02em; }
-      .kpi b small { font-size: 11px; font-weight: 500; }
-      .kpi b small { font-size: 11px; }
-      .kpi .up { font-size: 11px; }
+      .kpi b small { font-size: 12px; font-weight: 500; }
+      .kpi b small { font-size: 12px; }
+      .kpi .up { font-size: 12px; }
       .kpi .tr { display: none; }
 
       /* 卡片流:单列,卡宽 = 屏宽 − 32px;一屏约 1.5 张 */
@@ -141,13 +141,13 @@ export class ViewReportType extends LitElement {
       .row > :nth-child(1) { grid-area: s; align-self: start; }
       .row > :nth-child(2) { grid-area: t; }
       .row > :nth-child(3) { display: none; }
-      .row > :nth-child(4) { grid-area: x; text-align: right; font-size: 13px; }
-      .row > :nth-child(5) { grid-area: w; font-size: 13px; color: var(--kf-ink-3); }
+      .row > :nth-child(4) { grid-area: x; text-align: right; font-size: 14px; }
+      .row > :nth-child(5) { grid-area: w; font-size: 14px; color: var(--kf-ink-3); }
       .row > :nth-child(6) { display: none; }
       .row:active { background: rgba(18, 32, 64, 0.06); transform: scale(0.985); transition: transform 80ms cubic-bezier(0.4, 0, 0.2, 1); }
       .pager button { min-width: 44px; height: 44px; font-size: 14px; }
       .pager { gap: 6px; }
-      .pager .sz { font-size: 13px; }
+      .pager .sz { font-size: 14px; }
     }
   `];
 

+ 27 - 27
src/pages/view-report.js

@@ -71,11 +71,11 @@ export class ViewReport extends LitElement {
     .cover .top { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
     .cover h1 { margin: 4px 0 0; font-size: 20px; line-height: 1.3; font-weight: 600; color: var(--kf-ink); letter-spacing: -0.01em; }
     .cover .sub { font-size: 14px; line-height: 1.6; color: var(--kf-ink-2); }
-    .cover .meta { font-size: 11px; line-height: 1.5; color: var(--kf-ink-3); display: flex; gap: 16px; flex-wrap: wrap; font-variant-numeric: tabular-nums; }
+    .cover .meta { font-size: 12px; line-height: 1.5; color: var(--kf-ink-3); display: flex; gap: 16px; flex-wrap: wrap; font-variant-numeric: tabular-nums; }
     .cover .pipe { padding: 16px 0 0; border-top: 1px solid var(--kf-line); }
     .exec { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; counter-reset: e; }
     .exec li { display: grid; grid-template-columns: 34px 1fr; gap: 10px; font-size: 14px; color: var(--kf-ink); line-height: 1.6; }
-    .exec li::before { counter-increment: e; content: counter(e, decimal-leading-zero); font-family: var(--kf-font-num); font-size: 11px; color: var(--kf-accent-ink); padding-top: 4px; }
+    .exec li::before { counter-increment: e; content: counter(e, decimal-leading-zero); font-family: var(--kf-font-num); font-size: 12px; color: var(--kf-accent-ink); padding-top: 4px; }
     /* 章节目录 = 上层玻璃(.72 + blur 32),和正文卡片拉开层次 */
     .toc { position: sticky; top: 72px; z-index: 6; display: flex; gap: 4px; overflow-x: auto; padding: 6px; margin: 0 0 16px;
       border-radius: var(--kf-radius); background: var(--kf-surface); border: 1px solid var(--kf-line-soft); }
@@ -83,10 +83,10 @@ export class ViewReport extends LitElement {
     a.d { display: grid; grid-template-columns: 32px minmax(0,1fr); gap: 2px 8px; padding: 12px; border-radius: var(--r-sm); text-decoration: none;
       background: rgba(255,255,255,0.4); border: 1px solid var(--kf-line); color: inherit; transition: var(--kf-t); }
     a.d:hover { background: rgba(255,255,255,0.7); box-shadow: var(--kf-shadow-chip); }
-    a.d .dn { font: 600 11px/1.4 var(--kf-font-num); color: var(--kf-accent-ink); grid-row: span 2; padding-top: 2px; font-variant-numeric: tabular-nums; }
-    a.d .dt { font-size: 13px; font-weight: 600; color: var(--kf-ink); }
-    a.d .ds { font-size: 11px; color: var(--kf-ink-2); grid-column: 2; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
-    .toc a.jump, aside.rail a.jump { flex: none; font-size: 13px; line-height: 1; color: var(--kf-ink-2); text-decoration: none;
+    a.d .dn { font: 600 12px/1.4 var(--kf-font-num); color: var(--kf-accent-ink); grid-row: span 2; padding-top: 2px; font-variant-numeric: tabular-nums; }
+    a.d .dt { font-size: 14px; font-weight: 600; color: var(--kf-ink); }
+    a.d .ds { font-size: 12px; color: var(--kf-ink-2); grid-column: 2; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
+    .toc a.jump, aside.rail a.jump { flex: none; font-size: 14px; line-height: 1; color: var(--kf-ink-2); text-decoration: none;
       padding: 6px 10px; border-radius: var(--r-sm); display: inline-block; transition: background var(--kf-dur) var(--kf-ease); }
     .toc a.jump:hover, aside.rail a.jump:hover { background: rgba(255,255,255,0.6); color: var(--kf-ink); }
     .toc a.jump[aria-current="true"] { background: var(--kf-ink); color: #fff; }
@@ -99,61 +99,61 @@ export class ViewReport extends LitElement {
     .col, .main-col { display: block; min-width: 0; }
     article.ch { padding: var(--kf-space-card) var(--kf-space-card-gap); margin-bottom: var(--kf-space-card); scroll-margin-top: 144px; }
     .ch-h { display: flex; gap: 12px; align-items: baseline; }
-    .ch-no { font-family: var(--kf-font-num); font-size: 11px; line-height: 20px; color: var(--kf-accent-ink); border: 1px solid rgba(91, 91, 214, 0.30); border-radius: var(--r-sm); padding: 0 6px; font-variant-numeric: tabular-nums; }
+    .ch-no { font-family: var(--kf-font-num); font-size: 12px; line-height: 20px; color: var(--kf-accent-ink); border: 1px solid rgba(91, 91, 214, 0.30); border-radius: var(--r-sm); padding: 0 6px; font-variant-numeric: tabular-nums; }
     .ch-h h2 { margin: 0; font-size: 20px; line-height: 1.3; font-weight: 600; color: var(--kf-ink); letter-spacing: -0.01em; }
-    .en { font-size: 11px; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-weight: 600; text-transform: uppercase; }
+    .en { font-size: 12px; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-weight: 600; text-transform: uppercase; }
     /* 摘要块:6% 染色底 + 1px 同色半透明描边(v11 反例 #2:不用 3px 彩色左边栏) */
-    .lead { margin: 12px 0 16px; padding: 12px 16px; background: color-mix(in srgb, var(--kf-accent) 6%, transparent); font-size: 13px; color: var(--kf-ink-2);
+    .lead { margin: 12px 0 16px; padding: 12px 16px; background: color-mix(in srgb, var(--kf-accent) 6%, transparent); font-size: 14px; color: var(--kf-ink-2);
       line-height: var(--lh-body); border-radius: var(--r-md); box-shadow: 0 0 0 1px color-mix(in srgb, var(--kf-accent) 16%, transparent); }
     .lead b { color: var(--kf-accent-ink); font-weight: 600; }
     .sec { padding: 12px 0; border-top: 1px solid var(--kf-line); }
     .sec:first-of-type { border-top: 0; }
     .sec h4 { margin: 0 0 8px; font-size: 14px; line-height: 1.5; font-weight: 600; color: var(--kf-ink); display: flex; gap: 8px; align-items: baseline; }
-    .sec h4 .no { font-family: var(--kf-font-num); font-size: 11px; color: var(--kf-ink-3); }
+    .sec h4 .no { font-family: var(--kf-font-num); font-size: 12px; color: var(--kf-ink-3); }
     p.para { margin: 0 0 12px; font-size: 14px; line-height: 1.85; color: var(--kf-ink-2); max-width: 72ch; }
     .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(152px, 1fr)); gap: 12px; }
     .tbl-wrap { overflow-x: auto; border: 1px solid var(--kf-line); border-radius: var(--r-md); }
-    table.rt { width: 100%; border-collapse: collapse; font-size: 13px; }
-    .rt th { background: rgba(240,244,252,0.7); text-align: left; padding: 8px 12px; color: var(--kf-ink-3); font-weight: 600; font-size: 11px; letter-spacing: var(--ls-cn-small); border-bottom: 1px solid var(--kf-line); white-space: nowrap; }
+    table.rt { width: 100%; border-collapse: collapse; font-size: 14px; }
+    .rt th { background: rgba(240,244,252,0.7); text-align: left; padding: 8px 12px; color: var(--kf-ink-3); font-weight: 600; font-size: 12px; letter-spacing: var(--ls-cn-small); border-bottom: 1px solid var(--kf-line); white-space: nowrap; }
     .rt td { padding: 8px 12px; border-bottom: 1px solid var(--kf-line); vertical-align: middle; color: var(--kf-ink-2); font-variant-numeric: tabular-nums; }
     .rt tr:last-child td { border-bottom: 0; }
     .rt .k { color: var(--kf-ink); font-weight: 600; }
     .funnel { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
-    .funnel li { display: grid; grid-template-columns: 44px minmax(0, 1fr) 120px; gap: 10px; align-items: center; font-size: 13px; }
+    .funnel li { display: grid; grid-template-columns: 44px minmax(0, 1fr) 120px; gap: 10px; align-items: center; font-size: 14px; }
     .bar { height: 8px; border-radius: var(--r-sm); background: rgba(20,36,70,0.07); overflow: hidden; }
     .bar i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--kf-s1), var(--kf-s2)); }
     .finding { padding: 12px 16px; border-radius: var(--r-sm); border: 1px solid var(--kf-line); background: rgba(255,255,255,0.45); margin-bottom: 12px; }
     .finding h5 { margin: 0; font-size: 14px; line-height: 1.5; font-weight: 600; color: var(--kf-ink); }
-    .finding p { margin: 4px 0 8px; font-size: 13px; line-height: 1.6; color: var(--kf-ink-2); }
-    .ev { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: 11px; color: var(--kf-ink-3); }
+    .finding p { margin: 4px 0 8px; font-size: 14px; line-height: 1.6; color: var(--kf-ink-2); }
+    .ev { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: 12px; color: var(--kf-ink-3); }
     .ev b { color: var(--kf-ink); font-variant-numeric: tabular-nums; }
     .anchor { display: inline-flex; gap: 4px; align-items: center; height: 26px; padding: 0 10px; border-radius: var(--r-sm); border: 1px solid rgba(91, 91, 214, 0.22);
-      background: rgba(91, 91, 214, 0.10); color: var(--kf-accent-ink); font: 600 11px/1 var(--kf-font); cursor: pointer; transition: var(--kf-t); }
+      background: rgba(91, 91, 214, 0.10); color: var(--kf-accent-ink); font: 600 12px/1 var(--kf-font); cursor: pointer; transition: var(--kf-t); }
     .anchor:hover { background: rgba(91, 91, 214, 0.10); border-color: rgba(91, 91, 214, 0.45); }
     .two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
-    .two > div { padding: 12px; border-radius: var(--r-sm); border: 1px solid var(--kf-line); font-size: 13px; line-height: 1.6; }
+    .two > div { padding: 12px; border-radius: var(--r-sm); border: 1px solid var(--kf-line); font-size: 14px; line-height: 1.6; }
     .two .yes { background: rgba(31,107,69,0.06); border-color: rgba(31,107,69,0.25); }
     .two .no { background: rgba(164,36,28,0.05); border-color: rgba(164,36,28,0.22); }
     .two ul { margin: 6px 0 0; padding-left: 18px; }
     aside.rail { position: sticky; top: 128px; display: grid; gap: 12px; }
     .rail .r { padding: 16px; }
     .cover.digest .kf-kicker { color: var(--kf-ink-3); }
-    .rail .r .k { font-size: 11px; color: var(--kf-ink-3); letter-spacing: var(--ls-cn-small); font-weight: 600; }
-    .rail ol { margin: 6px 0 0; padding-left: 18px; font-size: 13px; color: var(--kf-ink-2); display: grid; gap: 4px; }
+    .rail .r .k { font-size: 12px; color: var(--kf-ink-3); letter-spacing: var(--ls-cn-small); font-weight: 600; }
+    .rail ol { margin: 6px 0 0; padding-left: 18px; font-size: 14px; color: var(--kf-ink-2); display: grid; gap: 4px; }
     .rail ol a { color: var(--kf-ink); text-decoration: none; border-bottom: 1px dashed var(--kf-line-strong); }
-    .rail .stat { display: flex; justify-content: space-between; font-size: 13px; padding: 3px 0; color: var(--kf-ink-2); }
+    .rail .stat { display: flex; justify-content: space-between; font-size: 14px; padding: 3px 0; color: var(--kf-ink-2); }
     .chain { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
     .chain div { display: grid; justify-items: center; gap: 4px; padding: 8px 4px; border-radius: var(--r-sm); background: rgba(255,255,255,0.45); border: 1px solid var(--kf-line); }
     .chain b { font: 600 14px/1.1 var(--kf-font-num); color: var(--kf-ink); font-variant-numeric: tabular-nums; }
-    .chain i { font-style: normal; font-size: 11px; line-height: 1.3; color: var(--kf-accent-ink); text-align: center; }
+    .chain i { font-style: normal; font-size: 12px; line-height: 1.3; color: var(--kf-accent-ink); text-align: center; }
     .rail .stat b { color: var(--kf-ink); font-variant-numeric: tabular-nums; }
-    .muted2 { font-size: 11px; color: var(--kf-ink-3); }
+    .muted2 { font-size: 12px; color: var(--kf-ink-3); }
     @media (max-width: 1100px) { .layout { grid-template-columns: 1fr; } aside.rail { position: static; } }
     /* 手机(任务书 1.3 / 2.4):阅读稿单栏;章节导航吸在顶栏下、左右通到屏幕边;封面标题 26→20;来源收成两行 */
     @media (max-width: 767px) {
       .toc { top: calc(80px + env(safe-area-inset-top, 0px)); margin: 0 -16px 12px; border-radius: 0 0 var(--r-md) var(--r-md); padding: 6px 12px; scrollbar-width: none; }
       .toc::-webkit-scrollbar { display: none; }
-      .toc a.jump { font-size: 13px; padding: 10px 12px; }
+      .toc a.jump { font-size: 14px; padding: 10px 12px; }
       .cover { padding: 16px; }
       .cover .top { row-gap: 6px; }
       .cover .top > span:last-child { margin-left: 0 !important; width: 100%; justify-content: flex-start; }
@@ -161,7 +161,7 @@ export class ViewReport extends LitElement {
       .cover .typeb { display: none; }
       .cover h1 { font-size: 20px; line-height: 1.35; font-weight: 600; }
       .cover .sub { font-size: 14px; line-height: 1.55; }
-      .cover .meta { font-size: 13px; gap: 12px; }
+      .cover .meta { font-size: 14px; gap: 12px; }
       .ch-no, .ch-h h2 { line-height: 1.4; }
       .art p, .ch-body p, .toc ~ * p { font-size: 14px; line-height: 1.7; }
     }
@@ -240,8 +240,8 @@ export class ViewReport extends LitElement {
           ${r.artifact.upload.agentName ? html`<kf-badge kind="method" .value=${r.artifact.upload.agentName}></kf-badge>` : ''}
           ${this.typeInfo ? html`<kf-badge class="typeb" kind="method" .value=${this.typeInfo.name} label=${'类型:' + this.typeInfo.name}></kf-badge>` : ''}
           <span style="margin-left:auto;display:flex;gap:10px;align-items:center">
-            ${this.typeInfo ? html`<a class="lnk" style="font-size: 11px" href=${href('/reports/' + this.typeInfo.key)} data-link>← ${this.typeInfo.name}</a>` : ''}
-            <a class="lnk" style="font-size: 11px" href=${href('/reports')} data-link>报告中心</a>
+            ${this.typeInfo ? html`<a class="lnk" style="font-size: 12px" href=${href('/reports/' + this.typeInfo.key)} data-link>← ${this.typeInfo.name}</a>` : ''}
+            <a class="lnk" style="font-size: 12px" href=${href('/reports')} data-link>报告中心</a>
           </span>
         </div>
         <div>
@@ -374,7 +374,7 @@ export class ViewReport extends LitElement {
       default: {
         const flat = (v) => (v && typeof v === 'object' ? JSON.stringify(v) : String(v ?? ''));
         return html`<h4>${title}</h4><div class="muted2">未知的结构类型「${s.kind}」,原始数据:</div>
-          <pre class="para" style="white-space:pre-wrap;font-size: 11px">${Object.entries(s.data && typeof s.data === 'object' ? s.data : s).map(([k, v]) => `${k}: ${flat(v)}`).join('\n')}</pre>`;
+          <pre class="para" style="white-space:pre-wrap;font-size: 12px">${Object.entries(s.data && typeof s.data === 'object' ? s.data : s).map(([k, v]) => `${k}: ${flat(v)}`).join('\n')}</pre>`;
       }
     }
   }

+ 11 - 8
src/pages/view-reports.js

@@ -79,37 +79,40 @@ export class ViewReports extends LitElement {
     .types kf-spark { --h: 16px; }
     .t .n b { font-size: 32px; font-weight: 600; line-height: 1; letter-spacing: var(--ls-cn-display); color: var(--gray-12); font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
       text-box-trim: trim-both; text-box-edge: cap alphabetic; display: inline-flex; align-items: baseline; }
-    .t .n b .u { margin-left: 0.12em; font-size: 11px; font-weight: 500; color: var(--kf-ink-3); letter-spacing: 0; }
+    .t .n b .u { margin-left: 0.12em; font-size: 12px; font-weight: 500; color: var(--kf-ink-3); letter-spacing: 0; }
     .t .n kf-spark { color: var(--kf-accent); margin-bottom: 2px; }
-    .t .tr { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
+    /* 阶段条独占一行,「最多 N 份」吊在它右上角(v15 字号上调后 340px 宽的卡里并排会折行把标签挤成竖条) */
+    .t .tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 8px; align-items: start; }
+    .t .tr > kf-bars { grid-column: 1 / -1; min-width: 0; }
+    .t .tr .doc { grid-column: 1 / -1; }
     /* 五阶段分布:条形对比(板块二 2.3)替代百分比文字 */
     .t kf-bars { margin-top: 4px; color: var(--kf-accent); }
-    .t .stgmax { font-size: 12px; font-weight: 500; color: var(--kf-ink-3); margin-left: auto; white-space: nowrap; }
+    .t .stgmax { grid-column: 2; grid-row: 1; justify-self: end; font-size: 12px; font-weight: 500; color: var(--kf-ink-3); white-space: nowrap; }
     .t .tr2 { display: flex; justify-content: flex-end; min-height: 16px; }
     .t .tr .doc { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
-    .t .last { padding-top: 8px; border-top: 1px solid var(--kf-line); font-size: 12px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); min-height: 32px; }
+    .t .last { padding-top: 8px; border-top: 1px solid var(--kf-line); font-size: 12px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); min-height: 32px; margin-top: auto; }
     .t .last .src { border-top: 0; padding-top: 0; margin-top: 0; }
     .t .last .src .av { display: none; }
     .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: var(--kf-space-in); margin-top: var(--kf-space-in); }
     .listwrap { overflow: hidden; }
     .listhead { display: grid; grid-template-columns: 48px minmax(0, 1.5fr) 96px 88px minmax(0, 1fr) 80px; gap: 12px; padding: 8px 12px;
-      font-size: 11px; line-height: 1.3; letter-spacing: var(--ls-cn-small); font-weight: 500; color: var(--kf-ink-3); border-bottom: 1px solid var(--kf-line); }
+      font-size: 12px; line-height: 1.3; letter-spacing: var(--ls-cn-small); font-weight: 500; color: var(--kf-ink-3); border-bottom: 1px solid var(--kf-line); }
     .pager { display: flex; align-items: center; justify-content: center; gap: 4px; margin: 24px 0 8px; }
     .pager button { all: unset; cursor: pointer; min-width: 32px; height: 32px; padding: 0 8px; display: grid; place-items: center; border-radius: var(--r-md);
       border: 1px solid transparent;
       background: var(--kf-surface);
-      font-size: 13px; font-weight: 500; color: var(--kf-ink-2); font-variant-numeric: tabular-nums; transition: var(--kf-t); }
+      font-size: 14px; font-weight: 500; color: var(--kf-ink-2); font-variant-numeric: tabular-nums; transition: var(--kf-t); }
     .pager button:hover { background: var(--kf-surface-btn); }
     .pager button[aria-current="page"] { background: var(--kf-accent); color: #fff; border-color: var(--kf-accent); }
     .pager button[disabled] { opacity: 0.35; cursor: default; }
-    .pager .sz { margin-left: 8px; font-size: 11px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+    .pager .sz { margin-left: 8px; font-size: 12px; font-weight: 500; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
     /* 5 张类型片:宽了 5 张一行(每张 ≥ 212px,够放「类型名 + 份数」),窄了 3 + 2 */
     .types { grid-template-columns: repeat(5, minmax(0, 1fr)); }
     @media (max-width: 1180px) { .types { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
     @media (max-width: 760px) { .types { grid-template-columns: 1fr; } }
     @media (max-width: 767px) {
       .pager button { width: 44px; min-width: 44px; height: 44px; padding: 0; font-size: 14px; }
-      .pager .sz { font-size: 13px; }
+      .pager .sz { font-size: 14px; }
     }
   `];
 

+ 4 - 4
src/pages/view-stages.js

@@ -52,14 +52,14 @@ export class ViewStages extends LitElement {
     .ov .b { grid-column: 1; } .ov kf-ring { grid-column: 2; grid-row: 1; }   /* 环与阶段徽标同一行右端 */
     .ov .n, .ov .mix { grid-column: 1 / -1; }
     /* 阶段色 S1–S5:和卡片徽标、筛选色块同一套 */
-    .ov .b { justify-self: start; font: 600 11px/1 var(--kf-font); letter-spacing: 0.02em; color: #fff; padding: 4px 6px; border-radius: var(--r-sm); text-box-trim: trim-both; text-box-edge: cap alphabetic; }
+    .ov .b { justify-self: start; font: 600 12px/1 var(--kf-font); letter-spacing: 0.02em; color: #fff; padding: 4px 6px; border-radius: var(--r-sm); text-box-trim: trim-both; text-box-edge: cap alphabetic; }
     .ov .b { justify-self: start; }
     .b.blue { background: var(--kf-s1); } .b.violet { background: var(--kf-s2); } .b.teal { background: var(--kf-s3); } .b.amber { background: var(--kf-s4); } .b.rose { background: var(--kf-s5); }
     /* 份数:大数字 + 小单位(1.2),后面跟「人 / 证据」两个小数字 */
     .ov .n { display: flex; align-items: baseline; gap: 0; }
     .ov .n b { font-size: 20px; font-weight: 600; line-height: 1; letter-spacing: var(--ls-cn-display); color: var(--kf-ink); font-variant-numeric: tabular-nums; text-box-trim: trim-both; text-box-edge: cap alphabetic; }
-    .ov .n .u { margin-left: 0.12em; font-size: 11px; font-weight: 500; color: var(--kf-ink-3); }
-    .ov .n span { display: inline-flex; align-items: center; gap: 4px; margin-left: var(--kf-space-in); font-size: 11px; font-weight: 500; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
+    .ov .n .u { margin-left: 0.12em; font-size: 12px; font-weight: 500; color: var(--kf-ink-3); }
+    .ov .n span { display: inline-flex; align-items: center; gap: 4px; margin-left: var(--kf-space-in); font-size: 12px; font-weight: 500; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
     .ov .n .ev { margin-left: var(--kf-space-in); }
     /* 占比条:4px、圆角 = 高度/2、单色(原来是一条蓝→紫的装饰渐变) */
     .ov .bar { height: 4px; border-radius: 999px; background: rgba(16, 24, 40, 0.07); overflow: hidden; }
@@ -69,7 +69,7 @@ export class ViewStages extends LitElement {
     .overview > .kf-card[aria-current="true"] { box-shadow: var(--kf-shadow-lift); outline: 2px solid var(--kf-focus); outline-offset: 1px; }
     .tool { display: flex; gap: 8px; align-items: center; margin: var(--kf-space-section) 0 var(--kf-space-in); flex-wrap: wrap; }
     .tool .grow { flex: 1; }
-    .tool .cnt { font: 500 11px var(--kf-font-num); color: var(--kf-ink); font-variant-numeric: tabular-nums; }
+    .tool .cnt { font: 500 12px var(--kf-font-num); color: var(--kf-ink); font-variant-numeric: tabular-nums; }
     /* v12 P2:同一排的卡等高(reportCard 的 fit 布局把来源行钉在底边) */
     .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: var(--kf-space-in); align-items: stretch; }
     [data-stage="S1"] { --tone: var(--kf-s1); } [data-stage="S2"] { --tone: var(--kf-s2); } [data-stage="S3"] { --tone: var(--kf-s3); }

+ 119 - 29
src/pages/view-workbench.js

@@ -16,7 +16,9 @@ import { forMe, poolOverview, liveBoard, STAGES, sortOpportunities } from '../co
 const sortByNewest = (rows) => sortOpportunities(rows, 'newest');
 import { href } from '../utils/router.js';
 import { t, VIEW_CSS, head } from './shared.js';
-import { cardFooter } from './report-card.js';
+import { cardFooter, reportCard, playCard, CARD_CSS } from './report-card.js';
+import { entryOfArtifact } from '../core/index.js';
+import '../components/kf-drawer.js';
 import { icon } from '../components/icons.js';
 
 const TONE = { S1: 'blue', S2: 'violet', S3: 'teal', S4: 'amber', S5: 'rose' };
@@ -36,12 +38,17 @@ export class ViewWorkbench extends LitElement {
   static properties = {
     db: { type: Object },
     me: { type: String },
+    /** 抽屉:{ id, title } —— 机会卡全貌(完整报告 + 评分 + 快捷决策) */
+    drawer: { type: Object },
   };
 
+  #mineLatest = null;
+
   constructor() {
     super();
     this.db = null;
     this.me = '';
+    this.drawer = null;
   }
 
   connectedCallback() {
@@ -49,7 +56,7 @@ export class ViewWorkbench extends LitElement {
     useTokens(this);
   }
 
-  static styles = [VIEW_CSS, css`
+  static styles = [VIEW_CSS, CARD_CSS, css`
     /* 纵向节奏(v15):区块之间 32 / 卡与卡 24 / 卡内元素 12。页头到第一块也是 32(Bento 第一块就是内容)。 */
     .bento { display: flex; flex-direction: column; gap: var(--kf-space-section); margin-top: var(--kf-space-section); }
     .bento > .row { display: grid; gap: var(--kf-space-card-gap); align-items: stretch; }
@@ -127,6 +134,8 @@ export class ViewWorkbench extends LitElement {
     .pending li a:hover { color: var(--kf-accent-ink); }
     .pending li .d { font-size: 12px; color: var(--kf-ink-3); flex: none; }
     .pending li .s { margin-left: auto; font-weight: 600; color: var(--kf-accent-ink); font-variant-numeric: tabular-nums; flex: none; }
+    .pending li .s.dim { color: var(--kf-ink-3); font-weight: 500; }
+    .pending li .nm { font-weight: 500; }
     .me-none { font-size: 14px; color: var(--kf-ink-3); padding-top: 10px; }
     .me-note { font-size: 12px; line-height: 1.5; color: var(--kf-ink-3); padding-top: 2px; }
     .prog { display: grid; gap: 0; margin-top: 4px; }
@@ -164,7 +173,10 @@ export class ViewWorkbench extends LitElement {
     .live-foot { margin-top: auto; padding-top: 8px; font-size: 12px; color: var(--live-ink-3); }
 
     /* ---------- C/D/E:三张短数据卡 ---------- */
-    .stat { display: grid; grid-template-rows: auto auto auto; gap: 8px; height: 100%; align-content: start; }
+    /* 三张短数据卡:近方形(dr6 的 KPI 行),卡内上标签 / 下数字说明 */
+    .stat { display: grid; grid-template-rows: auto 1fr auto; gap: 8px; height: 100%; align-content: start; min-height: 152px; }
+    .stat b { align-self: end; }
+    .stat .note { min-height: 0; }
     .stat .top { display: flex; align-items: center; gap: 8px; min-height: 24px; }
     .stat .top .go { margin-left: auto; font-size: 14px; }
     .stat b { font-size: 32px; font-weight: 600; line-height: 1; letter-spacing: var(--ls-cn-display); color: var(--gray-12);
@@ -174,22 +186,34 @@ export class ViewWorkbench extends LitElement {
     .stat .note { font-size: 12px; line-height: 1.5; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3);
       white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-height: 18px; }
 
-    /* ---------- F:最近动过(通栏) ---------- */
-    /* 分区标签:三个区块共用同一种「12px 灰小标 + 右侧箭头」,整页只有一种小标题样式 */
+    /* ---------- 底行:左侧机会卡(3 张,与池子同一张卡)+ 右侧「最近动过」时间线 ---------- */
+    .cards-row { grid-template-columns: repeat(12, minmax(0, 1fr)); align-items: start; }
+    .cards-main { grid-column: span 8; min-width: 0; }
+    .cards-main .feed-head { margin-bottom: 0; }
+    .cards-main .cards { margin-top: 12px; }
+    .cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--kf-space-card-gap); align-items: stretch; }
+    .cards > kf-glass-card { min-width: 0; }
+    .side-stack { grid-column: span 4; min-width: 0; }
+    .side-in { display: flex; flex-direction: column; height: 100%; }
+    .side-in [data-feed] { flex: 1 1 auto; }
     .feed-head { display: flex; align-items: center; gap: 8px; min-height: 22px; }
     .feed-head .go { margin-left: auto; }
-    /* 行:左标题 / 中阶段 / 右时间。网格而不是 flex —— 中间那列 auto 宽,右边永远贴右 */
-    .feed { display: grid; }
-    .feed li { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) 64px; align-items: baseline; gap: 16px; padding: 11px 0; border-top: 1px solid var(--kf-line); font-size: 14px; }
-    .feed li:first-child { border-top: 1px solid var(--kf-line); padding-top: 11px; margin-top: 8px; }
-    .feed li:last-child { padding-bottom: 4px; border-bottom: 0; }
-    .feed li:last-child a { font-weight: 500; }
-    .feed-more { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; font-size: 14px; }
-    .feed li a { color: var(--kf-ink); font-weight: 500; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+    .feed-head .lbl { font-size: 14px; font-weight: 600; color: var(--kf-ink); }
+    /* 时间线:时间在左(12 灰),标题在右;竖向虚线把时间与内容分开 */
+    .feed { display: grid; margin-top: 8px; }
+    .feed li { display: grid; grid-template-columns: 12px minmax(0, 1fr); column-gap: 12px; row-gap: 2px; padding: 10px 0; border-top: 1px solid var(--kf-line); position: relative; }
+    .feed li:first-child { border-top: 0; }
+    .feed li::before { content: ""; grid-row: 1 / 3; grid-column: 1; width: 7px; height: 7px; border-radius: 50%; border: 1.5px solid var(--kf-line-strong); background: var(--kf-surface); margin-top: 6px; justify-self: center; }
+    .feed li:first-child::before { background: var(--kf-ink); border-color: var(--kf-ink); }
+    .feed li a { grid-column: 2; color: var(--kf-ink); font-weight: 500; font-size: 14px; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
     .feed li a:hover { color: var(--kf-accent-ink); }
-    .feed .st { font-size: 12px; color: var(--kf-ink-3); white-space: nowrap; }
-    .feed .tm { font-size: 12px; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
+    .feed .st { grid-column: 2; font-size: 12px; color: var(--kf-ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+    .feed .tm { position: absolute; right: 0; top: 10px; font-size: 12px; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; }
     .feed-empty { font-size: 14px; color: var(--kf-ink-3); padding: 8px 0 0; }
+    /* 抽屉:机会卡全貌 */
+    .dr { display: grid; gap: 16px; }
+    .dr .act-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+    .dr .act-row .go { margin-left: auto; }
     .foot-links { display: none; }
 
     /* 平板:A/B 变 7/5 太挤,直接上下堆;C/D/E 保持一行 */
@@ -228,6 +252,12 @@ export class ViewWorkbench extends LitElement {
       .feed li a { grid-column: 1 / -1; white-space: normal; }
       .go, .hd .go, .me-id .go { min-height: 44px; min-width: 44px; justify-content: center; }
       .feed-more { min-height: 44px; align-items: center; }
+      /* 底行:手机上卡片一列(与卡池同一张卡,可整张点开抽屉),时间线在下面 */
+      .bento > .row.cards-row { grid-template-columns: minmax(0, 1fr); gap: var(--kf-space-card); }
+      .cards-main, .side-stack { grid-column: auto; }
+      .cards { grid-template-columns: minmax(0, 1fr); gap: var(--kf-space-card); }
+      .cards-main .feed-head { margin-bottom: 8px; }
+      .feed .tm { position: static; grid-column: 2; justify-self: end; grid-row: 1; }
       .pending li { min-height: 44px; }
       .empty { font-size: 14px; }
       .foot-links { display: flex; gap: 8px; margin-top: var(--kf-space-card); }
@@ -242,7 +272,13 @@ export class ViewWorkbench extends LitElement {
     const me = forMe(db, this.me);
     const pool = poolOverview(db);
     const live = liveBoard(db);
-    const latest = sortByNewest(pool.latest3);
+    /* 左卡片:我的待评分卡在前,再补最近上传,最多 3 张;右侧时间线取最近 6 条 */
+    const latest = sortByNewest(pool.sorted);
+    const seen = new Set();
+    const play = [...(me.pendingScores || []), ...latest].filter((o) => (seen.has(o.id) ? false : seen.add(o.id))).slice(0, 2);
+    const recent = latest.slice(0, 5);
+    /* C 卡「我的环节」:我最近一条成果 */
+    this.#mineLatest = me.mine?.[0] || null;
     return html`
       ${head('我的推进', '五阶段进度与现场状态', html`<a class="go" href=${href('/pool')} data-link>机会卡池 →</a>`)}
       <div class="bento">
@@ -252,14 +288,32 @@ export class ViewWorkbench extends LitElement {
         </div>
 
         <div class="row cde kf-enter" style="--i:1">
-          <kf-glass-card data-role="c">${this.#stat('open', pool)}</kf-glass-card>
-          <kf-glass-card data-role="d">${this.#stat('top', pool)}</kf-glass-card>
-          <kf-glass-card data-role="e">${this.#stat('decided', pool)}</kf-glass-card>
+          <kf-glass-card data-role="c" fill-to>${this.#stat('open', pool)}</kf-glass-card>
+          <kf-glass-card data-role="d" fill-to>${this.#stat('top', pool)}</kf-glass-card>
+          <kf-glass-card data-role="e" fill-to>${this.#stat('decided', pool)}</kf-glass-card>
         </div>
 
-        <kf-glass-card data-role="f" class="kf-enter" style="--i:2">${this.#feed(latest, pool)}</kf-glass-card>
+        <div class="row cards-row kf-enter" style="--i:2">
+          <div class="cards-main">
+            <div class="feed-head"><span class="lbl">${t('最近动过')}</span>
+              <a class="go" href=${href('/pool')} data-link aria-label="全部机会卡">全部 ${pool.total} →</a></div>
+            ${play.length ? html`<div class="cards">${play.map((o) => html`<kf-glass-card lift fill-to @card-activate=${() => this.#openCard(o.id)}>${playCard({ opp: o, status: o.status, onStatus: (st) => this.#setStatus({ id: o.id, status: st }) })}</kf-glass-card>`)}</div>`
+              : html`<p class="feed-empty">还没有机会卡</p>`}
+          </div>
+          <kf-glass-card class="side-stack" data-role="f" fill-to>
+            <div class="side-in">
+              <div class="feed-head"><span class="lbl">${t('最近动过')}</span>
+                <a class="go" href=${href('/pool')} data-link aria-label="全部机会卡">全部 →</a></div>
+              ${this.#feed(recent, pool)}
+            </div>
+          </kf-glass-card>
+        </div>
       </div>
-      ${cardFooter({ shown: latest.length, all: pool.total, filtered: false, label: '张机会卡,最近 3 张列在上方', onTop: () => window.scrollTo({ top: 0, behavior: 'smooth' }) })}
+      <kf-drawer .open=${Boolean(this.drawer)} .heading=${this.drawer?.title || '机会卡'} sub=${t('完整报告 · 评分 · 决策')} .width=${this.drawer ? "min(760px, 96vw)" : ""}
+        @drawer-close=${() => { this.drawer = null; }}>
+        ${this.drawer ? this.#drawerBody() : ''}
+      </kf-drawer>
+      ${cardFooter({ shown: play.length, all: pool.total, filtered: false, label: '张机会卡,最近 3 张列在上方', onTop: () => window.scrollTo({ top: 0, behavior: 'smooth' }) })}
       <!-- 只有手机显示:证据台账从底部 Tab 移到工作台页脚(真实链接,部署前缀同源) -->
       <nav class="foot-links" aria-label="更多入口">
         <a href=${href('/ledger')} data-link>${icon('ledger', 16)} 证据台账</a>
@@ -268,6 +322,36 @@ export class ViewWorkbench extends LitElement {
   }
 
   /** A 卡:选了「我是」→ 我的进度;没选 → 全员进度(v12 定稿:空态也放真实内容,不摆空框) */
+  /** 打开一张机会卡的抽屉(全貌:完整报告 + 给卡打分 + 决策) */
+  #openCard(id) {
+    const o = this.db?.opportunityById?.[id];
+    if (!o) return;
+    this.drawer = { id, title: o.title };
+  }
+
+  /** 抽屉内容:所有关联成果的报告卡(同一套 .rc 卡片,不另造样式) */
+  #drawerBody() {
+    const o = this.db?.opportunityById?.[this.drawer?.id];
+    if (!o) return '';
+    /* 关联成果 = 机会卡上登记的 linkedArtifacts(正常化的 linkedArtifactIds),不猜别的字段 */
+    const ids = new Set(o.linkedArtifactIds || []);
+    const arts = (this.db.artifacts || []).filter((a) => ids.has(a.id)).slice(0, 4);
+    return html`<div class="dr">
+      <div class="act-row">
+        <span class="lbl">${o.stageObj ? `${o.stageObj.code} · ${o.stageObj.name}` : ''} · ${o.upload?.uploadedBy || '上传人未记录'}</span>
+        <span class="st" style="margin-left:auto">${o.status === 'approved' ? '已立项' : o.status === 'rejected' ? '已否决' : '候选'}</span>
+        <a class="go" href=${href(`/pool/${o.id}`)} data-link>在卡池里打开 →</a>
+      </div>
+      ${arts.length ? arts.map((a) => reportCard({ e: entryOfArtifact(this.db, a, a.typeKey), fit: true }))
+        : html`<div class="empty"><span>这张卡还没有关联成果报告</span></div>`}
+    </div>`;
+  }
+
+  /** 快捷决策:复用 app 的 status-change(带操作人校验与写入),与卡池同一路径 */
+  #setStatus({ id, status }) {
+    this.dispatchEvent(new CustomEvent('status-change', { detail: { id, status }, bubbles: true, composed: true }));
+  }
+
   #mine(me, pool, live) {
     if (!this.me) {
       const maxSub = Math.max(1, ...live.stages.map((r) => r.submitted));
@@ -288,6 +372,9 @@ export class ViewWorkbench extends LitElement {
     const done = new Set(me.stagesDone);
     const max = Math.max(1, ...STAGES.map((s) => me.mine.filter((a) => a.stageObj?.code === s.code).length));
     const pending = me.pendingScores || [];
+    /* 队友动态:没卡住、但一段时间没动静的人(按空闲时长),3 人以内 */
+    const idle = (this.db?.members || []).filter((m) => !m.stuck && m.lastActiveAt)
+      .sort((a2, b2) => (b2.idleHours || 0) - (a2.idleHours || 0)).slice(0, 3);
     return html`<div class="me" data-empty=${me.count === 0 ? 'no-result' : 'none'}>
       <div class="me-id">
         <div class="avatar">${Array.from(me.me).slice(-1).join('')}</div>
@@ -325,6 +412,10 @@ export class ViewWorkbench extends LitElement {
             <a class="s" href=${href(`/pool/${o.id}`)} data-link aria-label="去评分">${o.stats.avg != null ? Math.round(o.stats.avg) : '去评'}</a></li>`)}</ul>
             ${pending.length > 4 ? html`<a class="feed-more go" href=${href('/pool')} data-link>还有 ${pending.length - 4} 张待评分 →</a>` : ''}`
             : html`<p class="me-none">没有等你评分的卡</p>`}
+          ${idle.length ? html`<div class="lbl" style="margin-top:14px">${t('队友动态')}</div>
+            <ul class="pending">${idle.map((m) => html`<li><span class="nm">${m.name}</span>
+              <span class="d">${m.stageObj ? `${m.stageObj.code} · ${m.stageObj.name}` : t('未开工')}</span>
+              <span class="s dim">${m.idleHours == null ? '—' : m.idleHours + 'h'}</span></li>`)}</ul>` : ''}
         </div>
       </div>
     </div>`;
@@ -384,18 +475,17 @@ export class ViewWorkbench extends LitElement {
         <b class=${avg == null ? 'dim' : ''}>${avg != null ? Math.round(avg) : '—'}<span class="u">分</span></b>
         <span class="note" title=${top?.title || ''}>${top?.title || '暂无评分'}</span></div>`;
     }
-    const dec = pool.byStatus.approved + pool.byStatus.rejected;
-    return html`<div class="stat" data-kpi="decided">
-      <div class="top"><span class="lbl">已决策</span></div>
-      <b>${dec}<span class="u">张</span></b>
-      <span class="note">立项 ${pool.byStatus.approved} · 否决 ${pool.byStatus.rejected}</span></div>`;
+    /* C:我的环节 —— 最近一条成果落在哪个阶段(没选 / 没成果时如实说) */
+    const last = this.#mineLatest;
+    return html`<div class="stat" data-kpi="stage">
+      <div class="top"><span class="lbl">我的环节</span></div>
+      <b class=${last ? '' : 'dim'}>${last ? last.stageObj.code : '—'}</b>
+      <span class="note">${last ? `${last.stageObj.name} · ${ago(last.createdAt)}` : this.me ? '本月还没有你的成果' : '还没选「我是」'}</span></div>`;
   }
 
-  /** F:最近动过(最多 3 条,右对齐时间) */
+  /** 右侧「最近动过」:时间线(6 条) */
   #feed(latest, pool) {
     return html`<div data-feed>
-      <div class="feed-head"><span class="lbl">最近动过</span>
-        <a class="go" href=${href('/pool')} data-link aria-label="全部机会卡">全部 →</a></div>
       ${latest.length ? html`<ul class="feed">${latest.map((o) => html`<li>
         <a href=${href(`/pool/${o.id}`)} data-link>${o.title}</a>
         <span class="st">${(o.upload?.uploadedBy || '') + (o.stageObj ? ' · ' + o.stageObj.name : '')}</span>

+ 8 - 8
src/styles/tokens.js

@@ -164,7 +164,8 @@ export const TOKENS_CSS = `:root, :host {
   --kf-t-colors: background-color var(--kf-dur) var(--kf-ease), color var(--kf-dur) var(--kf-ease), border-color var(--kf-dur) var(--kf-ease);
 
   /* 字号阶梯只留 6 档(任务书 2.1):11 / 13 / 14 / 16 / 20 / 32;字重只留 400 / 500 / 600 */
-  --fs-xs: 11px; --fs-sm: 13px; --fs-base: 14px; --fs-md: 16px; --fs-lg: 20px; --fs-xl: 32px;
+  /* v15:字号只留 5 档 —— 12 / 14 / 16 / 20 / 32。原来的 11 档(--fs-xs)并入 12;13 档只作过渡,逐处按上下文收。 */
+  --fs-xs: 12px; --fs-sm: 14px; --fs-base: 14px; --fs-md: 16px; --fs-lg: 20px; --fs-xl: 32px;
   --fw-body: 400; --fw-medium: 500; --fw-bold: 600;
 
   /* ===== 中文排版(v11 板块一)=====
@@ -367,7 +368,7 @@ summary:focus:not(:focus-visible), [tabindex]:focus:not(:focus-visible) { outlin
   box-sizing: border-box; height: 32px; padding: 0 14px; border-radius: var(--kf-radius-btn);
   border: 1px solid var(--kf-line-soft);
   background: var(--kf-surface-btn);
-  color: var(--kf-ink); font: 500 13px/1 var(--kf-font); cursor: pointer;
+  color: var(--kf-ink); font: 500 14px/1 var(--kf-font); cursor: pointer;
   text-box-trim: trim-both; text-box-edge: cap alphabetic;
   transition: var(--kf-t);
 }
@@ -389,7 +390,7 @@ summary:focus:not(:focus-visible), [tabindex]:focus:not(:focus-visible) { outlin
   display: inline-flex; align-items: center; gap: 4px; box-sizing: border-box; height: 28px; padding: 0 10px;
   border-radius: var(--r-sm); border: 1px solid var(--kf-line-soft);
   background: var(--kf-surface);
-  font: 500 13px/1 var(--kf-font); color: var(--kf-ink-2); cursor: pointer;
+  font: 500 14px/1 var(--kf-font); color: var(--kf-ink-2); cursor: pointer;
   text-box-trim: trim-both; text-box-edge: cap alphabetic;
   transition: var(--kf-t);
 }
@@ -423,8 +424,7 @@ h1, h2, h3 {
 .kf-title { letter-spacing: var(--ls-cn-display); line-height: 1.25; }
 /* 1.2 单位分层:小一号、灰一档、留 0.12em —— 数字成为视觉主角 */
 .u { margin-left: 0.12em; font-size: 0.92em; color: var(--kf-ink-3); font-weight: 400; letter-spacing: 0; }
-/* 大号数字旁的单位不跟随 em:32px 的 .92em = 29.4px,单位就和大数一样大了。只缩到最近的字号档(11 / 13) */
-/* 大号数字旁(32 / 20)的单位统一用 11px:em 会跟着父级的字号走,0.92em 在 32px 上就是 29.4px */
+/* 大号数字旁的单位不跟随 em:32px 的 .92em = 29.4px,单位就和大数一样大了。只缩到最近的字号档(v15:12) */
 [data-big] .u, .num-big .u { font-size: var(--fs-xs); font-weight: 500; }
 /* 1.6 基线补偿:图标随中文视觉重心下移 0.5px;数字贴汉字基线时上提 0.02em */
 .icon-label > svg { transform: translateY(-0.5px); }
@@ -482,15 +482,15 @@ svg.i { flex: none; vertical-align: -0.125em; }
 .bars .fill { display: block; height: 100%; border-radius: 2px; background: currentColor; width: calc(var(--v) * 1%); min-width: 2px; transition: width 500ms cubic-bezier(0.4, 0, 0.2, 1); }
 .heat { display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, var(--cell, 12px)); grid-auto-columns: var(--cell, 12px); gap: 2px; }
 .heat i { width: var(--cell, 12px); height: var(--cell, 12px); border-radius: 2px; background: color-mix(in oklab, var(--kf-accent) calc(var(--v) * 100%), var(--gray-5)); }
-.kv { display: flex; align-items: baseline; gap: 8px; font-size: 13px; line-height: 1.6; }
+.kv { display: flex; align-items: baseline; gap: 8px; font-size: 14px; line-height: 1.6; }
 .kv::before { content: ""; order: 1; flex: 1; border-bottom: 1px dotted color-mix(in srgb, currentColor 20%, transparent); transform: translateY(-3px); }
 .kv .k { order: 0; color: var(--kf-ink-3); }
 .kv .v { order: 2; font-variant-numeric: tabular-nums; color: var(--kf-ink); }
 /* 空态 / 错误态(v11 板块八):空态保留卡片框,只换内容;错误态 6% 染色 + 1px 同色半透明描边,不用红大块 */
-.empty { display: grid; place-items: center; gap: 8px; min-height: 64px; padding: 10px 12px; box-sizing: border-box; color: var(--kf-ink-3); font-size: 13px;
+.empty { display: grid; place-items: center; gap: 8px; min-height: 64px; padding: 10px 12px; box-sizing: border-box; color: var(--kf-ink-3); font-size: 14px;
   border: 1px dashed color-mix(in srgb, var(--kf-ink-3) 24%, transparent); border-radius: var(--r-sm); }
 .alert { border-left: none; background: color-mix(in srgb, #E5484D 6%, rgba(255, 255, 255, 0.72)); border-radius: var(--r-sm);
-  padding: 12px 14px; font-size: 13px; line-height: 1.6; box-shadow: var(--elev-1), 0 0 0 1px color-mix(in srgb, #E5484D 16%, transparent); }
+  padding: 12px 14px; font-size: 14px; line-height: 1.6; box-shadow: var(--elev-1), 0 0 0 1px color-mix(in srgb, #E5484D 16%, transparent); }
 
 @media (prefers-reduced-motion: reduce) {
   *, *::before, *::after { animation-duration: 0.01ms !important; animation-delay: 0ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }