315 lines
11 KiB
TypeScript
315 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import { animate } from "motion";
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
import CurvyRect from "@/components/shared/layout/curvy-rect";
|
|
import { sleep } from "@/utils/sleep";
|
|
|
|
import BrowserMobile from "./_svg/BrowserMobile";
|
|
import BrowserTab from "./_svg/BrowserTab";
|
|
import HeroScrapingCode from "./Code/Code";
|
|
import HeroScrapingTag from "./Tag/Tag";
|
|
|
|
import "./HeroScraping.css";
|
|
|
|
export default function HeroScraping() {
|
|
const [step, setStep] = useState(-1);
|
|
|
|
const navigationRef = useRef<HTMLDivElement>(null);
|
|
const buttonRef = useRef<HTMLDivElement>(null);
|
|
const h1Ref = useRef<HTMLDivElement>(null);
|
|
const descriptionRef = useRef<HTMLDivElement>(null);
|
|
const ctaRef = useRef<HTMLDivElement>(null);
|
|
const highlightRef = useRef<HTMLDivElement>(null);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const wrapElement = async (
|
|
element: HTMLElement,
|
|
{ borderRadius }: { borderRadius?: number } = {},
|
|
) => {
|
|
if (!containerRef.current) return;
|
|
|
|
const containerBnds = containerRef.current.getBoundingClientRect();
|
|
const elementBnds = element.getBoundingClientRect();
|
|
|
|
if (!highlightRef.current) return;
|
|
|
|
try {
|
|
if (highlightRef.current) {
|
|
await animate(highlightRef.current, { opacity: 0 }, { duration: 0.3 });
|
|
}
|
|
} catch (error) {
|
|
console.error("Error animating highlight:", error);
|
|
}
|
|
|
|
if (!highlightRef.current) return;
|
|
|
|
Object.assign(highlightRef.current.style, {
|
|
left: elementBnds.left - containerBnds.left - 4 + "px",
|
|
top: elementBnds.top - containerBnds.top - 4 + "px",
|
|
width: elementBnds.width + 8 + "px",
|
|
height: elementBnds.height + 8 + "px",
|
|
borderRadius: borderRadius ? `${borderRadius}px` : undefined,
|
|
});
|
|
|
|
try {
|
|
await animate(
|
|
highlightRef.current,
|
|
{ opacity: [1, 0.5, 0.3, 0.8, 0.6, 0.9, 0.7, 1] },
|
|
{ duration: 0.4 },
|
|
);
|
|
} catch (error) {
|
|
console.error("Error animating highlight:", error);
|
|
}
|
|
};
|
|
|
|
const start = async () => {
|
|
setStep(0);
|
|
if (!highlightRef.current) return;
|
|
|
|
await animate(highlightRef.current, {
|
|
scale: 1,
|
|
opacity: 1,
|
|
});
|
|
|
|
await sleep(700);
|
|
|
|
setTimeout(() => setStep(1), 300);
|
|
if (navigationRef.current) {
|
|
await wrapElement(navigationRef.current);
|
|
}
|
|
|
|
await sleep(1200);
|
|
|
|
setTimeout(() => setStep(2), 300);
|
|
if (buttonRef.current) {
|
|
await wrapElement(buttonRef.current);
|
|
}
|
|
|
|
await sleep(1200);
|
|
|
|
setTimeout(() => setStep(3), 300);
|
|
if (h1Ref.current) {
|
|
await wrapElement(h1Ref.current, { borderRadius: 12 });
|
|
}
|
|
|
|
await sleep(1200);
|
|
|
|
setTimeout(() => setStep(4), 300);
|
|
if (descriptionRef.current) {
|
|
await wrapElement(descriptionRef.current, { borderRadius: 8 });
|
|
}
|
|
|
|
await sleep(1200);
|
|
|
|
setTimeout(() => setStep(5), 300);
|
|
if (ctaRef.current) {
|
|
await wrapElement(ctaRef.current, { borderRadius: 24 });
|
|
}
|
|
|
|
await sleep(1500);
|
|
setTimeout(() => setStep(6), 300);
|
|
|
|
if (highlightRef.current) {
|
|
await animate(highlightRef.current, { opacity: 0 }, { duration: 0.3 });
|
|
}
|
|
};
|
|
|
|
let started = false;
|
|
|
|
const onScroll = () => {
|
|
if (started) return;
|
|
|
|
if (window.scrollY > 100) {
|
|
started = true;
|
|
start();
|
|
window.removeEventListener("scroll", onScroll);
|
|
}
|
|
};
|
|
|
|
setTimeout(() => {
|
|
if (started) return;
|
|
|
|
started = true;
|
|
start();
|
|
window.removeEventListener("scroll", onScroll);
|
|
}, 2000);
|
|
|
|
window.addEventListener("scroll", onScroll);
|
|
|
|
return () => window.removeEventListener("scroll", onScroll);
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
className="pt-56 lg:pt-25 lg:px-25 container -mt-36 relative"
|
|
ref={containerRef}
|
|
>
|
|
<div className="h-53 absolute top-[calc(100%-1px)] w-full left-0">
|
|
<div className="h-1 bg-border-faint bottom-0 left-0 w-full absolute" />
|
|
</div>
|
|
|
|
<div
|
|
className="left-61 top-89 rounded-[16px] size-32 absolute hero-scraping-highlight inside-border before:border-border-loud opacity-0 scale-[0.9]"
|
|
ref={highlightRef}
|
|
/>
|
|
|
|
<div className="overlay lg-max:hidden">
|
|
<div className="h-1 absolute bottom-0 w-full left-0 bg-border-faint" />
|
|
<CurvyRect className="overlay" bottom />
|
|
</div>
|
|
|
|
<div className="lg:h-370 rounded-t-16 lg-max:pt-70 relative">
|
|
<div className="overlay mask-border lg-max:hidden p-1 bg-gradient-to-b from-black/7 to-transparent" />
|
|
|
|
<div className="top-17 left-17 flex gap-8 items-center absolute lg-max:hidden">
|
|
{Array.from({ length: 3 }).map((_, index) => (
|
|
<div
|
|
className="w-10 h-10 rounded-full relative inside-border before:border-border-muted"
|
|
key={index}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<div className="pt-42 lg:px-6">
|
|
<BrowserMobile className="absolute top-0 cw-316 lg:hidden" />
|
|
|
|
<BrowserTab className="absolute top-[7.5px] left-70 lg-max:hidden bg-background-base z-[1]" />
|
|
<div className="absolute size-18 top-17 left-89 lg-max:hidden inside-border before:border-border-muted z-[2] rounded-full" />
|
|
|
|
<div className="rounded-t-16 relative lg:h-330 lg:p-6">
|
|
<div className="overlay mask-border lg-max:hidden p-1 bg-gradient-to-b from-black/7 to-transparent" />
|
|
|
|
<div className="lg:h-322 rounded-t-10 relative">
|
|
<div className="overlay mask-border lg-max:hidden p-1 bg-gradient-to-b z-[2] from-black/7 to-transparent" />
|
|
|
|
<div className="px-28 lg-max:hidden py-20 flex justify-between items-center relative border-b border-border-faint">
|
|
<div className="flex gap-8 items-center relative">
|
|
<div className="size-24 rounded-full relative inside-border before:border-border-muted" />
|
|
<div className="w-64 h-12 rounded-full relative inside-border before:border-border-muted" />
|
|
|
|
{step >= 0 && (
|
|
<HeroScrapingTag
|
|
active={step === 0}
|
|
className="absolute left-[calc(100%+24px)] top-0"
|
|
initial={{ x: -12, opacity: 0 }}
|
|
label="Logo"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div
|
|
className="absolute top-24 center-x flex gap-8"
|
|
ref={navigationRef}
|
|
>
|
|
{step >= 1 && (
|
|
<HeroScrapingTag
|
|
active={step === 1}
|
|
className="absolute right-[calc(100%+20px)] -top-4"
|
|
initial={{ x: 12, opacity: 0 }}
|
|
label="Navigation"
|
|
/>
|
|
)}
|
|
|
|
{Array.from({ length: 4 }).map((_, index) => (
|
|
<div
|
|
className="w-64 h-16 rounded-full relative inside-border before:border-border-muted"
|
|
key={index}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<div
|
|
className="w-72 h-24 rounded-full relative inside-border before:border-border-muted"
|
|
ref={buttonRef}
|
|
>
|
|
{step >= 2 && (
|
|
<HeroScrapingTag
|
|
active={step === 2}
|
|
className="absolute right-[calc(100%+20px)] top-0"
|
|
initial={{ x: 12, opacity: 0 }}
|
|
label="Button"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="lg:grid grid-cols-2">
|
|
<div className="pt-40 pl-151 flex gap-16 relative lg-max:hidden">
|
|
<CurvyRect
|
|
className="size-32 -top-1 -right-1 absolute"
|
|
topRight
|
|
/>
|
|
|
|
<div className="h-53 lg-max:hidden -left-37 bottom-1 absolute w-65">
|
|
<CurvyRect className="overlay" left />
|
|
</div>
|
|
|
|
<div>
|
|
<div
|
|
className="flex gap-16 mb-16 flex-wrap w-300 relative"
|
|
ref={h1Ref}
|
|
>
|
|
{step >= 3 && (
|
|
<HeroScrapingTag
|
|
active={step === 3}
|
|
className="absolute right-[calc(100%+16px)] top-0"
|
|
initial={{ x: 12, opacity: 0 }}
|
|
label="H1 Title"
|
|
/>
|
|
)}
|
|
<div className="w-144 h-32 rounded-8 relative inside-border before:border-border-muted" />
|
|
<div className="w-82 h-32 rounded-8 relative inside-border before:border-border-muted" />
|
|
<div className="w-100 h-32 rounded-8 relative inside-border before:border-border-muted" />
|
|
<div className="w-180 h-32 rounded-8 relative inside-border before:border-border-muted" />
|
|
</div>
|
|
|
|
<div
|
|
className="flex gap-6 mb-32 flex-wrap w-300 relative"
|
|
ref={descriptionRef}
|
|
>
|
|
{step >= 4 && (
|
|
<HeroScrapingTag
|
|
active={step === 4}
|
|
className="absolute top-0 right-[calc(100%+16px)]"
|
|
initial={{ x: 12, opacity: 0 }}
|
|
label="Description"
|
|
/>
|
|
)}
|
|
|
|
<div className="w-131 h-10 rounded-full relative inside-border before:border-border-muted" />
|
|
<div className="w-72 h-10 rounded-full relative inside-border before:border-border-muted" />
|
|
<div className="w-34 h-10 rounded-full relative inside-border before:border-border-muted" />
|
|
<div className="w-56 h-10 rounded-full relative inside-border before:border-border-muted" />
|
|
<div className="w-116 h-10 rounded-full relative inside-border before:border-border-muted" />
|
|
<div className="w-116 h-10 rounded-full relative inside-border before:border-border-muted" />
|
|
</div>
|
|
|
|
<div
|
|
className="w-64 h-24 rounded-full relative inside-border before:border-border-muted"
|
|
ref={ctaRef}
|
|
>
|
|
{step >= 5 && (
|
|
<HeroScrapingTag
|
|
active={step === 5}
|
|
className="absolute top-0 right-[calc(100%+16px)]"
|
|
initial={{ x: 12, opacity: 0 }}
|
|
label="CTA Button"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<HeroScrapingCode step={step} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|