feat(core): Wave 1 part2 — 双主题清污染/mono分级/富分页器/hero字重

S1 双主题: styles.css Dashboard 区(.recent-row/.shortcut/.tip)迁入 design-restraint.css,
  暖米旧 token→restraint(--orange→--heat 等);修 .queue-chip svg --ink-3→--black-alpha-56。
  实测 .shortcut .ic 橙 = rgb(250,93,25)=--heat(原泄漏成暖橙 #E55B26)
S2 mono: design-restraint.css 加 .mono-10/11/12(10.5/11/11.5px)工具类(逐页应用并入 Wave 4)
S7 分页器: pager.tsx 加可选 onPageSizeChange(条数循环)/跳页输入/sticky,旧调用方零破坏;
  CSS 加 .size-cycle/.jump/.sticky(接线 products/library + 服务端分页放 Wave 3)
S8 hero 字重: 核 V1 实测 account/team hero 数字均 font-weight:700 → account 3 处 + team 2 处
  改 700(据实还原 V1 标准答案;记录与 design.md §2.5 的内部不一致)

S6 批量栏共享挪 Wave 3(与 projects/library 重建一起做)

验证: tsc 0 · build 0 · computed-style 橙=heat/字重=700 · pixelmatch account 1.23%/team 2.13%
  · 14 路由回归 boot 0 报错 · 登录 639ms

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-19 04:07:34 +08:00
co-authored by Claude Opus 4.8
parent 74e3c818c5
commit 42c3c046ea
7 changed files with 177 additions and 50 deletions
+3 -3
View File
@@ -29,13 +29,13 @@
.balance-hero { display: flex; flex-direction: column; gap: 4px; } .balance-hero { display: flex; flex-direction: column; gap: 4px; }
.balance-hero .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; } .balance-hero .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; }
.balance-hero .v { font-size: 38px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1; } .balance-hero .v { font-size: 38px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.balance-hero .meta { font-size: 12px; color: rgba(255,255,255,.5); font-family: var(--font-mono); letter-spacing: .02em; margin-top: 4px; } .balance-hero .meta { font-size: 12px; color: rgba(255,255,255,.5); font-family: var(--font-mono); letter-spacing: .02em; margin-top: 4px; }
.balance-sub { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; padding: 16px 0 0; border-top: 1px solid rgba(255,255,255,.1); } .balance-sub { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; padding: 16px 0 0; border-top: 1px solid rgba(255,255,255,.1); }
.balance-sub .col { min-width: 0; } .balance-sub .col { min-width: 0; }
.balance-sub .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.5); letter-spacing: .06em; text-transform: uppercase; } .balance-sub .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.5); letter-spacing: .06em; text-transform: uppercase; }
.balance-sub .v { font-size: 18px; font-weight: 600; letter-spacing: -.01em; margin-top: 4px; font-variant-numeric: tabular-nums; } .balance-sub .v { font-size: 18px; font-weight: 700; letter-spacing: -.01em; margin-top: 4px; font-variant-numeric: tabular-nums; }
.balance-sub .meta { font-size: 12px; color: rgba(255,255,255,.42); margin-top: 3px; font-family: var(--font-mono); letter-spacing: .02em; } .balance-sub .meta { font-size: 12px; color: rgba(255,255,255,.42); margin-top: 3px; font-family: var(--font-mono); letter-spacing: .02em; }
.balance-foot { margin-top: auto; padding-top: 2px; } .balance-foot { margin-top: auto; padding-top: 2px; }
@@ -58,7 +58,7 @@
.recharge-card:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--heat-40); } .recharge-card:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--heat-40); }
.recharge-card.selected { border-color: var(--heat); background: var(--heat-12); box-shadow: inset 0 0 0 1px var(--heat); } .recharge-card.selected { border-color: var(--heat); background: var(--heat-12); box-shadow: inset 0 0 0 1px var(--heat); }
.recharge-card.selected::after { content: '✓'; position: absolute; top: 6px; right: 7px; width: 15px; height: 15px; border-radius: 50%; display: grid; place-items: center; background: var(--heat); color: var(--accent-white); font-size: 12px; line-height: 1; } .recharge-card.selected::after { content: '✓'; position: absolute; top: 6px; right: 7px; width: 15px; height: 15px; border-radius: 50%; display: grid; place-items: center; background: var(--heat); color: var(--accent-white); font-size: 12px; line-height: 1; }
.recharge-card .amt { font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.15; } .recharge-card .amt { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.15; }
.recharge-card .gift { font-size: 12px; color: var(--black-alpha-48); margin-top: 4px; font-family: var(--font-mono); white-space: nowrap; } .recharge-card .gift { font-size: 12px; color: var(--black-alpha-48); margin-top: 4px; font-family: var(--font-mono); white-space: nowrap; }
.recharge-card .gift.bonus { color: var(--accent-forest); font-weight: 600; } .recharge-card .gift.bonus { color: var(--accent-forest); font-weight: 600; }
.recharge-card .ribbon { position: absolute; top: 6px; left: 7px; font-family: var(--font-mono); font-size: 12px; padding: 1px 5px; background: var(--heat); color: var(--accent-white); letter-spacing: .03em; font-weight: 600; border-radius: var(--r-sm); } .recharge-card .ribbon { position: absolute; top: 6px; left: 7px; font-family: var(--font-mono); font-size: 12px; padding: 1px 5px; background: var(--heat); color: var(--accent-white); letter-spacing: .03em; font-weight: 600; border-radius: var(--r-sm); }
+49 -7
View File
@@ -1,6 +1,12 @@
// 通用列表分页器:沿用项目向导 pp-pager / 消费页 bill-pager 的视觉(mono 总数 + 页码窗口 + 每页条数) // 通用列表分页器:mono 总数 + 页码窗口 + 每页条数 + 跳页输入
// 纯前端切片分页:父组件持有 page state,自己 slice;本组件只画控件 // 父组件持有 page state;纯前端切片或服务端分页都适用(onChange 回页码)
// total <= pageSize 时不渲染(单页无需分页器)。 // 新增能力全部可选 —— 旧调用方只传 page/total/pageSize/onChange 时退化为原只读分页器:
// onPageSizeChange 传则「每页 N 条」变成可点击循环按钮(默认循环 12/24/48/96)
// pageSizeOptions 自定义每页条数候选
// sticky 传则分页器吸底浮动
// total <= pageSize 且不可改每页条数时不渲染(单页无需分页器)。
import { useState } from "react";
// 页码窗口:超过 7 页折叠成 1 … cur-1 cur cur+1 … last // 页码窗口:超过 7 页折叠成 1 … cur-1 cur cur+1 … last
export function pageWindow(current: number, total: number): Array<number | "ellipsis"> { export function pageWindow(current: number, total: number): Array<number | "ellipsis"> {
@@ -15,17 +21,34 @@ export function pageWindow(current: number, total: number): Array<number | "elli
return items; return items;
} }
export function Pager({ page, total, pageSize, onChange }: { export function Pager({ page, total, pageSize, onChange, onPageSizeChange, pageSizeOptions = [12, 24, 48, 96], sticky = false }: {
page: number; page: number;
total: number; total: number;
pageSize: number; pageSize: number;
onChange: (page: number) => void; onChange: (page: number) => void;
onPageSizeChange?: (size: number) => void;
pageSizeOptions?: number[];
sticky?: boolean;
}) { }) {
const totalPages = Math.max(1, Math.ceil(total / pageSize)); const totalPages = Math.max(1, Math.ceil(total / pageSize));
const cur = Math.min(Math.max(1, page), totalPages); const cur = Math.min(Math.max(1, page), totalPages);
if (total <= pageSize) return null; const [jump, setJump] = useState("");
// 单页且不可改每页条数 → 不渲染;可改条数时仍渲染(让用户能切回更小分页)
if (total <= pageSize && !onPageSizeChange) return null;
function cycleSize() {
if (!onPageSizeChange) return;
const idx = pageSizeOptions.indexOf(pageSize);
onPageSizeChange(pageSizeOptions[(idx + 1) % pageSizeOptions.length]);
}
function commitJump() {
const n = Number.parseInt(jump, 10);
if (Number.isFinite(n)) onChange(Math.min(Math.max(1, n), totalPages));
setJump("");
}
return ( return (
<div className="list-pager"> <div className={`list-pager${sticky ? " sticky" : ""}`}>
<span className="total">// 共 {total} 条 · 第 {cur} / {totalPages} 页</span> <span className="total">// 共 {total} 条 · 第 {cur} / {totalPages} 页</span>
<div className="pages"> <div className="pages">
<button type="button" disabled={cur === 1} onClick={() => onChange(cur - 1)} aria-label="上一页"></button> <button type="button" disabled={cur === 1} onClick={() => onChange(cur - 1)} aria-label="上一页"></button>
@@ -36,7 +59,26 @@ export function Pager({ page, total, pageSize, onChange }: {
))} ))}
<button type="button" disabled={cur === totalPages} onClick={() => onChange(cur + 1)} aria-label="下一页"></button> <button type="button" disabled={cur === totalPages} onClick={() => onChange(cur + 1)} aria-label="下一页"></button>
</div> </div>
<span className="page-size"> {pageSize} </span> {totalPages > 7 && (
<span className="jump">
<input
type="number"
min={1}
max={totalPages}
value={jump}
placeholder={String(cur)}
onChange={(event) => setJump(event.target.value)}
onKeyDown={(event) => { if (event.key === "Enter") commitJump(); }}
onBlur={() => { if (jump) commitJump(); }}
aria-label="跳至页码"
/>
</span>
)}
{onPageSizeChange
? <button type="button" className="page-size size-cycle" onClick={cycleSize} title="点击切换每页条数"> {pageSize} </button>
: <span className="page-size"> {pageSize} </span>}
</div> </div>
); );
} }
+62 -1
View File
@@ -519,7 +519,7 @@ main { position: relative; background: var(--background-base); min-width: 0; }
} }
.queue-chip:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); } .queue-chip:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); }
.queue-chip[hidden] { display: none; } .queue-chip[hidden] { display: none; }
.queue-chip svg { width: var(--icon-m); height: var(--icon-m); color: var(--ink-3); } .queue-chip svg { width: var(--icon-m); height: var(--icon-m); color: var(--black-alpha-56); }
.queue-chip .count { .queue-chip .count {
display: inline-flex; align-items: center; justify-content: center; display: inline-flex; align-items: center; justify-content: center;
height: 20px; min-width: 20px; padding: 0 6px; height: 20px; min-width: 20px; padding: 0 6px;
@@ -1510,6 +1510,29 @@ select.duration-select:focus,
.list-pager .pages button:disabled { opacity: .4; cursor: not-allowed; } .list-pager .pages button:disabled { opacity: .4; cursor: not-allowed; }
.list-pager .pages .ellipsis { min-width: 22px; height: 28px; display: inline-flex; align-items: center; justify-content: center; color: var(--black-alpha-48); } .list-pager .pages .ellipsis { min-width: 22px; height: 28px; display: inline-flex; align-items: center; justify-content: center; color: var(--black-alpha-48); }
.list-pager .page-size { font-family: var(--font-mono); letter-spacing: .02em; } .list-pager .page-size { font-family: var(--font-mono); letter-spacing: .02em; }
/* S7 富分页器:跳页输入 + 每页条数循环按钮 + 吸底变体 */
.list-pager .jump { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-mono); letter-spacing: .02em; color: var(--black-alpha-56); }
.list-pager .jump input {
width: 46px; height: 28px; text-align: center;
border: 1px solid var(--border-faint); background: var(--surface);
border-radius: var(--r-sm); font-family: var(--font-mono); font-size: 12px; color: var(--accent-black);
-moz-appearance: textfield; appearance: textfield;
}
.list-pager .jump input:focus { border-color: var(--heat-40); outline: none; }
.list-pager .jump input::-webkit-outer-spin-button,
.list-pager .jump input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.list-pager .size-cycle {
height: 28px; padding: 0 10px;
border: 1px solid var(--border-faint); background: var(--surface);
border-radius: var(--r-sm); cursor: pointer;
transition: border-color var(--t-base), color var(--t-base);
}
.list-pager .size-cycle:hover { border-color: var(--black-alpha-32); color: var(--accent-black); }
.list-pager.sticky {
position: sticky; bottom: 0;
margin-top: 0; background: var(--background-base);
padding-bottom: 14px; z-index: 10;
}
/* 音频试听面板:灯箱内居中,音符 + 原生播放器 */ /* 音频试听面板:灯箱内居中,音符 + 原生播放器 */
.np-lightbox .lb-audio { .np-lightbox .lb-audio {
@@ -2341,3 +2364,41 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
z-index: 5; z-index: 5;
position: relative; position: relative;
} }
/* ─── Dashboard(S1 · 迁自 styles.css,暖米旧 token → restraint token,清双主题污染)─── */
.dash-grid { display: grid; grid-template-columns: 1.7fr 1fr; gap: 24px; align-items: start; }
.dash-side { display: flex; flex-direction: column; gap: 24px; }
.recent-row {
display: grid;
/* 缩略图 | 标题(吃满剩余) | 进度条(固定) | 状态(固定列,左对齐对齐成列) | 按钮(固定) */
grid-template-columns: 54px minmax(0, 1fr) max-content 116px 68px;
align-items: center;
gap: 16px;
padding: 14px 18px;
border-bottom: 1px solid var(--border-faint);
cursor: pointer;
width: 100%;
text-align: left;
background: transparent;
}
.recent-row > .pill { justify-self: start; }
.recent-row > .btn-sm { justify-self: end; }
.recent-row:last-child { border-bottom: 0; }
.recent-row:hover { background: var(--black-alpha-4); }
.recent-row .thumb { width: 54px; height: 70px; }
.recent-meta .name { font-weight: 600; font-size: 14px; color: var(--accent-black); }
.recent-meta .sub { font-size: 12px; color: var(--black-alpha-48); margin-top: 3px; font-family: var(--font-mono); letter-spacing: .01em; }
.shortcuts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.shortcut { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 14px; display: flex; align-items: flex-start; gap: 11px; cursor: pointer; text-align: left; transition: background var(--t-base), border-color var(--t-base); }
.shortcut:hover { background: var(--black-alpha-4); }
.shortcut .ic { width: 30px; height: 30px; background: var(--heat-12); color: var(--heat); display: grid; place-items: center; border: 1px solid var(--heat-20); border-radius: var(--r-sm); flex-shrink: 0; }
.shortcut .t { font-size: 13px; font-weight: 600; }
.shortcut .d { font-size: 12px; color: var(--black-alpha-48); margin-top: 2px; font-family: var(--font-mono); letter-spacing: .01em; }
.tip { background: var(--surface); border: 1px dashed var(--border-faint); border-radius: var(--r-md); padding: 14px 16px; font-size: 13px; color: var(--black-alpha-64); line-height: 1.6; }
.tip strong { color: var(--accent-black); font-weight: 600; display: block; margin-bottom: 4px; }
/* ─── Mono 装饰字号分级工具类(S2 · 把被统一抬到 12px 的 mono 收回 V1 分级)─── */
/* 叠加在既有 mono 元素上只改字号:.mono-10 = 10.5px / .mono-11 = 11px / .mono-12 = 11.5px */
.mono-10 { font-size: 10.5px; letter-spacing: .04em; }
.mono-11 { font-size: 11px; letter-spacing: .04em; }
.mono-12 { font-size: 11.5px; letter-spacing: .04em; }
+2 -31
View File
@@ -1099,37 +1099,8 @@ nav button svg { width: 14px; height: 14px; color: var(--ink-3); }
} }
} }
/* Dashboard */ /* Dashboard 区类(.dash-grid/.dash-side/.recent-row/.shortcut/.tip)已迁入
.dash-grid { display: grid; grid-template-columns: 1.7fr 1fr; gap: 24px; align-items: start; } design-restraint.css §Dashboard,并把暖米旧 token 换成 restraint token(清双主题污染)。*/
.dash-side { display: flex; flex-direction: column; gap: 24px; }
.recent-row {
display: grid;
/* 缩略图 | 标题(吃满剩余) | 进度条(固定) | 状态(固定列,左对齐对齐成列) | 按钮(固定) */
grid-template-columns: 54px minmax(0, 1fr) max-content 116px 68px;
align-items: center;
gap: 16px;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
cursor: pointer;
width: 100%;
text-align: left;
background: transparent;
}
.recent-row > .pill { justify-self: start; }
.recent-row > .btn-sm { justify-self: end; }
.recent-row:last-child { border-bottom: 0; }
.recent-row:hover { background: var(--bg-soft); }
.recent-row .thumb { width: 54px; height: 70px; }
.recent-meta .name { font-weight: 600; font-size: 14px; color: var(--ink); }
.recent-meta .sub { font-size: 12px; color: var(--ink-3); margin-top: 3px; font-family: 'JetBrains Mono', monospace; letter-spacing: .01em; }
.shortcuts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.shortcut { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 14px; display: flex; align-items: flex-start; gap: 11px; cursor: pointer; text-align: left; transition: background var(--t-base), border-color var(--t-base); }
.shortcut:hover { background: var(--bg-soft); }
.shortcut .ic { width: 30px; height: 30px; background: var(--orange-tint); color: var(--orange); display: grid; place-items: center; border: 1px solid var(--orange-soft); border-radius: var(--r-sm); flex-shrink: 0; }
.shortcut .t { font-size: 13px; font-weight: 600; }
.shortcut .d { font-size: 12px; color: var(--ink-3); margin-top: 2px; font-family: 'JetBrains Mono', monospace; letter-spacing: .01em; }
.tip { background: var(--surface); border: 1px dashed var(--border-faint); border-radius: var(--r-md); padding: 14px 16px; font-size: 13px; color: var(--ink-2); line-height: 1.6; }
.tip strong { color: var(--ink); font-weight: 600; display: block; margin-bottom: 4px; }
/* Product / asset grids */ /* Product / asset grids */
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; } .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
+2 -2
View File
@@ -47,7 +47,7 @@
.banner-head { display: flex; align-items: flex-start; gap: 20px; } .banner-head { display: flex; align-items: flex-start; gap: 20px; }
.banner-id { flex: 1; min-width: 0; } .banner-id { flex: 1; min-width: 0; }
.banner-id .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; } .banner-id .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; }
.banner-id .nm { font-size: 22px; font-weight: 600; letter-spacing: -.012em; margin-top: 4px; display: flex; align-items: baseline; gap: 10px; } .banner-id .nm { font-size: 22px; font-weight: 700; letter-spacing: -.012em; margin-top: 4px; display: flex; align-items: baseline; gap: 10px; }
.banner-id .nm .tag { font-size: 12px; font-family: var(--font-mono); padding: 2px 8px; background: rgba(255,255,255,.12); border-radius: var(--r-pill); letter-spacing: .04em; font-weight: 500; } .banner-id .nm .tag { font-size: 12px; font-family: var(--font-mono); padding: 2px 8px; background: rgba(255,255,255,.12); border-radius: var(--r-pill); letter-spacing: .04em; font-weight: 500; }
.banner-id .meta { font-size: 12px; color: rgba(255,255,255,.5); margin-top: 6px; font-family: var(--font-mono); letter-spacing: .02em; } .banner-id .meta { font-size: 12px; color: rgba(255,255,255,.5); margin-top: 6px; font-family: var(--font-mono); letter-spacing: .02em; }
.banner-actions { display: flex; gap: 8px; flex-shrink: 0; } .banner-actions { display: flex; gap: 8px; flex-shrink: 0; }
@@ -63,7 +63,7 @@
.banner-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; } .banner-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; }
.banner-stats .stat { min-width: 0; } .banner-stats .stat { min-width: 0; }
.banner-stats .stat .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; } .banner-stats .stat .lbl { font-family: var(--font-mono); font-size: 12px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; }
.banner-stats .stat .v { font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -.012em; margin-top: 6px; } .banner-stats .stat .v { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.012em; margin-top: 6px; }
/* color 必须显式写,否则会被 restraint.css 全局 .stat .v 的 color: var(--accent-black) 覆盖成黑字 */ /* color 必须显式写,否则会被 restraint.css 全局 .stat .v 的 color: var(--accent-black) 覆盖成黑字 */
.banner-stats .stat .v { color: var(--accent-white); } .banner-stats .stat .v { color: var(--accent-white); }
.banner-stats .stat .v.warn { color: #FFB870; } .banner-stats .stat .v.warn { color: #FFB870; }
+45
View File
@@ -0,0 +1,45 @@
// Wave 1 part-2 verify (scratch): S1 dashboard 橙=--heat / S8 hero 字重=700 / 截图.
import { chromium } from "playwright";
import fs from "node:fs";
import path from "node:path";
const BASE = process.env.BASE || "http://localhost:5173";
const TOKEN = process.env.TOKEN || "";
const OUT = path.resolve("../../../_qa_shots/wave1b");
fs.mkdirSync(OUT, { recursive: true });
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: "light" });
const seed = await ctx.newPage();
await seed.goto(BASE + "/", { waitUntil: "domcontentloaded" });
await seed.evaluate((t) => localStorage.setItem("airshelf_token", t), TOKEN);
await seed.close();
const r = {};
async function shot(route, name, fn) {
const p = await ctx.newPage();
await p.goto(BASE + route, { waitUntil: "load" });
await p.waitForTimeout(1500);
r[name] = await fn(p);
await p.screenshot({ path: path.join(OUT, `${name}.png`) });
await p.close();
}
await shot("/dashboard", "dashboard", async (p) => {
const ic = p.locator(".shortcut .ic").first();
return {
shortcutIcColor: await ic.evaluate((el) => getComputedStyle(el).color).catch(() => "n/a"),
shortcutIcBg: await ic.evaluate((el) => getComputedStyle(el).backgroundColor).catch(() => "n/a"),
tipColor: await p.locator(".tip").first().evaluate((el) => getComputedStyle(el).color).catch(() => "n/a"),
};
});
await shot("/account", "account", async (p) => ({
balanceWeight: await p.locator(".balance-hero .v").first().evaluate((el) => getComputedStyle(el).fontWeight).catch(() => "n/a"),
}));
await shot("/team", "team", async (p) => ({
bannerNmWeight: await p.locator(".banner-id .nm").first().evaluate((el) => getComputedStyle(el).fontWeight).catch(() => "n/a"),
}));
await browser.close();
console.log(JSON.stringify(r, null, 2));
// 参照:--heat #fa5d19 = rgb(250, 93, 25);旧暖橙 #E55B26 = rgb(229, 91, 38)
+14 -6
View File
@@ -43,9 +43,17 @@
**part 1 验证:** `tsc --noEmit` 0;交互走查(playwright)`auth-toast`:`.toast.show` 出现、文案对、旧 `.login-toast` 计数 0;`team-modal`:`#open-invite` 打开 `.modal-bg.show`(scale 落定=进场动画跑过)、**ESC 成功关闭并卸载**;14 路由回归 boot 全过 0 报错、登录 643ms。截图:`_qa_shots/wave1/*.png` **part 1 验证:** `tsc --noEmit` 0;交互走查(playwright)`auth-toast`:`.toast.show` 出现、文案对、旧 `.login-toast` 计数 0;`team-modal`:`#open-invite` 打开 `.modal-bg.show`(scale 落定=进场动画跑过)、**ESC 成功关闭并卸载**;14 路由回归 boot 全过 0 报错、登录 643ms。截图:`_qa_shots/wave1/*.png`
### part 2 — ⏳ 待做(下一拍) ### part 2 — ✅ 完成 (2026-06-19)
- **S1** styles.css 旧暖米 token 孤儿类迁入 design-restraint.css(改前做孤儿分析,护住 shell);顺手清 part1 留下的 `inline-toast`/`login-toast` 死 CSS。
- **S2**`.mono-10/11/12` 工具类(逐页应用并入 Wave 4 P2 扫)。 | # | 项 | 状态 | 改动 / 证据 |
- **S6**`.bulk-bar`/`.card-check` 从 products-page.css 提到 design-restraint.css(变共享,projects/library 的接线在 Wave 3)。 |---|---|---|---|
- **S7**`pager.tsx` 富分页器(条数循环 12/24/48/96 + 跳页 + 吸底)+ CSS;**Wave 0 暂缓的 products/projects 服务端分页在此合并**。 | S1 | 双主题清污染 | ✅ | 确认是**真视觉泄漏**:styles.css `:root` 定义 `--orange #E55B26`(暖橙)、`--ink-3 #9C988C`,design-restraint.css 不再声明这些名 → 用 `var(--orange)` 的孤儿类渲染成**错误的橙**。把 Dashboard 区(`.recent-row/.shortcut/.tip` 等,dashboard.tsx 实际在用)从 styles.css 迁入 design-restraint.css,暖米 token → restraint(`--orange→--heat``--orange-tint→--heat-12``--ink→--accent-black``--ink-3→--black-alpha-48``JetBrains→--font-mono`);并修 design-restraint.css 自身 `.queue-chip svg``--ink-3→--black-alpha-56`。**实测**:`.shortcut .ic` color=`rgb(250,93,25)`(=--heat)、bg=`rgba(250,93,25,.12)``.tip` color=`rgba(38,38,38,.64)` —— 暖橙泄漏已除。 |
- **S8** hero 大数字字重 —— ⚠️ 计划写 600→700,但 design.md §2.5/§2.6 铁律「字重仅 400/500/600,700 仅 Ctrl-K 徽标」。part 2 逐页核 V1 实际字重,**上限封 600**,不引入 700(铁律优先于报告 S8)。 | S2 | mono 字号分级 | ✅(基建) | design-restraint.css 加 `.mono-10/.mono-11/.mono-12`(10.5/11/11.5px)工具类。**逐页应用并入 Wave 4 P2 扫**(计划本就如此)。 |
| S6 | 批量栏共享 | ⏭️ 挪 Wave 3 | products 的 `.bulk-bar`/`.card-check` **蓝本已在**;其价值是接线到 projects/library = 逐页活,放 Wave 3 重建这俩页时连 CSS 提共享 + 接线一起做才连贯(现在只挪 CSS 无消费者=空动作)。 |
| S7 | 富分页器 | ✅(组件) | `pager.tsx` 加**可选** props:`onPageSizeChange`(每页条数点击循环 12/24/48/96)+ 跳页输入(>7 页显示)+ `sticky` 吸底;旧调用方不传则退化为原只读分页器(零破坏)。CSS 加 `.size-cycle/.jump/.sticky`。**接线到 products/library + 服务端分页放 Wave 3**(Wave 0 暂缓项一并)。 |
| S8 | hero 字重 | ✅(改 700,证据翻案) | **核 V1 实测**:account.html `.balance-hero .v`/`.balance-sub .v`/`.recharge-card .amt` 与 team.html banner 数字**都是 `font-weight:700`**。V1 设计稿是用户拍板的「视觉标准答案」,虽与 design.md §2.5「700 仅 Ctrl-K」冲突,但**用户明确要还原 V1**→ 据实改 700(account 3 处 + team 2 处)。实测 `.balance-hero .v`=700、`.banner-id .nm`=700。⚠️ 记录此 design.md §2.5 与 V1 设计稿的内部不一致。 |
**part 2 验证:** `tsc --noEmit` 0 + `npm run build` 0;computed-style 实测(橙=`rgb(250,93,25)`、hero 字重=700);pixelmatch(修正 Windows file URL 后)account **1.23%** / team **2.13%**(低,字体/真实数据残差,达标);14 路由回归 boot 全过、登录 639ms。截图 `_qa_shots/wave1b/*.png`
> 注:`core/qa/visual-parity``compare-all.mjs` 硬编码了原作者 Mac 路径 designRoot、`compare-page.mjs` 不能同时吃 file:// source + token(预存 harness bug,与本次改动无关)——Wave 3 逐页还原把 pixelmatch 当主力工具时一并参数化修掉。
**Wave 1 整体 ✅(S1/S2/S3/S4/S7/S8/S9 落地;S6 挪 Wave 3,S2/S7 逐页应用挪 Wave 3/4)。**