chore(cleanup): 清理废弃组件与旧版本归档

- 删除 detail-v2 旧版叙事组件
- 删除 narrative 旧版区块组件
- 删除 theme-toggle 主题切换与 theme-context 上下文
- 删除 detail-data-proof/service-value/solution-value 旧版组件
- 删除 home-content-v2/v3 旧版首页
- 归档旧版营销页面内容至 _archive 目录
This commit is contained in:
张翔
2026-07-07 06:54:10 +08:00
parent b5245f9aa2
commit 8def296301
33 changed files with 6200 additions and 3645 deletions
@@ -1,122 +0,0 @@
'use client';
import { useRef, useState } from 'react';
import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { ArrowRight, MessageCircle } from 'lucide-react';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
export interface NarrativeCTAProps {
title?: string;
description?: string;
primaryLabel?: string;
primaryHref?: string;
secondaryLabel?: string;
secondaryHref?: string;
}
/**
* 第四层:行动号召
* 全宽深色背景 + 水墨纹理 + 双CTA按钮
*/
export function NarrativeCTA({
title = '一起聊聊您的数字化需求',
description = '无论您处于数字化转型的哪个阶段,我们都愿意坐下来一起想办法。首次咨询免费,无任何销售压力。',
primaryLabel = '预约免费咨询',
primaryHref = '/contact',
secondaryLabel = '了解我们的方法',
secondaryHref = '/team',
}: NarrativeCTAProps) {
const shouldReduceMotion = useReducedMotion();
const containerRef = useRef<HTMLDivElement>(null);
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const [isHovered, setIsHovered] = useState(false);
const springX = useSpring(mouseX, { stiffness: 80, damping: 20 });
const springY = useSpring(mouseY, { stiffness: 80, damping: 20 });
const glowX = useTransform(springX, [0, 1], ['-10%', '110%']);
const glowY = useTransform(springY, [0, 1], ['-10%', '110%']);
function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
if (!containerRef.current || shouldReduceMotion) return;
const rect = containerRef.current.getBoundingClientRect();
mouseX.set((e.clientX - rect.left) / rect.width);
mouseY.set((e.clientY - rect.top) / rect.height);
}
return (
<section
id="cta"
ref={containerRef}
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className="relative py-24 md:py-32 overflow-hidden"
style={{ backgroundColor: 'var(--color-cta-bg)' }}
>
{/* Ink texture background */}
<div className="pointer-events-none absolute inset-0">
{!shouldReduceMotion && (
<motion.div
className="absolute inset-[-20%]"
style={{
background: 'radial-gradient(circle at center, rgba(196, 30, 58, 0.06) 0%, transparent 50%)',
x: glowX,
y: glowY,
opacity: isHovered ? 1 : 0.4,
}}
transition={{ opacity: { duration: 0.6 } }}
/>
)}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
backgroundRepeat: 'repeat',
backgroundSize: '200px 200px',
}}
/>
</div>
<div className="container-wide relative z-10">
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, scale: 0.97, y: 16 }}
whileInView={{ opacity: 1, scale: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.6, ease: [0.25, 1, 0.5, 1] }}
className="text-center max-w-3xl mx-auto"
>
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-semibold text-white mb-6 tracking-tight leading-tight">
{title}
</h2>
<p className="text-lg text-white/60 leading-relaxed mb-10 max-w-2xl mx-auto">
{description}
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<Button size="lg" asChild className="group relative overflow-hidden">
<StaticLink href={primaryHref}>
<span className="relative z-10 flex items-center">
<MessageCircle className="w-4 h-4 mr-2" />
{primaryLabel}
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
</span>
</StaticLink>
</Button>
<Button
size="lg"
variant="outline"
className="border-white/20 text-white hover:bg-white/10 hover:border-white/35 backdrop-blur-sm"
asChild
>
<StaticLink href={secondaryHref}>{secondaryLabel}</StaticLink>
</Button>
</div>
</motion.div>
</div>
</section>
);
}
@@ -1,169 +0,0 @@
'use client';
import { motion } from 'framer-motion';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { InkWashBackground } from '@/components/ui/ink-wash';
import { ArrowRight } from 'lucide-react';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import type { ReactNode } from 'react';
export interface NarrativeHeroProps {
/** Page badge/tag */
badge?: { text: string; variant?: 'brand' | 'blue' | 'green' | 'purple' };
/** Main headline */
title: string;
/** Keyword highlighted in calligraphy font + brand red */
highlight?: string;
/** Subtitle / one-line value proposition */
subtitle: string;
/** Description paragraph */
description?: string;
/** Primary CTA */
primaryCta?: { label: string; href: string };
/** Secondary CTA */
secondaryCta?: { label: string; href: string };
/** Right side content (card, image, etc.) */
rightContent?: ReactNode;
/** Stats row */
stats?: { value: string; label: string }[];
/** Extra content below description */
extraContent?: ReactNode;
}
const EASE = [0.25, 1, 0.5, 1] as const;
export function NarrativeHero({
badge,
title,
highlight,
subtitle,
description,
primaryCta,
secondaryCta,
rightContent,
stats,
extraContent,
}: NarrativeHeroProps) {
const shouldReduceMotion = useReducedMotion();
const fadeUp = (delay: number) => ({
initial: shouldReduceMotion ? {} : { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.6, delay, ease: EASE },
});
return (
<section
className="relative min-h-[80vh] flex flex-col justify-center overflow-hidden bg-[var(--color-bg-primary)]"
>
<InkWashBackground variant="hero" />
<div className="container-wide py-20 md:py-28 lg:py-36 relative z-10 flex-1 flex items-center">
<div className={`grid grid-cols-1 ${rightContent ? 'lg:grid-cols-2 gap-10 lg:gap-16' : 'max-w-4xl mx-auto text-center'} items-center w-full`}>
<div className={rightContent ? '' : 'mx-auto'}>
{/* Badge */}
{badge && (
<motion.div {...fadeUp(0)} className="mb-6">
<span className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[var(--color-brand-primary-bg)] text-[var(--color-brand-primary)] text-xs font-medium border border-[var(--color-brand-primary)]/10">
{badge.text}
</span>
</motion.div>
)}
{/* Subtitle above title */}
<motion.p
{...fadeUp(0.05)}
className="text-base sm:text-lg text-[var(--color-text-muted)] mb-3"
>
{subtitle}
</motion.p>
{/* Title with calligraphy highlight */}
<motion.h1
{...fadeUp(0.1)}
className="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl tracking-tight mb-6 leading-[1.12]"
>
{highlight ? (
<>
{title}{' '}
<span className="text-[var(--color-brand-primary)] font-calligraphy">{highlight}</span>
</>
) : (
title
)}
</motion.h1>
{/* Description */}
{description && (
<motion.p
{...fadeUp(0.2)}
className="text-base sm:text-lg text-[var(--color-text-muted)] max-w-lg leading-relaxed mb-8"
>
{description}
</motion.p>
)}
{/* CTAs */}
{(primaryCta || secondaryCta) && (
<motion.div
{...fadeUp(0.3)}
className={`flex ${rightContent ? 'flex-col sm:flex-row items-start' : 'flex-col sm:flex-row items-center justify-center'} gap-4 mb-10`}
>
{primaryCta && (
<Button size="lg" asChild className="min-h-[52px] px-8 text-base font-semibold shadow-lg shadow-[var(--color-brand-primary)]/15">
<StaticLink href={primaryCta.href}>
{primaryCta.label}
<ArrowRight className="w-4 h-4 ml-2" />
</StaticLink>
</Button>
)}
{secondaryCta && (
<Button size="lg" variant="ghost" asChild className="text-[var(--color-text-muted)]">
<StaticLink href={secondaryCta.href}>{secondaryCta.label}</StaticLink>
</Button>
)}
</motion.div>
)}
{/* Extra content */}
{extraContent}
</div>
{/* Right side content */}
{rightContent && (
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 28 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.4, ease: EASE }}
>
{rightContent}
</motion.div>
)}
</div>
</div>
{/* Stats row */}
{stats && stats.length > 0 && (
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.6, ease: EASE }}
className="container-wide pb-16 relative z-10"
>
<div className="ink-divider mb-10" />
<div className="flex flex-wrap justify-center gap-8 md:gap-16">
{stats.map((stat) => (
<div key={stat.label} className="text-center">
<div className="text-3xl md:text-4xl font-bold text-[var(--color-text-primary)] leading-none mb-1">
{stat.value}
</div>
<div className="text-xs text-[var(--color-text-subtle)] tracking-wide">{stat.label}</div>
</div>
))}
</div>
</motion.div>
)}
</section>
);
}
@@ -1,97 +0,0 @@
'use client';
import { motion } from 'framer-motion';
import { InkWashBackground } from '@/components/ui/ink-wash';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import type { ReactNode } from 'react';
export interface NarrativeTrustProps {
/** Section badge */
badge?: string;
/** Section title */
title: string;
/** Title highlight */
highlight?: string;
/** Stat metrics */
metrics?: { value: string; label: string; description?: string }[];
/** Custom content (e.g., case studies, testimonials) */
children?: ReactNode;
/** Background variant */
bgVariant?: 'default' | 'section';
}
/**
* 第三层:信任证明
* 数据指标 + 案例摘要 + 资质认证
*/
export function NarrativeTrust({
badge,
title,
highlight,
metrics,
children,
bgVariant = 'default',
}: NarrativeTrustProps) {
const shouldReduceMotion = useReducedMotion();
return (
<section className={`relative section-padding overflow-hidden ${bgVariant === 'section' ? 'bg-[var(--color-bg-section)]' : 'bg-[var(--color-bg-primary)]'}`}>
<InkWashBackground variant="subtle" />
<div className="container-wide relative z-10">
{/* Header */}
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6, ease: [0.25, 1, 0.5, 1] }}
className="mb-14"
>
{badge && (
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-[var(--color-brand-primary-bg)] border border-[var(--color-brand-primary)]/10 mb-4">
<span className="w-1.5 h-1.5 rounded-full bg-[var(--color-brand-primary)]" />
<span className="text-xs font-medium tracking-wider text-[var(--color-brand-primary)]">{badge}</span>
</div>
)}
<h2 className="text-3xl sm:text-4xl font-semibold text-[var(--color-text-primary)] mb-4">
{title}
{highlight && <span className="text-[var(--color-brand-primary)] font-calligraphy ml-1">{highlight}</span>}
</h2>
</motion.div>
{/* Metrics strip */}
{metrics && metrics.length > 0 && (
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, ease: [0.25, 1, 0.5, 1] }}
className="grid grid-cols-2 md:grid-cols-4 gap-6 md:gap-8 mb-16"
>
{metrics.map((metric, idx) => (
<motion.div
key={metric.label}
initial={shouldReduceMotion ? {} : { opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: idx * 0.06 }}
className="text-center p-6 rounded-xl bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)]"
>
<div className="text-3xl md:text-4xl font-bold text-[var(--color-brand-primary)] leading-none mb-2">
{metric.value}
</div>
<div className="text-sm font-medium text-[var(--color-text-primary)] mb-1">{metric.label}</div>
{metric.description && (
<div className="text-xs text-[var(--color-text-subtle)]">{metric.description}</div>
)}
</motion.div>
))}
</motion.div>
)}
{/* Custom content */}
{children}
</div>
</section>
);
}
@@ -1,117 +0,0 @@
'use client';
import { motion } from 'framer-motion';
import { InkCard } from '@/components/ui/ink-wash';
import { InkWashBackground } from '@/components/ui/ink-wash';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import type { LucideIcon } from 'lucide-react';
export interface ValuePoint {
icon: LucideIcon;
title: string;
description: string;
/** Optional stat number shown in corner */
stat?: string;
statLabel?: string;
}
export interface NarrativeValueProps {
/** Section badge */
badge?: string;
/** Section title */
title: string;
/** Title highlight (calligraphy) */
highlight?: string;
/** Subtitle */
subtitle?: string;
/** Value points */
points: ValuePoint[];
/** Grid columns */
columns?: 2 | 3 | 4 | 5;
/** Background variant */
bgVariant?: 'default' | 'section';
}
export function NarrativeValue({
badge,
title,
highlight,
subtitle,
points,
columns = 3,
bgVariant = 'section',
}: NarrativeValueProps) {
const shouldReduceMotion = useReducedMotion();
const gridCols = {
2: 'md:grid-cols-2',
3: 'md:grid-cols-2 lg:grid-cols-3',
4: 'md:grid-cols-2 lg:grid-cols-4',
5: 'md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5',
};
return (
<section className={`relative section-padding overflow-hidden ${bgVariant === 'section' ? 'bg-[var(--color-bg-section)]' : 'bg-[var(--color-bg-primary)]'}`}>
<InkWashBackground variant="subtle" />
<div className="container-wide relative z-10">
{/* Header */}
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6, ease: [0.25, 1, 0.5, 1] }}
className="mb-14"
>
{badge && (
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-[var(--color-brand-primary-bg)] border border-[var(--color-brand-primary)]/10 mb-4">
<span className="w-1.5 h-1.5 rounded-full bg-[var(--color-brand-primary)]" />
<span className="text-xs font-medium tracking-wider text-[var(--color-brand-primary)]">{badge}</span>
</div>
)}
<h2 className="text-3xl sm:text-4xl font-semibold text-[var(--color-text-primary)] mb-4">
{title}
{highlight && <span className="text-[var(--color-brand-primary)] font-calligraphy ml-1">{highlight}</span>}
</h2>
{subtitle && (
<p className="text-base text-[var(--color-text-muted)] max-w-2xl">{subtitle}</p>
)}
</motion.div>
{/* Value cards grid */}
<div className={`grid grid-cols-1 ${gridCols[columns]} gap-6 md:gap-8`}>
{points.map((point, idx) => {
const Icon = point.icon;
return (
<motion.div
key={point.title}
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.5, delay: idx * 0.08, ease: [0.25, 1, 0.5, 1] }}
>
<InkCard padding="md" className="h-full">
<div className="flex items-start justify-between mb-4">
<div className="w-11 h-11 rounded-xl flex items-center justify-center bg-[var(--color-brand-primary-bg)]">
<Icon className="w-5 h-5 text-[var(--color-brand-primary)]" strokeWidth={1.8} />
</div>
{point.stat && (
<div className="text-right">
<div className="text-2xl font-bold text-[var(--color-brand-primary)] leading-none">{point.stat}</div>
{point.statLabel && (
<div className="text-[10px] text-[var(--color-text-subtle)] mt-0.5">{point.statLabel}</div>
)}
</div>
)}
</div>
<h3 className="text-base font-semibold text-[var(--color-text-primary)] mb-2">{point.title}</h3>
<p className="text-sm text-[var(--color-text-muted)] leading-relaxed">{point.description}</p>
</InkCard>
</motion.div>
);
})}
</div>
</div>
</section>
);
}
@@ -1,8 +0,0 @@
export { NarrativeHero } from './NarrativeHero';
export type { NarrativeHeroProps } from './NarrativeHero';
export { NarrativeValue } from './NarrativeValue';
export type { NarrativeValueProps, ValuePoint } from './NarrativeValue';
export { NarrativeTrust } from './NarrativeTrust';
export type { NarrativeTrustProps } from './NarrativeTrust';
export { NarrativeCTA } from './NarrativeCTA';
export type { NarrativeCTAProps } from './NarrativeCTA';