15 Commits

Author SHA1 Message Date
014f2cf1c6 Merge version_7_1776319843781 into main
Merge version_7_1776319843781 into main
2026-04-16 06:16:03 +00:00
kudinDmitriyUp
d9c74ddab9 Bob AI: fix build errors (attempt 2) 2026-04-16 06:15:40 +00:00
kudinDmitriyUp
eaa26d9599 Bob AI: fix build errors (attempt 1) 2026-04-16 06:13:21 +00:00
kudinDmitriyUp
688d670892 Bob AI: Implement a 3D parallax effect on the feature cards within t 2026-04-16 06:12:33 +00:00
6d3a8c3e19 Merge version_6_1776318983396 into main
Merge version_6_1776318983396 into main
2026-04-16 06:02:51 +00:00
kudinDmitriyUp
b76d2874f6 Bob AI: fix build errors (attempt 2) 2026-04-16 06:02:33 +00:00
kudinDmitriyUp
40d71e0f0a Bob AI: fix build errors (attempt 1) 2026-04-16 06:01:21 +00:00
kudinDmitriyUp
e0e655c575 Bob AI: Implement a 3D parallax effect on the feature cards in the ' 2026-04-16 06:00:29 +00:00
9bad83cd4d Merge version_5_1776317976255 into main
Merge version_5_1776317976255 into main
2026-04-16 05:40:57 +00:00
kudinDmitriyUp
9bbe263701 Bob AI: Apply CSS styles to the buttons in the hero section to give 2026-04-16 05:40:37 +00:00
253bf96646 Merge version_4_1776316352200 into main
Merge version_4_1776316352200 into main
2026-04-16 05:14:52 +00:00
kudinDmitriyUp
c46d47952f Bob AI: Reorder the elements within the hero section: move the text 2026-04-16 05:14:31 +00:00
725ae37201 Merge version_3_1776316186565 into main
Merge version_3_1776316186565 into main
2026-04-16 05:11:27 +00:00
kudinDmitriyUp
dad69dac5b Bob AI: Adjust the vertical alignment of the text, subtext, and butt 2026-04-16 05:11:06 +00:00
0c1d2ac648 Merge version_2_1776316001450 into main
Merge version_2_1776316001450 into main
2026-04-16 05:08:35 +00:00
5 changed files with 117 additions and 38 deletions

View File

@@ -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>

View File

@@ -38,11 +38,6 @@ const HeroAnimated = ({
className="hero-animated-container"
onMouseMove={handleMouseMove}
>
<div className="hero-animated-image-container">
<div className="hero-animated-parallax-wrapper" style={parallaxStyle}>
<ImageOrVideo imageSrc={imageSrc} videoSrc={videoSrc} className="hero-animated-image" />
</div>
</div>
<div className="hero-animated-content w-content-width mx-auto">
<div className="flex flex-col items-center gap-3 md:gap-5">
<span className="px-3 py-1 mb-1 text-sm card rounded">{tag}</span>
@@ -59,11 +54,16 @@ const HeroAnimated = ({
className="max-w-8/10 text-lg md:text-xl leading-tight text-center"
/>
<div className="flex flex-wrap justify-center gap-3 mt-2">
<Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate />
<Button text={secondaryButton.text} href={secondaryButton.href} variant="secondary" animate delay={0.1} />
<Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate className="btn-3d" />
<Button text={secondaryButton.text} href={secondaryButton.href} variant="secondary" animate delay={0.1} className="btn-3d" />
</div>
</div>
</div>
<div className="hero-animated-image-container">
<div className="hero-animated-parallax-wrapper" style={parallaxStyle}>
<ImageOrVideo imageSrc={imageSrc} videoSrc={videoSrc} className="hero-animated-image" />
</div>
</div>
</section>
);
};

36
src/hooks/useParallax.ts Normal file
View File

@@ -0,0 +1,36 @@
import { useState, useEffect, type RefObject } from "react";
export const useParallax = (ref: RefObject<HTMLElement | null>, 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;
};

View File

@@ -151,6 +151,17 @@ h6 {
font-family: var(--font-manrope), sans-serif;
}
#hero {
padding-top: 4rem;
}
.hero-animated-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
}
/* WEBILD_CARD_STYLE */
/* @cards/gradient-mesh */
.card {
@@ -183,3 +194,13 @@ h6 {
2.10837px 3.16256px 9.48767px color-mix(in srgb, var(--color-accent) 10%, transparent);
border: 1px solid var(--color-secondary-cta);
}
.btn-3d {
transition: transform 0.1s ease-out, border-bottom-width 0.1s ease-out;
border-bottom: 4px solid rgba(0, 0, 0, 0.25);
}
.btn-3d:active {
transform: translateY(2px);
border-bottom-width: 2px;
}

View File

@@ -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;
}