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:
co-authored by
Claude Opus 4.8
parent
74e3c818c5
commit
42c3c046ea
@@ -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); }
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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
@@ -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)。**
|
||||||
|
|||||||
Reference in New Issue
Block a user