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,184 @@
"use client";
import { Fragment } from "react";
import CurvyRect from "@/components/shared/layout/curvy-rect";
import CenterStar from "./_svg/CenterStar";
export default function HomeHeroBackground() {
return (
<div className="overlay contain-layout pointer-events-none lg-max:hidden">
<div className="top-100 h-[calc(100%-99px)] border-border-faint border-y w-full left-0 absolute" />
<div className="cw-[1314px] z-[105] absolute top-0 border-x border-border-faint h-full">
<div className="text-mono-x-small font-mono text-black-alpha-12 select-none">
<div className="absolute top-111 -left-1 w-102 text-center">
{" "}
[ 200 OK ]{" "}
</div>
<div className="absolute bottom-10 -left-1 w-102 text-center">
{" "}
[ .JSON ]{" "}
</div>
<div className="absolute top-111 -right-1 w-102 text-center">
{" "}
[ SCRAPE ]{" "}
</div>
<div className="absolute bottom-10 -right-1 w-102 text-center">
{" "}
[ .MD ]{" "}
</div>
</div>
<div className="top-302 h-1 left-0 bg-border-faint w-303 absolute" />
<div className="top-403 h-1 left-0 bg-border-faint w-303 absolute" />
<div className="top-504 h-1 left-100 bg-border-faint w-203 absolute" />
<div className="top-302 h-1 right-0 bg-border-faint w-303 absolute" />
<div className="top-403 h-1 right-0 bg-border-faint w-303 absolute" />
<div className="top-504 h-1 right-100 bg-border-faint w-203 absolute" />
{Array.from({ length: 2 }, (_, i) => (
<Fragment key={i}>
<CurvyRect
bottomLeft={i === 1}
bottomRight={i === 0}
className="w-101 h-[calc(100%-99px)] top-100 absolute"
style={{ [i === 0 ? "left" : "right"]: -101 }}
/>
<CurvyRect
className="w-102 h-203 top-100 absolute"
style={{ [i === 0 ? "left" : "right"]: -1 }}
allSides
/>
<CurvyRect
className="size-102 top-302 absolute"
style={{ [i === 0 ? "left" : "right"]: -1 }}
allSides
/>
<CurvyRect
className="w-102 h-203 top-403 absolute"
style={{ [i === 0 ? "left" : "right"]: -1 }}
allSides
/>
</Fragment>
))}
</div>
<div className="cw-[910px] absolute top-100 border-x border-border-faint h-[calc(100%-99px)]" />
<div className="cw-[708px] absolute top-100 border-x border-border-faint h-[calc(100%-99px)]">
<CenterStar className="absolute top-77 -right-24 z-[1]" />
<CenterStar className="absolute top-77 -left-24 z-[1]" />
</div>
<CurvyRect
className="cw-[708px] absolute top-100 h-[calc(100%-99px)]"
bottom
/>
<div className="cw-[506px] absolute top-100 border-x border-border-faint h-102" />
<div className="cw-[304px] absolute top-100 border-x border-border-faint h-102" />
<div className="cw-[102px] absolute top-100 border-x border-border-faint h-102" />
<div className="top-201 h-1 bg-border-faint cw-[1112px] absolute" />
<div className="cw-[1112px] absolute top-0 h-full">
<CurvyRect className="w-full absolute top-full h-100 left-0" top />
<CurvyRect
className="w-100 absolute top-full h-100 -left-99"
topRight
/>
<CurvyRect
className="w-100 absolute top-full h-100 -right-99"
topLeft
/>
{Array.from({ length: 5 }, (_, i) => (
<Fragment key={i}>
<CurvyRect
className="size-102 absolute left-0"
style={{
top: 100 + i * 101,
}}
allSides
/>
<CurvyRect
className="size-102 absolute right-0"
style={{
top: 100 + i * 101,
}}
allSides
/>
</Fragment>
))}
<CurvyRect
className="size-102 absolute left-101 top-100"
bottomLeft
top
/>
<CurvyRect
className="size-102 absolute left-101 top-201"
bottom
topLeft
/>
<CurvyRect
className="size-102 absolute right-101 top-100"
bottomRight
top
/>
<CurvyRect
className="size-102 absolute right-101 top-201"
bottom
topRight
/>
{Array.from({ length: 3 }, (_, i) => (
<Fragment key={i}>
<CurvyRect
className="size-102 absolute left-101"
style={{
top: 302 + i * 101,
}}
allSides
/>
<CurvyRect
className="size-102 absolute right-101"
style={{
top: 302 + i * 101,
}}
allSides
/>
</Fragment>
))}
<CurvyRect
className="size-102 absolute top-100 left-202"
bottomRight
top
/>
{Array.from({ length: 5 }, (_, i) => (
<CurvyRect
className="size-102 absolute top-100"
key={i}
style={{ left: 303 + i * 101 }}
allSides
/>
))}
<CurvyRect
className="size-102 absolute top-100 right-202"
bottomLeft
top
/>
</div>
</div>
);
}
@@ -0,0 +1,56 @@
"use client";
import { useEffect, useState } from "react";
import { Connector } from "@/components/shared/layout/curvy-rect";
import {
useHeaderContext,
useHeaderHeight,
} from "@/components/shared/header/HeaderContext";
import { cn } from "@/utils/cn";
export const BackgroundOuterPiece = () => {
const [noRender, setNoRender] = useState(false);
const { dropdownContent } = useHeaderContext();
const { headerHeight } = useHeaderHeight();
useEffect(() => {
const heroContent = document.getElementById("hero-content");
if (!heroContent) {
// If hero-content doesn't exist, don't render the background piece
setNoRender(true);
return;
}
const heroContentHeight = heroContent.clientHeight;
const onScroll = () => {
setNoRender(window.scrollY > heroContentHeight - 120);
};
onScroll();
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
return (
<div
className={cn(
"cw-[1335px] transition-all z-[105] absolute top-0 flex justify-between h-[calc(100%+21px)] duration-[200ms] pointer-events-none",
{ "opacity-0": noRender || dropdownContent || !headerHeight },
)}
style={{
paddingTop: headerHeight - 10,
}}
>
<div className="h-[3000px] w-[calc(100%-21px)] left-[10.5px] absolute bottom-21 border-x border-border-faint" />
<Connector className="sticky" style={{ top: headerHeight - 10 }} />
<Connector className="sticky" style={{ top: headerHeight - 10 }} />
</div>
);
};
@@ -0,0 +1,21 @@
export default function CenterStar({
...props
}: React.SVGProps<SVGSVGElement>) {
return (
<svg
fill="none"
height="47"
viewBox="0 0 47 47"
width="47"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M24 18C24 21.3137 26.6863 24 30 24H34V25H30C26.6863 25 24 27.6863 24 31V35H23V31C23 27.6863 20.3137 25 17 25H13V24H17C20.3137 24 23 21.3137 23 18V14H24V18Z"
fill="var(--heat-100)"
fillOpacity="1"
/>
<circle cx="23.5" cy="23.5" r="23" stroke="#EDEDED" strokeOpacity="1" />
</svg>
);
}
@@ -0,0 +1,43 @@
import Link from "next/link";
export default function HomeHeroBadge() {
return (
<Link
className="p-4 rounded-full flex w-max mx-auto mb-12 lg:mb-16 items-center relative inside-border before:border-border-faint group"
href="#"
onClick={(e) => e.preventDefault()}
>
<div className="px-8 text-label-x-small">Website Builder</div>
<div className="p-1">
<div className="size-18 bg-accent-black flex-center rounded-full group-hover:bg-heat-100 transition-all group-hover:w-30">
<svg
fill="none"
height="8"
viewBox="0 0 10 8"
width="10"
xmlns="http://www.w3.org/2000/svg"
>
<path
className="transition-all -translate-x-2 group-hover:translate-x-0"
d="M6 1L9 4L6 7"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.25"
/>
<path
className="transition-all -translate-x-3 group-hover:translate-x-0 scale-x-[0] group-hover:scale-x-[1] origin-right"
d="M1 4L9 4"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.25"
/>
</svg>
</div>
</div>
</Link>
);
}
@@ -0,0 +1,62 @@
import Link from "next/link";
import { Connector } from "@/components/shared/layout/curvy-rect";
import HeroFlame from "@/components/shared/effects/flame/hero-flame";
import HomeHeroBackground from "./Background/Background";
import { BackgroundOuterPiece } from "./Background/BackgroundOuterPiece";
import HomeHeroBadge from "./Badge/Badge";
import HomeHeroPixi from "./Pixi/Pixi";
import HomeHeroTitle from "./Title/Title";
import HeroInput from "../hero-input/HeroInput";
import HeroScraping from "../hero-scraping/HeroScraping";
export default function HomeHero() {
return (
<section className="overflow-x-clip" id="home-hero">
<div
className="pt-28 lg:pt-254 lg:-mt-100 pb-115 relative"
id="hero-content"
>
<HomeHeroPixi />
<HeroFlame />
<BackgroundOuterPiece />
<HomeHeroBackground />
<div className="relative container px-16">
<HomeHeroBadge />
<HomeHeroTitle />
<p className="text-center text-body-large">
Power your AI apps with clean data crawled
<br className="lg-max:hidden" />
from any website.
<Link
className="bg-black-alpha-4 hover:bg-black-alpha-6 lg:ml-4 rounded-6 px-8 lg:px-6 text-label-large lg-max:py-2 h-30 lg:h-24 block lg-max:mt-8 lg-max:mx-auto lg-max:w-max lg:inline-block gap-4 transition-all"
href="https://github.com/firecrawl/firecrawl"
target="_blank"
>
It&apos;s also open source.
</Link>
</p>
</div>
</div>
<div className="container lg:contents !p-16 relative -mt-90">
<div className="absolute top-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint lg:hidden" />
<div className="absolute bottom-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint lg:hidden" />
<Connector className="-top-10 -left-[10.5px] lg:hidden" />
<Connector className="-top-10 -right-[10.5px] lg:hidden" />
<Connector className="-bottom-10 -left-[10.5px] lg:hidden" />
<Connector className="-bottom-10 -right-[10.5px] lg:hidden" />
<HeroInput />
</div>
<HeroScraping />
</section>
);
}
@@ -0,0 +1,46 @@
"use client";
import { Suspense, lazy, useState, useEffect } from "react";
const Pixi = lazy(() => import("@/components/shared/pixi/Pixi"));
import features from "./tickers/features";
function PixiContent() {
return (
<Pixi
canvasAttrs={{
className: "cw-[1314px] h-506 absolute top-100 lg-max:hidden",
}}
fps={Infinity}
initOptions={{ backgroundAlpha: 0 }}
smartStop={false}
tickers={[features]}
/>
);
}
export default function HomeHeroPixi() {
const [hasError, setHasError] = useState(false);
useEffect(() => {
const handleError = (e: ErrorEvent) => {
if (e.message.includes('pixi') || e.message.includes('ChunkLoadError')) {
setHasError(true);
}
};
window.addEventListener('error', handleError);
return () => window.removeEventListener('error', handleError);
}, []);
if (hasError) {
// Return empty div as fallback if Pixi fails to load
return <div className="cw-[1314px] h-506 absolute top-100 lg-max:hidden" />;
}
return (
<Suspense fallback={<div className="cw-[1314px] h-506 absolute top-100 lg-max:hidden" />}>
<PixiContent />
</Suspense>
);
}
@@ -0,0 +1,141 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import PixiAssetManager from "@/components/shared/pixi/PixiAssetManager";
import { RenderTexture, Sprite, Text } from "pixi.js";
// Add more contrast to the ASCII_CHARS and ensure 'X' is used
// const ASCII_CHARS = [' ', '.', ':', '-', '=', '+', 'X'];
const ASCII_CHARS = ' .":,-_^=+';
function getAsciiChar(luminance: number) {
if (luminance < 50) return " ";
const norm = Math.max(0, Math.min(1, (luminance - 16) / (250 - 16)));
const skewed = Math.pow(norm, 1.5);
const minIdx = 1;
const maxIdx = ASCII_CHARS.length - 1;
const idx = minIdx + Math.floor(skewed * (maxIdx - minIdx + 1));
const safeIdx = Math.max(minIdx, Math.min(maxIdx, idx));
return ASCII_CHARS[safeIdx];
}
// Sprinkle logic is now a no-op, as getAsciiChar handles the randomness
function sprinkleAscii(line: string) {
return line;
}
const tickAscii: Ticker = async ({ app, canvas }) => {
const textures = await Promise.all(
Array.from({ length: 150 }, async (_, i) => {
const texture = await PixiAssetManager.load(
`/Arşiv/FAQ Demo/FAQ_${i.toString().padStart(5, "0")}.png`,
);
return texture!;
}),
);
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const sprites = textures.map((texture) => new Sprite(texture));
sprites.forEach((sprite) => {
sprite.width = width;
sprite.height = height;
sprite.x = 0;
sprite.y = 0;
app.stage.addChild(sprite);
sprite.alpha = 0;
});
// Render the texture to a renderTexture to extract pixels
const renderTexture = RenderTexture.create({ width, height });
let ascii = "";
const asciiText = new Text({
text: ascii,
style: {
fontFamily: "monospace",
fontSize: 8,
fill: 0x000,
align: "left",
lineHeight: 8,
whiteSpace: "pre",
},
});
asciiText.alpha = 0.2;
asciiText.x = 0;
asciiText.y = 0;
const variants: string[] = [];
const render = (index: number) => {
ascii = "";
const sprite = sprites[index];
sprites.forEach((sprite) => {
sprite.alpha = 0;
});
sprite.alpha = 1;
app.renderer.render({ container: sprite, target: renderTexture });
sprite.alpha = 0;
const pixels = app.renderer.extract.pixels(renderTexture).pixels;
const charWidth = 4.81640625;
for (let y = 0; y < height; y += 8) {
let line = "";
for (let x = 0; x < width; x += charWidth) {
let totalLum = 0;
let count = 0;
for (let dy = 0; dy < 8; dy++) {
for (let dx = 0; dx < 4; dx++) {
const px = Math.floor(x + dx);
const py = Math.floor(y + dy);
if (px >= width || py >= height) continue;
const idx = (py * width + px) * 4;
const r = pixels[idx];
const g = pixels[idx + 1];
const b = pixels[idx + 2];
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
totalLum += lum;
count++;
}
}
const avgLum = count ? totalLum / count : 0;
line += getAsciiChar(avgLum);
}
ascii += sprinkleAscii(line) + "\n";
}
variants[index] = ascii;
asciiText.text = ascii;
};
app.stage.addChild(asciiText);
for (let i = 0; i < sprites.length; i++) {
render(i);
}
let i = 0;
//@ts-expect-error - safeAdd method exists on extended ticker
app.ticker.safeAdd(() => {
i++;
if (i >= sprites.length) i = 0;
render(i);
});
};
export default tickAscii;
@@ -0,0 +1,65 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import AnimatedRect from "./components/AnimatedRect";
import BlinkingContainer from "./components/BlinkingContainer";
import crawl from "./crawl";
import mapping from "./mapping";
import scrape from "./scrape";
import search from "./search";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
};
export const CELL_SIZE = 80;
export const MAIN_COLOR = 0xe6e6e6;
const animations = [scrape, mapping, search, crawl];
let lastActive = -1;
export default function cell(props: Props) {
const blinkingContainer = BlinkingContainer({
x: props.x + 10,
y: props.y + 10,
app: props.app,
});
const anchorGraphic = AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 4,
height: 4,
radius: 10,
color: MAIN_COLOR,
});
blinkingContainer.container.addChild(anchorGraphic.graphic);
props.app.stage.addChild(blinkingContainer.container);
let running = false;
return {
trigger: async () => {
if (running) return;
running = true;
lastActive = (lastActive + 1) % animations.length;
const fn = animations[lastActive];
await fn({
...props,
blinkingContainer,
anchorGraphic,
});
running = false;
},
};
}
@@ -0,0 +1,51 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import AnimatedRect from "./components/AnimatedRect";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
};
export default function cellReveal(props: Props) {
const graphic = AnimatedRect({
app: props.app,
x: props.x + 0.5,
y: props.y + 0.5,
width: 101,
height: 101,
radius: 0,
alpha: 0,
color: 0x000,
centering: false,
});
props.app.stage.addChild(graphic.graphic);
return {
trigger: async () => {
let cycleCount = 0;
const cycle = async () => {
await graphic.animate(
{
alpha: Math.random() * 0.04,
},
{
ease: "linear",
duration: 0.03,
},
);
if (cycleCount < 5) {
cycleCount += 1;
cycle();
} else {
await graphic.animate({ alpha: 0 });
graphic.graphic.destroy();
}
};
cycle();
},
};
}
@@ -0,0 +1,103 @@
import { AnimationOptions, cubicBezier } from "motion";
import { Application, Container, Graphics, Sprite } from "pixi.js";
import { isDestroyed } from "@/components/shared/pixi/utils";
type Props = {
app: Application;
x: number;
y: number;
width: number;
height: number;
radius: number;
color: number;
scale?: number;
rotation?: number;
type?: "rect" | "arc" | "container" | Sprite;
animationConfig?: AnimationOptions;
alpha?: number;
centering?: boolean;
};
export type IAnimatedRect = ReturnType<typeof AnimatedRect>;
export default function AnimatedRect(props: Props) {
const graphic = (() => {
if (props.type === "container") return new Container();
if (props.type instanceof Sprite) return props.type;
return new Graphics();
})();
props.alpha ??= 1;
props.scale ??= 1;
props.centering ??= true;
props.rotation ??= 0;
const p = {
...props,
};
const render = () => {
if (isDestroyed(props.app) || graphic.destroyed) return;
graphic.scale.set(p.scale!);
graphic.alpha = p.alpha!;
graphic.rotation = p.rotation!;
if (!(graphic instanceof Graphics)) {
if (graphic instanceof Sprite) {
graphic.x = p.x;
graphic.y = p.y;
}
return;
}
const g = graphic as Graphics;
g.clear();
if (p.type !== "arc") {
g.roundRect(
p.centering ? p.x - p.width / 2 : p.x,
p.centering ? p.y - p.height / 2 : p.y,
p.width,
p.height,
p.radius,
);
} else {
g.arc(p.x, p.y, p.width / 2, 0, Math.PI * 2);
}
g.fill({ color: p.color });
};
render();
p.animationConfig ??= {
duration: 0.4,
ease: cubicBezier(0.83, 0, 0.17, 1),
};
return {
defaultProps: props,
currentProps: p,
graphic,
setStyle: (style: Partial<Props>) => {
Object.assign(p, style);
render();
},
render,
animate: (renderProps: Partial<Props>, settings?: AnimationOptions) =>
(props.app as any).animate(p, renderProps, {
...p.animationConfig,
...settings,
onUpdate: render,
}),
reset: () => (props.app as any).animate(p, props, { onUpdate: render }),
};
}
@@ -0,0 +1,82 @@
import { Application, Graphics } from "pixi.js";
import { CELL_SIZE } from "@/components/app/(home)/sections/hero/Pixi/tickers/features/cell";
import AnimatedRect from "./AnimatedRect";
export type IBlinkingContainer = ReturnType<typeof BlinkingContainer>;
export default function BlinkingContainer({
x,
y,
app,
}: {
x: number;
y: number;
app: Application;
}) {
const animatedRect = AnimatedRect({
app,
x: 0,
y: 0,
width: CELL_SIZE,
height: CELL_SIZE,
radius: 0,
color: 0xededed,
type: "container",
});
animatedRect.graphic.pivot.set(CELL_SIZE / 2, CELL_SIZE / 2);
animatedRect.graphic.x = x + CELL_SIZE / 2;
animatedRect.graphic.y = y + CELL_SIZE / 2;
animatedRect.graphic.addChild(
new Graphics()
.rect(0, 0, CELL_SIZE, CELL_SIZE)
.fill({ color: "#EDEDED", alpha: 0 }),
);
const blinkLayer = new Graphics()
.rect(0, 0, CELL_SIZE, CELL_SIZE)
.fill({ color: "#F9F9F9" });
blinkLayer.zIndex = 1;
blinkLayer.alpha = 0;
animatedRect.graphic.addChild(blinkLayer);
return {
container: animatedRect.graphic,
animate: animatedRect.animate,
reset: animatedRect.reset,
shrink: async () => {
await animatedRect.animate({ scale: 0.92 });
animatedRect.animate({ scale: 1 });
},
blink: ({ delay = 0 }: { delay?: number } = {}) => {
(app as any)
.animate(0, 0.32, {
repeatType: "reverse",
repeat: 2,
delay,
duration: 0.065,
ease: "linear",
onUpdate: (value: any) => {
blinkLayer.alpha = value as number;
},
})
.then(() => {
(app as any).animate(0.32, 0, {
duration: 0.065,
ease: "linear",
onUpdate: (value: any) => {
blinkLayer.alpha = value as number;
},
});
});
},
};
}
@@ -0,0 +1,19 @@
import { MAIN_COLOR } from "@/components/app/(home)/sections/hero/Pixi/tickers/features/cell";
import AnimatedRect from "./AnimatedRect";
export default function Dot(
props: Pick<
Parameters<typeof AnimatedRect>[0],
"x" | "y" | "app" | "animationConfig"
>,
) {
return AnimatedRect({
...props,
width: 2,
height: 2,
radius: 10,
color: MAIN_COLOR,
type: "arc",
});
}
@@ -0,0 +1,200 @@
import { animate } from "motion";
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function crawl(props: Props) {
const rects = Array.from({ length: 6 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 8,
height: 8,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 16 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
/* Step 1: Reveal the main square, reveal the corner dots */
await Promise.all(
[
dots[0].animate({ x: 30, y: 30 }, { delay: 0.2 }),
dots[1].animate({ x: CELL_SIZE - 30, y: 30 }, { delay: 0.2 }),
dots[2].animate({ x: 30, y: CELL_SIZE - 30 }, { delay: 0.2 }),
dots[3].animate({ x: CELL_SIZE - 30, y: CELL_SIZE - 30 }, { delay: 0.2 }),
props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
}),
].flat(),
);
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.3 });
await props.blinkingContainer.shrink();
let spriteOverlay: IAnimatedRect | null = null;
// Use fallback rectangle instead of trying to load missing image
spriteOverlay = AnimatedRect({
x: 13,
y: 39,
color: MAIN_COLOR,
width: 54,
height: 34,
app: props.app,
radius: 4,
centering: false,
});
spriteOverlay.graphic.zIndex = -1;
props.blinkingContainer.container.addChild(spriteOverlay.graphic);
await Promise.all(
[
spriteOverlay?.animate({ height: 23, y: 50 }),
rects[0].animate({ width: 16, height: 16, y: 34 }),
rects.slice(1, 4).map((rect) => rect.animate({ x: 24, y: 50 })),
rects.slice(4, 8).map((rect) => rect.animate({ x: 56, y: 50 })),
dots[0].animate({ x: 28, y: 22 }),
dots[1].animate({ x: 52, y: 22 }),
dots[2].animate({ x: 16, y: 58 }),
dots[3].animate({ x: 64, y: 58 }),
dots[4].animate({ x: 16, y: 42 }),
dots[5].animate({ x: 64, y: 42 }),
dots[6].animate({ x: 32, y: 58 }),
dots[7].animate({ x: 48, y: 58 }),
dots.slice(8, 12).map((dot) => dot.animate({ x: 24, y: 50 })),
dots.slice(12, 16).map((dot) => dot.animate({ x: 56, y: 50 })),
].flat().filter(Boolean),
);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.3 });
await props.blinkingContainer.shrink();
try {
await Promise.all(
[
spriteOverlay?.animate({ height: 8, y: 58 }),
rects[0].animate({ y: 28 }),
[1, 4].map((i) => rects[i].animate({ y: 44 })),
[2, 3].map((i) => rects[i].animate({ x: 12, y: 56 })),
[5, 6].map((i) => rects[i].animate({ x: 68, y: 56 })),
dots[0].animate({ y: 16 }),
dots[1].animate({ y: 16 }),
dots[2].animate({ x: 4, y: 64 }),
dots[3].animate({ x: 76, y: 64 }),
dots[4].animate({ x: 4, y: 48 }),
dots[5].animate({ x: 76, y: 48 }),
dots[6].animate({ x: 20, y: 64 }),
dots[7].animate({ x: 60, y: 64 }),
dots[8].animate({ x: 16, y: 36 }),
dots[12].animate({ x: 64, y: 36 }),
dots[9].animate({ x: 32, y: 52 }),
dots[13].animate({ x: 48, y: 52 }),
[10, 11].map((i) => dots[i].animate({ x: 12, y: 56 })),
[14, 15].map((i) => dots[i].animate({ x: 68, y: 56 })),
].flat().filter(Boolean),
);
} catch (e) {
console.error(e);
}
await sleep(500);
props.blinkingContainer.blink({ delay: 0.3 });
await props.blinkingContainer.shrink();
await Promise.all(
[
spriteOverlay.animate({ height: 0, y: 66 }),
rects[0].animate({ y: 20 }),
[1, 4].map((i) => rects[i].animate({ y: 36 })),
[2, 5].map((i) => rects[i].animate({ y: 48 })),
[3, 6].map((i) => rects[i].animate({ y: 60, x: i === 3 ? 24 : 56 })),
[0, 1, 4, 5, 8, 9, 12, 13].map((i) =>
dots[i].animate({ y: dots[i].currentProps.y - 8 }),
),
dots[2].animate({ x: 4, y: 56 }),
dots[3].animate({ x: 76, y: 56 }),
dots[6].animate({ x: 32, y: 68 }),
dots[7].animate({ x: 48, y: 68 }),
dots[10].animate({ x: 32, y: 52 }),
dots[11].animate({ x: 16, y: 68 }),
dots[14].animate({ x: 48, y: 52 }),
dots[15].animate({ x: 64, y: 68 }),
].flat(),
);
await sleep(2000);
await Promise.all(
[
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
dots.map((dot) =>
dot.animate(dot.defaultProps, { delay: Math.random() * 0.3 }),
),
].flat(),
);
rects.shift();
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
spriteOverlay.graphic.destroy();
}
@@ -0,0 +1,124 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import setTimeoutOnVisible from "@/utils/set-timeout-on-visible";
import cell from "./cell";
import cellReveal from "./cellReveal";
const CELL_GRID = [
"-ooooooooooo-",
"-oo-------oo-",
"ooo-------ooo",
"-oo-------oo-",
"-oo-------oo-",
];
const REVEAL_ANIMATION_GRID = [
[
"---ooooooo---",
"--o-------o--",
"--o-------o--",
"--o-------o--",
"--o-------o--",
],
[
"--o-------o--",
"-o---------o-",
"-o---------o-",
"-o---------o-",
"-o---------o-",
],
[
"-o---------o-",
"-------------",
"o-----------o",
"-------------",
"-------------",
],
[
"-------------",
"-------------",
"o-----------o",
"-------------",
"-------------",
],
];
const features: Ticker = (params) => {
const cells: ReturnType<typeof cell>[] = [];
const cellReveals: {
cell: ReturnType<typeof cellReveal>;
row: number;
column: number;
}[] = [];
for (let i = 0; i < CELL_GRID.length; i++) {
const row = CELL_GRID[i];
for (let j = 0; j < row.length; j++) {
if (row[j] === "o") {
cells.push(
cell({
...params,
x: j * 101,
y: i * 101,
}),
);
cellReveals.push({
cell: cellReveal({
...params,
x: j * 101,
y: i * 101,
}),
row: i,
column: j,
});
}
}
}
const cycle = () =>
setTimeoutOnVisible({
element: params.canvas,
callback: () => {
const cell = cells[Math.floor(Math.random() * cells.length)];
if (cell) {
cell.trigger().then(() => cycle());
}
},
timeout: 3000 * Math.random(),
});
for (let i = 0; i < 5; i++) {
cycle();
}
let revealIndex = -1;
const revealCycle = () => {
revealIndex += 1;
for (let i = 0; i < REVEAL_ANIMATION_GRID[revealIndex].length; i++) {
const row = REVEAL_ANIMATION_GRID[revealIndex][i];
for (let j = 0; j < row.length; j++) {
if (row[j] === "o") {
cellReveals
.find((cell) => cell.row === i && cell.column === j)
?.cell.trigger();
}
}
}
if (revealIndex < REVEAL_ANIMATION_GRID.length - 1) {
setTimeout(() => {
revealCycle();
}, 150);
}
};
revealCycle();
};
export default features;
@@ -0,0 +1,207 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function mapping(props: Props) {
const rects = Array.from({ length: 8 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 10,
height: 10,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 20 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
await props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
});
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
dots.slice(0, 16).map((dot, index) => {
const x = 13 + (index % 4) * 18;
const y = 13 + Math.floor(index / 4) * 18;
return dot.animate({ x, y });
}),
rects[0].animate({ width: 10, height: 10 }),
rects.map((rect, index) => {
const x = 22 + (index % 3) * 18;
const y = 22 + Math.floor(index / 3) * 18;
return rect.animate({ x, y });
}),
].flat(),
);
await sleep(300);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
const baseDotPositions = [
[13, 13],
[31, 31],
[49, 31],
[13, 31],
[49, 49],
[67, 49],
[13, 49],
[31, 67],
[67, 67],
];
const dotPositions: string[] = [];
for (const [x, y] of baseDotPositions) {
const positions = [
{ x: x - 9, y: y - 9 },
{ x: x + 9, y: y - 9 },
{ x: x - 9, y: y + 9 },
{ x: x + 9, y: y + 9 },
];
for (const position of positions) {
if (!dotPositions.includes(`${position.x},${position.y}`)) {
dotPositions.push(`${position.x},${position.y}`);
}
}
}
await Promise.all(
[
rects[0].animate({ x: 13, y: 13 }),
rects[1].animate({ x: 31, y: 31 }),
rects[2].animate({ x: 49, y: 31 }),
rects[3].animate({ x: 13, y: 31 }),
rects[4].animate({ x: 49, y: 49 }),
rects[5].animate({ x: 67, y: 49 }),
rects[6].animate({ x: 13, y: 49 }),
rects[7].animate({ x: 31, y: 67 }),
rects[8].animate({ x: 67, y: 67 }),
dots.map((dot, index) => {
const position = dotPositions[index].split(",").map(Number);
return dot.animate({ x: position[0], y: position[1] });
}),
].flat(),
);
await sleep(500);
const lines = Array.from({ length: 8 }, () => {
return AnimatedRect({
app: props.app,
x: 0,
y: 0,
width: 0,
height: 0,
radius: 0,
color: MAIN_COLOR,
centering: false,
animationConfig: {
duration: 0.25,
ease: "linear",
},
});
});
lines.forEach((graphic) =>
props.blinkingContainer.container.addChild(graphic.graphic),
);
(async () => {
lines[0].setStyle({ width: 1, height: 0, y: 18, x: 12.5 });
await lines[0].animate({ height: 9 });
lines[1].setStyle({ width: 0, height: 1, y: 30.5, x: 18 });
await lines[1].animate({ width: 9 });
lines[2].setStyle({ width: 0, height: 1, y: 30.5, x: 36 });
await lines[2].animate({ width: 9 });
lines[3].setStyle({ width: 1, height: 3, y: 36, x: 48.5 });
await lines[3].animate({ height: 9 });
lines[4].setStyle({ width: 0, height: 1, y: 48.5, x: 54 });
await lines[4].animate({ width: 9 });
})();
lines[5].setStyle({ width: 0, height: 1, y: 66.5, x: 62 });
await lines[5].animate({ width: 28, x: 62 - 28 }, { duration: 0.4 });
lines[6].setStyle({ width: 0, height: 1, y: 66.5, x: 26 });
await lines[6].animate({ width: 13.5, x: 26 - 13.5 });
lines[7].setStyle({ width: 1, height: 0, y: 66.5, x: 12.5 });
await lines[7].animate({ height: 14.5, y: 66.5 - 13.5 });
await sleep(2000);
props.blinkingContainer.blink({ delay: 0.1 });
await Promise.all(
[
lines.map((line) => line.animate({ alpha: 0 })),
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
dots.map((dot) =>
dot.animate(dot.defaultProps, { delay: Math.random() * 0.3 }),
),
].flat(),
);
rects.shift();
lines.forEach((line) => line.graphic.destroy());
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
}
@@ -0,0 +1,219 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function scrape(props: Props) {
const rects = Array.from({ length: 15 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 10,
height: 10,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 25 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
await Promise.all(
[
[0, 12, 13, 14].map((index) =>
dots[index].animate({ x: 30, y: 30 }, { delay: 0.2 }),
),
[1, 15, 16, 17].map((index) =>
dots[index].animate({ x: CELL_SIZE - 30, y: 30 }, { delay: 0.2 }),
),
[2, 18, 19, 20].map((index) =>
dots[index].animate({ x: 30, y: CELL_SIZE - 30 }, { delay: 0.2 }),
),
[3, 21, 22, 23].map((index) =>
dots[index].animate(
{ x: CELL_SIZE - 30, y: CELL_SIZE - 30 },
{ delay: 0.2 },
),
),
props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
}),
].flat(),
);
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
[0, 12, 13, 14].map((index) => dots[index].animate({ x: 22, y: 22 })),
[1, 15, 16, 17].map((index) =>
dots[index].animate({ x: CELL_SIZE - 22, y: 22 }),
),
[2, 18, 19, 20].map((index) =>
dots[index].animate({ x: 22, y: CELL_SIZE - 22 }),
),
[3, 21, 22, 23].map((index) =>
dots[index].animate({ x: CELL_SIZE - 22, y: CELL_SIZE - 22 }),
),
dots[4].animate({ x: 40, y: 22 }),
dots[5].animate({ x: 22, y: 40 }),
dots[6].animate({ x: CELL_SIZE - 22, y: 40 }),
dots[7].animate({ x: 40, y: 58 }),
dots[8].animate({ x: 40, y: 22 }),
dots[9].animate({ x: 22, y: 40 }),
dots[10].animate({ x: CELL_SIZE - 22, y: 40 }),
dots[11].animate({ x: 40, y: 58 }),
rects[0].animate({ width: 10, height: 10 }),
rects.slice(0, 4).map((rect) => rect.animate({ x: 31, y: 31 })),
rects
.slice(4, 8)
.map((rect) => rect.animate({ x: CELL_SIZE - 31, y: 31 })),
rects
.slice(8, 12)
.map((rect) => rect.animate({ x: 31, y: CELL_SIZE - 31 })),
rects
.slice(12, 16)
.map((rect) => rect.animate({ x: CELL_SIZE - 31, y: CELL_SIZE - 31 })),
].flat(),
);
await sleep(1000);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
dots[0].animate({ x: 4, y: 4 }),
dots[1].animate({ x: CELL_SIZE - 4, y: 4 }),
dots[2].animate({ x: 4, y: CELL_SIZE - 4 }),
dots[3].animate({ x: CELL_SIZE - 4, y: CELL_SIZE - 4 }),
dots[4].animate({ x: 40, y: 4 }),
dots[5].animate({ x: 4, y: 40 }),
dots[6].animate({ x: 76, y: 40 }),
dots[7].animate({ x: 40, y: 76 }),
dots[13].animate({ x: 22, y: 4 }),
dots[14].animate({ x: 4, y: 22 }),
dots[16].animate({ x: 58, y: 4 }),
dots[17].animate({ x: 76, y: 22 }),
dots[19].animate({ x: 4, y: 58 }),
dots[20].animate({ x: 22, y: 76 }),
dots[22].animate({ x: 58, y: 76 }),
dots[23].animate({ x: 76, y: 58 }),
rects.map((rect, index) => {
const quadrant = Math.floor(index / 4);
const position = index % 4;
const col = (position % 2 === 0 ? 1 : 2) + (quadrant % 2 === 0 ? 0 : 2);
const row = Math.floor(position / 2) + (quadrant < 2 ? 1 : 3);
return rect.animate({
x: 13 + (col - 1) * 18,
y: 13 + (row - 1) * 18,
});
}),
].flat(),
);
await sleep(1200);
Promise.all(
dots.map((dot) =>
dot.animate({ alpha: 0 }, { delay: Math.random() * 0.3 }),
),
);
await sleep(100);
props.blinkingContainer.blink({ delay: 0.2 });
const newWidths: number[] = [];
for (let i = 0; i < rects.length; i++) {
if (i % 2 === 0) {
newWidths.push(20 + Math.random() * 28);
} else {
const remainingSpace = 62 - newWidths[i - 1];
newWidths.push(10 + Math.random() * remainingSpace);
}
}
await Promise.all([
rects.map((rect, index) => {
const y = 8 + Math.floor(index / 2) * 6 + Math.floor(index / 4) * 8;
return rect.animate(
{
y,
x:
(index % 2 === 0 ? 8 : newWidths[index - 1] + 10) +
newWidths[index] / 2,
height: 4,
width: newWidths[index],
},
{
delay: Math.random() * 0.1,
},
);
}),
]);
props.blinkingContainer.blink({ delay: 0.1 });
await sleep(2000);
await Promise.all(
[
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
].flat(),
);
rects.shift();
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
}
@@ -0,0 +1,144 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function search(props: Props) {
const rects = Array.from({ length: 8 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 10,
height: 10,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 16 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
await props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
});
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
dots.map((dot, index) => {
const x = 13 + (index % 4) * 18;
const y = 13 + Math.floor(index / 4) * 18;
return dot.animate({ x, y });
}),
rects[0].animate({ width: 10, height: 10 }),
rects.map((rect, index) => {
const x = 22 + (index % 3) * 18;
const y = 22 + Math.floor(index / 3) * 18;
return rect.animate({ x, y });
}),
].flat(),
);
await sleep(300);
Promise.all(
[
rects.map((rect) => rect.animate({ alpha: 0.68 })),
dots.map((dot) => dot.animate({ alpha: 0.68 })),
].flat(),
);
props.blinkingContainer.blink();
await sleep(400);
for await (const rect of rects) {
// Get the surrounding dots of this rect
const rectX = rect.currentProps.x;
const rectY = rect.currentProps.y;
const surroundingDots = dots.filter((dot) => {
const dx = Math.abs(dot.currentProps.x - rectX);
const dy = Math.abs(dot.currentProps.y - rectY);
// Consider "surrounding" as adjacent horizontally, vertically, or diagonally (distance 18)
return (
(dx === 0 && dy === 9) ||
(dx === 9 && dy === 0) ||
(dx === 9 && dy === 9)
);
});
await Promise.all(
[
surroundingDots.map((dot) =>
dot.animate({ alpha: 1 }, { duration: 0.75 }),
),
rect.animate({ alpha: 1, width: 14, height: 14 }, { duration: 0.75 }),
].flat(),
);
rect.animate({ alpha: 0.68, width: 10, height: 10 }, { duration: 0.75 });
Promise.all(
surroundingDots.map((dot) =>
dot.animate({ alpha: 0.68 }, { duration: 0.75 }),
),
);
}
await Promise.all(
[
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
dots.map((dot) =>
dot.animate(dot.defaultProps, { delay: Math.random() * 0.3 }),
),
].flat(),
);
rects.shift();
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
}
@@ -0,0 +1,263 @@
"use client";
// import dynamic from "next/dynamic";
// import { useRef, useEffect, forwardRef } from "react";
// const originalText =
// "";
type Options = {
randomizeChance?: number;
reversed?: boolean;
};
export const encryptText = (
text: string,
progress: number,
_options?: Options,
) => {
const options = {
randomizeChance: 0.7,
..._options,
};
const encryptionChars = "a-zA-Z0-9*=?!";
const skipTags = ["<br class='lg-max:hidden'>", "<span>", "</span>"];
// Calculate how many characters should be encrypted
const totalChars = text.length;
const encryptedCount = Math.floor(totalChars * (1 - progress));
let result = "";
let charIndex = 1;
for (let i = 0; i < text.length; i++) {
const char = text[i];
// Check if we're at the start of a tag to skip
let shouldSkip = false;
for (const tag of skipTags) {
if (text.substring(i, i + tag.length) === tag) {
result += tag;
i += tag.length - 1; // -1 because loop will increment
shouldSkip = true;
break;
}
}
if (shouldSkip) continue;
// Skip spaces - keep them as is
if (char === " ") {
result += char;
charIndex++;
continue;
}
// If this character should be encrypted
if (
options.reversed
? charIndex < encryptedCount
: text.length - charIndex < encryptedCount
) {
// 40% chance to show original character, 60% chance to encrypt
if (Math.random() < options.randomizeChance) {
result += char;
} else {
// Use random character from encryption set
const randomIndex = Math.floor(Math.random() * encryptionChars.length);
result += encryptionChars[randomIndex];
}
} else {
// Keep original character
result += char;
}
charIndex++;
}
return result;
};
// const Wrapper = forwardRef<
// HTMLDivElement,
// React.HTMLAttributes<HTMLDivElement>
// >((props, ref) => {
// return (
// <div className="text-title-h1 mx-auto text-center [&_span]:text-heat-100 mb-12 lg:mb-16">
// <div {...props} className="hidden lg:contents" ref={ref} />
// <div
// className="lg:hidden contents"
// dangerouslySetInnerHTML={{ __html: originalText }}
// />
// </div>
// );
// });
// Wrapper.displayName = "Wrapper";
// export default dynamic(() => Promise.resolve(HomeHeroTitle), {
// ssr: false,
// loading: () => (
// <Wrapper
// dangerouslySetInnerHTML={{ __html: encryptText(originalText, 0) }}
// />
// ),
// });
// function HomeHeroTitle() {
// const textRef = useRef<HTMLDivElement>(null);
// useEffect(() => {
// if (window.innerWidth < 996) {
// return;
// }
// let progress = 0;
// let increaseProgress = -10;
// const animate = () => {
// increaseProgress = (increaseProgress + 1) % 5;
// if (increaseProgress === 4) {
// progress += 0.3;
// }
// if (progress > 1) {
// progress = 1;
// textRef.current!.innerHTML = encryptText(originalText, progress);
// return;
// }
// textRef.current!.innerHTML = encryptText(originalText, progress);
// const interval = 50 + progress * 20;
// setTimeout(animate, interval);
// };
// animate();
// }, []);
// return (
// <Wrapper
// dangerouslySetInnerHTML={{ __html: encryptText(originalText, 0) }}
// ref={textRef}
// />
// );
// }
// import dynamic from "next/dynamic";
// import { useRef, useEffect, forwardRef } from "react";
// const originalText =
// "Turn websites into <br class='lg-max:hidden'><span>LLM-ready</span> data";
// type Options = {
// randomizeChance?: number;
// reversed?: boolean;
// };
// export const encryptText = (
// text: string,
// progress: number,
// _options?: Options,
// ) => {
// const options = {
// randomizeChance: 0.7,
// ..._options,
// };
// const encryptionChars = "a-zA-Z0-9*=?!";
// const skipTags = ["<br class='lg-max:hidden'>", "<span>", "</span>"];
// // Calculate how many characters should be encrypted
// const totalChars = text.length;
// const encryptedCount = Math.floor(totalChars * (1 - progress));
// let result = "";
// let charIndex = 1;
// for (let i = 0; i < text.length; i++) {
// const char = text[i];
// // Check if we're at the start of a tag to skip
// let shouldSkip = false;
// for (const tag of skipTags) {
// if (text.substring(i, i + tag.length) === tag) {
// result += tag;
// i += tag.length - 1; // -1 because loop will increment
// shouldSkip = true;
// break;
// }
// }
// if (shouldSkip) continue;
// // Skip spaces - keep them as is
// if (char === " ") {
// result += char;
// charIndex++;
// continue;
// }
// // If this character should be encrypted
// if (
// options.reversed
// ? charIndex < encryptedCount
// : text.length - charIndex < encryptedCount
// ) {
// // 40% chance to show original character, 60% chance to encrypt
// if (Math.random() < options.randomizeChance) {
// result += char;
// } else {
// // Use random character from encryption set
// const randomIndex = Math.floor(Math.random() * encryptionChars.length);
// result += encryptionChars[randomIndex];
// }
// } else {
// // Keep original character
// result += char;
// }
// charIndex++;
// }
// return result;
// };
// const Wrapper = forwardRef<
// HTMLDivElement,
// React.HTMLAttributes<HTMLDivElement>
// >((props, ref) => {
// return (
// <div className="text-title-h1 mx-auto text-center [&_span]:text-heat-100 mb-12 lg:mb-16">
// <div {...props} className="hidden lg:contents" ref={ref} />
// <div
// className="lg:hidden contents"
// dangerouslySetInnerHTML={{ __html: originalText }}
// />
// </div>
// );
// });
// Wrapper.displayName = "Wrapper";
// export default dynamic(() => Promise.resolve(HomeHeroTitle), {
// ssr: false,
// loading: () => (
// <Wrapper
// dangerouslySetInnerHTML={{ __html: encryptText(originalText, 0) }}
// />
// ),
// });
export default function HomeHeroTitle() {
return (
<h1 className="text-title-h1 mx-auto text-center [&_span]:text-heat-100 mb-12 lg:mb-16">
Open Lovable <span>v3</span>
</h1>
);
}