This commit is contained in:
Developers Digest
2025-11-19 10:15:21 -05:00
320 changed files with 38446 additions and 7311 deletions
@@ -0,0 +1,159 @@
import { useCallback, useEffect, useState } from "react";
import CurvyRect, { Connector } from "@/components/shared/layout/curvy-rect";
import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title";
import HeroScrapingCodeLoading from "./Loading/Loading";
import Code from "@/components/ui/code";
const URL = {
value: "https://example.com",
encrypted: "h=t*A:!/z!aap?A-cZz",
};
const MARKDOWN = {
value: "# Getting Started...",
encrypted: "# ?0z-ang S*a-Z-a0*9",
};
const TITLE = {
value: "Guide",
encrypted: "G!=*?",
};
const SCREENSHOT = {
value: "https://example.com/hero",
encrypted: "ht-=*:/?*Za!zl=-?a9?h0-!",
};
export default function HeroScrapingCode({ step }: { step: number }) {
const [url, setUrl] = useState(URL.encrypted);
const [markdown, setMarkdown] = useState(MARKDOWN.encrypted);
const [title, setTitle] = useState(TITLE.encrypted);
const [screenshot, setScreenshot] = useState(SCREENSHOT.encrypted);
const reveal = useCallback((value: string, setter: (v: string) => void) => {
let progress = 0;
let increaseProgress = -10;
const animate = () => {
increaseProgress = (increaseProgress + 1) % 5;
if (increaseProgress === 4) {
progress += 0.2;
}
if (progress > 1) {
progress = 1;
setter(encryptText(value, progress, { randomizeChance: 0.3 }));
return;
}
setter(encryptText(value, progress, { randomizeChance: 0.3 }));
const interval = 70 + progress * 30;
setTimeout(animate, interval);
};
animate();
}, []);
useEffect(() => {
if (step >= 0 && url === URL.encrypted) reveal(URL.value, setUrl);
if (step >= 3 && title === TITLE.encrypted) reveal(TITLE.value, setTitle);
if (step >= 4 && markdown === MARKDOWN.encrypted)
reveal(MARKDOWN.value, setMarkdown);
if (step >= 5 && screenshot === SCREENSHOT.encrypted)
reveal(SCREENSHOT.value, setScreenshot);
const interval = setInterval(() => {
if (step < 0) {
URL.encrypted = encryptText(URL.value, 0, { randomizeChance: 0.3 });
setUrl(URL.encrypted);
}
if (step < 3) {
TITLE.encrypted = encryptText(TITLE.value, 0, { randomizeChance: 0.3 });
setTitle(TITLE.encrypted);
}
if (step < 4) {
MARKDOWN.encrypted = encryptText(MARKDOWN.value, 0, {
randomizeChance: 0.3,
});
setMarkdown(MARKDOWN.encrypted);
}
if (step < 5) {
SCREENSHOT.encrypted = encryptText(SCREENSHOT.value, 0, {
randomizeChance: 0.3,
});
setScreenshot(SCREENSHOT.encrypted);
}
}, 70);
return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [step, reveal]);
return (
<div className="h-280 lg:h-310 flex z-[1] w-full relative -top-1 bg-background-base">
<Connector className="lg:hidden absolute -top-10 -left-[10.5px]" />
<Connector className="lg:hidden absolute -top-10 -right-[10.5px]" />
<div className="lg:hidden absolute top-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint" />
<Connector className="lg:hidden absolute -bottom-10 -left-[10.5px]" />
<Connector className="lg:hidden absolute -bottom-10 -right-[10.5px]" />
<div className="lg:hidden absolute bottom-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint" />
<div className="flex-1 lg-max:min-w-0 h-full relative lg:inside-border before:border-border-faint">
<CurvyRect className="overlay" allSides />
<CurvyRect
className="size-32 absolute bottom-0 -left-31 lg-max:hidden"
bottomRight
/>
<div className="pl-15 border-b border-border-faint p-13 flex justify-between items-center">
<div className="flex gap-10 items-center">
{Array.from({ length: 3 }).map((_, index) => (
<div
className="w-12 h-12 rounded-full relative inside-border before:border-border-muted"
key={index}
/>
))}
</div>
<div className="text-mono-x-small font-mono text-black-alpha-20">
[ .JSON ]
</div>
</div>
<div className="overflow-x-scroll hide-scrollbar lg:contents relative">
<Code
code={`[
{
"url": "${url}",
"markdown": "${markdown}",
"json": { "title": "${title}", "docs": "..." },
"screenshot": "${screenshot}.png"
}
]`}
language="json"
/>
</div>
<HeroScrapingCodeLoading finished={step >= 6} />
</div>
<div className="w-28 lg-max:hidden -ml-1 relative">
<div className="h-1 w-[calc(100%-1px)] top-0 left-0 absolute bg-border-faint" />
<CurvyRect className="overlay" topLeft />
</div>
<div className="h-53 lg-max:hidden -right-37 bottom-0 absolute w-65">
<CurvyRect className="overlay" bottom topRight />
<div className="overlay border-y border-border-faint" />
</div>
</div>
);
}
@@ -0,0 +1,63 @@
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useState } from "react";
import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title";
import AnimatedWidth from "@/components/shared/layout/animated-width";
import Spinner from "@/components/ui/spinner";
export default function HeroScrapingCodeLoading({
finished,
}: {
finished: boolean;
}) {
const [scrapingText, setScrapingText] = useState("Scraping...");
useEffect(() => {
if (finished) return;
let timeout = 0;
let tick = 0;
const animate = () => {
tick += 1;
if (tick % 3 !== 0) {
setScrapingText(
encryptText("Scraping", 0, {
randomizeChance: 0.6 + Math.random() * 0.3,
}) + "...",
);
} else {
setScrapingText("Scraping...");
}
const interval = 80;
timeout = window.setTimeout(animate, interval);
};
animate();
return () => {
window.clearTimeout(timeout);
};
}, [finished]);
return (
<div className="flex gap-6 p-6 pr-0 rounded-full inside-border before:border-border-faint absolute right-20 bottom-20 text-mono-small font-mono text-accent-black">
<Spinner finished={finished} />
<AnimatedWidth initial={{ width: "auto" }}>
<AnimatePresence initial={false} mode="popLayout">
<motion.div
animate={{ opacity: 1, x: 0 }}
className="pr-12"
exit={{ opacity: 0, x: 10 }}
initial={{ opacity: 0, x: -10 }}
>
{finished ? "Scrape Completed" : scrapingText}
</motion.div>
</AnimatePresence>
</AnimatedWidth>
</div>
);
}
@@ -0,0 +1,18 @@
export default function Check() {
return (
<svg
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
d="M10 2.5C5.85786 2.5 2.5 5.85786 2.5 10C2.5 14.1421 5.85786 17.5 10 17.5C14.1421 17.5 17.5 14.1421 17.5 10C17.5 5.85786 14.1421 2.5 10 2.5ZM12.8305 8.59995C13.0928 8.27937 13.0455 7.80685 12.7249 7.54455C12.4043 7.28226 11.9318 7.32951 11.6695 7.65009L8.81932 11.1337L7.90533 10.2197C7.61244 9.9268 7.13756 9.9268 6.84467 10.2197C6.55178 10.5126 6.55178 10.9875 6.84467 11.2804L8.34467 12.7804C8.4945 12.9302 8.70073 13.0096 8.91236 12.9991C9.12399 12.9885 9.32129 12.8889 9.45547 12.725L12.8305 8.59995Z"
fill="#FA5D19"
fillRule="evenodd"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
.hero-scraping-highlight::before {
animation: hero-scraping-highlight-before 1s linear infinite;
transition: none !important;
}
@keyframes hero-scraping-highlight-before {
0% {
border-color: var(--border-loud);
opacity: 1;
}
40% {
opacity: 0.25;
border-color: var(--heat-100);
}
80% {
border-color: var(--border-loud);
opacity: 1;
}
}
@@ -0,0 +1,314 @@
"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>
);
}
@@ -0,0 +1,69 @@
import { motion } from "motion/react";
import { ComponentProps, useEffect, useState } from "react";
import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title";
import { cn } from "@/utils/cn";
export default function HeroScrapingTag({
active,
label,
...attrs
}: ComponentProps<typeof motion.div> & { active?: boolean; label: string }) {
const [value, setValue] = useState(
encryptText(label, 0, { randomizeChance: 0 }),
);
useEffect(() => {
let progress = 0;
let increaseProgress = -10;
const animate = () => {
increaseProgress = (increaseProgress + 1) % 5;
if (increaseProgress === 4) {
progress += 0.2;
}
if (progress > 1) {
progress = 1;
setValue(encryptText(label, progress, { randomizeChance: 0 }));
return;
}
setValue(encryptText(label, progress, { randomizeChance: 0 }));
const interval = 40 + progress * 20;
setTimeout(animate, interval);
};
animate();
}, [label]);
return (
<motion.div
{...attrs}
animate={{
x: 0,
y: 0,
scale: 1,
opacity: 1,
filter: "blur(0px)",
}}
className={cn(
"py-4 h-max font-mono w-max px-6 text-mono-x-small rounded-6 transition-colors",
active
? "bg-heat-12 text-heat-100"
: "bg-black-alpha-4 text-black-alpha-56",
attrs.className,
)}
transition={{
type: "spring",
stiffness: 100,
damping: 18,
}}
>
{value}
</motion.div>
);
}
@@ -0,0 +1,137 @@
export default function BrowserMobile(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
fill="none"
height="112"
viewBox="0 0 316 112"
width="316"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g clipPath="url(#clip0_2254_6088)">
<rect
height="370"
rx="15.5"
stroke="url(#paint0_linear_2254_6088)"
strokeOpacity="0.07"
width="315"
x="0.5"
y="0.5"
/>
<mask fill="white" id="path-2-inside-1_2254_6088">
<path d="M240 32C240 37.5228 244.477 42 250 42H294C302.837 42 310 49.1634 310 58V361C310 366.523 305.523 371 300 371H16C10.4772 371 6 366.523 6 361V58C6 49.1634 13.1634 42 22 42H70C75.5228 42 80 37.5228 80 32V18C80 12.4772 84.4772 8 90 8H230C235.523 8 240 12.4772 240 18V32Z" />
</mask>
<path
d="M310 58L311 58L310 58ZM22 42L22 41L22 42ZM250 42V43H294V42V41H250V42ZM294 42V43C302.284 43 309 49.7157 309 58L310 58L311 58C311 48.6112 303.389 41 294 41V42ZM310 58H309V361H310H311V58H310ZM300 371V370H16V371V372H300V371ZM6 361H7V58H6H5V361H6ZM6 58H7C7 49.7157 13.7157 43 22 43L22 42L22 41C12.6112 41 5 48.6112 5 58H6ZM22 42V43H70V42V41H22V42ZM80 32H81V18H80H79V32H80ZM90 8V9H230V8V7H90V8ZM240 18H239V32H240H241V18H240ZM230 8V9C234.971 9 239 13.0294 239 18H240H241C241 11.9249 236.075 7 230 7V8ZM70 42V43C76.0751 43 81 38.0751 81 32H80H79C79 36.9706 74.9706 41 70 41V42ZM16 371V370C11.0294 370 7 365.971 7 361H6H5C5 367.075 9.92487 372 16 372V371ZM80 18H81C81 13.0294 85.0294 9 90 9V8V7C83.9249 7 79 11.9249 79 18H80ZM310 361H309C309 365.971 304.971 370 300 370V371V372C306.075 372 311 367.075 311 361H310ZM250 42V41C245.029 41 241 36.9706 241 32H240H239C239 38.0751 243.925 43 250 43V42Z"
fill="url(#paint1_linear_2254_6088)"
fillOpacity="0.07"
mask="url(#path-2-inside-1_2254_6088)"
/>
<rect
height="310"
rx="9.5"
stroke="url(#paint2_linear_2254_6088)"
strokeOpacity="0.07"
width="291"
x="12.5"
y="48.5"
/>
<rect
height="9"
rx="4.5"
stroke="#E8E8E8"
width="9"
x="17.5"
y="17.5"
/>
<rect
height="9"
rx="4.5"
stroke="#E8E8E8"
width="9"
x="35.5"
y="17.5"
/>
<rect
height="9"
rx="4.5"
stroke="#E8E8E8"
width="9"
x="53.5"
y="17.5"
/>
<rect
height="17"
rx="8.5"
stroke="#E8E8E8"
width="17"
x="89.5"
y="17.5"
/>
<mask fill="white" id="path-10-inside-2_2254_6088">
<path d="M12 48H304V112H12V48Z" />
</mask>
<path
d="M304 112V111H12V112V113H304V112Z"
fill="#EDEDED"
mask="url(#path-10-inside-2_2254_6088)"
/>
<rect
height="23"
rx="11.5"
stroke="#E8E8E8"
width="71"
x="212.5"
y="68.5"
/>
<circle cx="44" cy="80" r="11.5" stroke="#E8E8E8" />
<rect
height="11"
rx="5.5"
stroke="#E8E8E8"
width="63"
x="64.5"
y="74.5"
/>
</g>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear_2254_6088"
x1="158"
x2="158"
y1="0"
y2="371"
>
<stop />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint1_linear_2254_6088"
x1="529.5"
x2="529.5"
y1="8"
y2="324"
>
<stop offset="0.4" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint2_linear_2254_6088"
x1="158"
x2="158"
y1="48"
y2="359"
>
<stop />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<clipPath id="clip0_2254_6088">
<rect fill="white" height="112" width="316" />
</clipPath>
</defs>
</svg>
);
}
@@ -0,0 +1,19 @@
import { HTMLAttributes } from "react";
export default function BrowserTab(attrs: HTMLAttributes<SVGSVGElement>) {
return (
<svg
fill="none"
height="36"
viewBox="0 0 226 36"
width="226"
xmlns="http://www.w3.org/2000/svg"
{...attrs}
>
<path
d="M0 35C5.52285 35 10 30.5228 10 25V11C10 5.47715 14.4772 1 20 1H206C211.523 1 216 5.47715 216 11V25C216 30.5228 220.477 35 226 35"
stroke="#E8E8E8"
/>
</svg>
);
}