- 重构 About、Contact、Team 等静态营销页面 - 重构 News 新闻列表与详情页 - 重构 Products 产品目录、详情与独立产品页面 - 重构 Services 与 Solutions 服务/解决方案页面 - 重构 Detail 四层叙事组件 (Hero → Value → Trust → CTA) - 重构 Sections 页面区块组件 (Hero, CTA, SocialProof, WhyUs 等) - 新增 SectionHeader、ServiceCard、CaseCard 等可复用组件
101 lines
2.9 KiB
TypeScript
101 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import { motion } from 'framer-motion';
|
|
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
|
|
|
interface SectionHeaderProps {
|
|
/** English label shown above title (e.g. "What We Do") */
|
|
label: string;
|
|
/** Main title text */
|
|
title: string;
|
|
/** Optional highlighted part of the title (rendered in brand red) */
|
|
highlight?: string;
|
|
/** Optional description below the title */
|
|
desc?: string;
|
|
/** Whether this header is on a dark background */
|
|
light?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
const EASE = [0.22, 1, 0.36, 1] as const;
|
|
|
|
export function SectionHeader({
|
|
label,
|
|
title,
|
|
highlight,
|
|
desc,
|
|
light = false,
|
|
className = '',
|
|
}: SectionHeaderProps) {
|
|
const shouldReduceMotion = useReducedMotion();
|
|
|
|
return (
|
|
<motion.div
|
|
className={`mb-12 md:mb-16 lg:mb-20 ${className}`}
|
|
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, margin: '-40px' }}
|
|
transition={{ duration: 0.6, ease: EASE }}
|
|
>
|
|
{/* Eyebrow label with brand accent bar */}
|
|
<div className="flex items-center gap-3 mb-4 md:mb-5">
|
|
{/* Brand accent bar — grows from bottom on entry */}
|
|
<motion.div
|
|
className="w-1 rounded-full origin-bottom"
|
|
style={{
|
|
height: '1rem',
|
|
backgroundColor: light
|
|
? 'rgba(255,255,255,0.5)'
|
|
: 'var(--color-brand)',
|
|
}}
|
|
initial={shouldReduceMotion ? {} : { scaleY: 0 }}
|
|
whileInView={{ scaleY: 1 }}
|
|
viewport={{ once: true, margin: '-40px' }}
|
|
transition={{ duration: 0.4, delay: 0.1, ease: EASE }}
|
|
/>
|
|
<motion.div
|
|
className="text-[10px] tracking-[4px] uppercase font-medium"
|
|
style={{
|
|
color: light
|
|
? 'rgba(255,255,255,0.5)'
|
|
: 'var(--color-brand)',
|
|
}}
|
|
initial={shouldReduceMotion ? {} : { opacity: 0, x: -8 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true, margin: '-40px' }}
|
|
transition={{ duration: 0.4, delay: 0.2, ease: EASE }}
|
|
>
|
|
{label}
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Title */}
|
|
<h2
|
|
className="font-sans text-[clamp(28px,6vw,52px)] font-bold leading-[1.1] tracking-[-1px] mb-4 md:mb-5"
|
|
style={{ color: light ? '#FFFFFF' : 'var(--color-text-primary)' }}
|
|
>
|
|
{title}
|
|
{highlight && (
|
|
<span style={{ color: 'var(--color-brand)' }}>
|
|
{highlight}
|
|
</span>
|
|
)}
|
|
</h2>
|
|
|
|
{/* Description */}
|
|
{desc && (
|
|
<p
|
|
className="text-[15px] leading-relaxed max-w-[480px]"
|
|
style={{
|
|
color: light
|
|
? 'rgba(255,255,255,0.5)'
|
|
: 'var(--color-text-muted)',
|
|
}}
|
|
>
|
|
{desc}
|
|
</p>
|
|
)}
|
|
</motion.div>
|
|
);
|
|
}
|