# 颜色对比度和标题层级结构优化实现计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 修复网站的颜色对比度问题和标题层级结构问题,提升可访问性和SEO评分 **Architecture:** 采用渐进式修复策略,首先优化颜色对比度以提升可访问性,然后标准化标题层级结构以改善SEO。使用CSS变量系统进行全局颜色管理,建立标题层级规范并添加自动化检查工具。 **Tech Stack:** Next.js 16, Tailwind CSS 4, TypeScript, Playwright (测试), @axe-core/playwright (可访问性测试) --- ## 阶段1: 颜色对比度优化 ### Task 1: 创建对比度检测工具 **Files:** - Create: `src/lib/color-contrast.ts` **Step 1: Write the failing test** ```typescript // tests/lib/color-contrast.test.ts import { calculateContrastRatio, meetsWCAGStandard } from '@/lib/color-contrast'; describe('Color Contrast Calculator', () => { test('should calculate correct contrast ratio for black on white', () => { const ratio = calculateContrastRatio('#000000', '#FFFFFF'); expect(ratio).toBeCloseTo(21, 1); }); test('should identify WCAG AA compliance for normal text', () => { const result = meetsWCAGStandard('#000000', '#FFFFFF', 'AA', 'normal'); expect(result.passes).toBe(true); expect(result.ratio).toBeGreaterThan(4.5); }); test('should fail WCAG AA for low contrast colors', () => { const result = meetsWCAGStandard('#808080', '#FFFFFF', 'AA', 'normal'); expect(result.passes).toBe(false); expect(result.ratio).toBeLessThan(4.5); }); }); ``` **Step 2: Run test to verify it fails** Run: `npm test -- tests/lib/color-contrast.test.ts` Expected: FAIL with "Cannot find module '@/lib/color-contrast'" **Step 3: Write minimal implementation** ```typescript // src/lib/color-contrast.ts interface ColorRGB { r: number; g: number; b: number; } interface ContrastResult { passes: boolean; ratio: number; requiredRatio: number; } function hexToRgb(hex: string): ColorRGB { const cleanHex = hex.replace('#', ''); const r = parseInt(cleanHex.substring(0, 2), 16); const g = parseInt(cleanHex.substring(2, 4), 16); const b = parseInt(cleanHex.substring(4, 6), 16); return { r, g, b }; } function getLuminance(rgb: ColorRGB): number { const { r, g, b } = rgb; const a = [r, g, b].map(v => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }); return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; } export function calculateContrastRatio(foreground: string, background: string): number { const fgRgb = hexToRgb(foreground); const bgRgb = hexToRgb(background); const fgLuminance = getLuminance(fgRgb); const bgLuminance = getLuminance(bgRgb); const lighter = Math.max(fgLuminance, bgLuminance); const darker = Math.min(fgLuminance, bgLuminance); return (lighter + 0.05) / (darker + 0.05); } export function meetsWCAGStandard( foreground: string, background: string, level: 'AA' | 'AAA', textSize: 'normal' | 'large' ): ContrastResult { const ratio = calculateContrastRatio(foreground, background); let requiredRatio: number; if (level === 'AA') { requiredRatio = textSize === 'normal' ? 4.5 : 3; } else { requiredRatio = textSize === 'normal' ? 7 : 4.5; } return { passes: ratio >= requiredRatio, ratio, requiredRatio }; } ``` **Step 4: Run test to verify it passes** Run: `npm test -- tests/lib/color-contrast.test.ts` Expected: PASS **Step 5: Commit** ```bash git add src/lib/color-contrast.ts tests/lib/color-contrast.test.ts git commit -m "feat: add color contrast calculation utility with WCAG standards" ``` --- ### Task 2: 优化CSS变量中的颜色对比度 **Files:** - Modify: `src/app/globals.css:33-67` **Step 1: Write the failing test** ```typescript // tests/styles/color-contrast.test.ts import { calculateContrastRatio, meetsWCAGStandard } from '@/lib/color-contrast'; describe('CSS Variables Color Contrast', () => { test('primary text on primary background should meet WCAG AA', () => { const result = meetsWCAGStandard('#1C1C1C', '#FAFAFA', 'AA', 'normal'); expect(result.passes).toBe(true); }); test('tertiary text on primary background should meet WCAG AA', () => { const result = meetsWCAGStandard('#5C5C5C', '#FAFAFA', 'AA', 'normal'); expect(result.passes).toBe(true); }); test('muted text on primary background should meet WCAG AA', () => { const result = meetsWCAGStandard('#8C8C8C', '#FAFAFA', 'AA', 'normal'); expect(result.passes).toBe(true); }); }); ``` **Step 2: Run test to verify it fails** Run: `npm test -- tests/styles/color-contrast.test.ts` Expected: FAIL with muted text contrast ratio < 4.5 **Step 3: Write minimal implementation** ```css /* src/app/globals.css - 修改颜色变量部分 */ :root { /* 主色调 - 墨黑系(水墨画主色) */ --color-primary: #1C1C1C; --color-primary-hover: #0A0A0A; --color-primary-light: #3D3D3D; --color-primary-lighter: #F5F5F5; /* 品牌色 - 朱砂红(印章红) */ --color-brand-primary: #C41E3A; --color-brand-primary-hover: #A01830; --color-brand-primary-light: #E04A68; --color-brand-primary-bg: #FEF2F4; /* 背景色系 - 宣纸白 */ --color-bg-primary: #FFFFFF; /* 从 #FAFAFA 改为 #FFFFFF 提升对比度 */ --color-bg-secondary: #FFFBF5; --color-bg-tertiary: #F5F5F5; --color-bg-hover: #EFEFEF; /* 文字色系 - 墨色层次(优化对比度) */ --color-text-primary: #1C1C1C; --color-text-secondary: #3D3D3D; --color-text-tertiary: #4A4A4A; /* 从 #5C5C5C 改为 #4A4A4A 提升对比度 */ --color-text-muted: #6B6B6B; /* 从 #8C8C8C 改为 #6B6B6B 提升对比度 */ /* 边框色系 */ --color-border-primary: #E5E5E5; --color-border-secondary: #D4D4D4D; --color-border-accent: #1C1C1C; /* 链接色 */ --color-link: #1C1C1C; --color-link-hover: #C41E3A; /* 状态色 */ --color-success: #16A34A; --color-success-bg: #F0FDF4; --color-warning: #D97706; --color-warning-bg: #FFFBEB; --color-info: #5C5C5C; --color-info-bg: #F5F5F5; --color-error: #C41E3A; --color-error-bg: #FEF2F4; /* 阴影 */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.05); --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.05); --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.05); /* 尺寸变量 */ --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; --font-size-4xl: 2.25rem; --font-size-5xl: 3rem; --font-size-6xl: 3.75rem; --line-height-tight: 1.1; --line-height-snug: 1.25; --line-height-normal: 1.5; --line-height-relaxed: 1.625; --letter-spacing-tight: -0.025em; --letter-spacing-normal: 0; --letter-spacing-wide: 0.025em; --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 1.5rem; --spacing-xl: 2rem; --spacing-2xl: 3rem; --spacing-3xl: 4rem; --spacing-4xl: 6rem; --spacing-5xl: 8rem; --border-width-thin: 0.5px; --border-width-normal: 1px; --transition-fast: 150ms; --transition-normal: 200ms; --transition-slow: 300ms; --ease-out: cubic-bezier(0.16, 1, 0.3, 1); --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); } ``` **Step 4: Run test to verify it passes** Run: `npm test -- tests/styles/color-contrast.test.ts` Expected: PASS **Step 5: Commit** ```bash git add src/app/globals.css tests/styles/color-contrast.test.ts git commit -m "fix: optimize color contrast for better accessibility (WCAG AA compliance)" ``` --- ### Task 3: 添加对比度检查脚本 **Files:** - Create: `scripts/check-color-contrast.ts` **Step 1: Write the failing test** ```bash # Test script exists and runs test -f scripts/check-color-contrast.ts echo "Script exists" ``` **Step 2: Run test to verify it fails** Run: `test -f scripts/check-color-contrast.ts` Expected: FAIL with "No such file or directory" **Step 3: Write minimal implementation** ```typescript // scripts/check-color-contrast.ts import { calculateContrastRatio, meetsWCAGStandard } from '../src/lib/color-contrast'; interface ColorPair { name: string; foreground: string; background: string; textSize?: 'normal' | 'large'; } const criticalColorPairs: ColorPair[] = [ { name: 'Primary text on primary background', foreground: '#1C1C1C', background: '#FFFFFF' }, { name: 'Secondary text on primary background', foreground: '#3D3D3D', background: '#FFFFFF' }, { name: 'Tertiary text on primary background', foreground: '#4A4A4A', background: '#FFFFFF' }, { name: 'Muted text on primary background', foreground: '#6B6B6B', background: '#FFFFFF' }, { name: 'Brand primary on white', foreground: '#C41E3A', background: '#FFFFFF' }, { name: 'Brand primary on brand bg', foreground: '#C41E3A', background: '#FEF2F4' }, { name: 'Link on hover', foreground: '#C41E3A', background: '#FFFFFF' }, ]; function checkContrast() { console.log('🎨 Color Contrast Check\n'); let failures = 0; let passes = 0; criticalColorPairs.forEach(pair => { const result = meetsWCAGStandard( pair.foreground, pair.background, 'AA', pair.textSize || 'normal' ); const status = result.passes ? '✅ PASS' : '❌ FAIL'; const ratio = result.ratio.toFixed(2); console.log(`${status} ${pair.name}`); console.log(` Ratio: ${ratio}:1 (Required: ${result.requiredRatio}:1)`); console.log(` FG: ${pair.foreground} | BG: ${pair.background}\n`); if (result.passes) { passes++; } else { failures++; } }); console.log(`\n📊 Summary:`); console.log(` Total: ${criticalColorPairs.length}`); console.log(` ✅ Passes: ${passes}`); console.log(` ❌ Failures: ${failures}`); if (failures > 0) { console.log('\n⚠️ Some color pairs do not meet WCAG AA standards!'); process.exit(1); } else { console.log('\n✅ All color pairs meet WCAG AA standards!'); process.exit(0); } } checkContrast(); ``` **Step 4: Add to package.json** ```json // package.json - add to scripts section { "scripts": { "check:contrast": "tsx scripts/check-color-contrast.ts", // ... existing scripts } } ``` **Step 5: Run script to verify it works** Run: `npm run check:contrast` Expected: All color pairs pass WCAG AA standards **Step 6: Commit** ```bash git add scripts/check-color-contrast.ts package.json git commit -m "feat: add color contrast checking script for CI/CD" ``` --- ## 阶段2: 标题层级结构优化 ### Task 4: 创建标题层级规范文档 **Files:** - Create: `docs/heading-hierarchy-guidelines.md` **Step 1: Write the failing test** ```bash # Test documentation exists test -f docs/heading-hierarchy-guidelines.md echo "Documentation exists" ``` **Step 2: Run test to verify it fails** Run: `test -f docs/heading-hierarchy-guidelines.md` Expected: FAIL with "No such file or directory" **Step 3: Write minimal implementation** ```markdown # 标题层级结构规范 ## 概述 本文档定义了Novalon网站的标题层级结构规范,确保符合SEO最佳实践和可访问性要求。 ## 基本原则 1. **层级连续性**: 标题必须按H1 → H2 → H3 → H4的顺序使用,不能跳级 2. **唯一性**: 每个页面只能有一个H1标签 3. **语义化**: 标题应准确描述其下方内容的主题 4. **简洁性**: 标题文本应简洁明了,避免过长 ## 页面标题层级规范 ### 首页 ``` H1: Novalon - 开启数字化转型之旅 ├─ H2: 核心业务 │ ├─ H3: [服务卡片标题] │ └─ H3: [服务卡片标题] ├─ H2: 产品 │ ├─ H3: [产品卡片标题] │ └─ H3: [产品卡片标题] ├─ H2: 与谁同行,决定能走多远 │ ├─ H3: [案例卡片标题] │ └─ H3: [案例卡片标题] ├─ H2: 关于Novalon │ ├─ H3: [统计数据标题] │ └─ H3: [统计数据标题] └─ H2: 最新资讯 ├─ H3: [新闻卡片标题] └─ H3: [新闻卡片标题] ``` ### 关于页面 ``` H1: 关于我们 - Novalon ├─ H2: 品牌故事 ├─ H2: 企业文化 ├─ H2: 发展历程 └─ H2: 联系我们 ``` ### 联系页面 ``` H1: 开启合作 ├─ H2: 联系方式 ├─ H2: 工作时间 ├─ H2: 我们的承诺 └─ H2: 发送消息 ``` ### 服务页面 ``` H1: 我们的核心业务 ├─ H2: [服务类别] │ ├─ H3: [具体服务] │ └─ H3: [具体服务] └─ H2: [服务类别] ├─ H3: [具体服务] └─ H3: [具体服务] ``` ### 产品页面 ``` H1: 我们的产品 ├─ H2: [产品类别] │ ├─ H3: [具体产品] │ └─ H3: [具体产品] └─ H2: [产品类别] ├─ H3: [具体产品] └─ H3: [具体产品] ``` ### 案例页面 ``` H1: 成功案例 ├─ H2: [行业分类] │ ├─ H3: [案例标题] │ └─ H3: [案例标题] └─ H2: [行业分类] ├─ H3: [案例标题] └─ H3: [案例标题] ``` ### 新闻页面 ``` H1: 最新资讯 ├─ H2: [新闻类别] │ ├─ H3: [新闻标题] │ └─ H3: [新闻标题] └─ H2: [新闻类别] ├─ H3: [新闻标题] └─ H3: [新闻标题] ``` ## 组件标题规范 ### Section组件 - 每个section应该有一个H2标题 - Section内的子内容使用H3或H4 ### Card组件 - 卡片标题使用H3或H4,取决于其在页面中的层级 - 卡片内部的小标题使用H4或H5 ### List组件 - 列表项不使用标题标签 - 使用语义化的`