feat: V2/V3组件体系与水墨雅致视觉改造

- 新增detail-v2组件库(HeroV2/V3, TrustSectionV2, CTASectionV2等)
- Hero组件水墨雅致改造:浅色宣纸底/深色墨色文字/墨韵纹理
- 方案卡片添加推荐组合徽标(Package图标)
- 独立产品页从V1组件迁移到V2/V3
- 修复why-us-section未使用导入和ESLint引号转义错误
This commit is contained in:
张翔
2026-06-07 16:19:44 +08:00
parent 2d602c0e57
commit 724a00f582
32 changed files with 3690 additions and 87 deletions
@@ -0,0 +1,139 @@
'use client';
import { motion, useMotionValue, useTransform, useSpring } from 'framer-motion';
import { useRef, ReactNode } from 'react';
interface TiltCardProps {
children: ReactNode;
className?: string;
tiltAmount?: number;
glareEnable?: boolean;
glareMaxOpacity?: number;
}
export function TiltCard({
children,
className = '',
tiltAmount = 10,
glareEnable = true,
glareMaxOpacity = 0.15,
}: TiltCardProps) {
const ref = useRef<HTMLDivElement>(null);
const x = useMotionValue(0.5);
const y = useMotionValue(0.5);
const rotateX = useSpring(useTransform(y, [0, 1], [tiltAmount, -tiltAmount]), {
stiffness: 300,
damping: 30,
});
const rotateY = useSpring(useTransform(x, [0, 1], [-tiltAmount, tiltAmount]), {
stiffness: 300,
damping: 30,
});
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
x.set((e.clientX - rect.left) / rect.width);
y.set((e.clientY - rect.top) / rect.height);
};
const handleMouseLeave = () => {
x.set(0.5);
y.set(0.5);
};
return (
<motion.div
ref={ref}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
style={{
rotateX,
rotateY,
transformStyle: 'preserve-3d',
perspective: 1000,
}}
className={`relative ${className}`}
>
{children}
{glareEnable && (
<div
className="pointer-events-none absolute inset-0 rounded-[inherit] transition-opacity duration-300"
style={{
opacity: glareMaxOpacity,
background: `radial-gradient(circle at ${x.get() * 100}% ${y.get() * 100}%, white 0%, transparent 60%)`,
}}
/>
)}
</motion.div>
);
}
interface PressableButtonProps {
children: ReactNode;
className?: string;
onClick?: () => void;
variant?: 'primary' | 'secondary' | 'ghost';
}
export function PressableButton({
children,
className = '',
onClick,
variant = 'primary',
}: PressableButtonProps) {
const baseStyles = {
primary:
'bg-gradient-to-r from-[#C41E3A] to-[#99182d] text-white shadow-lg shadow-red-500/25 hover:shadow-xl hover:shadow-red-500/40',
secondary:
'bg-white text-gray-700 border-2 border-gray-200 hover:border-gray-300 shadow-md hover:shadow-lg',
ghost:
'bg-transparent text-[#C41E3A] hover:bg-red-50',
};
return (
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98, transition: { duration: 0.1 } }}
onClick={onClick}
className={`inline-flex items-center gap-2 px-6 py-3 font-semibold rounded-xl transition-all duration-200 ${baseStyles[variant]} ${className}`}
>
{children}
</motion.button>
);
}
interface InkGlowCardProps {
children: ReactNode;
className?: string;
active?: boolean;
}
export function InkGlowCard({ children, className = '', active = false }: InkGlowCardProps) {
return (
<motion.div
whileHover={{
y: -4,
boxShadow: active
? '0 20px 40px rgba(196, 30, 58, 0.15), 0 0 0 1px rgba(196, 30, 58, 0.1)'
: '0 20px 40px rgba(0, 0, 0, 0.08)',
transition: { duration: 0.3 },
}}
className={`relative bg-white rounded-2xl overflow-hidden ${active ? 'border border-red-100' : 'border border-gray-100'} ${className}`}
>
{active && (
<div className="absolute inset-0 bg-gradient-to-br from-red-50/50 to-transparent pointer-events-none" />
)}
<div className="absolute inset-0 opacity-0 hover:opacity-100 transition-opacity duration-500">
<div
className="w-full h-full"
style={{
background: `conic-gradient(from var(--angle), transparent 0%, rgba(196, 30, 58, 0.08) 10%, transparent 20%)`,
}}
/>
</div>
<div className="relative">{children}</div>
</motion.div>
);
}