merge: merge color-heading-optimization into feat-init

- Add color contrast optimization and checking tools
- Add heading hierarchy guidelines and checker
- Resolve package-lock.json conflict (keeping feat-init version)
This commit is contained in:
张翔
2026-03-07 10:47:43 +08:00
16 changed files with 218 additions and 8 deletions
+1 -1
View File
@@ -223,7 +223,7 @@ export function AboutClient() {
<span className="text-sm font-medium text-[#C41E3A]">{milestone.date}</span>
</div>
<div className="flex-1">
<h4 className="font-semibold text-[#1A1A2E] mb-1">{milestone.title}</h4>
<h3 className="font-semibold text-[#1A1A2E] mb-1">{milestone.title}</h3>
<p className="text-[#718096] text-sm">{milestone.description}</p>
</div>
</motion.div>
+4 -4
View File
@@ -183,7 +183,7 @@ export default function ContactPage() {
`}
>
<div>
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-6"></h3>
<h2 className="text-lg font-semibold text-[#1C1C1C] mb-6"></h2>
<div className="space-y-4" data-testid="contact-info">
<div className="flex items-start gap-4 group" data-testid="email-info">
<div className="w-10 h-10 bg-[#C41E3A] rounded-md flex items-center justify-center flex-shrink-0 transition-transform duration-200 group-hover:scale-105">
@@ -222,7 +222,7 @@ export default function ContactPage() {
<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]" />
<h4 className="text-sm font-medium text-[#1C1C1C]"></h4>
<h2 className="text-sm font-medium text-[#1C1C1C]"></h2>
</div>
<div className="space-y-1">
<div className="flex justify-between text-sm" data-testid="work-hours-row">
@@ -235,7 +235,7 @@ export default function ContactPage() {
<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]" />
<h4 className="text-sm font-medium text-[#1C1C1C]"></h4>
<h2 className="text-sm font-medium text-[#1C1C1C]"></h2>
</div>
<div className="space-y-3">
<div className="flex items-start gap-2">
@@ -262,7 +262,7 @@ export default function ContactPage() {
`}
>
<div className="bg-[#F5F7FA] p-6 sm:p-8 rounded-lg border border-[#E2E8F0] flex-1 flex flex-col">
<h3 className="text-lg font-semibold text-[#1A1A2E] mb-6"></h3>
<h2 className="text-lg font-semibold text-[#1A1A2E] mb-6"></h2>
{isSubmitted ? (
<div className="text-center py-12 flex-1 flex items-center justify-center">
+3 -3
View File
@@ -31,7 +31,7 @@
--color-brand-primary-bg: #FEF2F4;
/* 背景色系 - 宣纸白 */
--color-bg-primary: #FAFAFA;
--color-bg-primary: #FFFFFF;
--color-bg-secondary: #FFFBF5;
--color-bg-tertiary: #F5F5F5;
--color-bg-hover: #EFEFEF;
@@ -39,8 +39,8 @@
/* 文字色系 - 墨色层次 */
--color-text-primary: #1C1C1C;
--color-text-secondary: #3D3D3D;
--color-text-tertiary: #5C5C5C;
--color-text-muted: #8C8C8C;
--color-text-tertiary: #4A4A4A;
--color-text-muted: #6B6B6B;
/* 边框色系 */
--color-border-primary: #E5E5E5;
+63
View File
@@ -0,0 +1,63 @@
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
};
}