Compare commits
17 Commits
version_1_
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 014f2cf1c6 | |||
|
|
d9c74ddab9 | ||
|
|
eaa26d9599 | ||
|
|
688d670892 | ||
| 6d3a8c3e19 | |||
|
|
b76d2874f6 | ||
|
|
40d71e0f0a | ||
|
|
e0e655c575 | ||
| 9bad83cd4d | |||
|
|
9bbe263701 | ||
| 253bf96646 | |||
|
|
c46d47952f | ||
| 725ae37201 | |||
|
|
dad69dac5b | ||
| 0c1d2ac648 | |||
|
|
c21aa71c35 | ||
| 3c7f328eff |
@@ -1,7 +1,7 @@
|
||||
import ContactSplitForm from '@/components/sections/contact/ContactSplitForm';
|
||||
import FeaturesRevealCards from '@/components/sections/features/FeaturesRevealCards';
|
||||
import FooterMinimal from '@/components/sections/footer/FooterMinimal';
|
||||
import HeroSplit from '@/components/sections/hero/HeroSplit';
|
||||
import HeroAnimated from '@/components/sections/hero/HeroAnimated';
|
||||
import NavbarCentered from '@/components/ui/NavbarCentered';
|
||||
import PricingHighlightedCards from '@/components/sections/pricing/PricingHighlightedCards';
|
||||
import TestimonialMetricsCards from '@/components/sections/testimonial/TestimonialMetricsCards';
|
||||
@@ -26,7 +26,7 @@ export default function App() {
|
||||
</div>
|
||||
|
||||
<div id="hero" data-section="hero">
|
||||
<HeroSplit
|
||||
<HeroAnimated
|
||||
tag="Revolutionary AI Opportunity"
|
||||
title="Turn Zero Into Growth With AI"
|
||||
description="Don't let your situation define your outcome. Leverage our AI-driven platform to identify, scale, and secure high-value opportunities designed for everyone."
|
||||
|
||||
@@ -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>
|
||||
|
||||
71
src/components/sections/hero/HeroAnimated.tsx
Normal file
71
src/components/sections/hero/HeroAnimated.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useState } from "react";
|
||||
import Button from "@/components/ui/Button";
|
||||
import TextAnimation from "@/components/ui/TextAnimation";
|
||||
import ImageOrVideo from "@/components/ui/ImageOrVideo";
|
||||
|
||||
type HeroAnimatedProps = {
|
||||
tag: string;
|
||||
title: string;
|
||||
description: string;
|
||||
primaryButton: { text: string; href: string };
|
||||
secondaryButton: { text: string; href: string };
|
||||
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
|
||||
|
||||
const HeroAnimated = ({
|
||||
tag,
|
||||
title,
|
||||
description,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
imageSrc,
|
||||
videoSrc,
|
||||
}: HeroAnimatedProps) => {
|
||||
const [parallaxStyle, setParallaxStyle] = useState({});
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLElement>) => {
|
||||
const { clientX, clientY, currentTarget } = e;
|
||||
const { left, top, width, height } = currentTarget.getBoundingClientRect();
|
||||
const x = (clientX - left - width / 2) / (width / 2);
|
||||
const y = (clientY - top - height / 2) / (height / 2);
|
||||
setParallaxStyle({
|
||||
transform: `translate(${x * -20}px, ${y * -20}px)`,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label="Hero section"
|
||||
className="hero-animated-container"
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<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>
|
||||
<TextAnimation
|
||||
text={title}
|
||||
variant="fade"
|
||||
tag="h1"
|
||||
className="text-7xl 2xl:text-8xl font-medium text-center text-balance"
|
||||
/>
|
||||
<TextAnimation
|
||||
text={description}
|
||||
variant="fade"
|
||||
tag="p"
|
||||
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 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>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeroAnimated;
|
||||
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 | 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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hero-float {
|
||||
0% {
|
||||
transform: translateY(-5px) scale(1.1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(5px) scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-5px) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInTranslate {
|
||||
from {
|
||||
transform: translateY(0.75vh);
|
||||
@@ -156,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