Files
open-lovable/components/app/(home)/sections/hero-scraping/HeroScraping.tsx
T
Developers Digest 836b085f75 continue re-design
2025-09-05 13:06:17 -04:00

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