Эх сурвалжийг харах

feat(v15): 去玻璃 —— 普通卡片/面板实底+1px 极浅描边+无阴影,只有浮层保留模糊与阴影

- .kf-card / .kf-glass:实底 var(--kf-surface) + var(--kf-line-soft) 描边,去掉 backdrop-filter / 阴影 / 顶部白斑
- .is-overlay(顶栏、抽屉、筛选 sheet、下拉、章节导航、阶段浮起卡)是唯一允许模糊+阴影的地方
- kf-glass-card:surface 1=镂空、2/3=实底;tone 只改描边不再染底;dark 变体
- 表格 .wrap 同步降级
- B 卡次要文字 8.6:1(原色 6.91:1 不到 7 的要求,调亮一档)
- 工作台页头 → 第一块 32
FmodeCode 4 өдөр өмнө
parent
commit
1f5f7ddadb

+ 32 - 16
scripts/verify-v15.mjs

@@ -96,6 +96,9 @@ const main = async () => {
       return !!v && v.shadowRoot && v.shadowRoot.childElementCount > 0;
     }, null, { timeout: 20000 });
     await page.waitForTimeout(600);
+    /* 光场 / 入场动效会让第一轮几何还没稳定:滚动回顶部,动效按系统偏好关掉 */
+    try { await page.waitForFunction(() => window.scrollY === 0, null, { timeout: 3000 }); } catch { await page.evaluate(() => window.scrollTo(0, 0)); }
+    await page.waitForTimeout(200);
   };
 
   const DESKTOP = `(() => { ${DEEP}
@@ -115,35 +118,44 @@ const main = async () => {
     const cards = [...vr.querySelectorAll('kf-glass-card')].filter((c) => c.getBoundingClientRect().height > 0);
 
     /* ---------- 栅格:找行容器 ---------- */
-    const gridRows = [...vr.children].filter((el) => {
+    const gridRows = [...vr.querySelectorAll('*')].filter((el) => {
       const s = getComputedStyle(el); return /grid/.test(s.display) && el.querySelector(':scope > kf-glass-card');
     }).map((el) => {
       const kids = [...el.querySelectorAll(':scope > kf-glass-card')].map((k) => ({ rect: R(k), role: k.dataset.role || '' }));
-      return { cls: el.className, cols: getComputedStyle(el).gridTemplateColumns, kids };
+      return { el, cls: el.className, cols: getComputedStyle(el).gridTemplateColumns, kids };
     });
     const byRole = (r) => gridRows.flatMap((g) => g.kids).find((k) => k.role === r)?.rect || null;
-    const A = byRole('a'), B = byRole('b'), C = byRole('c'), D = byRole('d'), E = byRole('e'), F = byRole('f');
+    const A = byRole('a'), B = byRole('b'), C = byRole('c'), D = byRole('d'), E = byRole('e'), F = byRole('f')
+      || (vr.querySelector('kf-glass-card[data-role="f"]') ? R(vr.querySelector('kf-glass-card[data-role="f"]')) : null);
     /* 列数:找 A 所在的栅格容器,读它的 grid-template-columns 与 column-gap,算出单列宽,再数 A 占几列 */
     const aEl = vr.querySelector('kf-glass-card[data-role="a"]');
-    const rowABC = aEl && [...aEl.parentElement.children].find((el) => /grid/.test(getComputedStyle(el).display));
-    let gapRow = null, colW = null;
+    /* 直接在已找到的行容器里挑「装着 A 的那一行」——不要 [...vr.children](vr 是 ShadowRoot,children 是 HTMLCollection,
+       展开后第一项是属性本身,不是元素) */
+    const rowABC = gridRows.find((g) => g.el.contains(aEl))?.el || null;
+    let gapRow = null, colW = null, colDbg = null;
     if (rowABC) {
-      gapRow = parseFloat(getComputedStyle(rowABC).columnGap) || 0;
-      const tracks = getComputedStyle(rowABC).gridTemplateColumns.split(/\\s+/).map(parseFloat);
+      gapRow = parseFloat(getComputedStyle(rowABC).columnGap) || parseFloat(getComputedStyle(rowABC).gap) || 0;
+      const tracks = getComputedStyle(rowABC).gridTemplateColumns.split(/ +/).filter(Boolean).map(parseFloat);
       colW = tracks.length ? (rowABC.getBoundingClientRect().width - gapRow * (tracks.length - 1)) / tracks.length : null;
+      colDbg = { n: tracks.length, w: rowABC.getBoundingClientRect().width, gapRow, colW };
     }
-    const aColsExact = rowABC && rowABC.querySelectorAll(':scope > kf-glass-card').length ? Math.round((rowABC.querySelector(':scope > kf-glass-card').getBoundingClientRect().width + gapRow) / (colW + gapRow)) : null;
+    /* 列数:单列宽 = (行宽 − 间距 × (列数 − 1)) / 列数;A 占几列 = (A 宽 + 间距) / (单列宽 + 间距) */
+    const aColsExact = A && colW ? Math.round((A.w + gapRow) / (colW + gapRow)) : null;
+    const colWDbg = colW;
 
     /* ---------- B 卡深色 ---------- */
-    let Bbg = null, BbgText = '', Bcontrast = null;
+    let Bbg = null, BbgText = '', Bcontrast = null, Bother = [];
     const bc = cards.find((c) => c.dataset.role === 'b');
     if (bc) {
       const frame = frameOf(bc);
-      Bbg = rgb(getComputedStyle(frame).backgroundColor);
-      const texts = [...bc.shadowRoot.querySelectorAll('*')].filter((e) => [...e.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim()));
+      Bbg = rgb(getComputedStyle(frame)?.backgroundColor || '');
+      if (!Bbg) Bbg = paintOf(bc);
+      /* 文本都在 slotted 内容里(bc 的 light DOM),不在它自己的 shadow 内 */
+      const texts = [...bc.querySelectorAll('*')].filter((e) => [...e.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim()));
       const worst = texts.map((e) => { const fg = rgb(getComputedStyle(e).color); return { t: e.textContent.trim().slice(0, 16), fg, c: +contrast(fg, Bbg).toFixed(2) }; }).sort((p, q) => p.c - q.c);
       Bcontrast = worst[0] || null;
-      /* 原始字符串另存,供打印比对 */
+      BbgText = 'rgb(' + Bbg.r + ', ' + Bbg.g + ', ' + Bbg.b + ')';
+      Bother = texts.map((e) => ({ t: e.textContent.trim().slice(0, 16), c: +contrast(rgb(getComputedStyle(e).color), Bbg).toFixed(2) }));
     }
 
     /* ---------- 主列直接子块间距 ---------- */
@@ -184,12 +196,12 @@ const main = async () => {
       rows: gridRows.map((g) => ({ cls: g.cls, cols: g.cols, kids: g.kids.map((k) => ({ role: k.role, ...k.rect })) })),
       A, B, C, D, E, F,
       bento: {
-        A: A?.w ?? null, B: B?.w ?? null, aCols: aColsExact,
+        A: A?.w ?? null, B: B?.w ?? null, aCols: aColsExact, colDbg,
         ABheightDiff: A && B ? +Math.abs(A.h - B.h).toFixed(1) : null,
         CDE: C && D && E ? { w: [C.w, D.w, E.w], h: [C.h, D.h, E.h], hDiff: +(Math.max(C.h, D.h, E.h) - Math.min(C.h, D.h, E.h)).toFixed(1) } : null,
         F: F || null,
       },
-      Bbg, BbgText, Bcontrast,
+      Bbg, BbgText, Bcontrast, Bother,
       gaps,
       facePad,
       fonts: { tiers: Object.keys(fsCount).sort((p, q) => parseFloat(p) - parseFloat(q)), count: fsCount, where: Object.fromEntries(Object.entries(fsWhere).map(([k, v]) => [k, [...v]])) },
@@ -389,13 +401,17 @@ const main = async () => {
   L(`  B 背景 ${desktopDet.BbgText || '(无 data-role="b" 的卡)'}`);
   L(`  C/D/E ${desktopDet.bento.CDE ? `宽 ${desktopDet.bento.CDE.w.join(' / ')}  高 ${desktopDet.bento.CDE.h.join(' / ')}  高差 ${desktopDet.bento.CDE.hDiff}px` : '(无 data-role=c/d/e 的卡)'}`);
   L(`  F 通栏 ${desktopDet.bento.F ? `宽 ${desktopDet.bento.F.w}px 高 ${desktopDet.bento.F.h}px` : '(无 data-role="f" 的卡)'}`);
-  L(`  — 实时数据(页面真身):A ${desktop.bento.A}×? B ${desktop.bento.B}×? 高差 ${desktop.bento.ABheightDiff}px;C/D/E 高差 ${desktop.bento.CDE?.hDiff}px`);
+  L(`  A 占 ${desktopDet.bento.aCols} / 12 列`);
+  L(`  — 实时数据(页面真身):A ${desktop.bento.A} / B ${desktop.bento.B}  高差 ${desktop.bento.ABheightDiff}px;C/D/E 高差 ${desktop.bento.CDE?.hDiff}px`);
   L(`  B 背景 ${desktop.BbgText}`);
   if (desktop.Bbg) {
     const lum = ({ r, g, b }) => { const f = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }; return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b); };
     L(`  亮度 ${lum(desktop.Bbg).toFixed(3)}  ${lum(desktop.Bbg) < 0.35 ? '✓(<0.35)' : '✗'}`);
   }
-  if (desktop.Bcontrast) L(`  最差对比度 ${desktop.Bcontrast.c}:1(“${desktop.Bcontrast.t}”)  ${desktop.Bcontrast.c >= 7 ? '✓(≥7)' : '✗(要求 ≥7)'}`);
+  if (desktop.Bcontrast) {
+    L(`  最差对比度 ${desktop.Bcontrast.c}:1(“${desktop.Bcontrast.t}”)  ${desktop.Bcontrast.c >= 7 ? '✓(≥7)' : '✗(要求 ≥7)'}`);
+    L(`  逐段:${desktop.Bother.map((o) => `${o.c}:1「${o.t}」`).join('  ')}`);
+  }
   L(`\n[3] 主列相邻块间距(上→下)`);
   for (const g of desktopDet.gaps) L(`  ${String(g.gap).padStart(6)}px  ${g.between}`);
   L(`\n[4] 卡片 .face 内边距`);

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

@@ -126,7 +126,7 @@ export class KfDrawer extends LitElement {
   render() {
     return html`
       <div class="scrim" @click=${this.#close}></div>
-      <aside class="panel" style=${`--drawer-w:${this.width}`} tabindex="-1" role="dialog" aria-modal="true" aria-label=${this.heading}>
+      <aside class="panel is-overlay" style=${`--drawer-w:${this.width}`} tabindex="-1" role="dialog" aria-modal="true" aria-label=${this.heading}>
         <div class="head">
           <div class="k">
             <div class="kicker">${this.sub ? '详情' : ''}</div>

+ 9 - 14
src/components/kf-glass-card.js

@@ -71,15 +71,9 @@ export class KfGlassCard extends LitElement {
       opacity: 0; transition: opacity 240ms cubic-bezier(0.4, 0, 0.2, 1); }
     :host([lift]) .frame:hover::before { opacity: 1; }
     @media (prefers-reduced-motion: reduce) { :host([lift]) .frame::before { display: none; } }
-    /* 层次:1 更透(列表 / 空态外框),2 内容卡,3 浮层最实 */
-    /* v11 板块六:层次只调整白面,不再叠一层半透明 .face(套两层半透明 = 内容糊) */
-    :host([surface="1"]) .frame { background: linear-gradient(rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0.58)) padding-box, linear-gradient(180deg, rgba(255, 255, 255, 0.90), rgba(255, 255, 255, 0.12)) border-box; }
-    :host([surface="3"]) .frame { background: linear-gradient(rgba(255, 255, 255, 0.86), rgba(255, 255, 255, 0.86)) padding-box, linear-gradient(180deg, rgba(255, 255, 255, 0.90), rgba(255, 255, 255, 0.12)) border-box; }
-    /* 手机(v9 实测保留):卡片白面 .62 + blur(24px),圆角 16,内边距 16。
-       点按反馈沿用共享表 .kf-card.is-lift:active 的 scale(.985) / 80ms。 */
-    @media (max-width: 767px) {
-      .frame { background: linear-gradient(rgba(255, 255, 255, 0.80), rgba(255, 255, 255, 0.80)) padding-box, linear-gradient(180deg, rgba(255, 255, 255, 0.90), rgba(255, 255, 255, 0.12)) border-box; border-radius: var(--r-md); }
-    }
+    /* 层次(v15 保留属性、去掉玻璃):1 = 镂空(虚线 / 静态外框,列表与空态用),
+       2 / 3 = 实底白卡 + 1px 极浅描边 + 无阴影。surface 不再改变透明度 —— 半透明内容卡 = 色块乱。 */
+    :host([surface="1"]) .frame { background: transparent; border-color: var(--kf-line-soft); }
     /* tone 原来是一条 3px 彩色左边栏(v11 反例 #2 / Vercel「colored side rails」):
        改成 6% 同色染色背景 + 1px 同色半透明描边 —— 同样是「这张卡有状态」,但不是装饰条 */
     .frame { --tone: transparent; }
@@ -91,11 +85,12 @@ export class KfGlassCard extends LitElement {
     :host([dark]) .frame, :host([dark]) .frame.tone-accent, :host([dark]) .frame.tone-warn, :host([dark]) .frame.tone-ok, :host([dark]) .frame.tone-danger {
       background: var(--kf-ink); color: var(--kf-ink-inv);
     }
-    /* 状态色只染「底」,描边仍是中性发丝线:box-shadow 不新增变体(板块四的 3 档断言要过) */
-    .frame.tone-accent, .frame.tone-warn, .frame.tone-ok, .frame.tone-danger {
-      background: linear-gradient(color-mix(in srgb, var(--tone) 6%, rgba(255, 255, 255, 0.72)), color-mix(in srgb, var(--tone) 6%, rgba(255, 255, 255, 0.72))) padding-box,
-                  linear-gradient(180deg, color-mix(in srgb, var(--tone) 28%, rgba(255, 255, 255, 0.90)), rgba(255, 255, 255, 0.12)) border-box;
-    }
+    /* v15:状态只改描边(1px 同色 1/3 强度),不再裹一层 6% 半透明染底 —— 卡片仍是实底白,状态靠边 + 卡内色点表达。
+       box-shadow 仍然不新增变体(板块四的 3 档断言要过)。 */
+    .frame.tone-accent { border-color: color-mix(in srgb, var(--tone) 34%, transparent); }
+    .frame.tone-warn { border-color: color-mix(in srgb, var(--tone) 34%, transparent); }
+    .frame.tone-ok { border-color: color-mix(in srgb, var(--tone) 34%, transparent); }
+    .frame.tone-danger { border-color: color-mix(in srgb, var(--tone) 34%, transparent); }
   `;
 
   render() {

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

@@ -50,11 +50,11 @@ export class KfTable extends LitElement {
     .skel .hd, .skel .rl { display: grid; grid-template-columns: 28px minmax(0, 1fr) 96px 88px; gap: 12px; align-items: center; padding: 8px 0; }
     .skel .hd { border-bottom: 1px solid var(--kf-line); }
     .skel .rl + .rl { border-top: 1px solid var(--kf-line); }
-    /* 表格 = 内容卡同一层玻璃(v11 板块六:.72 白面 + 1px 描边走阴影 + 轻微 blur(8px)) */
+    /* 表格 = 内容卡同一层次(v15):实底白 + 1px 极浅描边,无模糊、无阴影 */
     .wrap { overflow-x: auto; border-radius: var(--kf-radius);
-      background: var(--kf-glass-card);
-      -webkit-backdrop-filter: blur(var(--kf-glass-card-blur)); backdrop-filter: blur(var(--kf-glass-card-blur));
-      box-shadow: var(--kf-glass-card-shadow); }
+      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);

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

@@ -85,7 +85,7 @@ export class KfTopbar extends LitElement {
   render() {
     const initials = this.me ? Array.from(this.me).slice(-1).join('') : '?';
     return html`
-      <header class="bar kf-glass">
+      <header class="bar kf-glass is-overlay">
         <div class="t"><h1>${this.title}</h1>${this.sub ? html`<div class="s">${this.sub}</div>` : ''}</div>
         <div class="acts"><slot name="actions"></slot></div>
         <div class="sync ${this.status === 'error' ? 'err' : this.status === 'loading' ? 'warn' : ''}" role="status">

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

@@ -157,7 +157,7 @@ export function cardToolbar({ q, counts = {}, whoList = [], shown, all, me, extr
             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>
-            <div class="panel" role="group">
+            <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);
                 return html`<button class="opt" role=${d.key === 'who' ? 'menuitemradio' : 'menuitemcheckbox'} aria-checked=${on ? 'true' : 'false'}

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

@@ -92,7 +92,7 @@ export class KfFilterSheet extends LitElement {
     const active = this.groups.find((g) => g.key === this._active);
     return html`
       <div class="scrim" @click=${this.#close}></div>
-      <section class="sheet" role="dialog" aria-modal="true" aria-label="筛选" tabindex="-1" @keydown=${(e) => { if (e.key === 'Escape') this.#close(); }}>
+      <section class="sheet is-overlay" role="dialog" aria-modal="true" aria-label="筛选" tabindex="-1" @keydown=${(e) => { if (e.key === 'Escape') this.#close(); }}>
         <div class="grip" @pointerdown=${this.#down} @pointermove=${this.#move} @pointerup=${this.#up} @pointercancel=${this.#up}>
           <span class="handle" aria-hidden="true"></span>
         </div>

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

@@ -228,7 +228,7 @@ export class ViewReport extends LitElement {
     }
     const active = this._active || (this.routeQuery?.ch ? `ch-${String(this.routeQuery.ch).replace(/^ch-/, '')}` : '');
     return html`
-      <div class="toc kf-glass is-top" aria-label="章节导航" data-toc>
+      <div class="toc kf-glass is-top is-overlay" aria-label="章节导航" data-toc>
         ${r.chapters.map((c) => html`<a class="jump" href=${`#ch-${c.id}`} data-jump=${c.id} aria-current=${active === `ch-${c.id}` ? 'true' : 'false'}
           @click=${(e) => { e.preventDefault(); this.#jump(`ch-${c.id}`); }}>${c.no}. ${c.title}</a>`)}
       </div>

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

@@ -102,7 +102,7 @@ export class ViewStages extends LitElement {
 
     return html`
       <div class="overview" data-overview>
-        ${rows.map((r, i) => html`<div class="kf-card is-lift kf-enter" style=${`--i:${i}`} role="button" tabindex="0" data-stage=${r.stage.code} aria-current=${only === r.stage.code ? 'true' : 'false'}
+        ${rows.map((r, i) => html`<div class="kf-card is-lift is-overlay kf-enter" style=${`--i:${i}`} role="button" tabindex="0" data-stage=${r.stage.code} aria-current=${only === r.stage.code ? 'true' : 'false'}
           @click=${() => this.#set({ stage: only === r.stage.code ? '' : r.stage.code })}
           @keydown=${(e) => { if (e.key === 'Enter') this.#set({ stage: only === r.stage.code ? '' : r.stage.code }); }}>
           <div class="ov">

+ 3 - 1
src/pages/view-workbench.js

@@ -59,6 +59,8 @@ export class ViewWorkbench extends LitElement {
     .bento > .row.ab > kf-glass-card[data-role="b"] { grid-column: span 4; }
     .bento > .row.cde { grid-template-columns: repeat(3, minmax(0, 1fr)); }
     .p-head { padding-top: var(--kf-space-top); padding-bottom: 0; }
+    /* 页头 → Bento 第一块:32(v15 区块之间统一 32) */
+    .p-head + .bento { margin-top: 32px; }
 
     /* ---------- 通用小件 ---------- */
     .lbl { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 500; line-height: 1.4; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); }
@@ -110,7 +112,7 @@ export class ViewWorkbench extends LitElement {
 
     /* ---------- B 卡:现场(深色) ---------- */
     .live { display: grid; gap: var(--kf-space-in); height: 100%; align-content: start;
-      --live-ink: #F4F4F5; --live-ink-3: #A1A1AA; --live-line: rgba(255, 255, 255, 0.12); }
+      --live-ink: #F4F4F5; --live-ink-3: #B4B4BC; --live-line: rgba(255, 255, 255, 0.12); }
     .live .lbl { color: var(--live-ink-3); }
     .live-head { display: flex; align-items: center; gap: 8px; }
     .live-head .go { margin-left: auto; color: var(--live-ink-3); }

+ 24 - 15
src/styles/tokens.js

@@ -44,6 +44,9 @@ export const TOKENS_CSS = `:root, :host {
   --kf-gray-2: #3F3F46;
   --kf-gray-3: #4B5563;   /* 辅助文字 */
   --kf-line: #EAEAEA;                 /* gray-5 分隔线 */
+  --kf-line-soft: rgba(0, 0, 0, 0.06); /* v15 卡片 / 面板的 1px 极浅描边 */
+  --kf-surface: #FFFFFF;              /* v15 普通卡片实底 */
+  --kf-surface-raised: #FCFCFD;       /* 卡片 hover 底色(比实底略亮一档,不加阴影) */
   --kf-line-strong: rgba(24, 24, 27, 0.22);
   --kf-fill: rgba(24, 24, 27, 0.05);
 
@@ -271,24 +274,29 @@ summary::-webkit-details-marker { display: none; }
    更实的白面 .72 + 1px 发丝描边(做进 box-shadow)+ 内高光 + 轻微 blur(8px)。全页玻璃 = 性能灾难 + 内容糊。
    描边走 shadow 不走 border:hover 时不影响盒模型,内容不位移 1px(4.3)。 */
 .kf-glass, .glass {
+  /* v15:保留名字,不再做玻璃 —— 不透明实底 + 1px 极浅描边。全站普通面板 / 工具条不再有管不着像素的模糊。
+     只有浮层、抽屉、下拉(.is-overlay)允许模糊与阴影。 */
   position: relative;
   border-radius: var(--kf-radius);
-  border: 1px solid transparent;
-  background:
-    linear-gradient(var(--kf-glass-alpha, var(--kf-glass-mid)), var(--kf-glass-alpha, var(--kf-glass-mid))) padding-box,
-    linear-gradient(180deg, rgba(255, 255, 255, 0.90), rgba(255, 255, 255, 0.12)) border-box;
-  -webkit-backdrop-filter: blur(var(--kf-glass-blur, var(--kf-blur-mid))) saturate(var(--kf-glass-sat, var(--kf-sat-mid)));
-  backdrop-filter: blur(var(--kf-glass-blur, var(--kf-blur-mid))) saturate(var(--kf-glass-sat, var(--kf-sat-mid)));
-  box-shadow: var(--kf-shadow);
+  border: 1px solid var(--kf-line-soft);
+  background: var(--kf-surface);
   color: var(--kf-ink-2);
 }
+/* 浮层:抽屉 / 底部 sheet / 下拉 / 顶栏下的浮动条 —— 唯一允许 backdrop-filter + 阴影的地方 */
+.kf-glass.is-overlay, .glass.is-overlay {
+  background: var(--kf-glass-top);
+  -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(--elev-2);
+}
 .kf-card {
+  /* v15 定稿:内容卡 = 实底 #fff + 1px 极浅描边(rgba(0,0,0,.06))/ 无阴影。
+     之前的半透明白面 + 轻微模糊在灰底上只剩「脏」,用户看到的是色块乱。 */
   position: relative; isolation: isolate;
   border-radius: var(--kf-radius);
-  background: var(--kf-glass-card);
-  -webkit-backdrop-filter: blur(var(--kf-glass-card-blur));
-  backdrop-filter: blur(var(--kf-glass-card-blur));
-  box-shadow: var(--kf-glass-card-shadow);
+  background: var(--kf-surface);
+  border: 1px solid var(--kf-line-soft);
+  box-shadow: none;
   color: var(--kf-ink-2);
 }
 /* 层:panel(导航等大面)< mid < top(顶栏 / 抽屉)< chip */
@@ -298,7 +306,7 @@ summary::-webkit-details-marker { display: none; }
 /* 不支持 backdrop-filter:白面提到 .9,仍然是一张卡片 */
 @supports not (backdrop-filter: blur(1px)) {
   .kf-glass, .glass { --kf-glass-alpha: rgba(255, 255, 255, 0.9); }
-  .kf-card { background: rgba(255, 255, 255, 0.92); }
+  .kf-card { background: var(--kf-surface); }
 }
 
 /* 卡片 hover:上浮 1px + 阴影升一档;按下 80ms 内缩到 .985。
@@ -313,7 +321,8 @@ summary::-webkit-details-marker { display: none; }
 }
 .kf-card.is-lift:hover, .kf-card.is-lift:focus-visible {
   transform: translateY(-1px);
-  box-shadow: var(--kf-shadow-lift);
+  border-color: rgba(0, 0, 0, 0.14);
+  background: var(--kf-surface-raised);
 }
 .kf-card.is-lift:active { transform: scale(0.985); transition: transform 80ms cubic-bezier(0.4, 0, 0.2, 1); }
 /* 光斑跟随鼠标(v11 5.3):只亮 1px 边框,不是整卡发光(整卡发光 = 廉价)。
@@ -328,8 +337,8 @@ summary::-webkit-details-marker { display: none; }
   pointer-events: none;
 }
 .kf-card.is-lift:hover::before { opacity: 1; }
-/* 顶部柔光:只留左上一点极淡的漫射光(v10 去掉斜扫光——那层在灰底上显脏) */
-.kf-card::after {
+/* 顶部柔光:v15 去掉 —— 实底卡上这一层等于给每张卡铺一块白斑 */
+.kf-card::after { display: none; }
   content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit; z-index: 0;
   background: radial-gradient(70% 60% at 18% -10%, rgba(255, 255, 255, 0.5), transparent 70%);
   opacity: 0.85; transition: opacity var(--kf-dur) var(--kf-ease);