Files
Profeto/frontend/src/index.css
T
shangfangjian 2cfc9ccc0f 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 限定)
2026-09-22 23:44:46 +08:00

270 lines
8.4 KiB
CSS

@tailwind base;
@tailwind components;
@tailwind utilities;
/*
设计令牌(颜色)的 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('./assets/fonts/LiuJianMaoCao-subset.woff2') format('woff2');
font-display: swap;
font-weight: 400;
unicode-range: U+5148, U+77E5; /* 先 U+5148 / 知 U+77E5 */
}
/* 粗毛笔字效果:笔触加粗 + 微描边 */
.font-brush {
font-weight: 700;
-webkit-text-stroke: 0.3px currentColor;
text-shadow: 0.5px 0.5px 0 currentColor;
}
/* 页面内容横向轻扫+淡入 */
.page-content-enter {
animation: pageSlideIn 0.3s ease-out;
}
@keyframes pageSlideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@layer base {
html {
-webkit-text-size-adjust: 100%;
text-rendering: optimizeLegibility;
}
body {
@apply bg-paper-50 text-ink-800 font-sans antialiased;
font-feature-settings: 'tnum' 1; /* 数字等宽:比分/百分比不跳动 */
}
::selection {
@apply bg-press-wash text-press;
}
/* 统一焦点环:印报红细线,键盘可达 */
:focus-visible {
outline: 2px solid var(--press);
outline-offset: 2px;
}
/* 细滚动条 */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
@apply rounded-full bg-ink-300;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-ink-400;
}
/* 尊重「减少动态效果」偏好 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
}
@layer components {
/* ── 报头双线:粗线在上、细线在下 ── */
.masthead-rule {
border-top: 3px solid var(--ink-900);
}
/* ── 按钮:方正边框式,悬停反白 ── */
.btn {
@apply inline-flex items-center justify-center gap-1.5 border border-ink-300 bg-transparent px-3
text-sm text-ink-700 transition-colors duration-150
hover:border-ink-900 hover:bg-ink-900 hover:text-paper-50
disabled:cursor-not-allowed disabled:opacity-40
disabled:hover:border-ink-300 disabled:hover:bg-transparent disabled:hover:text-ink-700
min-h-[44px] py-1.5;
}
.btn-sm {
@apply px-2.5 text-xs min-h-[36px];
}
.btn-solid {
@apply border-ink-900 bg-ink-900 text-paper-50 hover:border-press hover:bg-press;
}
.btn-danger {
@apply border-press bg-transparent text-press hover:bg-press hover:text-paper-50;
}
/* 描边确认按钮:次级动作中需要比默认 btn 更明确轮廓的场合
(此前三处使用但从未定义,样式静默失效) */
.btn-outline {
@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 {
animation: modal-fade 0.18s ease-out both;
}
.modal-panel-enter {
animation: modal-rise 0.2s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes modal-fade {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes modal-rise {
from { opacity: 0; transform: translateY(12px); }
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;
}
.empty-state-title {
@apply font-serif text-sm text-ink-600;
}
.empty-state-sub {
@apply mt-1.5 text-xs text-ink-400;
}
.empty-state-action {
@apply mt-4 inline-flex items-center gap-2 text-2xs text-press hover:text-press-dark transition-colors;
}
/* ── 统一错误横幅(前台用,后台用 Alert) ── */
.error-banner {
@apply flex items-start justify-between gap-3 border border-press bg-press-wash px-4 py-3;
}
.error-banner-title {
@apply text-sm font-medium text-press;
}
.error-banner-detail {
@apply mt-0.5 text-xs text-ink-600;
}
/* ── 表单控件:方正、无圆角 ── */
.field {
@apply border border-ink-300 bg-transparent px-2.5 py-1.5 text-sm text-ink-800
transition-colors hover:border-ink-400
focus:border-press focus:outline-none;
}
/* ── 版面切换文字标签(联赛/状态/模式) ── */
.tab {
@apply relative whitespace-nowrap px-0.5 py-1 text-sm text-ink-500 transition-colors hover:text-ink-900;
}
.tab-on {
@apply font-medium text-press;
}
.tab-on::after {
content: '';
@apply absolute inset-x-0 bottom-0 h-0.5 bg-press;
}
/* ── 小节标题:宋体加粗 + 墨色底线 ── */
.section-head {
@apply border-b border-ink-900 pb-1.5 font-serif text-sm font-bold text-ink-900;
}
/* ── 骨架占位:低调脉动,不用渐变扫光 ── */
.skeleton {
@apply animate-pulse rounded-none bg-ink-200;
}
/* ── 侧边栏图标:统一样式 + hover过渡到品牌色 ── */
.nav-icon {
width: 1.125rem;
height: 1.125rem;
stroke-width: 1.5;
flex-shrink: 0;
opacity: 0.5;
transition: all 0.2s ease;
}
.nav-icon-wrap:hover .nav-icon,
.nav-icon-wrap.active .nav-icon {
opacity: 1;
color: var(--press);
stroke-width: 2;
}
}
/* ── Admin 后台 ──
与前台共用同一套报刊风组件(.btn/.field/.tab/.section-head/.skeleton),
不再单独维护暗色主题。 */