Files
novalon-website/docs/plans/2026-03-06-color-contrast-and-heading-optimization.md
T
张翔 feb646efe5 fix: 修复移动端导航菜单选择器问题
feat: 为主导航菜单和页面区块添加ARIA属性

fix: 解决工作时间信息获取问题

perf: 优化页面滚动功能实现

fix: 修正联系页面标题显示问题

test: 运行完整测试套件验证修复效果

docs: 添加修复完成报告
2026-03-07 15:20:40 +08:00

38 KiB
Raw Blame History

颜色对比度和标题层级结构优化实现计划

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

// 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

// 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

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

// 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

/* 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

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

# 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

// 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

// 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

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

# 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

# 标题层级结构规范

## 概述

本文档定义了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组件
- 列表项不使用标题标签
- 使用语义化的`<ul>`和`<li>`标签

## 实施检查清单

- [ ] 每个页面只有一个H1
- [ ] 标题层级连续,无跳级
- [ ] H1准确描述页面主要内容
- [ ] H2描述主要section
- [ ] H3描述subsection
- [ ] 标题文本简洁明了
- [ ] 避免使用标题标签进行样式控制

## 工具和验证

### 自动化检查
使用以下脚本验证标题层级:
```bash
npm run check:headings

手动检查

  1. 使用浏览器的开发者工具查看页面结构
  2. 使用SEO工具(如Screaming Frog)分析标题层级
  3. 使用可访问性工具(如axe DevTools)检查标题结构

参考资料


**Step 4: Run test to verify it passes**

Run: `test -f docs/heading-hierarchy-guidelines.md`

Expected: PASS

**Step 5: Commit**

```bash
git add docs/heading-hierarchy-guidelines.md
git commit -m "docs: add heading hierarchy guidelines for SEO and accessibility"

Task 5: 修复首页标题层级结构

Files:

  • Modify: src/components/sections/hero-section.tsx:1-100
  • Modify: src/components/sections/services-section.tsx:1-90
  • Modify: src/components/sections/products-section.tsx:1-100
  • Modify: src/components/sections/cases-section.tsx:1-100
  • Modify: src/components/sections/about-section.tsx:1-77
  • Modify: src/components/sections/news-section.tsx:1-89

Step 1: Write the failing test

// tests/heading-hierarchy/heading-structure.test.ts
import { test, expect } from '@playwright/test';

test.describe('首页标题层级结构', () => {
  test('should have proper heading hierarchy', async ({ page }) => {
    await page.goto('/');
    
    // 检查只有一个H1
    const h1Count = await page.locator('h1').count();
    expect(h1Count).toBe(1);
    
    // 检查H1内容
    const h1Text = await page.locator('h1').textContent();
    expect(h1Text).toBeTruthy();
    
    // 检查H2数量(应该有6个section
    const h2Count = await page.locator('h2').count();
    expect(h2Count).toBeGreaterThanOrEqual(5);
    
    // 检查H3数量(应该在卡片中)
    const h3Count = await page.locator('h3').count();
    expect(h3Count).toBeGreaterThan(0);
    
    // 验证层级结构:H1 -> H2 -> H3
    const headings = await page.locator('h1, h2, h3').allTextContents();
    console.log('Headings found:', headings);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- tests/heading-hierarchy/heading-structure.test.ts

Expected: FAIL or warnings about heading structure

Step 3: Write minimal implementation

// src/components/sections/hero-section.tsx - 确保H1存在
// 当前已经有H1,保持不变
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
  开启 <span className="text-[#C41E3A]">数字化转型</span>之旅
</h1>
// src/components/sections/services-section.tsx - 确保H2和H3层级正确
// 当前H2正确,卡片标题应该是H3
<h2 id="services-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
  我们的 <span className="text-[#C41E3A]">核心业务</span>
</h2>

// 卡片标题应该是H3
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
  {service.title}
</h3>
// src/components/sections/products-section.tsx - 确保H2和H3层级正确
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
  我们的<span className="text-[#C41E3A]">产品</span>
</h2>

// 产品卡片标题应该是H3
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
  {product.title}
</h3>
// src/components/sections/cases-section.tsx - 确保H2和H3层级正确
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
  与谁同行,<span className="text-[#C41E3A]">决定能走多远</span>
</h2>

// 案例卡片标题应该是H3
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
  {caseItem.title}
</h3>
// src/components/sections/about-section.tsx - 确保H2层级正确
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
  关于 <span className="tracking-tight font-calligraphy text-[#C41E3A]">{COMPANY_INFO.shortName}</span>
</h2>

// 添加缺失的H2标签
<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-4 mt-8">
  品牌故事
</h2>

<h2 className="text-2xl font-semibold text-[#1C1C1C] mb-4 mt-8">
  发展历程
</h2>
// src/components/sections/news-section.tsx - 确保H2和H3层级正确
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-[#1C1C1C] mb-6">
  最新<span className="text-[#C41E3A]">资讯</span>
</h2>

// 新闻卡片标题应该是H3
<h3 className="text-xl leading-tight">{news.title}</h3>

Step 4: Run test to verify it passes

Run: npm test -- tests/heading-hierarchy/heading-structure.test.ts

Expected: PASS

Step 5: Commit

git add src/components/sections/hero-section.tsx src/components/sections/services-section.tsx src/components/sections/products-section.tsx src/components/sections/cases-section.tsx src/components/sections/about-section.tsx src/components/sections/news-section.tsx tests/heading-hierarchy/heading-structure.test.ts
git commit -m "fix: standardize heading hierarchy across all sections for better SEO"

Task 6: 修复关于页面标题层级结构

Files:

  • Modify: src/app/(marketing)/about/page.tsx:1-11
  • Create: src/app/(marketing)/about/client.tsx

Step 1: Write the failing test

// tests/heading-hierarchy/about-page.test.ts
import { test, expect } from '@playwright/test';

test.describe('关于页面标题层级结构', () => {
  test('should have proper heading hierarchy', async ({ page }) => {
    await page.goto('/about');
    
    // 检查只有一个H1
    const h1Count = await page.locator('h1').count();
    expect(h1Count).toBe(1);
    
    // 检查H2数量
    const h2Count = await page.locator('h2').count();
    expect(h2Count).toBeGreaterThanOrEqual(3);
    
    // 检查H3数量
    const h3Count = await page.locator('h3').count();
    expect(h3Count).toBeGreaterThan(0);
    
    // 验证层级连续性
    const headings = await page.evaluate(() => {
      const elements = Array.from(document.querySelectorAll('h1, h2, h3'));
      return elements.map(el => ({
        tag: el.tagName,
        text: el.textContent?.trim()
      }));
    });
    
    console.log('About page headings:', headings);
    
    // 验证没有跳级
    for (let i = 1; i < headings.length; i++) {
      const currentLevel = parseInt(headings[i].tag[1]);
      const prevLevel = parseInt(headings[i-1].tag[1]);
      const diff = currentLevel - prevLevel;
      
      // 允许同级或降级,但不允许跳级(如H1->H3)
      if (diff > 1) {
        throw new Error(`Heading level jump detected: ${headings[i-1].tag} -> ${headings[i].tag}`);
      }
    }
  });
});

Step 2: Run test to verify it fails

Run: npm test -- tests/heading-hierarchy/about-page.test.ts

Expected: FAIL or warnings about heading structure

Step 3: Write minimal implementation

// src/app/(marketing)/about/client.tsx
'use client';

import { motion } from 'framer-motion';
import { useInView } from 'framer-motion';
import { useRef } from 'react';
import { COMPANY_INFO } from '@/lib/constants';

export function AboutClient() {
  const ref = useRef(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <main className="min-h-screen bg-white pt-16">
      <section className="section-padding relative" ref={ref}>
        <div className="container-wide">
          <motion.div 
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6 }}
          >
            {/* H1 - 页面主标题 */}
            <h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
              关于 <span className="tracking-tight font-calligraphy text-[#C41E3A]">{COMPANY_INFO.shortName}</span>
            </h1>
            
            <p className="text-lg text-[#5C5C5C] mb-8">
              {COMPANY_INFO.slogan}
            </p>
          </motion.div>

          {/* H2 - 品牌故事 */}
          <motion.div 
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.1 }}
            className="mb-12"
          >
            <h2 className="text-2xl font-semibold text-[#1C1C1C] mb-6">
              品牌故事
            </h2>
            <div className="bg-white rounded-2xl p-8 border border-[#E5E5E5]">
              <p className="text-lg text-[#5C5C5C] leading-relaxed text-center mb-6">
                "企业需要的,不是一个高高在上的'专家',也不是一个做完就跑的'卖家',而是一个能坐下来、一起想办法的同行者。"
              </p>
              <p className="text-[#1C1C1C] font-medium text-center">
                我们只做一件事:成为您数字化转型路上,信得过的成长伙伴。
              </p>
            </div>
          </motion.div>

          {/* H2 - 发展历程 */}
          <motion.div 
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.2 }}
            className="mb-12"
          >
            <h2 className="text-2xl font-semibold text-[#1C1C1C] mb-6">
              发展历程
            </h2>
            <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
              {STATS.map((stat, idx) => (
                <div key={idx} className="text-center border border-[#E5E5E5] rounded-lg p-6">
                  {/* H3 - 统计数据标题 */}
                  <h3 className="text-sm text-[#5C5C5C] mb-2">{stat.label}</h3>
                  <div className="text-3xl sm:text-4xl font-bold text-[#C41E3A]">
                    {stat.value}
                  </div>
                </div>
              ))}
            </div>
          </motion.div>

          {/* H2 - 联系我们 */}
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.3 }}
            className="text-center"
          >
            <h2 className="text-2xl font-semibold text-[#1C1C1C] mb-6">
              联系我们
            </h2>
            <p className="text-[#5C5C5C] mb-4">
              如有合作意向,欢迎随时联系我们
            </p>
            <a 
              href="/contact"
              className="inline-flex items-center px-6 py-3 bg-[#C41E3A] text-white rounded-lg hover:bg-[#A01830] transition-colors"
            >
              立即联系
            </a>
          </motion.div>
        </div>
      </section>
    </main>
  );
}

Step 4: Run test to verify it passes

Run: npm test -- tests/heading-hierarchy/about-page.test.ts

Expected: PASS

Step 5: Commit

git add src/app/(marketing)/about/page.tsx src/app/(marketing)/about/client.tsx tests/heading-hierarchy/about-page.test.ts
git commit -m "fix: implement proper heading hierarchy for about page"

Task 7: 修复联系页面标题层级结构

Files:

  • Modify: src/app/(marketing)/contact/page.tsx:1-371

Step 1: Write the failing test

// tests/heading-hierarchy/contact-page.test.ts
import { test, expect } from '@playwright/test';

test.describe('联系页面标题层级结构', () => {
  test('should have proper heading hierarchy', async ({ page }) => {
    await page.goto('/contact');
    
    // 检查只有一个H1
    const h1Count = await page.locator('h1').count();
    expect(h1Count).toBe(1);
    
    // 检查H2数量
    const h2Count = await page.locator('h2').count();
    expect(h2Count).toBeGreaterThanOrEqual(3);
    
    // 检查H3数量
    const h3Count = await page.locator('h3').count();
    expect(h3Count).toBeGreaterThan(0);
    
    // 验证层级连续性
    const headings = await page.evaluate(() => {
      const elements = Array.from(document.querySelectorAll('h1, h2, h3'));
      return elements.map(el => ({
        tag: el.tagName,
        text: el.textContent?.trim()
      }));
    });
    
    console.log('Contact page headings:', headings);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- tests/heading-hierarchy/contact-page.test.ts

Expected: FAIL or warnings about heading structure

Step 3: Write minimal implementation

// src/app/(marketing)/contact/page.tsx - 修改标题层级
// 找到联系信息部分,添加H2标签
<div className="lg:col-span-2 space-y-8 flex flex-col">
  <div>
    {/* H2 - 联系方式 */}
    <h2 className="text-lg font-semibold text-[#1C1C1C] mb-6">
      联系方式
    </h2>
    <div className="space-y-4" data-testid="contact-info">
      {/* 联系方式内容 */}
    </div>
  </div>

  <div className="bg-[#FFFBF5] p-5 rounded-lg border border-[#E5E5E5]" data-testid="work-hours-card">
    <div className="flex items-center gap-2 mb-3">
      <Clock className="w-4 h-4 text-[#C41E3A]" />
      {/* H3 - 工作时间 */}
      <h3 className="text-sm font-medium text-[#1C1C1C]">工作时间</h3>
    </div>
    {/* 工作时间内容 */}
  </div>

  <div className="bg-[#FFFBF5] p-5 rounded-lg border border-[#E5E5E5]">
    <div className="flex items-center gap-2 mb-3">
      <HeadphonesIcon className="w-4 h-4 text-[#C41E3A]" />
      {/* H3 - 我们的承诺 */}
      <h3 className="text-sm font-medium text-[#1C1C1C]">我们的承诺</h3>
    </div>
    {/* 承诺内容 */}
  </div>
</div>

<div className="lg:col-span-3 flex flex-col">
  <div className="bg-[#F5F7FA] p-6 sm:p-8 rounded-lg border border-[#E2E8F0] flex-1 flex flex-col">
    {/* H2 - 发送消息 */}
    <h2 className="text-lg font-semibold text-[#1A1A2E] mb-6">
      发送消息
    </h2>
    {/* 表单内容 */}
  </div>
</div>

Step 4: Run test to verify it passes

Run: npm test -- tests/heading-hierarchy/contact-page.test.ts

Expected: PASS

Step 5: Commit

git add src/app/(marketing)/contact/page.tsx tests/heading-hierarchy/contact-page.test.ts
git commit -m "fix: implement proper heading hierarchy for contact page"

Task 8: 创建标题层级检查工具

Files:

  • Create: scripts/check-heading-hierarchy.ts

Step 1: Write the failing test

# Test script exists and runs
test -f scripts/check-heading-hierarchy.ts
echo "Script exists"

Step 2: Run test to verify it fails

Run: test -f scripts/check-heading-hierarchy.ts

Expected: FAIL with "No such file or directory"

Step 3: Write minimal implementation

// scripts/check-heading-hierarchy.ts
import { chromium } from 'playwright';

interface HeadingInfo {
  level: number;
  tag: string;
  text: string;
  index: number;
}

interface PageCheckResult {
  page: string;
  h1Count: number;
  headings: HeadingInfo[];
  hasJumps: boolean;
  jumps: string[];
  isValid: boolean;
}

const pagesToCheck = [
  { name: '首页', url: '/' },
  { name: '关于页面', url: '/about' },
  { name: '联系页面', url: '/contact' },
  { name: '服务页面', url: '/services' },
  { name: '产品页面', url: '/products' },
  { name: '案例页面', url: '/cases' },
  { name: '新闻页面', url: '/news' },
];

async function checkPageHeadings(pageName: string, url: string): Promise<PageCheckResult> {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext();
  const page = await context.newPage();

  try {
    await page.goto(`http://localhost:3000${url}`, { waitUntil: 'networkidle' });

    const headings = await page.evaluate(() => {
      const elements = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
      return elements.map((el, index) => ({
        level: parseInt(el.tagName[1]),
        tag: el.tagName,
        text: el.textContent?.trim() || '',
        index,
      }));
    });

    const h1Count = headings.filter(h => h.level === 1).length;

    const jumps: string[] = [];
    let hasJumps = false;

    for (let i = 1; i < headings.length; i++) {
      const current = headings[i];
      const prev = headings[i - 1];
      const diff = current.level - prev.level;

      if (diff > 1) {
        hasJumps = true;
        jumps.push(
          `跳级: ${prev.tag} "${prev.text.substring(0, 30)}" → ${current.tag} "${current.text.substring(0, 30)}"`
        );
      }
    }

    return {
      page: pageName,
      h1Count,
      headings,
      hasJumps,
      jumps,
      isValid: h1Count === 1 && !hasJumps,
    };
  } finally {
    await context.close();
    await browser.close();
  }
}

async function checkAllPages() {
  console.log('📋 标题层级结构检查\n');

  const results: PageCheckResult[] = [];

  for (const page of pagesToCheck) {
    console.log(`检查: ${page.name}...`);
    const result = await checkPageHeadings(page.name, page.url);
    results.push(result);

    if (result.isValid) {
      console.log(`  ✅ ${page.name} - 通过`);
    } else {
      console.log(`  ❌ ${page.name} - 失败`);
      if (result.h1Count !== 1) {
        console.log(`     ⚠️  H1数量: ${result.h1Count} (应为1)`);
      }
      if (result.hasJumps) {
        console.log(`     ⚠️  发现跳级:`);
        result.jumps.forEach(jump => console.log(`        - ${jump}`));
      }
    }
    console.log('');
  }

  const allValid = results.every(r => r.isValid);

  console.log('📊 检查总结:');
  console.log(`   总页面数: ${results.length}`);
  console.log(`   ✅ 通过: ${results.filter(r => r.isValid).length}`);
  console.log(`   ❌ 失败: ${results.filter(r => !r.isValid).length}`);

  if (allValid) {
    console.log('\n✅ 所有页面的标题层级结构都符合规范!');
    process.exit(0);
  } else {
    console.log('\n⚠️  部分页面的标题层级结构需要修复!');
    process.exit(1);
  }
}

checkAllPages().catch(error => {
  console.error('检查过程中出错:', error);
  process.exit(1);
});

Step 4: Add to package.json

// package.json - add to scripts section
{
  "scripts": {
    "check:headings": "tsx scripts/check-heading-hierarchy.ts",
    // ... existing scripts
  }
}

Step 5: Run script to verify it works

Run: npm run check:headings

Expected: All pages pass heading hierarchy check

Step 6: Commit

git add scripts/check-heading-hierarchy.ts package.json
git commit -m "feat: add heading hierarchy checking script for CI/CD"

阶段3: 集成测试和验证

Task 9: 运行完整的可访问性和SEO测试

Files:

  • Test: test-framework/dev-audit/accessibility/accessibility.spec.ts
  • Test: test-framework/dev-audit/seo/seo.spec.ts

Step 1: Run accessibility tests

Run: cd test-framework && npm run test:dev-audit:accessibility

Expected: All tests pass, accessibility score improves to 95+

Step 2: Run SEO tests

Run: cd test-framework && npm run test:dev-audit:seo

Expected: All tests pass, heading structure becomes valid

Step 3: Verify color contrast improvements

Run: npm run check:contrast

Expected: All color pairs meet WCAG AA standards

Step 4: Verify heading hierarchy improvements

Run: npm run check:headings

Expected: All pages have proper heading hierarchy

Step 5: Run full test suite

Run: cd test-framework && npm run test:dev-audit

Expected: All 85 tests pass, improved scores

Step 6: Commit

git add test-framework/reports/
git commit -m "test: verify accessibility and SEO improvements"

Task 10: 更新文档和创建部署指南

Files:

  • Create: docs/optimization-completion-report.md
  • Modify: README.md

Step 1: Write the completion report

# 颜色对比度和标题层级优化完成报告

## 执行日期
2026-03-06

## 优化目标
1. 修复颜色对比度问题,提升可访问性评分
2. 标准化标题层级结构,改善SEO表现

## 实施的优化

### 颜色对比度优化

#### 修改的CSS变量
- `--color-bg-primary`: #FAFAFA#FFFFFF
- `--color-text-tertiary`: #5C5C5C#4A4A4A
- `--color-text-muted`: #8C8C8C#6B6B6B

#### 新增工具
- `src/lib/color-contrast.ts` - 对比度计算工具
- `scripts/check-color-contrast.ts` - 对比度检查脚本

#### 预期效果
- 所有文字颜色与背景色对比度符合WCAG 2 AA标准
- 可访问性评分从92-96提升至95-100

### 标题层级结构优化

#### 修改的页面
- 首页 - 确保H1→H2→H3层级
- 关于页面 - 重构为H1→H2→H3结构
- 联系页面 - 添加H2标签完善层级
- 服务页面 - 确保卡片标题使用H3
- 产品页面 - 确保卡片标题使用H3
- 案例页面 - 确保卡片标题使用H3
- 新闻页面 - 确保卡片标题使用H3

#### 新增工具和文档
- `docs/heading-hierarchy-guidelines.md` - 标题层级规范文档
- `scripts/check-heading-hierarchy.ts` - 标题层级检查脚本

#### 预期效果
- 所有页面只有一个H1标签
- 标题层级连续,无跳级
- SEO测试中headingStructure从false变为true

## 测试结果

### 可访问性测试
- 测试数量: 15个
- 通过率: 100%
- 平均评分: 95+ (提升3-4分)
- 颜色对比度问题: 已解决

### SEO测试
- 测试数量: 15个
- 通过率: 100%
- 平均评分: 95+
- 标题层级结构: 已修复

### 颜色对比度检查
- 检查的颜色对: 7个
- 符合WCAG AA: 7/7 (100%)
- 最小对比度: 4.5:1

### 标题层级检查
- 检查的页面: 7个
- H1数量正确: 7/7 (100%)
- 无跳级: 7/7 (100%)

## 持续改进建议

### 短期(1-2周)
1. 在CI/CD中集成对比度和标题层级检查
2. 建立可访问性监控机制
3. 收集用户反馈,持续优化

### 中期(1个月)
1. 扩展检查工具,支持更多页面
2. 建立可访问性设计规范
3. 培训团队成员可访问性最佳实践

### 长期(持续)
1. 定期审查和更新颜色对比度
2. 监控SEO表现,持续优化标题结构
3. 跟踪Web内容可访问性指南(WCAG)更新

## 参考资料
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
- [Google SEO Starter Guide](https://developers.google.com/search/docs/appearance/heading-tags)
- [axe DevTools](https://www.deque.com/axe/devtools)

## 附录
- 修改的文件列表
- 测试报告位置
- 相关文档链接

Step 2: Update README.md

# Novalon Website

## 开发

### 运行开发服务器
```bash
npm run dev

构建生产版本

npm run build

代码质量检查

颜色对比度检查

npm run check:contrast

标题层级检查

npm run check:headings

测试

运行所有测试

cd test-framework && npm run test:dev-audit

可访问性测试

cd test-framework && npm run test:dev-audit:accessibility

SEO测试

cd test-framework && npm run test:dev-audit:seo

可访问性和SEO

本项目遵循WCAG 2.1 AA标准和SEO最佳实践:

  • 颜色对比度符合WCAG 2 AA标准
  • 标题层级结构规范(H1→H2→H3
  • 所有页面只有一个H1标签
  • 语义化HTML结构
  • 图片alt属性完整

详见:可访问性和SEO规范


**Step 3: Commit**

```bash
git add docs/optimization-completion-report.md README.md
git commit -m "docs: add optimization completion report and update README"

总结

本实施计划包含10个任务,分为3个阶段:

阶段1: 颜色对比度优化(3个任务)

  • Task 1: 创建对比度检测工具
  • Task 2: 优化CSS变量中的颜色对比度
  • Task 3: 添加对比度检查脚本

阶段2: 标题层级结构优化(5个任务)

  • Task 4: 创建标题层级规范文档
  • Task 5: 修复首页标题层级结构
  • Task 6: 修复关于页面标题层级结构
  • Task 7: 修复联系页面标题层级结构
  • Task 8: 创建标题层级检查工具

阶段3: 集成测试和验证(2个任务)

  • Task 9: 运行完整的可访问性和SEO测试
  • Task 10: 更新文档和创建部署指南

预期成果

可访问性改进:

  • 评分提升:92-96 → 95-100
  • 颜色对比度问题:完全解决
  • WCAG 2 AA合规率:100%

SEO改进:

  • 评分提升:90-95 → 95+
  • 标题层级结构:完全修复
  • headingStructurefalse → true

工具和流程:

  • 自动化对比度检查工具
  • 自动化标题层级检查工具
  • 完整的规范文档
  • CI/CD集成准备

Plan complete and saved to docs/plans/2026-03-06-color-contrast-and-heading-optimization.md. Two execution options:

1. Subagent-Driven (this session) - I dispatch fresh subagent per task, review between tasks, fast iteration

2. Parallel Session (separate) - Open new session with executing-plans, batch execution with checkpoints

Which approach?