feat: V2/V3组件体系与水墨雅致视觉改造
- 新增detail-v2组件库(HeroV2/V3, TrustSectionV2, CTASectionV2等) - Hero组件水墨雅致改造:浅色宣纸底/深色墨色文字/墨韵纹理 - 方案卡片添加推荐组合徽标(Package图标) - 独立产品页从V1组件迁移到V2/V3 - 修复why-us-section未使用导入和ESLint引号转义错误
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user