Merge version_6_1776318983396 into main
Merge version_6_1776318983396 into main
This commit was merged in pull request #6.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { motion } from "motion/react";
|
||||
import { Info } from "lucide-react";
|
||||
import { useRef } from "react";
|
||||
import TextAnimation from "@/components/ui/TextAnimation";
|
||||
import ImageOrVideo from "@/components/ui/ImageOrVideo";
|
||||
import GridOrCarousel from "@/components/ui/GridOrCarousel";
|
||||
import Button from "@/components/ui/Button";
|
||||
import { useParallax } from "@/hooks/useParallax";
|
||||
|
||||
type FeatureItem = {
|
||||
title: string;
|
||||
@@ -19,6 +21,47 @@ interface FeaturesRevealCardsProps {
|
||||
items: FeatureItem[];
|
||||
}
|
||||
|
||||
const ParallaxFeatureCard = ({ item, index }: { item: FeatureItem; index: number }) => {
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const transform = useParallax(cardRef, { maxRotate: 5 });
|
||||
|
||||
return (
|
||||
<div ref={cardRef} className="group relative overflow-hidden aspect-6/7 rounded parallax-card-container">
|
||||
<div className="w-full h-full parallax-card" style={{ transform }}>
|
||||
<ImageOrVideo imageSrc={item.imageSrc} videoSrc={item.videoSrc} className="absolute inset-0" />
|
||||
|
||||
<div className="absolute top-5 left-5 z-20 perspective-[1000px]">
|
||||
<div className="relative size-8 transform-3d transition-transform duration-400 group-hover:rotate-y-180">
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded bg-background backface-hidden">
|
||||
<span className="text-sm font-medium text-foreground">{index + 1}</span>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded bg-background backface-hidden rotate-y-180">
|
||||
<Info className="h-1/2 w-1/2 text-foreground" strokeWidth={1.5} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 h-2/5 backdrop-blur-xl mask-fade-top-overlay" aria-hidden="true" />
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 z-10 p-1">
|
||||
<div className="relative flex flex-col gap-1 p-3">
|
||||
<div className="absolute inset-0 -z-10 card rounded translate-y-full opacity-0 transition-all duration-400 ease-out group-hover:translate-y-0 group-hover:opacity-100" />
|
||||
|
||||
<h3 className="text-2xl font-semibold leading-tight text-background transition-colors duration-400 group-hover:text-foreground">
|
||||
{item.title}
|
||||
</h3>
|
||||
<div className="grid grid-rows-[0fr] transition-all duration-400 ease-out group-hover:grid-rows-[1fr]">
|
||||
<p className="overflow-hidden text-sm leading-tight text-foreground opacity-0 transition-opacity duration-400 group-hover:opacity-100">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FeaturesRevealCards = ({
|
||||
tag,
|
||||
title,
|
||||
@@ -63,37 +106,7 @@ const FeaturesRevealCards = ({
|
||||
>
|
||||
<GridOrCarousel>
|
||||
{items.map((item, index) => (
|
||||
<div key={item.title} className="group relative overflow-hidden aspect-6/7 rounded">
|
||||
<ImageOrVideo imageSrc={item.imageSrc} videoSrc={item.videoSrc} className="absolute inset-0" />
|
||||
|
||||
<div className="absolute top-5 left-5 z-20 perspective-[1000px]">
|
||||
<div className="relative size-8 transform-3d transition-transform duration-400 group-hover:rotate-y-180">
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded bg-background backface-hidden">
|
||||
<span className="text-sm font-medium text-foreground">{index + 1}</span>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded bg-background backface-hidden rotate-y-180">
|
||||
<Info className="h-1/2 w-1/2 text-foreground" strokeWidth={1.5} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 h-2/5 backdrop-blur-xl mask-fade-top-overlay" aria-hidden="true" />
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 z-10 p-1">
|
||||
<div className="relative flex flex-col gap-1 p-3">
|
||||
<div className="absolute inset-0 -z-10 card rounded translate-y-full opacity-0 transition-all duration-400 ease-out group-hover:translate-y-0 group-hover:opacity-100" />
|
||||
|
||||
<h3 className="text-2xl font-semibold leading-tight text-background transition-colors duration-400 group-hover:text-foreground">
|
||||
{item.title}
|
||||
</h3>
|
||||
<div className="grid grid-rows-[0fr] transition-all duration-400 ease-out group-hover:grid-rows-[1fr]">
|
||||
<p className="overflow-hidden text-sm leading-tight text-foreground opacity-0 transition-opacity duration-400 group-hover:opacity-100">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ParallaxFeatureCard key={item.title} item={item} index={index} />
|
||||
))}
|
||||
</GridOrCarousel>
|
||||
</motion.div>
|
||||
|
||||
36
src/hooks/useParallax.ts
Normal file
36
src/hooks/useParallax.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useState, useEffect, type RefObject } from "react";
|
||||
|
||||
export const useParallax = (ref: RefObject<HTMLElement>, options?: { maxRotate?: number }) => {
|
||||
const [transform, setTransform] = useState("rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)");
|
||||
const maxRotate = options?.maxRotate || 5;
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) return;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const { left, top, width, height } = element.getBoundingClientRect();
|
||||
const x = e.clientX - left;
|
||||
const y = e.clientY - top;
|
||||
|
||||
const rotateX = ((y / height) - 0.5) * -maxRotate * 2;
|
||||
const rotateY = ((x / width) - 0.5) * maxRotate * 2;
|
||||
|
||||
setTransform(`rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.05, 1.05, 1.05)`);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setTransform("rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)");
|
||||
};
|
||||
|
||||
element.addEventListener("mousemove", handleMouseMove);
|
||||
element.addEventListener("mouseleave", handleMouseLeave);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener("mousemove", handleMouseMove);
|
||||
element.removeEventListener("mouseleave", handleMouseLeave);
|
||||
};
|
||||
}, [ref, maxRotate]);
|
||||
|
||||
return transform;
|
||||
};
|
||||
@@ -168,3 +168,12 @@
|
||||
.animate-marquee-horizontal-reverse {
|
||||
animation: marquee-horizontal-reverse 15s linear infinite;
|
||||
}
|
||||
|
||||
.parallax-card-container {
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
.parallax-card {
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user