refactor(ui): 合并双组件库为 components/ui 基元层,设计令牌变量化,报头字体子集自托管

- 新建 components/ui/(Button/Input/Select/Modal/Tabs/Spinner/Skeleton 等),变体用联合类型约束,根治幻影变体
- admin/components.tsx 与 matches/ui.tsx 改为再导出壳,消除 Spinner 三份重复
- 日期工具收口到 lib/date.ts,滚动监听收口到 lib/useScroll.ts
- index.css 引入 :root RGB 三元组令牌,tailwind.config 接 <alpha-value>(修复透明度修饰符静默失效)
- 刘建毛草 5MB 全字库(jsDelivr 未锁版) → 1KB 自托管子集(仅「先知」二字,unicode-range 限定)
This commit is contained in:
2026-09-22 23:44:46 +08:00
parent a7e8b75503
commit 2cfc9ccc0f
18 changed files with 1226 additions and 569 deletions
+72 -5
View File
@@ -2,11 +2,57 @@
@tailwind components;
@tailwind utilities;
/* 毛体草书(刘建毛草) - 国内 CDN,离线回退粗楷体 */
/*
设计令牌(颜色)的 CSS 侧来源。
─────────────────────────────────────────────────────────────
此前 index.css 里三处直接写死了十六进制字面量:
· :focus-visible 的 outline → #9e1b1b
· .masthead-rule 的 border-top → #17140f
· .font-brush 的 color → #9E1B1B(还是大写写法)
它们与 tailwind.config.js 的 press / ink 令牌是同一批颜色,却没有
共享来源。后果:调整品牌红时改令牌不会影响这三处 —— 焦点轮廓、
报头粗线、毛笔字会保持旧色,出现「两个印报红,其中一个没有名字」。
现在把颜色定义在 :root,由 tailwind.config.js 消费同一份变量,
使 CSS 与工具类共享单一来源。改色只需改这里一处。
⚠️ 值必须写成 RGB 三元组(如 23 20 15)而非 #17140F。
原因:Tailwind 的透明度修饰符(bg-press/60)需要把颜色拆成
rgb(R G B / <alpha-value>) 才能注入 alpha。若变量本身是完整
十六进制,`bg-press-wash/60` 这类写法会**静默不生成任何 CSS** ——
编译不报错,但样式消失(实测踩到过)。三元组写法同时兼容
不需要 alpha 的场景。
*/
:root {
/* 纸白:微暖底色,像新闻纸而不是纯白画布 */
--paper-50: 253 252 248;
--paper-100: 247 244 236;
--paper-200: 237 233 222;
--paper-300: 221 215 199;
/* 墨色:暖黑灰阶 */
--ink-900: 23 20 15;
--ink-700: 59 54 46;
--ink-500: 110 103 91;
--ink-400: 156 149 135;
--ink-300: 201 196 184;
--ink-200: 226 223 215;
/* 印报红:全站唯一强调色 */
--press: 158 27 27;
--press-dark: 124 20 20;
--press-wash: 247 233 228;
}
/* 毛体草书(刘建毛草) - 本地自托管子集(仅「先知」两字,约 1KB)
原先直连 jsDelivr 的 @main 分支(未锁版本、国内加载不稳、5MB 全字库)。
现仅保留报头用字,构建期随包发布,消除外部单点依赖。 */
@font-face {
font-family: 'Liu Jian Mao Cao';
src: url('https://cdn.jsdelivr.net/gh/google/fonts@main/ofl/liujianmaocao/LiuJianMaoCao-Regular.ttf') format('truetype');
src: url('./assets/fonts/LiuJianMaoCao-subset.woff2') format('woff2');
font-display: swap;
font-weight: 400;
unicode-range: U+5148, U+77E5; /* 先 U+5148 / 知 U+77E5 */
}
/* 粗毛笔字效果:笔触加粗 + 微描边 */
@@ -48,7 +94,7 @@
/* 统一焦点环:印报红细线,键盘可达 */
:focus-visible {
outline: 2px solid #9e1b1b;
outline: 2px solid var(--press);
outline-offset: 2px;
}
@@ -82,7 +128,7 @@
@layer components {
/* ── 报头双线:粗线在上、细线在下 ── */
.masthead-rule {
border-top: 3px solid #17140f;
border-top: 3px solid var(--ink-900);
}
/* ── 按钮:方正边框式,悬停反白 ── */
@@ -109,6 +155,15 @@
@apply border-ink-900 bg-transparent text-ink-900
hover:border-press hover:bg-press-wash hover:text-press-dark;
}
/* 幽灵按钮:无边、无底色,仅 hover 时浮出浅纸底。
用于「重置」「取消」等最轻量的次要动作。
(EvalPage 曾使用但从未定义,与 btn-outline 同型问题;
现由 src/components/ui 的 Button variant 类型约束兜底,不再可能静默失效) */
.btn-ghost {
@apply border-transparent bg-transparent text-ink-500
hover:bg-paper-200 hover:text-ink-900
disabled:hover:bg-transparent disabled:hover:text-ink-500;
}
/* ── 弹窗入场:遮罩淡入 + 面板上浮(仅 opacity/transform,GPU 友好) ── */
.modal-overlay-enter {
@@ -126,6 +181,18 @@
to { opacity: 1; transform: translateY(0); }
}
/* ── 不确定态进度条(indeterminate) ──
用于「耗时未知」的等待场景(如多专家预测)。与确定性进度条的区别
是它不表达百分比,只表达「仍在进行」—— 因此不存在「卡在 95%」
这种误导。transform-only,GPU 友好。 */
.predict-scan {
animation: predict-scan 1.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes predict-scan {
0% { transform: translateX(-100%); }
100% { transform: translateX(300%); }
}
/* ── 统一空态 ── */
.empty-state {
@apply border-y border-ink-200 py-12 text-center;
@@ -192,7 +259,7 @@
.nav-icon-wrap:hover .nav-icon,
.nav-icon-wrap.active .nav-icon {
opacity: 1;
color: #9E1B1B;
color: var(--press);
stroke-width: 2;
}
}