refactor(theme): 营销页 bg-white token 化(暗黑模式 Phase 2 · marketing 批次)

问题:Tailwind 原生 bg-white 是硬编码值,深色主题下不翻转,
导致整页/整 section 恒为纯白,与转深色的页头页脚割裂。
(globals.css:--color-bg-primary-rgb 浅色 255 255 255 → 深色 10 14 20)

改动:10 文件 67 处 bg-white → 项目 token,按语义二分类
- bg-bg-primary  (33 处):页面/大块骨架(min-h-screen 容器、<section>、<main>)
- bg-bg-elevated (34 处):浮起层(带 border 的卡片、表单输入、图标盒、grid 单元格)
  --color-bg-elevated 浅色 #FFFFFF → 深色 #151B23,同样翻转。

浅色主题下 bg-white / bg-bg-primary / bg-bg-elevated 三者同值(纯白),
故本次改动在浅色主题下零像素差异,只修复深色主题。

新增审计工具:
- scripts/audit/hardcoded-color-audit.mjs:硬编码颜色分级盘点
  (含 alpha 误报修正:bg-white/NN 半透明叠加两主题均成立,判 INFO 非 P0)
- scripts/audit/darkmode-bg-verify.mjs:直接验证背景随主题翻转
  (浅色应纯白、深色应 rgb(10,14,20) 且无残留纯白块)

审计结果:marketing P0 67 → 0;全站 P0 97 → 30
(剩余 components 16 / other 8 / layout 3 / ui-kit 3 为第二批;admin 本就为 0)

门禁:
- tsc 0 error
- eslint 0 error(50 warnings 全为既有,改动文件未新增)
- jest 129/130 套件通过,1548 通过 / 2 跳过 / **0 断言失败**
  唯一失败 src/lib/admin-api.test.ts 为**沙箱环境限制非代码问题**:
  CODEBUDDY_BROKER_DENY 拦截 readFileSync,单独复验同样失败,
  与本次改动零交集(改动仅限 src/app/(marketing)/ 下 10 个页面组件)
- 暗黑模式背景验证 18/18 PASS(9 路由 × 浅深双版)
This commit is contained in:
2026-09-04 13:46:29 +08:00
parent 8f4a860180
commit 846585af1f
13 changed files with 339 additions and 67 deletions
+2
View File
@@ -316,6 +316,8 @@ dogfood-output*/
dogfood-bain/
dogfood-motion-audit/
dogfood-b2-verify/
# 硬编码颜色审计产物(暗黑模式 Phase 2)—— 只提交脚本与报告
dogfood-color-audit/
# UI/UX/UE 审计采集产物(截图 + probes.json,单轮可达 39MB)—— 只提交脚本与报告
dogfood-ui-audit/
+102
View File
@@ -0,0 +1,102 @@
// 暗黑模式背景翻转验证(Phase 2 token 化专用)
//
// 用途:直接验证「背景随主题翻转」这一改动目标,而不只是验证「页面没报错」。
//
// 机理(globals.css):
// --color-bg-primary-rgb 浅色 255 255 255 (L70) → 深色 10 14 20 (L409)
// --color-bg-elevated 浅色 #FFFFFF (L69) → 深色 #151B23 (L415)
// 因此浅色主题下 bg-bg-primary / bg-bg-elevated 都渲染为纯白 —— 与改动前的
// bg-white **像素一致**(零回归);深色主题下才显现差异。
//
// 断言:
// light : 根节点背景 == rgb(255, 255, 255)(与改动前一致)
// dark : 根节点背景 != rgb(255, 255, 255) 且亮度低(已翻转为深色)
// dark : 页面内不得残留大面积纯白块(抽样主要 section / 卡片)
//
// 环境约束(2026-09-02 踩坑固化):
// - 必须 localhost(非 127.0.0.1):Next dev allowedDevOrigins 白名单不含 127.0.0.1
// - 必须 --no-proxy-server:本机系统代理会拦 403
//
// CLI: node scripts/audit/darkmode-bg-verify.mjs [--routes /,/contact] [--out <dir>]
import { chromium } from 'playwright';
import { writeFileSync, mkdirSync } from 'node:fs';
const args = process.argv.slice(2);
const getList = (flag, def) => {
const i = args.indexOf(flag);
return i >= 0 && args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1].split(',') : def;
};
const ROUTES = getList('--routes', ['/', '/contact', '/products', '/solutions', '/team', '/cases', '/news', '/methodology', '/products/erp-upgrade']);
const BASE = 'http://localhost:3000';
const OUT = 'dogfood-color-audit';
mkdirSync(OUT, { recursive: true });
const browser = await chromium.launch({ args: ['--no-proxy-server'] });
/** 亮度:0(黑) ~ 255(白) */
function luma(rgb) {
const m = rgb.match(/\d+/g);
if (!m) return null;
const [r, g, b] = m.map(Number);
return Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b);
}
async function probe(path, theme) {
const ctx = await browser.newContext();
const page = await ctx.newPage();
await page.addInitScript((t) => {
try { localStorage.setItem('novalon-theme', t); } catch {}
}, theme);
const errors = [];
page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); });
page.on('pageerror', (e) => errors.push('PAGEERROR: ' + e.message));
await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 45000 });
const info = await page.evaluate(() => {
const pick = (el) => (el ? getComputedStyle(el).backgroundColor : null);
const body = pick(document.body);
// 抽样:主内容区的前若干 section / 大块容器
const blocks = Array.from(document.querySelectorAll('section, main, [class*="min-h-screen"]'))
.slice(0, 12)
.map((el) => getComputedStyle(el).backgroundColor);
return { body, blocks, html: getComputedStyle(document.documentElement).backgroundColor };
});
await ctx.close();
const bodyLuma = luma(info.body);
// 页面里仍然纯白的块(排除透明)
const whiteBlocks = info.blocks.filter((c) => luma(c) !== null && luma(c) >= 250);
let ok;
let note;
if (theme === 'light') {
ok = bodyLuma !== null && bodyLuma >= 250;
note = `body=${info.body} (luma=${bodyLuma}),浅色应为纯白(与改动前一致)`;
} else {
ok = bodyLuma !== null && bodyLuma < 60 && whiteBlocks.length === 0;
note = `body=${info.body} (luma=${bodyLuma}),残留纯白块 ${whiteBlocks.length}`;
}
return { path, theme, ok, bodyLuma, whiteBlocks: whiteBlocks.length, errors, note };
}
const results = [];
for (const r of ROUTES) {
results.push(await probe(r, 'light'));
results.push(await probe(r, 'dark'));
}
await browser.close();
const failed = results.filter((r) => !r.ok || r.errors.length > 0);
writeFileSync(`${OUT}/darkmode-bg-verify.json`, JSON.stringify({ generatedAt: new Date().toISOString(), routes: ROUTES, results, failedCount: failed.length }, null, 2));
for (const r of results) {
console.log(`${r.ok && r.errors.length === 0 ? 'PASS' : 'FAIL'} ${r.theme.padEnd(5)} ${r.path.padEnd(24)} ${r.note}`);
r.errors.slice(0, 2).forEach((e) => console.log(`${e.slice(0, 140)}`));
}
console.log(`\n${results.length - failed.length}/${results.length} PASS`);
process.exit(failed.length > 0 ? 1 : 0);
+168
View File
@@ -0,0 +1,168 @@
// 硬编码颜色审计(暗黑模式 Phase 2 盘点)
//
// 权威约束:CONTEXT.md / globals.css
// - 项目 tokenbg-bg-primary / text-text-primary / border-border-primary 等)
// 已重映射到 --color-*-rgb,在 html[data-theme='dark'] 下**会翻转**。
// - Tailwind 原生色(bg-white / bg-gray-100 / text-gray-900 等)是**硬编码值**
// 深色主题下**不翻转** → 浅底/深字在深底上不可读。
//
// 判定准则(务必区分,勿一刀切):
// P0 背景类浅色 bg-white / bg-gray-50..300 → 深底上仍是白块,视觉破损
// P0 文字类深色 text-gray-600..900 / text-black → 深底上对比度不足
// P1 边框浅色 border-gray-100..300 → 深底上过亮,层次错乱
// INFO 需人工判 text-white / border-white / bg-black
// (品牌红按钮白字、深色覆盖层白字是**正确**的,不是违规)
// INFO 半透明叠加 bg-white/10 / bg-black/20 等带 alpha 修饰符
// ——alpha 叠加是相对底色取混合,浅深两主题下**都成立**,非违规
//
// 排除:_archive / *.test.* / *.spec.* / node_modules
//
// CLI: node scripts/audit/hardcoded-color-audit.mjs [--json]
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { execSync } from 'node:child_process';
const OUT = 'dogfood-color-audit';
mkdirSync(OUT, { recursive: true });
const files = execSync(
"find src -type f \\( -name '*.ts' -o -name '*.tsx' \\) " +
"! -path '*/_archive/*' ! -name '*.test.*' ! -name '*.spec.*' | sort",
{ encoding: 'utf8' }
).trim().split('\n');
// Tailwind 原生中性色(会随主题翻转的 token 色不在其中)
const NEUTRAL = '(?:white|black|gray|slate|neutral|zinc|stone)';
// 规则表:顺序敏感,先匹配更具体的
const RULES = [
// 半透明叠加层:alpha 混合相对底色取值,浅深两主题下都成立 —— 非违规
// (务必放在实色规则之前,并用 (?!\/) 排除实色规则误吃 alpha 写法)
{ re: /\bbg-(?:white|black)\/\d+\b/g, sev: 'INFO', why: '半透明叠加层,两主题均成立' },
{ re: new RegExp(`\\b(?:text|border|divide)-${NEUTRAL}-\\d+/\\d+\\b`, 'g'), sev: 'INFO', why: '半透明叠加层,两主题均成立' },
// 背景:浅色背景在深底上变成白块 —— P0
{ re: new RegExp(`\\bbg-${NEUTRAL}-(?:50|100|200|300)(?!/)\\b`, 'g'), sev: 'P0', why: '浅色背景不翻转' },
{ re: /\bbg-white(?!\/)\b/g, sev: 'P0', why: '纯白背景不翻转' },
// 背景:深色背景在浅色主题下是黑块 —— P0(对称违规)
{ re: new RegExp(`\\bbg-${NEUTRAL}-(?:800|900|950)\\b`, 'g'), sev: 'P0', why: '深色背景不翻转' },
{ re: /\bbg-black\b/g, sev: 'P0', why: '纯黑背景不翻转' },
{ re: new RegExp(`\\bbg-${NEUTRAL}-(?:400|500|600|700)\\b`, 'g'), sev: 'P1', why: '中性背景不翻转' },
// 文字:深色文字在深底上不可读 —— P0
{ re: new RegExp(`\\btext-${NEUTRAL}-(?:600|700|800|900|950)\\b`, 'g'), sev: 'P0', why: '深色文字在深底上对比度不足' },
{ re: /\btext-black\b/g, sev: 'P0', why: '黑色文字在深底上不可读' },
{ re: new RegExp(`\\btext-${NEUTRAL}-(?:400|500)\\b`, 'g'), sev: 'P1', why: '中性文字不翻转' },
// 文字:浅色文字需人工判(品牌红按钮 / 深色覆盖层上是正确的)
{ re: /\btext-white\b/g, sev: 'INFO', why: '需人工判:品牌红按钮/深色覆盖层上为正确用法' },
{ re: new RegExp(`\\btext-${NEUTRAL}-(?:100|200|300)\\b`, 'g'), sev: 'INFO', why: '需人工判:深色底上为正确用法' },
// 边框:浅色边框在深底上过亮 —— P1
{ re: new RegExp(`\\bborder-${NEUTRAL}-(?:100|200|300)\\b`, 'g'), sev: 'P1', why: '浅色边框不翻转' },
{ re: new RegExp(`\\bborder-${NEUTRAL}-(?:700|800|900)\\b`, 'g'), sev: 'P1', why: '深色边框不翻转' },
{ re: /\bborder-white\b/g, sev: 'INFO', why: '需人工判' },
{ re: new RegExp(`\\bborder-${NEUTRAL}-(?:400|500|600)\\b`, 'g'), sev: 'P2', why: '中性边框不翻转' },
// 分割线 / 渐变 / 描边
{ re: new RegExp(`\\bdivide-${NEUTRAL}-(?:100|200|300)\\b`, 'g'), sev: 'P1', why: '分割线不翻转' },
{ re: new RegExp(`\\bplaceholder-${NEUTRAL}-\\d+\\b`, 'g'), sev: 'P2', why: '占位符文字不翻转' },
];
/** 按路径归类区域,便于分期推进 */
function areaOf(file) {
if (file.includes('/admin') || file.includes('app/admin')) return 'admin';
if (file.includes('/(marketing)/')) return 'marketing';
if (file.includes('/components/ui/')) return 'ui-kit';
if (file.includes('/components/layout/')) return 'layout';
if (file.includes('/components/')) return 'components';
return 'other';
}
const rows = [];
for (const f of files) {
let src;
try { src = readFileSync(f, 'utf8'); } catch { continue; }
const lines = src.split('\n');
const seen = new Set(); // 同一行同一 class 只记一次
lines.forEach((line, i) => {
const ln = i + 1;
const trimmed = line.trim();
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) return;
for (const rule of RULES) {
rule.re.lastIndex = 0;
for (const m of line.matchAll(rule.re)) {
const key = `${ln}:${m[0]}`;
if (seen.has(key)) continue;
seen.add(key);
rows.push({
file: f, ln, cls: m[0], sev: rule.sev, why: rule.why,
area: areaOf(f),
});
}
}
});
}
// 聚合
const byFile = {};
for (const r of rows) {
byFile[r.file] ??= { P0: 0, P1: 0, P2: 0, INFO: 0, area: r.area, items: [] };
byFile[r.file][r.sev]++;
byFile[r.file].items.push(r);
}
const counts = { P0: 0, P1: 0, P2: 0, INFO: 0 };
for (const r of rows) counts[r.sev]++;
const byArea = {};
for (const r of rows) {
byArea[r.area] ??= { P0: 0, P1: 0, P2: 0, INFO: 0 };
byArea[r.area][r.sev]++;
}
const report = {
generatedAt: new Date().toISOString(),
scannedFiles: files.length,
totalMatches: rows.length,
counts,
byArea,
filesWithViolations: Object.entries(byFile)
.filter(([, v]) => v.P0 + v.P1 + v.P2 > 0)
.map(([file, v]) => ({ file, area: v.area, P0: v.P0, P1: v.P1, P2: v.P2, INFO: v.INFO }))
.sort((a, b) => (b.P0 - a.P0) || (b.P1 - a.P1) || (b.P2 - a.P2)),
allViolations: rows.filter((r) => r.sev !== 'INFO'),
};
writeFileSync(`${OUT}/color-audit.json`, JSON.stringify(report, null, 2));
// Markdown 摘要
const md = [];
md.push('# 硬编码颜色审计(暗黑模式 Phase 2 盘点)\n');
md.push(`- 扫描文件:${files.length}(排除 _archive / test / spec`);
md.push(`- 匹配总数:${rows.length}\n`);
md.push('## 按严重度\n');
md.push('| 级别 | 数量 | 含义 |');
md.push('|---|---|---|');
md.push(`| **P0** | ${counts.P0} | 深底上白块 / 深字不可读(真 bug) |`);
md.push(`| P1 | ${counts.P1} | 边框 / 中性色不翻转 |`);
md.push(`| P2 | ${counts.P2} | 占位符等次要 |`);
md.push(`| INFO | ${counts.INFO} | 需人工判(多为正确用法) |\n`);
md.push('## 按区域(分期依据)\n');
md.push('| 区域 | P0 | P1 | P2 | INFO |');
md.push('|---|---|---|---|---|');
for (const [area, c] of Object.entries(byArea).sort((a, b) => b[1].P0 - a[1].P0)) {
md.push(`| ${area} | ${c.P0} | ${c.P1} | ${c.P2} | ${c.INFO} |`);
}
md.push('');
md.push('## P0 文件排行(前 25\n');
md.push('| 文件 | 区域 | P0 | P1 | P2 |');
md.push('|---|---|---|---|---|');
for (const f of report.filesWithViolations.slice(0, 25)) {
md.push(`| \`${f.file}\` | ${f.area} | ${f.P0} | ${f.P1} | ${f.P2} |`);
}
writeFileSync(`${OUT}/color-audit.md`, md.join('\n'));
console.log(md.join('\n'));
console.log(`\n完整数据:${OUT}/color-audit.json`);
@@ -26,7 +26,7 @@ function CaseCard({ caseItem, index }: { caseItem: CaseStudy; index: number }) {
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.3, delay: index * 0.06, ease: EASE_OUT }}
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-white hover:bg-bg-secondary transition-all duration-150 flex flex-col h-full"
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-bg-elevated hover:bg-bg-secondary transition-all duration-150 flex flex-col h-full"
>
<div className="absolute top-0 left-0 h-0 w-[2px] group-hover:h-full transition-all duration-150 origin-top bg-brand" />
@@ -120,9 +120,9 @@ export default function CasesContentV3({
}, [selectedIndustry, casesProp]);
return (
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
<div className="min-h-screen bg-bg-primary text-ink overflow-x-hidden">
{/* Hero Section */}
<section className="relative min-h-[70vh] flex items-center overflow-hidden bg-white">
<section className="relative min-h-[70vh] flex items-center overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-40">
<div className="max-w-4xl">
<motion.div
@@ -224,7 +224,7 @@ export default function CasesContentV3({
</section>
{/* Cases List Section */}
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 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 lg:flex-row lg:items-end lg:justify-between gap-8 mb-12 sm:mb-16 md:mb-20">
<motion.div
@@ -205,7 +205,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
const commitmentItems = data.commitmentItems ?? [];
return (
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
<div className="min-h-screen bg-bg-primary text-ink overflow-x-hidden">
<BreadcrumbSchema items={[{ name: '首页', href: '/' }, { name: '联系我们', href: '/contact' }]} />
{showToast && (
<Toast
@@ -217,7 +217,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
)}
{/* Hero Section */}
<section className="relative min-h-[80vh] flex items-center overflow-hidden bg-white">
<section className="relative min-h-[80vh] flex items-center overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-44 lg:py-56">
<div className="max-w-4xl">
<motion.div
@@ -285,7 +285,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
<h2 className="text-xl font-bold text-ink mb-8">{data.contactTitle || ''}</h2>
<div className="space-y-6" data-testid="contact-info">
<div className="flex items-start gap-4" data-testid="email-info">
<div className="w-12 h-12 border border-border-primary bg-white flex items-center justify-center shrink-0" aria-hidden="true">
<div className="w-12 h-12 border border-border-primary bg-bg-elevated flex items-center justify-center shrink-0" aria-hidden="true">
<Mail className="w-5 h-5 text-text-secondary" />
</div>
<div className="mt-3.5">
@@ -301,7 +301,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
</div>
<div className="flex items-start gap-4" data-testid="address-info">
<div className="w-12 h-12 border border-border-primary bg-white flex items-center justify-center shrink-0" aria-hidden="true">
<div className="w-12 h-12 border border-border-primary bg-bg-elevated flex items-center justify-center shrink-0" aria-hidden="true">
<MapPin className="w-5 h-5 text-text-secondary" />
</div>
<div className="mt-3.5">
@@ -314,7 +314,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
</div>
</div>
<div className="relative border border-border-primary bg-white p-8 overflow-hidden">
<div className="relative border border-border-primary bg-bg-elevated p-8 overflow-hidden">
<div className="absolute top-0 left-0 bottom-0 w-0.5 bg-brand/60" />
<div className="flex items-center gap-3 mb-4">
<Clock className="w-5 h-5 text-brand" aria-hidden="true" />
@@ -326,7 +326,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
</div>
</div>
<div className="relative border border-border-primary bg-white p-8 overflow-hidden">
<div className="relative border border-border-primary bg-bg-elevated p-8 overflow-hidden">
<div className="absolute top-0 left-0 bottom-0 w-0.5 bg-brand/60" />
<div className="flex items-center gap-3 mb-4">
<HeadphonesIcon className="w-5 h-5 text-text-secondary" aria-hidden="true" />
@@ -351,7 +351,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
transition={{ duration: 0.3, delay: 0.1, ease: EASE_OUT }}
className="lg:col-span-3"
>
<div className="relative border border-border-primary bg-white p-6 sm:p-8 lg:p-10 h-full overflow-hidden" id="contact-form-section">
<div className="relative border border-border-primary bg-bg-elevated p-6 sm:p-8 lg:p-10 h-full overflow-hidden" id="contact-form-section">
<div className="absolute top-0 left-0 right-0 h-0.5 bg-brand" />
<h2 className="text-xl font-bold text-ink mb-8">{data.formTitle || ''}</h2>
@@ -411,7 +411,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
onChange={(e) => handleChange('name', e.target.value)}
onBlur={(e) => handleBlur('name', e.target.value)}
error={errors.name}
className="bg-white border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
className="bg-bg-elevated border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
/>
</div>
<div className="relative">
@@ -434,7 +434,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
onChange={(e) => handleChange('phone', e.target.value)}
onBlur={(e) => handleBlur('phone', e.target.value)}
error={errors.phone}
className="bg-white border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
className="bg-bg-elevated border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
/>
</div>
</div>
@@ -450,7 +450,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
onChange={(e) => handleChange('email', e.target.value)}
onBlur={(e) => handleBlur('email', e.target.value)}
error={errors.email}
className="bg-white border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
className="bg-bg-elevated border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
/>
<Input
name="subject"
@@ -463,7 +463,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
onChange={(e) => handleChange('subject', e.target.value)}
onBlur={(e) => handleBlur('subject', e.target.value)}
error={errors.subject}
className="bg-white border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
className="bg-bg-elevated border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
/>
<Textarea
name="message"
@@ -477,7 +477,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
onChange={(e) => handleChange('message', e.target.value)}
onBlur={(e) => handleBlur('message', e.target.value)}
error={errors.message}
className="bg-white border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
className="bg-bg-elevated border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
/>
<Button
type="submit"
@@ -507,7 +507,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
</section>
{/* CTA Section */}
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<motion.div
initial={{ opacity: 0, y: 30 }}
@@ -545,7 +545,7 @@ function ContactFormContent({ data }: { data: ContactData }) {
function EmptyState() {
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="min-h-screen bg-bg-primary flex items-center justify-center">
<div className="text-text-muted text-lg"></div>
</div>
);
@@ -562,7 +562,7 @@ export default function ContactContentV3({ data }: { data: Record<string, unknow
return (
<Suspense
fallback={
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="min-h-screen bg-bg-primary flex items-center justify-center">
<div className="animate-pulse text-text-muted">...</div>
</div>
}
@@ -28,7 +28,7 @@ export function MethodologyContent({ data }: MethodologyContentProps) {
return (
<main>
{/* Hero:品牌化定位,轻量 CTA */}
<section className="relative min-h-[60vh] flex items-center overflow-hidden bg-white">
<section className="relative min-h-[60vh] flex items-center overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-24 sm:py-28 lg:py-32">
<div className="max-w-4xl">
<motion.div
@@ -84,7 +84,7 @@ export function MethodologyContent({ data }: MethodologyContentProps) {
{phases.map((phase, idx) => (
<motion.div
key={phase.title || idx}
className="relative overflow-hidden bg-white border border-border-primary transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
className="relative overflow-hidden bg-bg-elevated border border-border-primary transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
>
<div className="p-7 md:p-8">
<div className="w-11 h-11 rounded-full flex items-center justify-center mb-5 text-base font-bold bg-brand/10 text-brand">
@@ -131,7 +131,7 @@ export function MethodologyContent({ data }: MethodologyContentProps) {
</StaggerReveal>
</div>
) : (
<div className="bg-white border border-border-primary p-10 sm:p-16 text-center">
<div className="bg-bg-elevated border border-border-primary p-10 sm:p-16 text-center">
<h3 className="text-xl font-bold text-ink mb-3"></h3>
<p className="text-text-secondary max-w-xl mx-auto leading-relaxed">
@@ -142,7 +142,7 @@ export function MethodologyContent({ data }: MethodologyContentProps) {
</section>
{/* CTA:轻量导向服务与联系 */}
<section className="relative py-20 sm:py-28 lg:py-32 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 lg:py-32 overflow-hidden bg-bg-primary">
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
<div className="max-w-container mx-auto px-6 lg:px-10">
<ScrollReveal className="text-center max-w-3xl mx-auto">
+5 -5
View File
@@ -27,7 +27,7 @@ function NewsCard({ newsItem, index }: { newsItem: NewsItem; index: number }) {
>
<StaticLink
href={`/news/${newsItem.id}`}
className="group relative block border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 hover:-translate-y-2 overflow-hidden h-full"
className="group relative block border border-border-primary hover:border-brand/30 bg-bg-elevated transition-all duration-150 hover:-translate-y-2 overflow-hidden h-full"
>
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
@@ -119,9 +119,9 @@ export default function NewsContentV3({ news, pageCopy }: { news?: NewsItem[]; p
};
return (
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
<div className="min-h-screen bg-bg-primary text-ink overflow-x-hidden">
{/* Hero Section */}
<section className="relative min-h-[75vh] flex items-center overflow-hidden bg-white">
<section className="relative min-h-[75vh] flex items-center overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-44 lg:py-56">
<div className="max-w-4xl">
<ScrollReveal>
@@ -173,7 +173,7 @@ export default function NewsContentV3({ news, pageCopy }: { news?: NewsItem[]; p
placeholder="搜索新闻..."
value={searchQuery}
onChange={handleSearchChange}
className="pl-12 h-12 bg-white border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
className="pl-12 h-12 bg-bg-elevated border-border-primary text-ink placeholder:text-text-muted focus:border-brand/50"
/>
</div>
</ScrollReveal>
@@ -237,7 +237,7 @@ export default function NewsContentV3({ news, pageCopy }: { news?: NewsItem[]; p
</section>
{/* CTA Section */}
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-primary">
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
@@ -19,7 +19,7 @@ function RelatedNewsCard({ news, index }: { news: NewsItem; index: number }) {
>
<a
href={`/news/${news.id}`}
className="group relative block border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 hover:-translate-y-2 overflow-hidden h-full"
className="group relative block border border-border-primary hover:border-brand/30 bg-bg-elevated transition-all duration-150 hover:-translate-y-2 overflow-hidden h-full"
>
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
@@ -59,7 +59,7 @@ interface NewsDetailContentV3Props {
export default function NewsDetailContentV3({ news, relatedNews = [] }: NewsDetailContentV3Props) {
return (
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
<div className="min-h-screen bg-bg-primary text-ink overflow-x-hidden">
{/* Hero Section */}
<section className="relative pt-28 sm:pt-32 md:pt-40 pb-16 sm:pb-20 md:pb-24 overflow-hidden">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
@@ -95,7 +95,7 @@ export default function NewsDetailContentV3({ news, relatedNews = [] }: NewsDeta
</section>
{/* Article Body Section */}
<section className="relative pb-20 sm:pb-28 md:pb-36 overflow-hidden bg-white">
<section className="relative pb-20 sm:pb-28 md:pb-36 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<ScrollReveal className="max-w-4xl mx-auto">
{/* Featured Image */}
@@ -125,7 +125,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
const certifications = (data.certifications ?? []) as Array<{ name: string; issuer: string; link?: string }>;
return (
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
<div className="min-h-screen bg-bg-primary text-ink overflow-x-hidden">
{/* Hero Section */}
<section className="relative pt-32 pb-32 lg:pt-40 lg:pb-40 overflow-hidden">
<div className="max-w-container mx-auto px-6 lg:px-10 relative z-10">
@@ -183,7 +183,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{PAIN_POINTS.map((item, index) => (
<div
key={index}
className="group relative border border-border-primary hover:border-brand/30 bg-white p-8 lg:p-10 transition-all duration-150 hover:-translate-y-1 overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-bg-elevated p-8 lg:p-10 transition-all duration-150 hover:-translate-y-1 overflow-hidden"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
@@ -200,7 +200,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
</section>
{/* Benefits Section */}
<section className="relative py-24 lg:py-32 overflow-hidden bg-white">
<section className="relative py-24 lg:py-32 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-6 lg:px-10 relative z-10">
<ScrollReveal className="max-w-3xl mb-16">
<SectionLabel>Upgrade Benefits</SectionLabel>
@@ -220,7 +220,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{UPGRADE_BENEFITS.map((item, index) => (
<div
key={index}
className="text-center p-8 border border-border-primary bg-white hover:border-brand/30 hover:bg-bg-secondary transition-all duration-150 group"
className="text-center p-8 border border-border-primary bg-bg-elevated hover:border-brand/30 hover:bg-bg-secondary transition-all duration-150 group"
>
<div className="w-12 h-12 mx-auto mb-6 bg-brand/10 border border-brand/20 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<item.icon className="w-6 h-6 text-brand" />
@@ -254,7 +254,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{FEATURE_MODULES.map((item, index) => (
<div
key={index}
className="group relative border border-border-primary hover:border-brand/30 bg-white p-8 transition-all duration-150 hover:-translate-y-1 overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-bg-elevated p-8 transition-all duration-150 hover:-translate-y-1 overflow-hidden"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
@@ -274,7 +274,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
</section>
{/* Process Section */}
<section className="relative py-24 lg:py-32 overflow-hidden bg-white">
<section className="relative py-24 lg:py-32 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-6 lg:px-10 relative z-10">
<ScrollReveal className="max-w-3xl mb-16">
<SectionLabel>Upgrade Process</SectionLabel>
@@ -331,7 +331,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
</section>
{/* Certifications Section */}
<section className="relative py-24 lg:py-32 overflow-hidden bg-white">
<section className="relative py-24 lg:py-32 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-6 lg:px-10 relative z-10">
<ScrollReveal className="max-w-3xl mb-16 text-center mx-auto">
<SectionLabel className="justify-center">
@@ -357,7 +357,7 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
{certifications.map((cert, index) => (
<div
key={index}
className="group text-center p-8 border border-border-primary bg-white hover:border-brand/30 hover:bg-bg-secondary transition-all duration-150 min-w-[200px]"
className="group text-center p-8 border border-border-primary bg-bg-elevated hover:border-brand/30 hover:bg-bg-secondary transition-all duration-150 min-w-[200px]"
>
<div className="w-16 h-16 mx-auto mb-4 bg-brand/10 border border-brand/20 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<Shield className="w-8 h-8 text-brand" />
@@ -90,7 +90,7 @@ function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) {
const ctaSecondary = (pageCopy?.productsCtaSecondary as string) || '查看行业方案';
return (
<section className="relative min-h-[85vh] flex items-center overflow-hidden bg-white">
<section className="relative min-h-[85vh] flex items-center overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-24 sm:py-28 md:py-32 lg:py-40">
<div className="max-w-4xl">
<motion.h1
@@ -176,7 +176,7 @@ function ProductCard({ product, size = 'small' }: { product: Product; size?: 'la
>
<StaticLink
href={linkHref}
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-white hover:bg-bg-secondary hover:-translate-y-1 hover:shadow-lg transition-all duration-300 flex flex-col h-full after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:origin-left after:scale-x-0 after:bg-brand after:transition-transform after:duration-300 group-hover:after:scale-x-100"
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-bg-elevated hover:bg-bg-secondary hover:-translate-y-1 hover:shadow-lg transition-all duration-300 flex flex-col h-full after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:origin-left after:scale-x-0 after:bg-brand after:transition-transform after:duration-300 group-hover:after:scale-x-100"
{...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
>
<div className={cn('p-6 sm:p-7 lg:p-8 flex flex-col flex-1', isLarge && 'lg:p-10')}>
@@ -233,7 +233,7 @@ function BundleOverview({ products, pageCopy }: { products: Product[]; pageCopy?
const bundleDescription = (pageCopy?.productsBundleDescription as string) || '企业套装以组合形式解决核心管理问题,专业产品聚焦独立场景深度赋能。';
return (
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-primary">
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
@@ -265,7 +265,7 @@ function BundleOverview({ products, pageCopy }: { products: Product[]; pageCopy?
<div className="space-y-3">
{enterpriseProducts.map((p) => (
<div key={p.id} className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-white border border-border-primary flex items-center justify-center text-text-secondary">
<div className="w-8 h-8 rounded-lg bg-bg-elevated border border-border-primary flex items-center justify-center text-text-secondary">
{PRODUCT_ICONS[p.title] || <Package className="w-4 h-4" />}
</div>
<div>
@@ -277,7 +277,7 @@ function BundleOverview({ products, pageCopy }: { products: Product[]; pageCopy?
</div>
</div>
<div className="bg-white p-8 sm:p-10 lg:p-12">
<div className="bg-bg-elevated p-8 sm:p-10 lg:p-12">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-accent-blue-soft flex items-center justify-center text-accent-blue">
<Cpu className="w-5 h-5" />
@@ -353,7 +353,7 @@ function SpecializedProductsSection({ products }: { products: Product[] }) {
if (specializedProducts.length === 0) return null;
return (
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-primary">
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
@@ -425,7 +425,7 @@ function SuiteCombosSection({ products }: { products: Product[] }) {
<StaticLink
key={idx}
href="/solutions"
className="group p-10 lg:p-12 bg-white hover:bg-bg-secondary transition-all duration-150 overflow-hidden"
className="group p-10 lg:p-12 bg-bg-elevated hover:bg-bg-secondary transition-all duration-150 overflow-hidden"
>
<div>
<div className="flex items-center gap-4 mb-6">
@@ -475,7 +475,7 @@ function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) {
const ctaTitleLines = ((pageCopy?.productsCtaTitle as string) || '以自研产品\n支撑数字化落地').split('\n');
const ctaDescription = (pageCopy?.productsCtaDescription as string) || '从企业套装到专业产品,以自研能力与专业服务,陪伴企业完成数字化升级。';
return (
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-primary">
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
@@ -40,7 +40,7 @@ function DetailHero({ solution }: DetailHeroProps) {
const industryIcon = INDUSTRY_ICONS[solution.industry] || <Factory className="w-8 h-8" />;
return (
<section className="relative min-h-[80vh] flex items-center overflow-hidden bg-white pt-24">
<section className="relative min-h-[80vh] flex items-center overflow-hidden bg-bg-primary pt-24">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-20 sm:py-24 md:py-28 lg:py-32">
<motion.div
initial={{ opacity: 0, y: 30 }}
@@ -122,7 +122,7 @@ function ChallengesSection({ challenges }: ChallengesSectionProps) {
{challenges.map((challenge, idx) => (
<div
key={idx}
className="group relative p-8 bg-white hover:bg-bg-secondary transition-all duration-150"
className="group relative p-8 bg-bg-elevated hover:bg-bg-secondary transition-all duration-150"
>
<div className="absolute left-0 top-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
<div className="flex items-start gap-6">
@@ -147,7 +147,7 @@ interface SolutionsSectionProps {
function SolutionsSection({ solutions }: SolutionsSectionProps) {
return (
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<ScrollReveal className="text-center max-w-3xl mx-auto mb-16 sm:mb-20 md:mb-20">
<SectionLabel className="justify-center">
@@ -169,7 +169,7 @@ function SolutionsSection({ solutions }: SolutionsSectionProps) {
{solutions.map((solution, idx) => (
<div
key={idx}
className="group relative p-10 bg-white hover:bg-bg-secondary transition-all duration-150"
className="group relative p-10 bg-bg-elevated hover:bg-bg-secondary transition-all duration-150"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
@@ -226,7 +226,7 @@ function ValuePropositionSection({ valueProposition }: ValuePropositionProps) {
return (
<div
key={idx}
className="group relative p-10 text-center bg-white hover:bg-bg-secondary transition-all duration-150"
className="group relative p-10 text-center bg-bg-elevated hover:bg-bg-secondary transition-all duration-150"
>
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1/3 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-center" />
@@ -257,7 +257,7 @@ function SuiteCombinationSection({ suiteCombination, products }: SuiteCombinatio
.filter(Boolean);
return (
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<ScrollReveal className="text-center max-w-3xl mx-auto mb-16 sm:mb-20 md:mb-20">
<SectionLabel className="justify-center">
@@ -279,7 +279,7 @@ function SuiteCombinationSection({ suiteCombination, products }: SuiteCombinatio
{primaryProducts.map((product) => product && (
<div
key={product.id}
className="group relative p-8 bg-white hover:bg-bg-secondary transition-all duration-150 text-center lg:col-span-2"
className="group relative p-8 bg-bg-elevated hover:bg-bg-secondary transition-all duration-150 text-center lg:col-span-2"
>
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
@@ -296,7 +296,7 @@ function SuiteCombinationSection({ suiteCombination, products }: SuiteCombinatio
))}
</StaggerReveal>
<ScrollReveal delay={0.2} className="flex items-center justify-center lg:col-span-1 bg-white">
<ScrollReveal delay={0.2} className="flex items-center justify-center lg:col-span-1 bg-bg-elevated">
<div className="w-20 h-20 rounded-full bg-brand-soft/30 border-2 border-brand/30 flex items-center justify-center text-brand font-bold text-xl">
+
</div>
@@ -29,7 +29,7 @@ function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) {
const ctaSecondary = (pageCopy?.solutionsCtaSecondary as string) || '浏览产品';
return (
<section className="relative min-h-[85vh] flex items-center overflow-hidden bg-white">
<section className="relative min-h-[85vh] flex items-center overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-44 lg:py-56">
<div className="max-w-4xl">
<motion.h1
@@ -125,7 +125,7 @@ function SolutionCard({ solution, index, products, featured = false }: { solutio
<motion.div
data-testid={`solution-card-${solution.id}`}
data-featured={featured ? 'true' : 'false'}
className={featured ? 'md:col-span-2 bg-white' : 'md:col-span-1 bg-white'}
className={featured ? 'md:col-span-2 bg-bg-elevated' : 'md:col-span-1 bg-bg-elevated'}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
@@ -133,7 +133,7 @@ function SolutionCard({ solution, index, products, featured = false }: { solutio
>
<StaticLink
href={`/solutions/${solution.id}`}
className="group relative block transition-all duration-300 hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 bg-white border border-border-primary hover:border-brand/20 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:origin-left after:scale-x-0 after:bg-brand after:transition-transform after:duration-300 group-hover:after:scale-x-100"
className="group relative block transition-all duration-300 hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 bg-bg-elevated border border-border-primary hover:border-brand/20 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:origin-left after:scale-x-0 after:bg-brand after:transition-transform after:duration-300 group-hover:after:scale-x-100"
>
<div className="relative z-10 p-8 sm:p-10 md:p-12">
<div className="flex items-center gap-3 mb-6">
@@ -218,7 +218,7 @@ function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) {
const ctaTitle = (pageCopy?.solutionsCtaTitle as string) || '告诉我们您的行业场景';
const ctaDescription = (pageCopy?.solutionsCtaDescription as string) || '无论您处于哪个行业、哪个数字化阶段,我们都能为您推荐最合适的产品组合和服务包。';
return (
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<motion.div
initial={{ opacity: 0, y: 30 }}
@@ -251,7 +251,7 @@ export default function SolutionsContentV3({ solutions: solutionsProp, products,
const solutions = solutionsProp ?? [];
return (
<main className="bg-white text-ink">
<main className="bg-bg-primary text-ink">
<HeroSection pageCopy={pageCopy} />
<SolutionsGridSection solutions={solutions} products={products} pageCopy={pageCopy} />
<CTASection pageCopy={pageCopy} />
+7 -7
View File
@@ -65,7 +65,7 @@ function StrengthCard({ strength, index }: { strength: StrengthData; index: numb
return (
<motion.div
key={strength.title}
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-bg-elevated transition-all duration-150 overflow-hidden"
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
@@ -100,7 +100,7 @@ function CultureCard({ culture, index }: { culture: CultureData; index: number }
return (
<motion.div
key={culture.title}
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-150 text-center overflow-hidden"
className="group relative border border-border-primary hover:border-brand/30 bg-bg-elevated transition-all duration-150 text-center overflow-hidden"
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
@@ -136,16 +136,16 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
// Empty state when no CMS data is available
if (!data || Object.keys(data).length === 0) {
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="min-h-screen bg-bg-primary flex items-center justify-center">
<div className="text-text-muted text-lg"></div>
</div>
);
}
return (
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
<div className="min-h-screen bg-bg-primary text-ink overflow-x-hidden">
{/* ============ L1: Hero 团队情感入口 ============ */}
<section className="relative min-h-[85vh] flex items-center overflow-hidden bg-white">
<section className="relative min-h-[85vh] flex items-center overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-44 lg:py-56">
<div className="max-w-4xl">
{/* 眉标 Badge */}
@@ -270,7 +270,7 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<ScrollReveal>
<div className="max-w-4xl mx-auto p-8 sm:p-10 lg:p-16 border border-border-primary bg-white">
<div className="max-w-4xl mx-auto p-8 sm:p-10 lg:p-16 border border-border-primary bg-bg-elevated">
<div className="flex items-center gap-3 mb-8 justify-center">
<div className="w-10 h-px bg-brand" />
<span className="text-[13px] tracking-[0.15em] font-medium text-brand">
@@ -296,7 +296,7 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
{/* ============ L3: 团队文化信任证明 ============ */}
{culture.length > 0 && (
<section className="relative py-24 sm:py-32 md:py-40 overflow-hidden bg-white">
<section className="relative py-24 sm:py-32 md:py-40 overflow-hidden bg-bg-primary">
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">