feat(theme): 暗黑模式 Phase 1 基础设施 + 首页试点
为 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
This commit is contained in:
@@ -333,7 +333,7 @@ function TrustSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
</p>
|
||||
<div className="grid sm:grid-cols-3 gap-4 mb-8">
|
||||
{earlyStatus.map((s) => (
|
||||
<div key={s.label} className="p-4 bg-white border border-brand/10">
|
||||
<div key={s.label} className="p-4 bg-bg-primary border border-brand/10">
|
||||
<div className="text-sm font-semibold text-brand mb-1">{s.label}</div>
|
||||
<div className="text-xs text-text-secondary leading-relaxed">{s.desc}</div>
|
||||
</div>
|
||||
@@ -389,7 +389,7 @@ function NarrativeSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
}>;
|
||||
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white" data-testid="narrative-section">
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-primary" data-testid="narrative-section">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<ScrollReveal className="mb-16 sm:mb-20">
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
|
||||
@@ -400,7 +400,7 @@ function NarrativeSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
<div className="grid md:grid-cols-3 gap-px bg-border-primary">
|
||||
{NARRATIVE_ACTS_CMS.map((act) => (
|
||||
<StaggerReveal key={act.number} staggerDelay={0.08}>
|
||||
<div className="group relative h-full bg-white p-8 sm:p-10 md:p-12 transition-colors duration-300 hover:bg-bg-secondary">
|
||||
<div className="group relative h-full bg-bg-primary p-8 sm:p-10 md:p-12 transition-colors duration-300 hover:bg-bg-secondary">
|
||||
<div className="flex items-start justify-between mb-8">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -447,7 +447,7 @@ function InsightsSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
}>;
|
||||
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white" data-testid="insights-section">
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-primary" data-testid="insights-section">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-8 mb-16 sm:mb-20">
|
||||
<ScrollReveal className="md:max-w-3xl">
|
||||
@@ -480,7 +480,7 @@ function InsightsSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
<StaggerReveal key={i} staggerDelay={0.07}>
|
||||
<a
|
||||
href={insight.href}
|
||||
className="group flex flex-col h-full bg-white p-8 sm:p-10 md:p-12 transition-colors duration-300 hover:bg-bg-secondary"
|
||||
className="group flex flex-col h-full bg-bg-primary p-8 sm:p-10 md:p-12 transition-colors duration-300 hover:bg-bg-secondary"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
@@ -645,7 +645,7 @@ function CasesSection({ cases, pageCopy }: { cases?: Record<string, any>[]; page
|
||||
|
||||
if (CASE_ITEMS.length === 0) {
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white">
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-primary">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
<ScrollReveal>
|
||||
@@ -670,7 +670,7 @@ function CasesSection({ cases, pageCopy }: { cases?: Record<string, any>[]; page
|
||||
</StaticLink>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.15}>
|
||||
<div className="relative overflow-hidden rounded-[2rem] bg-ink p-10 sm:p-12 h-full flex flex-col shadow-card">
|
||||
<div className="relative overflow-hidden rounded-[2rem] bg-dark-bg p-10 sm:p-12 h-full flex flex-col shadow-card">
|
||||
{/* 背景纹理:极淡白色点阵(对标埃森哲深色 spotlight 画布) */}
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none opacity-[0.05]"
|
||||
@@ -722,7 +722,7 @@ function CasesSection({ cases, pageCopy }: { cases?: Record<string, any>[]; page
|
||||
const spotlightTestimonial = spotlightCase?.testimonial as Record<string, any> | undefined;
|
||||
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white">
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-primary">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-8 mb-16 sm:mb-20">
|
||||
<ScrollReveal className="md:max-w-3xl">
|
||||
@@ -746,7 +746,7 @@ function CasesSection({ cases, pageCopy }: { cases?: Record<string, any>[]; page
|
||||
<ScrollReveal className="mb-6">
|
||||
<a
|
||||
href={spotlightCase.href || `/cases/${spotlightCase.slug || ''}`}
|
||||
className="group relative block overflow-hidden rounded-[2rem] bg-ink p-10 sm:p-14 h-full flex flex-col shadow-card"
|
||||
className="group relative block overflow-hidden rounded-[2rem] bg-dark-bg p-10 sm:p-14 h-full flex flex-col shadow-card"
|
||||
>
|
||||
{/* 背景纹理:极淡白色点阵 */}
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user