为 html[data-theme='dark'] 添加完整 CSS 变量覆盖块,建立暗黑模式基石。
--color-ink 收窄为'文字/描边跟随主题'语义,深色 UI 元素(卡片/按钮/遮罩/
代码块/页脚)迁到 --color-dark-bg / --color-overlay 独立 token,避免 ink
反色为浅色文字时连带把深色区块变浅(双语义冲突)。
【基础设施】
- globals.css:
* html/body 顶部硬编码 #FFFFFF !important 改 var() 化(保留 !important)
* :root 新增 --color-overlay / --color-overlay-rgb(遮罩/深色叠加层,
永不反色)
* 新增 html[data-theme='dark'] 覆盖块:墨色反色为浅色文字,bg/text/
border 层级反色,link 反色;保留 --color-dark-* / --color-brand-section
* pre 背景 var(--color-ink) → var(--color-dark-bg)
- tailwind.config.js:注册 overlay 色(rgb() 形式,支持 /50 /60 修饰符)
- layout.tsx:head 内联防 FOUC 同步脚本,读 localStorage['novalon-theme']
设置 data-theme(当前无切换 UI,仅作未来挂载点)
【首页试点 + 布局组件】(浅色值完全等价 → 浅色零视觉差异)
- home-content-v15.tsx:7 处 bg-white → bg-bg-primary(narrative/insights/
cases sections 与卡片),2 处 bg-ink → bg-dark-bg(案例卡 bg-dark-bg 不
随反色,保留深色画布)
- header.tsx / mega-dropdown.tsx:bg-white → bg-bg-primary(导航栏/移动
端菜单/下拉面板)
- footer.tsx:bg-[#0A0E14] → bg-dark-bg(语义统一,视觉零差异)
【全局 UI 组件】(深色区块语义统一 + 遮罩固定深色)
- button.tsx:secondary 'bg-ink hover:bg-ink-light' → bg-dark-bg / hover:bg-dark-bg-secondary;
outline 'hover:bg-ink hover:border-ink' → hover:bg-dark-bg / border-dark-bg
- stats-showcase / milestone-timeline:isDark 分支 bg-ink → bg-dark-bg
- metric-card:isDark 分支 bg-ink/50 → bg-overlay/50,hover:bg-ink → hover:bg-overlay
- dialog / alert-dialog:遮罩 bg-ink/60 → bg-overlay/60(永不反色)
- button.test.tsx:断言 bg-ink → bg-dark-bg,注释同步
【验证】
- type-check 0 errors
- lint 0 errors(127 warnings 既有,非本次引入)
- jest 128 suites / 1623 passed(button 断言同步通过)
- 视觉回归先非 update:21 passed(浅色安全门零 diff)+ 1 failed
(仅 theme-dark-main,dark 快照从浅色名义变真正深色 = 语义修复预期)
- update-snapshots:22 passed,仅 theme-dark-main 基线重写
- computed-style 权威验证(一次性脚本,已删除):
dark 7 项断言全过(html/body/hero/导航栏/案例卡→深色 #0A0E14;
body 文字→浅色 #F8FAFC;footer/overlay→保持深色);light 4 项零回归
【未在 Phase 1 范围】(已识别,留待 Phase 2/3 决策)
- service-detail-content-v4.tsx 等营销页 bg-white:Phase 2 批量 token 化
- admin 页面 bg-white + RichTextEditor:admin 边界决策 + Phase 3
- 品牌红 #C41E3A 在深色底对比度复检:Phase 3
267 lines
10 KiB
JavaScript
267 lines
10 KiB
JavaScript
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: [
|
|
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
],
|
|
darkMode: 'class',
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
/* 重要:Tailwind v3 无法对纯 hex 的 CSS 变量套用 /50 这类透明度修饰符,
|
|
会生成无效 CSS 并静默失效(样式不报错,但效果不出现)。
|
|
故常用色改用 rgb(通道 / <alpha-value>) 形式 —— 无修饰符时 alpha 默认 1,
|
|
视觉结果与原来一致,同时 bg-brand/10、border-ink/30 等能正确编译。 */
|
|
ink: {
|
|
DEFAULT: 'rgb(var(--color-ink-rgb) / <alpha-value>)',
|
|
light: 'var(--color-ink-light)',
|
|
lighter: 'var(--color-ink-lighter)',
|
|
rgb: 'var(--color-ink-rgb)',
|
|
},
|
|
brand: {
|
|
DEFAULT: 'rgb(var(--color-brand-rgb) / <alpha-value>)',
|
|
hover: 'var(--color-brand-hover)',
|
|
light: 'var(--color-brand-light)',
|
|
soft: 'rgb(var(--color-brand-rgb) / calc(0.12 * <alpha-value>))',
|
|
bg: 'var(--color-brand-bg)',
|
|
rgb: 'var(--color-brand-rgb)',
|
|
section: 'var(--color-brand-section)',
|
|
'section-text': 'var(--color-brand-section-text)',
|
|
'section-muted': 'var(--color-brand-section-muted)',
|
|
},
|
|
accent: {
|
|
blue: 'rgb(var(--color-accent-blue-rgb) / <alpha-value>)',
|
|
'blue-soft': 'var(--color-accent-blue-soft)',
|
|
teal: 'rgb(var(--color-accent-teal-rgb) / <alpha-value>)',
|
|
'teal-soft': 'var(--color-accent-teal-soft)',
|
|
amber: 'rgb(var(--color-accent-amber-rgb) / <alpha-value>)',
|
|
'amber-soft': 'var(--color-accent-amber-soft)',
|
|
purple: 'rgb(var(--color-accent-purple-rgb) / <alpha-value>)',
|
|
'purple-soft': 'var(--color-accent-purple-soft)',
|
|
cyan: 'rgb(var(--color-accent-cyan-rgb) / <alpha-value>)',
|
|
'cyan-soft': 'var(--color-accent-cyan-soft)',
|
|
},
|
|
bg: {
|
|
primary: 'rgb(var(--color-bg-primary-rgb) / <alpha-value>)',
|
|
secondary: 'rgb(var(--color-bg-secondary-rgb) / <alpha-value>)',
|
|
tertiary: 'var(--color-bg-tertiary)',
|
|
section: 'var(--color-bg-section)',
|
|
hover: 'var(--color-bg-hover)',
|
|
elevated: 'var(--color-bg-elevated)',
|
|
},
|
|
text: {
|
|
primary: 'var(--color-text-primary)',
|
|
secondary: 'rgb(var(--color-text-secondary-rgb) / <alpha-value>)',
|
|
tertiary: 'var(--color-text-tertiary)',
|
|
muted: 'rgb(var(--color-text-muted-rgb) / <alpha-value>)',
|
|
subtle: 'var(--color-text-subtle)',
|
|
placeholder: 'var(--color-text-placeholder)',
|
|
hint: 'var(--color-text-hint)',
|
|
inverse: 'var(--color-text-inverse)',
|
|
},
|
|
border: {
|
|
primary: 'rgb(var(--color-border-primary-rgb) / <alpha-value>)',
|
|
secondary: 'rgb(var(--color-border-secondary-rgb) / <alpha-value>)',
|
|
accent: 'rgb(var(--color-border-accent-rgb) / <alpha-value>)',
|
|
light: 'rgb(var(--color-border-light-rgb) / <alpha-value>)',
|
|
dark: 'rgb(var(--color-border-dark-rgb) / <alpha-value>)',
|
|
brand: 'rgb(var(--color-border-brand-rgb) / <alpha-value>)',
|
|
},
|
|
link: {
|
|
DEFAULT: 'var(--color-link)',
|
|
hover: 'var(--color-link-hover)',
|
|
},
|
|
success: {
|
|
DEFAULT: 'rgb(var(--color-success-rgb) / <alpha-value>)',
|
|
hover: 'var(--color-success-hover)',
|
|
bg: 'var(--color-success-bg)',
|
|
rgb: 'var(--color-success-rgb)',
|
|
/** 文字请用此变体:DEFAULT 仅 3.30:1,不达 AA */
|
|
text: 'var(--color-success-text)',
|
|
},
|
|
warning: {
|
|
DEFAULT: 'rgb(var(--color-warning-rgb) / <alpha-value>)',
|
|
hover: 'var(--color-warning-hover)',
|
|
bg: 'var(--color-warning-bg)',
|
|
rgb: 'var(--color-warning-rgb)',
|
|
/** 文字请用此变体:DEFAULT 仅 3.19:1,不达 AA */
|
|
text: 'var(--color-warning-text)',
|
|
},
|
|
error: {
|
|
DEFAULT: 'rgb(var(--color-error-rgb) / <alpha-value>)',
|
|
hover: 'var(--color-error-hover)',
|
|
bg: 'var(--color-error-bg)',
|
|
rgb: 'var(--color-error-rgb)',
|
|
},
|
|
info: {
|
|
DEFAULT: 'rgb(var(--color-info-rgb) / <alpha-value>)',
|
|
bg: 'var(--color-info-bg)',
|
|
/** 文字请用此变体:DEFAULT 仅 3.68:1,不达 AA */
|
|
text: 'var(--color-info-text)',
|
|
},
|
|
dark: {
|
|
bg: 'var(--color-dark-bg)',
|
|
'bg-secondary': 'var(--color-dark-bg-secondary)',
|
|
'bg-tertiary': 'var(--color-dark-bg-tertiary)',
|
|
'text-primary': 'var(--color-dark-text-primary)',
|
|
'text-secondary': 'var(--color-dark-text-secondary)',
|
|
'text-muted': 'var(--color-dark-text-muted)',
|
|
border: 'var(--color-dark-border)',
|
|
},
|
|
overlay: 'rgb(var(--color-overlay-rgb) / <alpha-value>)',
|
|
},
|
|
fontFamily: {
|
|
sans: ['var(--font-sans)'],
|
|
display: ['var(--font-display)'],
|
|
mono: ['var(--font-mono)'],
|
|
brand: ['var(--font-brand)'],
|
|
serif: ['var(--font-serif)'],
|
|
},
|
|
fontSize: {
|
|
caption: 'var(--font-size-caption)',
|
|
xs: 'var(--font-size-xs)',
|
|
sm: 'var(--font-size-sm)',
|
|
base: 'var(--font-size-base)',
|
|
lg: 'var(--font-size-lg)',
|
|
xl: 'var(--font-size-xl)',
|
|
'2xl': 'var(--font-size-2xl)',
|
|
'3xl': 'var(--font-size-3xl)',
|
|
'4xl': 'var(--font-size-4xl)',
|
|
'5xl': 'var(--font-size-5xl)',
|
|
'6xl': 'var(--font-size-6xl)',
|
|
'7xl': 'var(--font-size-7xl)',
|
|
display: 'var(--font-size-display)',
|
|
},
|
|
lineHeight: {
|
|
caption: 'var(--line-height-caption)',
|
|
tight: 'var(--line-height-tight)',
|
|
snug: 'var(--line-height-snug)',
|
|
normal: 'var(--line-height-normal)',
|
|
relaxed: 'var(--line-height-relaxed)',
|
|
},
|
|
letterSpacing: {
|
|
tighter: 'var(--letter-spacing-tighter)',
|
|
tight: 'var(--letter-spacing-tight)',
|
|
normal: 'var(--letter-spacing-normal)',
|
|
wide: 'var(--letter-spacing-wide)',
|
|
wider: 'var(--letter-spacing-wider)',
|
|
widest: 'var(--letter-spacing-widest)',
|
|
eyebrow: 'var(--letter-spacing-eyebrow)',
|
|
},
|
|
spacing: {
|
|
xs: 'var(--spacing-xs)',
|
|
sm: 'var(--spacing-sm)',
|
|
md: 'var(--spacing-md)',
|
|
lg: 'var(--spacing-lg)',
|
|
xl: 'var(--spacing-xl)',
|
|
'2xl': 'var(--spacing-2xl)',
|
|
'3xl': 'var(--spacing-3xl)',
|
|
'4xl': 'var(--spacing-4xl)',
|
|
'5xl': 'var(--spacing-5xl)',
|
|
'6xl': 'var(--spacing-6xl)',
|
|
},
|
|
borderRadius: {
|
|
xs: 'var(--radius-xs)',
|
|
sm: 'var(--radius-sm)',
|
|
md: 'var(--radius-md)',
|
|
lg: 'var(--radius-lg)',
|
|
xl: 'var(--radius-xl)',
|
|
'2xl': 'var(--radius-2xl)',
|
|
'3xl': 'var(--radius-3xl)',
|
|
full: 'var(--radius-full)',
|
|
},
|
|
boxShadow: {
|
|
xs: 'var(--shadow-xs)',
|
|
sm: 'var(--shadow-sm)',
|
|
md: 'var(--shadow-md)',
|
|
lg: 'var(--shadow-lg)',
|
|
xl: 'var(--shadow-xl)',
|
|
brand: 'var(--shadow-brand)',
|
|
'brand-hover': 'var(--shadow-brand-hover)',
|
|
inset: 'var(--shadow-inset)',
|
|
glow: 'var(--shadow-glow)',
|
|
},
|
|
transitionDuration: {
|
|
instant: 'var(--transition-instant)',
|
|
fast: 'var(--transition-fast)',
|
|
normal: 'var(--transition-normal)',
|
|
slow: 'var(--transition-slow)',
|
|
slower: 'var(--transition-slower)',
|
|
gentle: 'var(--transition-gentle)',
|
|
},
|
|
transitionTimingFunction: {
|
|
standard: 'var(--ease-standard)',
|
|
decelerate: 'var(--ease-decelerate)',
|
|
accelerate: 'var(--ease-accelerate)',
|
|
'ease-out': 'var(--ease-ink)',
|
|
'ease-in-out': 'var(--ease-sharp)',
|
|
'ease-spring': 'var(--ease-spring)',
|
|
'ease-spring-soft': 'var(--ease-spring-soft)',
|
|
sharp: 'var(--ease-sharp)',
|
|
ink: 'var(--ease-ink)',
|
|
},
|
|
maxWidth: {
|
|
container: 'var(--container-max)',
|
|
},
|
|
animation: {
|
|
'fade-in-up': 'fadeInUp 0.7s var(--ease-out) forwards',
|
|
'fade-in': 'fadeIn 0.5s var(--ease-out) forwards',
|
|
'scale-in': 'scaleIn 0.5s var(--ease-out) forwards',
|
|
'slide-in-left': 'slideInLeft 0.6s var(--ease-out) forwards',
|
|
'slide-in-right': 'slideInRight 0.6s var(--ease-out) forwards',
|
|
'pulse-soft': 'pulse-soft 2s ease-in-out infinite',
|
|
'ripple': 'ripple 0.6s ease-out forwards',
|
|
'skeleton-pulse': 'skeletonPulse 2s ease-in-out infinite',
|
|
'accordion-down': 'accordionDown 0.2s ease-out forwards',
|
|
'accordion-up': 'accordionUp 0.2s ease-out forwards',
|
|
},
|
|
keyframes: {
|
|
fadeInUp: {
|
|
from: { opacity: '0', transform: 'translateY(24px)' },
|
|
to: { opacity: '1', transform: 'translateY(0)' },
|
|
},
|
|
fadeIn: {
|
|
from: { opacity: '0' },
|
|
to: { opacity: '1' },
|
|
},
|
|
slideInLeft: {
|
|
from: { opacity: '0', transform: 'translateX(-32px)' },
|
|
to: { opacity: '1', transform: 'translateX(0)' },
|
|
},
|
|
slideInRight: {
|
|
from: { opacity: '0', transform: 'translateX(32px)' },
|
|
to: { opacity: '1', transform: 'translateX(0)' },
|
|
},
|
|
scaleIn: {
|
|
from: { opacity: '0', transform: 'scale(0.95)' },
|
|
to: { opacity: '1', transform: 'scale(1)' },
|
|
},
|
|
'pulse-soft': {
|
|
'0%, 100%': { opacity: '1' },
|
|
'50%': { opacity: '0.6' },
|
|
},
|
|
ripple: {
|
|
'0%': { transform: 'scale(0)', opacity: '0.5' },
|
|
'100%': { transform: 'scale(4)', opacity: '0' },
|
|
},
|
|
skeletonPulse: {
|
|
'0%, 100%': { backgroundColor: 'var(--color-bg-tertiary)', opacity: '1' },
|
|
'50%': { backgroundColor: 'color-mix(in srgb, var(--color-brand) 8%, var(--color-bg-tertiary))', opacity: '0.8' },
|
|
},
|
|
accordionDown: {
|
|
from: { height: '0', opacity: '0' },
|
|
to: { height: 'var(--radix-accordion-content-height)', opacity: '1' },
|
|
},
|
|
accordionUp: {
|
|
from: { height: 'var(--radix-accordion-content-height)', opacity: '1' },
|
|
to: { height: '0', opacity: '0' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
plugins: [
|
|
require('tailwindcss-animate'),
|
|
],
|
|
};
|