chore: 更新构建ID和相关文件引用
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
'use client';
|
||||
|
||||
import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
|
||||
import { useRef, useState, type ReactNode, type MouseEvent } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const cardVariants: Variants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 30,
|
||||
scale: 0.95,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface InkCardProps extends HTMLMotionProps<'div'> {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
hoverScale?: number;
|
||||
hoverRotate?: number;
|
||||
inkColor?: string;
|
||||
showInkOnHover?: boolean;
|
||||
}
|
||||
|
||||
export function InkCard({
|
||||
children,
|
||||
className = '',
|
||||
hoverScale = 1.02,
|
||||
hoverRotate = 0,
|
||||
inkColor = 'rgba(196, 30, 58, 0.05)',
|
||||
showInkOnHover = true,
|
||||
...props
|
||||
}: InkCardProps) {
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const [inkPosition, setInkPosition] = useState({ x: 50, y: 50 });
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (!cardRef.current) return;
|
||||
const rect = cardRef.current.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||
setInkPosition({ x, y });
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={cardRef}
|
||||
variants={cardVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
whileHover={{
|
||||
scale: hoverScale,
|
||||
rotate: hoverRotate,
|
||||
y: -4,
|
||||
}}
|
||||
onHoverStart={() => setIsHovered(true)}
|
||||
onHoverEnd={() => setIsHovered(false)}
|
||||
onMouseMove={handleMouseMove}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
damping: 20,
|
||||
}}
|
||||
className={cn(
|
||||
'relative overflow-hidden bg-white border border-[#E5E5E5] rounded-xl transition-shadow duration-300',
|
||||
'hover:shadow-[0_12px_24px_rgba(28,28,28,0.08)]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{showInkOnHover && (
|
||||
<motion.div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: isHovered ? 1 : 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div
|
||||
className="absolute w-[200%] h-[200%] rounded-full"
|
||||
style={{
|
||||
background: `radial-gradient(circle, ${inkColor} 0%, transparent 70%)`,
|
||||
left: `${inkPosition.x - 100}%`,
|
||||
top: `${inkPosition.y - 100}%`,
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
<div className="relative z-10">{children}</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface GeometricCardProps extends HTMLMotionProps<'div'> {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
cornerColor?: string;
|
||||
}
|
||||
|
||||
export function GeometricCard({
|
||||
children,
|
||||
className = '',
|
||||
cornerColor = '#C41E3A',
|
||||
...props
|
||||
}: GeometricCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
variants={cardVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
whileHover={{
|
||||
y: -4,
|
||||
transition: { type: 'spring', stiffness: 300, damping: 20 },
|
||||
}}
|
||||
className={cn(
|
||||
'relative bg-white border border-[#E5E5E5] rounded-xl p-6 transition-all duration-300',
|
||||
'hover:shadow-[0_12px_24px_rgba(28,28,28,0.08)]',
|
||||
'group',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="absolute top-0 left-0 w-3 h-3 border-t-2 border-l-2 rounded-tl-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300" style={{ borderColor: cornerColor }} />
|
||||
<div className="absolute top-0 right-0 w-3 h-3 border-t-2 border-r-2 rounded-tr-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300" style={{ borderColor: cornerColor }} />
|
||||
<div className="absolute bottom-0 left-0 w-3 h-3 border-b-2 border-l-2 rounded-bl-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300" style={{ borderColor: cornerColor }} />
|
||||
<div className="absolute bottom-0 right-0 w-3 h-3 border-b-2 border-r-2 rounded-br-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300" style={{ borderColor: cornerColor }} />
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface FlipCardProps {
|
||||
front: ReactNode;
|
||||
back: ReactNode;
|
||||
className?: string;
|
||||
frontClassName?: string;
|
||||
backClassName?: string;
|
||||
}
|
||||
|
||||
export function FlipCard({
|
||||
front,
|
||||
back,
|
||||
className = '',
|
||||
frontClassName = '',
|
||||
backClassName = '',
|
||||
}: FlipCardProps) {
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={cn('relative cursor-pointer perspective-1000', className)}
|
||||
onClick={() => setIsFlipped(!isFlipped)}
|
||||
style={{ perspective: 1000 }}
|
||||
>
|
||||
<motion.div
|
||||
className="relative w-full h-full"
|
||||
initial={false}
|
||||
animate={{ rotateY: isFlipped ? 180 : 0 }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-0 backface-hidden',
|
||||
frontClassName
|
||||
)}
|
||||
style={{ backfaceVisibility: 'hidden' }}
|
||||
>
|
||||
{front}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-0 backface-hidden',
|
||||
backClassName
|
||||
)}
|
||||
style={{
|
||||
backfaceVisibility: 'hidden',
|
||||
transform: 'rotateY(180deg)',
|
||||
}}
|
||||
>
|
||||
{back}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TiltCardProps extends HTMLMotionProps<'div'> {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
maxTilt?: number;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export function TiltCard({
|
||||
children,
|
||||
className = '',
|
||||
maxTilt = 10,
|
||||
scale = 1.02,
|
||||
...props
|
||||
}: TiltCardProps) {
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const [rotateX, setRotateX] = useState(0);
|
||||
const [rotateY, setRotateY] = useState(0);
|
||||
|
||||
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (!cardRef.current) return;
|
||||
const rect = cardRef.current.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const mouseX = e.clientX - centerX;
|
||||
const mouseY = e.clientY - centerY;
|
||||
|
||||
const rotateXValue = (mouseY / (rect.height / 2)) * -maxTilt;
|
||||
const rotateYValue = (mouseX / (rect.width / 2)) * maxTilt;
|
||||
|
||||
setRotateX(rotateXValue);
|
||||
setRotateY(rotateYValue);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setRotateX(0);
|
||||
setRotateY(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={cardRef}
|
||||
className={cn('relative', className)}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
animate={{
|
||||
rotateX,
|
||||
rotateY,
|
||||
scale: rotateX || rotateY ? scale : 1,
|
||||
}}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface GlowCardProps extends HTMLMotionProps<'div'> {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
glowColor?: string;
|
||||
}
|
||||
|
||||
export function GlowCard({
|
||||
children,
|
||||
className = '',
|
||||
glowColor = 'rgba(196, 30, 58, 0.15)',
|
||||
...props
|
||||
}: GlowCardProps) {
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const [glowPosition, setGlowPosition] = useState({ x: 50, y: 50 });
|
||||
|
||||
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (!cardRef.current) return;
|
||||
const rect = cardRef.current.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||
setGlowPosition({ x, y });
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={cardRef}
|
||||
variants={cardVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
whileHover={{ y: -4 }}
|
||||
onMouseMove={handleMouseMove}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
||||
className={cn(
|
||||
'relative overflow-hidden bg-white border border-[#E5E5E5] rounded-xl',
|
||||
'transition-shadow duration-300',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute inset-0 pointer-events-none opacity-0 hover:opacity-100 transition-opacity duration-300"
|
||||
style={{
|
||||
background: `radial-gradient(circle at ${glowPosition.x}% ${glowPosition.y}%, ${glowColor} 0%, transparent 50%)`,
|
||||
}}
|
||||
/>
|
||||
<div className="relative z-10">{children}</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ExpandCardProps extends HTMLMotionProps<'div'> {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
expandedContent?: ReactNode;
|
||||
}
|
||||
|
||||
export function ExpandCard({
|
||||
children,
|
||||
className = '',
|
||||
expandedContent,
|
||||
...props
|
||||
}: ExpandCardProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
variants={cardVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
whileHover={{ y: -4 }}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
||||
className={cn(
|
||||
'relative overflow-hidden bg-white border border-[#E5E5E5] rounded-xl cursor-pointer',
|
||||
'transition-shadow duration-300',
|
||||
'hover:shadow-[0_12px_24px_rgba(28,28,28,0.08)]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="p-6">{children}</div>
|
||||
{expandedContent && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{
|
||||
height: isExpanded ? 'auto' : 0,
|
||||
opacity: isExpanded ? 1 : 0,
|
||||
}}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="px-6 pb-6 border-t border-[#E5E5E5] pt-4">
|
||||
{expandedContent}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user