- 新增detail-v2组件库(HeroV2/V3, TrustSectionV2, CTASectionV2等) - Hero组件水墨雅致改造:浅色宣纸底/深色墨色文字/墨韵纹理 - 方案卡片添加推荐组合徽标(Package图标) - 独立产品页从V1组件迁移到V2/V3 - 修复why-us-section未使用导入和ESLint引号转义错误
140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
'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>
|
|
);
|
|
}
|