continue re-design
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
"use client";
|
||||
|
||||
import copy from "copy-to-clipboard";
|
||||
import { animate, cubicBezier } from "motion";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import FirecrawlIcon from "@/components/shared/firecrawl-icon/firecrawl-icon";
|
||||
import Logo from "@/components/shared/header/_svg/Logo";
|
||||
import { useHeaderContext } from "@/components/shared/header/HeaderContext";
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
import Download from "./_svg/Download";
|
||||
import Guidelines from "./_svg/Guidelines";
|
||||
import Icon from "./_svg/Icon";
|
||||
|
||||
export default function HeaderBrandKit() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { dropdownContent, clearDropdown } = useHeaderContext();
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("click", () => {
|
||||
setOpen(false);
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dropdownContent) {
|
||||
setOpen(false);
|
||||
}
|
||||
}, [dropdownContent]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Link
|
||||
className="flex items-center gap-2 relative brand-kit-menu"
|
||||
href="/"
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setOpen(!open);
|
||||
|
||||
if (!open) {
|
||||
clearDropdown(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FirecrawlIcon className="size-28 -top-2 relative" />
|
||||
<Logo />
|
||||
</Link>
|
||||
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
{open && <Menu setOpen={setOpen} />}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Menu = ({ setOpen }: { setOpen: (open: boolean) => void }) => {
|
||||
const backgroundRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const timeoutRef = useRef<number | null>(null);
|
||||
|
||||
const onMouseEnter = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
const t = e.target as HTMLElement;
|
||||
|
||||
const target =
|
||||
t instanceof HTMLButtonElement
|
||||
? t
|
||||
: (t.closest("button") as HTMLButtonElement);
|
||||
|
||||
if (backgroundRef.current) {
|
||||
animate(backgroundRef.current, { scale: 0.98, opacity: 1 }).then(() => {
|
||||
if (backgroundRef.current) {
|
||||
animate(backgroundRef.current!, { scale: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
animate(
|
||||
backgroundRef.current,
|
||||
{
|
||||
y: target.offsetTop - 4,
|
||||
},
|
||||
{
|
||||
ease: cubicBezier(0.1, 0.1, 0.25, 1),
|
||||
duration: 0.2,
|
||||
},
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onMouseLeave = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
if (backgroundRef.current) {
|
||||
animate(backgroundRef.current, { scale: 1, opacity: 0 });
|
||||
}
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0, scale: 1, filter: "blur(0px)" }}
|
||||
className="absolute w-220 whitespace-nowrap rounded-16 p-4 bg-white left-0 top-[calc(100%+8px)] z-[2000] border border-border-faint"
|
||||
exit={{ opacity: 0, y: 8, scale: 0.98, filter: "blur(1px)" }}
|
||||
initial={{ opacity: 0, y: -6, filter: "blur(1px)" }}
|
||||
style={{
|
||||
boxShadow:
|
||||
"0px 12px 24px rgba(0, 0, 0, 0.08), 0px 4px 8px rgba(0, 0, 0, 0.04)",
|
||||
}}
|
||||
transition={{
|
||||
ease: cubicBezier(0.1, 0.1, 0.25, 1),
|
||||
duration: 0.2,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute top-4 opacity-0 z-[2] pointer-events-none inset-x-4 bg-black-alpha-4 rounded-8 h-32"
|
||||
ref={backgroundRef}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.open("/", "_blank");
|
||||
setOpen(false);
|
||||
}}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<svg
|
||||
className="w-16 h-16"
|
||||
fill="none"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 4.5V12.5C12 13.0523 11.5523 13.5 11 13.5H4C3.44772 13.5 3 13.0523 3 12.5V4.5C3 3.94772 3.44772 3.5 4 3.5H7.5M10.5 2.5H13.5M13.5 2.5V5.5M13.5 2.5L8.5 7.5"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
Open in new tab
|
||||
</Button>
|
||||
|
||||
<div className="px-8 py-4">
|
||||
<div className="h-1 w-full bg-black-alpha-5" />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
copy(`<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.7605 6.61389C13.138 6.79867 12.6687 7.21667 12.3251 7.67073C12.2513 7.76819 12.0975 7.69495 12.1268 7.57552C12.7848 4.86978 11.9155 2.6209 9.20582 1.51393C9.06836 1.4576 8.92527 1.58097 8.96132 1.72519C10.1939 6.67417 5.00941 6.25673 5.66459 11.8671C5.67585 11.9634 5.56769 12.0293 5.48882 11.973C5.2432 11.7967 4.96885 11.4288 4.78069 11.1702C4.72548 11.0942 4.60605 11.1156 4.5807 11.2063C4.43085 11.7482 4.35986 12.2586 4.35986 12.7656C4.35986 14.7373 5.37333 16.473 6.90734 17.4791C6.99522 17.5366 7.10789 17.4543 7.07804 17.3535C6.99917 17.0887 6.95466 16.8093 6.95128 16.5203C6.95128 16.3429 6.96255 16.1615 6.99015 15.9925C7.05438 15.5677 7.20197 15.1632 7.44985 14.7948C8.29995 13.5188 10.0041 12.2862 9.73199 10.6125C9.71453 10.5066 9.83959 10.4368 9.91846 10.5094C11.119 11.6063 11.3567 13.0817 11.1595 14.405C11.1426 14.5199 11.2868 14.5813 11.3595 14.4912C11.5432 14.2613 11.7674 14.0596 12.0113 13.9081C12.0722 13.8703 12.1533 13.8991 12.1764 13.9667C12.3121 14.3616 12.5138 14.7323 12.7042 15.1029C12.9318 15.5485 13.0529 16.0573 13.0338 16.5958C13.0242 16.8578 12.9808 17.1113 12.9082 17.3524C12.8772 17.4543 12.9887 17.5394 13.0783 17.4808C14.6134 16.4747 15.6275 14.739 15.6275 12.7662C15.6275 12.0806 15.5075 11.4085 15.2804 10.7787C14.8044 9.45766 13.5966 8.46561 13.9019 6.74403C13.9166 6.66178 13.8405 6.59023 13.7605 6.61389Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
</svg>`);
|
||||
|
||||
setOpen(false);
|
||||
}}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<Icon />
|
||||
Copy logo as SVG
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<Download />
|
||||
Download brand assets
|
||||
</Button>
|
||||
|
||||
<div className="px-8 py-4">
|
||||
<div className="h-1 w-full bg-black-alpha-5" />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<Guidelines />
|
||||
Visit brand guidelines
|
||||
</Button>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
const Button = (attributes: React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||
return (
|
||||
<button
|
||||
{...attributes}
|
||||
className={cn(
|
||||
"flex gap-8 w-full items-center text-label-small group text-accent-black p-6",
|
||||
attributes.className,
|
||||
)}
|
||||
>
|
||||
{attributes.children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
export default function Download() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="group-hover:stroke-heat-100 duration-[200ms] transition-all"
|
||||
d="M12.8334 10.8334L10.4715 13.1953C10.2111 13.4557 9.78904 13.4557 9.52869 13.1953L7.16675 10.8334M10.0001 3.83337V13.1667M14.8334 16.1667H5.16675"
|
||||
stroke="#262626"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default function Guidelines() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="group-hover:stroke-heat-100 duration-[200ms] transition-all"
|
||||
d="M10.0001 7.16663C10.0001 6.06206 10.8955 5.16663 12.0001 5.16663H15.8334C16.3857 5.16663 16.8334 5.61434 16.8334 6.16663V13.8333C16.8334 14.3856 16.3857 14.8333 15.8334 14.8333H12.1847C11.7311 14.8333 11.2865 14.9427 10.9006 15.1812C10.5148 15.4197 10.2029 15.7609 10.0001 16.1666M10.0001 7.16663C10.0001 6.06206 9.10465 5.16663 8.00008 5.16663H4.16675C3.61446 5.16663 3.16675 5.61434 3.16675 6.16663V13.8333C3.16675 14.3856 3.61446 14.8333 4.16675 14.8333H7.81541C8.26902 14.8333 8.71367 14.9427 9.09953 15.1812C9.48539 15.4197 9.79722 15.7609 10.0001 16.1666M10.0001 7.16663V16.1666"
|
||||
stroke="#262626"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export default function Icon() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="group-hover:fill-heat-100 duration-[200ms] transition-all"
|
||||
d="M13.7605 6.61389C13.138 6.79867 12.6687 7.21667 12.3251 7.67073C12.2513 7.76819 12.0975 7.69495 12.1268 7.57552C12.7848 4.86978 11.9155 2.6209 9.20582 1.51393C9.06836 1.4576 8.92527 1.58097 8.96132 1.72519C10.1939 6.67417 5.00941 6.25673 5.66459 11.8671C5.67585 11.9634 5.56769 12.0293 5.48882 11.973C5.2432 11.7967 4.96885 11.4288 4.78069 11.1702C4.72548 11.0942 4.60605 11.1156 4.5807 11.2063C4.43085 11.7482 4.35986 12.2586 4.35986 12.7656C4.35986 14.7373 5.37333 16.473 6.90734 17.4791C6.99522 17.5366 7.10789 17.4543 7.07804 17.3535C6.99917 17.0887 6.95466 16.8093 6.95128 16.5203C6.95128 16.3429 6.96255 16.1615 6.99015 15.9925C7.05438 15.5677 7.20197 15.1632 7.44985 14.7948C8.29995 13.5188 10.0041 12.2862 9.73199 10.6125C9.71453 10.5066 9.83959 10.4368 9.91846 10.5094C11.119 11.6063 11.3567 13.0817 11.1595 14.405C11.1426 14.5199 11.2868 14.5813 11.3595 14.4912C11.5432 14.2613 11.7674 14.0596 12.0113 13.9081C12.0722 13.8703 12.1533 13.8991 12.1764 13.9667C12.3121 14.3616 12.5138 14.7323 12.7042 15.1029C12.9318 15.5485 13.0529 16.0573 13.0338 16.5958C13.0242 16.8578 12.9808 17.1113 12.9082 17.3524C12.8772 17.4543 12.9887 17.5394 13.0783 17.4808C14.6134 16.4747 15.6275 14.739 15.6275 12.7662C15.6275 12.0806 15.5075 11.4085 15.2804 10.7787C14.8044 9.45766 13.5966 8.46561 13.9019 6.74403C13.9166 6.66178 13.8405 6.59023 13.7605 6.61389Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import CurvyRect from "@/components/shared/layout/curvy-rect";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
|
||||
interface Props {
|
||||
navigationItems: {
|
||||
label: string;
|
||||
items: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
description: string;
|
||||
href: string;
|
||||
target?: string;
|
||||
big?: boolean;
|
||||
ctas?: { label: string; href: string; target?: string }[];
|
||||
sectionLabel?: string;
|
||||
iconClassName?: string;
|
||||
}[];
|
||||
}[];
|
||||
sideLabel: string;
|
||||
sideContent: React.ReactNode;
|
||||
sideItem: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
description: string;
|
||||
href: string;
|
||||
target?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function HeaderDropdownContent({
|
||||
navigationItems,
|
||||
sideLabel,
|
||||
sideContent,
|
||||
sideItem,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className="lg-max:max-w-[unset] container lg:flex gap-16">
|
||||
{navigationItems.map((item, index) => (
|
||||
<div className="flex-1" key={index}>
|
||||
<GroupLabel label={item.label} />
|
||||
|
||||
{/* Default section (items without sectionLabel) */}
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-x-16",
|
||||
navigationItems.length === 1 && "lg:grid-cols-2",
|
||||
)}
|
||||
>
|
||||
{item.items
|
||||
.filter((it) => !it.sectionLabel && !it.big)
|
||||
.map((it) => (
|
||||
<Item item={it} key={it.label} />
|
||||
))}
|
||||
{item.items
|
||||
.filter((it) => !it.sectionLabel && it.big)
|
||||
.map((it) => (
|
||||
<ItemBig item={it} key={it.label} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Additional sections within the same column */}
|
||||
{Array.from(
|
||||
new Set(
|
||||
item.items
|
||||
.map((it) => it.sectionLabel)
|
||||
.filter(Boolean) as string[],
|
||||
),
|
||||
).map((section) => (
|
||||
<div key={section}>
|
||||
{/* <GroupLabel label={section} /> */}
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-x-16",
|
||||
navigationItems.length === 1 && "lg:grid-cols-2",
|
||||
)}
|
||||
>
|
||||
{item.items
|
||||
.filter((it) => it.sectionLabel === section && !it.big)
|
||||
.map((it) => (
|
||||
<Item item={it} key={it.label} />
|
||||
))}
|
||||
{item.items
|
||||
.filter((it) => it.sectionLabel === section && it.big)
|
||||
.map((it) => (
|
||||
<ItemBig item={it} key={it.label} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="h-42 border-t border-border-faint border-x -mt-1 relative lg-max:hidden">
|
||||
<CurvyRect
|
||||
className="-top-1 absolute -left-1 w-[calc(100%+2px)]"
|
||||
top
|
||||
/>
|
||||
<CurvyRect
|
||||
className="bottom-full absolute -left-1 w-[calc(100%+2px)]"
|
||||
bottom
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex-1 max-w-360 relative">
|
||||
<div className="h-full w-1 absolute top-0 left-0 bg-border-faint" />
|
||||
<GroupLabel label={sideLabel} />
|
||||
|
||||
<div className="hidden lg:contents">{sideContent}</div>
|
||||
|
||||
<Item item={sideItem} />
|
||||
|
||||
<div className="h-42 border-t border-border-faint border-r -mt-1 relative lg-max:hidden">
|
||||
<CurvyRect
|
||||
className="-top-1 absolute left-0 w-[calc(100%+1px)]"
|
||||
top
|
||||
/>
|
||||
<CurvyRect
|
||||
className="bottom-full absolute left-0 w-[calc(100%+1px)]"
|
||||
bottom
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const GroupLabel = ({ label }: { label: string }) => {
|
||||
return (
|
||||
<div className="text-body-medium py-16 lg:py-20 lg:border-x border-b border-border-faint px-24 lg:px-44 text-black-alpha-64">
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({
|
||||
item,
|
||||
}: {
|
||||
item: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
description: string;
|
||||
href: string;
|
||||
target?: string;
|
||||
iconClassName?: string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<a
|
||||
className="flex items-start gap-16 py-16 pl-24 lg-max:[&_svg]:size-24 lg:pl-44 group border-x hover:bg-black-alpha-2 border-b border-border-faint transition-all hover:text-heat-100"
|
||||
href={item.href}
|
||||
key={item.label}
|
||||
target={item.target}
|
||||
>
|
||||
<div className={item.iconClassName}>{item.icon}</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-label-medium">{item.label}</div>
|
||||
|
||||
<div className="text-body-medium mt-4 text-black-alpha-64 lg-max:hidden">
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
const ItemBig = ({
|
||||
item,
|
||||
}: {
|
||||
item: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
description: string;
|
||||
href: string;
|
||||
target?: string;
|
||||
ctas?: { label: string; href: string; target?: string }[];
|
||||
iconClassName?: string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-start gap-16 py-22 pl-24 lg-max:[&_svg]:size-24 lg:pl-44 group border-x border-b transition-colors border-border-faint"
|
||||
key={item.label}
|
||||
>
|
||||
<div className={item.iconClassName}>{item.icon}</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<a
|
||||
href={item.href}
|
||||
target={item.target}
|
||||
className="text-label-medium inline-block hover:text-heat-100 transition-colors"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
|
||||
<div className="text-body-medium mt-4 text-black-alpha-64 lg-max:hidden">
|
||||
{item.description}
|
||||
</div>
|
||||
|
||||
{item.ctas && item.ctas.length > 0 && (
|
||||
<div className="mt-12 flex items-center gap-8 lg-max:hidden">
|
||||
{item.ctas.map((cta) => (
|
||||
<a
|
||||
key={cta.label}
|
||||
href={cta.href}
|
||||
target={cta.target}
|
||||
className="inline-flex items-center gap-6 px-12 py-6 rounded-6 text-label-small text-heat-100 bg-heat-4 hover:bg-heat-8 transition-colors whitespace-nowrap shrink-0"
|
||||
>
|
||||
<span>{cta.label}</span>
|
||||
<ArrowUpRight className="size-14" aria-hidden="true" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
export interface NavItemRowProps {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
description: string;
|
||||
href: string;
|
||||
target?: string;
|
||||
iconClassName?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function NavItemRow({
|
||||
icon,
|
||||
label,
|
||||
description,
|
||||
href,
|
||||
target,
|
||||
iconClassName,
|
||||
className,
|
||||
}: NavItemRowProps) {
|
||||
return (
|
||||
<a
|
||||
className={cn(
|
||||
"flex items-start gap-16 py-16 pl-24 lg-max:[&_svg]:size-24 lg:pl-44 group border-x hover:bg-black-alpha-2 border-b border-border-faint transition-all hover:text-heat-100",
|
||||
className,
|
||||
)}
|
||||
href={href}
|
||||
target={target}
|
||||
>
|
||||
<div className={iconClassName}>{icon}</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-label-medium">{label}</div>
|
||||
|
||||
<div className="text-body-medium mt-4 text-black-alpha-64 lg-max:hidden">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export interface NavItemRowBigProps extends Omit<NavItemRowProps, "target"> {
|
||||
ctas?: { label: string; href: string; target?: string }[];
|
||||
}
|
||||
|
||||
export function NavItemRowBig({
|
||||
icon,
|
||||
label,
|
||||
description,
|
||||
href,
|
||||
iconClassName,
|
||||
ctas,
|
||||
}: NavItemRowBigProps) {
|
||||
return (
|
||||
<div className="flex items-start gap-16 py-22 pl-24 lg-max:[&_svg]:size-24 lg:pl-44 group border-x border-b transition-colors border-border-faint">
|
||||
<div className={iconClassName}>{icon}</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<a
|
||||
href={href}
|
||||
className="text-label-medium inline-block hover:text-heat-100 transition-colors"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
|
||||
<div className="text-body-medium mt-4 text-black-alpha-64 lg-max:hidden">
|
||||
{description}
|
||||
</div>
|
||||
|
||||
{ctas && ctas.length > 0 && (
|
||||
<div className="mt-12 flex items-center gap-8 lg-max:hidden">
|
||||
{ctas.map((cta) => (
|
||||
<a
|
||||
key={cta.label}
|
||||
href={cta.href}
|
||||
target={cta.target}
|
||||
className="inline-flex items-center gap-6 px-12 py-6 rounded-6 text-label-small text-heat-100 bg-heat-4 hover:bg-heat-8 transition-colors whitespace-nowrap shrink-0"
|
||||
>
|
||||
<span>{cta.label}</span>
|
||||
<ArrowUpRight className="size-14" aria-hidden="true" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { HTMLAttributes, useEffect, useRef } from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
|
||||
|
||||
import data from "./data.json";
|
||||
|
||||
export default function GithubFlame(attrs: HTMLAttributes<HTMLDivElement>) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let index = 0;
|
||||
|
||||
const interval = setIntervalOnVisible({
|
||||
element: wrapperRef.current,
|
||||
callback: () => {
|
||||
index++;
|
||||
if (index >= data.length) index = 0;
|
||||
|
||||
const newStr = data[index];
|
||||
|
||||
if (!ref.current) return;
|
||||
|
||||
ref.current!.innerHTML = newStr;
|
||||
},
|
||||
interval: 60,
|
||||
});
|
||||
|
||||
return () => interval?.();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute -top-20 left-180 w-194 h-192"
|
||||
style={{
|
||||
maskImage: "url('/assets-original/github-mask.png')",
|
||||
maskSize: "100% 100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
{...attrs}
|
||||
className={cn(
|
||||
"w-308 h-380 -top-20 -left-40 absolute pointer-events-none select-none",
|
||||
attrs.className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="text-black-alpha-20 relative top-0 left-0 font-ascii fc-decoration"
|
||||
ref={ref}
|
||||
style={{
|
||||
whiteSpace: "pre",
|
||||
fontSize: 8,
|
||||
lineHeight: "10px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
[
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ''.-_,.'' \n '.:;==+^_.' \n '-\";+;:,' \n ''''' \n \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ''' \n '-:\"\":,.' \n .:;+++;^:-' \n '-\"+===+;\"-' \n ',^+==++;:.' \n ',\"^^\":,.' \n ''''' \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n .::,'' \n '-\";;^\"\":,' \n '.:\";++;;;\"_-'' \n .:;+===++;\",' \n '_;+====+;:,'' \n '_;++=++;\",.' \n '-\"^^^\":_.'' \n '.--.''' \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '-::,' \n ':;+;^^\",.' \n .:\";+++;;^\":. \n '-\"^;+====+;\":-''' \n '-::;=====++;^:-'' \n ',\"+======+^:,-' \n '-:;+=====;\",.'' \n ._^;++=+;\"-'' \n '-::::,.''' \n ''-..'' \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '-_:_' \n -^;++++;;;. \n .\"+======+^:,_' \n .\";;+=====+;:__- \n -:_^+=====+;\"::,''' \n .-.\"+=======+^:__-.' \n '.._\"+======+\",-.'' \n '..,;======;_'''' \n '.._;+===++:.' \n '',::^\":.''' \n '''..'' \n ''' \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n -\"^++\". \n ';++=====+;^:- \n '\"+========+;;^' \n -;;;;++====+;\",,,. \n ':,__:;;;+===+^___-. \n '...-^;++++==+;::^:,' \n ''--,\"++=====;\"_,,-'' \n ''''_+++===++;-.,_-' \n '''.\";;+;^\"^_-''''' \n ''._;\"\":_--' \n ''._:_-' ' \n ''''''' \n ''' \n \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '-_,-' \n '':^+=====^' \n ':++========+;. \n ^+============+^;;- \n ,;;;;;+++====+^\";^: \n ';;^\":\";++++++;;:::_' \n ,,:_-,,\";^\"^;+=+;:::_-' \n '-.'''..,--,:,-_:;;+;^_ \n ''''--.-..'._:\";\"_.' \n '''..-,_-''.''..-.'' \n ''.-:,:_'''''''..' \n ' ''.,_,-'' \n '''--.' \n '..' \n ' \n \n \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '^+++\"::- \n ''':^;+=======^ \n '++=============+ \n -;;++===+======++^: \n .++++++++;;+++++++^\"\"\", \n '_\";;;;++;;^;;^;++;;;;;. \n ';;:_,_\"^\"_,_^++;;;;^:- \n \"\":,--:+;_-,,---,__\"^::.' \n '.-,.''..-.''''''''.-\";++^. \n ''''-----,.'''''',:\"\"^, \n '',--,-.'''''.-_,'' \n ''-.'''''''''' ' \n ''-.''' '' \n '.,' \n '.--' \n ''''' \n ' \n \n \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '''' \n '\"+====^;+\"- \n ''-+=============+' \n :+==========+=+====+- \n '+========++++++++==+ \n :^+++==++;;+;;+=====+;- \n '++++++++;^\"\"\";++;+;\"\"^\":- \n -^;;;;++;\"__:\";;^;;\":\";++: \n '.\"^;^:::\"\"::\":::\"\":::\";+:. \n .:\":_,,__:,--''--,___::;;:' \n -_,,-,,__--.'''''..._:;++;- \n ''-::::_::--....''-,:\"^;\" \n '''':^\":__----...-,::^- \n '''''--'''''.'-:-' \n '.' ''' \n '.-' \n '.-'' \n '..-.' \n '..'' \n '' \n \n \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n \n \n '-:_' \n '.' -;+++++:-\":' \n ,+===++==++++=====+- \n .:^+=========;+===+====' \n ';+=========++++;;++++==+- \n .;====++++++;;;;;+++++++. \n '-:;==++;^;;;;^;;+++==++=+ \n '+++++++;;;;^^\"^^^^^;++^\"^^_'' \n ,:;+;;;+;;\"_:::::\"\"::__::^;;+: \n .,:\"^;;^:,::_:,,_:\":_,__\"\";;- \n ,:\"\":_,___,-...---_,,___::;\"' \n .::_,,::::_-..,-.--,__,:^;+;' \n ''.,:\"\"\";:-,,---...,:^\";;;:' \n '-'\"^_\":_::_-----,,:\"^: \n ''''.....''.--,,-_: \n ''' '-_- \n ''''' \n '.-'' \n '.---.' \n '---' \n ''..' \n ' \n \n \n ",
|
||||
" \n \n \n \n \n \n \n \n \n \n \n '-,.' \n '..' ':++++++_ \n '+====+;+++++;+;+;;;^' \n +=====++=++=+;;++;+===+_ \n '\";=======++++++;;++=++===+' \n ,++=======++++;;;;;;+++++=+: \n '++==++++++++;;^;;;;+++++++^- \n '\"==+++;;;;^^\":::^;+++;;++;' \n -;;+++;;;^^^\":::::\"^+++++\"^\"::-,.' \n _++;;;^;;;;;\"::_:\"\"\"::_:\":___:\"^;^ \n -.,\"^^^^^\"::___::::__,--,,:::\"^\":' \n .-:\";\"::::_:::__,-,_,,-----_:_, \n .:\"^\"::_::::::::,,--,,,---,:^^- \n .,_::__,_\"\":\"^:--_,,,,_:\"^;;^' \n '--.-,_\"\"::__,_,,-,::\"\"\"\", \n '''------,___,-,:_\"\"' \n '' ''',,.''.' \n ''' \n .--'' \n '..--' \n '.--' \n .---. \n ''..' \n \n \n \n ",
|
||||
" \n \n \n \n \n \n \n \n \n '' \n ''' '-;;++;;\"' \n .^+++++::;;;;;;;;;+^.' \n ';======++++++;+;;;;;;;^\"' \n .+====+++++++++++;;;^;;++=+\"' \n -:+==++=+==+++;;;;^;;++++===+: \n ,;+====+++++++;;;;;;;;++++++++, \n ,+++++++++;++++;;;;;;;;++++;;\"' \n :+==++;;+;;;;^^^\"^;;;;;;;;;;^: \n ':+++;;;;;;^^\"\"::_::\";;;^,:_,-_,:,-' \n -;;;;;^^^;^\"\"\"::::___::^^:,,----,:^\":' \n ';;^:::::\"\":::::::::_,,_::-,,__:\"\"::-' \n '''-_:\"\"\"^\"::\"::__:::_-,,,,:----_:,' \n '.-:\"^^^^\":::_:\":\":___,_:_-,-,:_. \n ._:^\"\"\":,,__::::____-----_:_:^_ \n '''''.-.-::_,,--,---_::\"\"^^\"' \n ' ''---..-:::,____,,,_- \n '' '''''.'.--,- \n '' \n '.-.' \n ''.-' \n '..'' \n '.-..' \n ...-' \n ''''' \n \n \n \n ",
|
||||
" \n \n \n \n \n \n \n \n .^^^;\".'' \n '_^;^-''',:^;;;+;;;;;, \n '\"+==++++++;;^\"\"^;;;;;;;\". \n '+======+++++;;;;^;^\"^;^^;^_' \n \"+++===++++++++++;++\"\"^;^;;;++\" \n -\"+++++++++=++++;;;;;;^;++++++++: \n :;+===++++++++++;;;;;;;;;+++++++\"' \n .^+++++++;;+++;;^;;^;;;;;;++;;;;^. \n _+++++;;+;;;;;;;;^^^^^;;;;;;^:,. \n _++++;;;;;;;^^^\"\"::::\"\"::\"\"::,..-,,'' \n ,++;;^^^^^\":::::______:_-------..-:::- \n -^;;^::::::::\"\"\":_______,,---__,-_:::__' \n :^:,,_::::\"\"^^^:_:::__,__,,-,,__:__.'' \n '.,::\"\"^^\"::_::\":::__,::_,_:_,,-' \n '',:\"^^^:.--,::_____,,,__,_,,__- \n ''-,-.'''''',-----__,,____,_::- \n ''''''__---_,__:::- \n '''''''.-..-. \n ''' '''' \n '''' \n '''' \n ''''' \n ''''''' \n '''''' \n ''''' \n \n \n \n \n ",
|
||||
" \n \n \n \n \n \n .--.--' \n '''' ',\"^^;;;;:,-' \n ,;++;;\"\"..__\"\"\"\"^;;;^^^;\"' \n :+===++;;;;;;;\"\"\":::\"^^;^\"\"_' \n ';+====+++;;;++;;;^:::\"\"^^^^^\":' \n :++++++++++;;++++++^\";;\"^^^;;\"^;;\"' \n .;;+++++;;++++++;;;;;^^^^^^;;;;+++\"' \n ,\"+++=+++;;++++;;;;;;;;;\"\"^;++;+++;;' \n :;+++=++;;;;++;;;;^^;;;;;^;;;;;;;;-' \n -^;++;;+;;^^;;^^\"^^^^^;;;;;;;^\"::_- \n '^+++;;;;;;;^^^\"^;\"\":\":::\"::,--''''''' \n :++;^\"^^^\"\"\"\"\":::,,-,_,,------.'''.---. \n _;^\"::__:::::\"::,,,--------''------,,_, \n ':\"\":__,,:::\"::___:_,------..-,__,-,,,,-' \n '--..-_:::\"\":---_:::,-,_:,----,,--.'' \n ''-_:\"\"\"_''.__::,,,,,,,--.-.---' \n '-_\"_. '''''-_,---,-..--,,- \n '--.'..----,,-' \n '''''''''....' \n ''''' \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" \n \n \n \n \n '_:::\"^^\"_ \n '._-'' -\"\"\"\"\"^;;;:_-' \n '\"++;;^^\"\"\":_:\"^\"\"\":\"\"\"^^\"\"^:. \n -+===++;;;;;;;;^\"::::_:\"\"\"\"\"::-' \n ,+=====++;;;;;+;;^^::_:\"\"\"\"\"\"^^\":' \n '++==++=++;;;;+++;++;^_::::\"^^^^\":\":,- \n \"++++++;;;;;++++;;;;;:\"\":\"\"\"^^^^\";++\" \n ':++==+;;;;;++++;;;;;;\"\"^\"::^;+;^\";++_ \n _^++++=+;;;;++++;;;^^^^^^\"::\";;;;;;\"\"\"- \n -^+++++++;;^;+;;^^;^\"^^^^;^^^\"\":\"\":.' \n _;++;;;;;;;^^^\"\"\"\"\"^\"^^\"\"^^^\":_,-.'' \n ':++;;^\"\"^^\"\"::_______,---,,.'''..''''' \n .;;\"::::__:___,-,,--......''''...-.''''. \n '-___----_:::_-,_-,-..-....''.-......-.' \n '.__---,::::::,,_,-------.......''''.-.' \n '..-,_::::-'-__-.--,-...'''..'..' \n '.--_:' '.'''.--''''''''''..' \n '--.'''''''..'' \n '' ''''.'' \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" \n \n \n '''''''' \n ._::_:\"^^^, \n '...__.' ',_\":::\"\"^^^:,' \n '_+++;;;\"::__::\"^\"\"::____:\"\":::_' \n -;==++;;;;;;^;;^^^\"\":_,,_:::::,_,. \n .;+=+++;;;^;^;;^^^:::_,_::::::::\":- \n ^+++=++=+;^^^^^++;^;;^\",---:\"\"\"^\"\":_.' ' \n :;;++++;;;;^\"\"^;+;^;^^\":_,,,::\"\":__,:\"^:' \n -\"+++++;;;^\":\";;;^;;^;^::_:::\"^\"\"^-,\"^^' \n -;+;++++;^^;;;;;;;;^;^\":::.-\"^^\"\"_:^^^: \n -:;+;;;;;;^^^;;+;;\"^\"::::\":,-_\"^^^:\"_-,-' \n '_^;;;;;;;;\"\"^;;;^\"\"\":::\"\"\"\"::::____' \n ,;;;;;\"::^^^\"\":_:_:______::\"_-.'''' \n -;;^\":::::::_,----.'''''....'''''''' \n ,,--,,--,::,-,--..''''''''''' '''' ' \n ''.-...-_::,---..-.''..''''''' '''''' \n ''...--_--,-'-.''...''''' '''''' ''' \n '''''.'.. '.'''.''''' ''''''' \n ''''''' ''' \n '' ''''' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" \n \n '.-,-'.,..' \n '-,::__:\"\"\"\"- \n '..-\":.' '_::::_::::::_.' \n .\";+;;;;;^\"\"\"::\"\"^^\"::_,-,:_:::_,-' \n -;+==+;;;;^^^^^^;^\":::_,-..-___,----' \n _;++;;;^^\"\"^\"\";^\"::,-.,-.--_:::,-__,' \n -\";;;;++;^^\"^\"\"\";;^:\"::__-'.,_::\"\":::. \n .;;;++++;;^^\":::^+;;^\"\"\"::,''---_::-,,..--.. \n -:\";;+;;^\"\"\"::_:\"^\"^^\":::,----,,_:,-.'.,_- \n ',;;;;;;;^^\"::::^^^^^^^\":_--,::::-,.'-::^. \n '_;;;;;^\"\":^;^\"^;;^^\"\"\"::,,._:::__--,__,' \n '-_^;;+;;\"::::^;;;;^\":_:___-..,,::_,,-''.' \n '-\"^;;;\"\"\"\"::\"\"^\"^\"\":_,__,,,_,-----. \n '_\":\"^\"::::\"\"\":,.-,-.'''..-,_.' '''' \n ''-:_--.--,_:-....'' ''''''' ' \n ''...''.-_,-.'''.''''''' \n '''...-.'''''''' \n ''''''' '''' \n '''' \n ' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" \n '.,--.-_-.' \n ' '-_:__,_:::_,' \n ..._^\"-' ' .:;\"_,,,,_,---'' \n -\";++;;;;^^^^\":^^^;^\",-.-..---,__.'' \n \"+==++;;;;^\"\"^\"\"\"^^:_--.'''''.-,,-'''' \n ':;++;;^^^^^\"::_\"^^:,-..''.'''.___-.''-. \n ,^^^^^;;\"\"\"\":_:\"\"\"::,---..''-,,__::::.' \n ':^;;;;;;;\"::::::;+;;^\":\"__-'''..-,_--_,' \n ':\"\"^;;;^\":,_:_,_\"\":\"^\":_,--''.''''..'.' '..'' \n '.-:;+;^\"\":___,--_:\"^^\":::_.'-_-.--.'' '.,. \n ._\";;;;;\":::_:_::^^\"^\"\"\":-'._::,...' .,.,-' \n ',^;;+;\"_,_:\"\":\";;^\":::_-.'-,---..'''.''' \n '.-:\";+;^:-_,_:\"^;;^:_---..''''.--.'''''' \n '.-_\"\"\"::::___::_:\":_''...--.'''''' \n ',-.,_...-,__:,.''''' '''' \n '-.'''''..''''' \n ''''''''' ' \n '' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" '.,--.'-.'' \n '' ''_:,..-,__'-' \n '..'::^_'''' '-_\"^-..''..''''' \n '^;+++;;^\"\"^\":-:\"^^\"\":-''''''''''''' \n ':+==++;+;;;^\"\"^\":::_-.''' '' ''.--' \n _+==+;\"^^^^^\"_,-,_:\"-..''' ' '-,,' \n ',:^\":\"\"::::::,,___:,-_,' '' '''---.-,,.-' \n ,^^::\"^;:____,_^;^\"\"::,,..''''''''...--' \n ::\"^\"^^\"\"_-.----,\":::\":__..'' '' '''''.' \n .,_,\";^^\"_-.'''..--_\"^:_,,-''''' '' ' '' \n '-^;+;;;\":-,_,--.,\"^:::::,''-,.'''' '' \n .-\";;;;\"_-,_____::;^\"::_'''.,.''''' '''''' \n '':^;;;\"::,___,_:;^:_--'''''''''' \n ''._:\"\"\"\":_,,,::\"^\":.''''''' '' \n '.----.--..---''._-' \n ''''''''''''' \n '' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" '-' '.:,' ''''' ' \n ''',::,''''' '.-,. '' \n '\";;+;;^\":::_,,'-,,,,-.. \n .\"++++;;;;;::,,_:,,,.'''' \n _+==;^\"\":::::,..-_---''' '..' \n _^;;\":__-..--.'.'..--.''.' ''''.''' \n '.__-,_,-......'-_.'..'.''' ''' ''''' \n ..---_-__-''.''-::---.-,.' \n '--,,::::,''''''''.'.,_-..'' \n '''.\"^;;^:..'--.'''-::..--'''' \n .:\";+;;:----..'.._::_:-'''''' \n '.,:^^^^\":,,..-.--:\"_-.' '' \n '.-_:_,,:_-.--.-::-' \n '.-.'''''''.''--. \n '.'''' '.' \n '' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" '-__.' '' ''''' ' \n _;+++;^\"_--.'''''''''''' \n _;++++;;;\"_,..'..-,''''''' \n ':++^\"\":-..'.'''.'.-''' ' \n :==+^:_-' ''' ''''''''' ' \n '_\"^_.-.' '''''' ''''' \n '''''''''''''''..''''''' \n ''''----''''''..''''''''' \n ''''-\"^\"\"_.''..''''''--'''' \n ':^;+;^-'.-.''''-,-...' \n -:;++;^::::-''''.-:_.' \n ''_:,,:,..__'''''':-' \n '''''''' '''.-' \n '.'''' '' \n ''' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" ':;;+\"::' \n .;+++=;:,.''' '' \n ,:\"\"^\",'' ' '' \n ,\"=^:_--. '.' ' \n :++;:-'' '' \n '-:-' ' \n '' '''''''' \n ',\":,-..'.''' ' \n ._\"^\"_-'''-.-' '' \n ':;=++:.'.'.' ''''-' \n .\"+;;\"__,.-' ',. \n '''''' '' \n ''' \n '' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
|
||||
" '_;^;=;'' \n '-,:^^,' \n '.'-_-' '' \n '-+_.' '' \n ,\"^^,' \n '.' ''' \n ..'''''''' \n ',,'' \n .._,,' \n ';=+;. ' ''' \n '''-,''' \n \n '' \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n "
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
import Image from "@/components/shared/image/Image";
|
||||
|
||||
import GithubFlame from "./Flame/Flame";
|
||||
|
||||
export default function HeaderDropdownGithub() {
|
||||
return (
|
||||
<div className="py-24 px-44 border-b border-border-faint relative overflow-clip">
|
||||
<div className="size-40 relative mb-17">
|
||||
<Image
|
||||
alt="Firecrawl icon (blueprint)"
|
||||
className="cw-80 ch-80 absolute top-0 left-0 max-w-[unset]"
|
||||
height={80}
|
||||
src="developer-os-icon"
|
||||
width={80}
|
||||
raw
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-label-large">
|
||||
Firecrawl is open source. <br />
|
||||
Star us to show your support!
|
||||
</div>
|
||||
|
||||
<GithubFlame />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
import { AnimatePresence, cubicBezier, motion } from "motion/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
ConnectorToLeft,
|
||||
ConnectorToRight,
|
||||
} from "@/components/shared/layout/curvy-rect";
|
||||
import { NAV_ITEMS } from "@/components/shared/header/Nav/Nav";
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
export default function HeaderDropdownMobileItem({
|
||||
item,
|
||||
}: {
|
||||
item: (typeof NAV_ITEMS)[number];
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
className="p-24 flex group relative"
|
||||
href={item.href}
|
||||
onClick={() => {
|
||||
setOpen((v) => !v);
|
||||
}}
|
||||
>
|
||||
<div className="h-1 bottom-0 absolute left-0 w-full bg-border-faint" />
|
||||
<ConnectorToRight className="-top-11 left-0" />
|
||||
<ConnectorToRight className="-bottom-10 left-0" />
|
||||
<ConnectorToLeft className="-top-11 right-0" />
|
||||
<ConnectorToLeft className="-bottom-10 right-0" />
|
||||
|
||||
<span className="px-4 flex-1 text-label-medium text-accent-black">
|
||||
{item.label}
|
||||
</span>
|
||||
|
||||
{item.dropdown && (
|
||||
<svg
|
||||
className={cn(
|
||||
"transition-all duration-200",
|
||||
open ? "rotate-180 text-accent-black" : "text-black-alpha-48",
|
||||
)}
|
||||
fill="none"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.4001 10.2L12.0001 13.8L15.6001 10.2"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</a>
|
||||
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
animate={{ height: "auto", opacity: 1, filter: "blur(0px)" }}
|
||||
className="overflow-hidden"
|
||||
exit={{ height: 0, opacity: 0, filter: "blur(4px)" }}
|
||||
initial={{ height: 0, opacity: 0, filter: "blur(4px)" }}
|
||||
transition={{ duration: 0.3, ease: cubicBezier(0.4, 0, 0.2, 1) }}
|
||||
>
|
||||
{item.dropdown}
|
||||
|
||||
<div className="h-44 relative">
|
||||
<ConnectorToRight className="-top-11 left-0" />
|
||||
<ConnectorToRight className="-bottom-10 left-0" />
|
||||
<ConnectorToLeft className="-top-11 right-0" />
|
||||
<ConnectorToLeft className="-bottom-10 right-0" />
|
||||
<div className="h-1 bottom-0 absolute left-0 w-full bg-border-faint" />
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Fragment } from "react";
|
||||
|
||||
import Button from "@/components/ui/shadcn/button";
|
||||
import {
|
||||
ConnectorToBottom,
|
||||
ConnectorToLeft,
|
||||
ConnectorToRight,
|
||||
} from "@/components/shared/layout/curvy-rect";
|
||||
import HeaderGithubClient from "@/components/shared/header/Github/GithubClient";
|
||||
import { NAV_ITEMS } from "@/components/shared/header/Nav/Nav";
|
||||
|
||||
import HeaderDropdownMobileItem from "./Item/Item";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function HeaderDropdownMobile({
|
||||
ctaHref = "/signin/signup",
|
||||
ctaLabel = "Sign up",
|
||||
}: {
|
||||
ctaHref?: string;
|
||||
ctaLabel?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="overlay border-x pointer-events-none border-border-faint" />
|
||||
<ConnectorToBottom className="-top-1 -left-10" />
|
||||
<ConnectorToBottom className="-top-1 -right-10" />
|
||||
|
||||
<div>
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<Fragment key={item.label}>
|
||||
<HeaderDropdownMobileItem item={item} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-24 flex flex-col gap-8 border-b border-border-faint relative -mt-1">
|
||||
<HeaderGithubClient />
|
||||
<Link href={ctaHref}>
|
||||
<Button variant="secondary"> {ctaLabel} </Button>
|
||||
</Link>
|
||||
|
||||
<ConnectorToRight className="left-0 -bottom-11" />
|
||||
<ConnectorToLeft className="right-0 -bottom-11" />
|
||||
</div>
|
||||
|
||||
<div className="h-36" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { HTMLAttributes, useEffect, useRef } from "react";
|
||||
|
||||
import data from "@/components/app/(home)/sections/hero-flame/data.json";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
|
||||
|
||||
export default function StoriesFlame(attrs: HTMLAttributes<HTMLDivElement>) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let index = 0;
|
||||
|
||||
const interval = setIntervalOnVisible({
|
||||
element: wrapperRef.current,
|
||||
callback: () => {
|
||||
index++;
|
||||
if (index >= data.length) index = 0;
|
||||
|
||||
const newStr = data[index];
|
||||
|
||||
if (!ref.current) return;
|
||||
|
||||
ref.current!.innerHTML = newStr;
|
||||
},
|
||||
interval: 60,
|
||||
});
|
||||
|
||||
return () => interval?.();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute right-10 bottom-10 w-194 h-165"
|
||||
style={{
|
||||
maskImage: "url('/assets-original/replit-mask.png')",
|
||||
maskSize: "100% 100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
{...attrs}
|
||||
className={cn(
|
||||
"w-308 h-380 -top-20 -left-40 absolute pointer-events-none select-none",
|
||||
attrs.className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="text-black-alpha-20 relative top-0 left-0 font-ascii fc-decoration"
|
||||
ref={ref}
|
||||
style={{
|
||||
whiteSpace: "pre",
|
||||
fontSize: 8,
|
||||
lineHeight: "10px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import ArrowUp from "./_svg/ArrowUp";
|
||||
import Replit from "./_svg/Replit";
|
||||
import StoriesFlame from "./Flame/Flame";
|
||||
|
||||
export default function HeaderDropdownStories() {
|
||||
return (
|
||||
<a
|
||||
className="pt-32 pr-32 pl-44 pb-48 group block border-b border-border-faint relative overflow-clip"
|
||||
href="/blog/how-replit-uses-firecrawl-to-power-ai-agents"
|
||||
>
|
||||
<div className="flex mb-40 justify-between items-center">
|
||||
<div className="py-4 px-8 text-heat-100 text-[12px]/[16px] font-[450] bg-heat-8 rounded-6">
|
||||
Customer story
|
||||
</div>
|
||||
|
||||
<div className="p-2 text-black-alpha-56 group-hover:text-heat-100 transition-all group-hover:translate-x-1 group-hover:translate-y-[-1px]">
|
||||
<ArrowUp />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Replit />
|
||||
|
||||
<div className="text-title-h5 mt-31 pr-32">
|
||||
How Replit uses <span className="text-heat-100">Firecrawl</span> to
|
||||
power Replit Agent
|
||||
</div>
|
||||
|
||||
<StoriesFlame />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export default function ArrowUp() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.2083 14.7082V6.7915M17.2083 6.7915H9.29167M17.2083 6.7915L7 16.9998"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export default function Replit() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.54 10.8196H5.27864C4.26819 10.8196 3.4668 10.0148 3.4668 9.03879V3.11427C3.4668 2.12115 4.28561 1.3335 5.27864 1.3335H13.7281C14.7386 1.3335 15.54 2.13827 15.54 3.11427V10.8196Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M26.5452 21.1531H15.5508V10.8047H26.5452C27.6091 10.8047 28.4864 11.6811 28.4864 12.7439V19.214C28.4864 20.2955 27.6091 21.1531 26.5452 21.1531Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M13.7281 30.6668H5.27864C4.28561 30.6668 3.4668 29.8635 3.4668 28.8892V22.9583C3.4668 21.9841 4.28561 21.1807 5.27864 21.1807H15.54V28.8892C15.54 29.8635 14.7212 30.6668 13.7281 30.6668Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// @ts-nocheck
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, cubicBezier, motion } from "motion/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { Connector } from "@/components/shared/layout/curvy-rect";
|
||||
import { useHeaderContext } from "@/components/shared/header/HeaderContext";
|
||||
import { lockBody } from "@/components/shared/lockBody";
|
||||
import AnimatedHeight from "@/components/shared/layout/animated-height";
|
||||
export default function HeaderDropdownWrapper() {
|
||||
const {
|
||||
dropdownContent,
|
||||
resetDropdownTimeout,
|
||||
clearDropdown,
|
||||
dropdownKey,
|
||||
headerHeight,
|
||||
headerTop,
|
||||
} = useHeaderContext();
|
||||
|
||||
useEffect(() => {
|
||||
lockBody("header-dropdown", !!dropdownContent);
|
||||
}, [dropdownContent]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{dropdownContent && (
|
||||
<motion.div
|
||||
animate={{ opacity: 1 }}
|
||||
className="h-screen w-screen fixed left-0 z-[2000] bg-black-alpha-40"
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: {
|
||||
duration: 0.3,
|
||||
delay: 0.1,
|
||||
ease: cubicBezier(0.4, 0, 0.2, 1),
|
||||
},
|
||||
}}
|
||||
initial={{ opacity: 0 }}
|
||||
style={{
|
||||
top: headerTop.current + headerHeight.current + 1,
|
||||
}}
|
||||
transition={{ duration: 0.3, ease: cubicBezier(0.4, 0, 0.2, 1) }}
|
||||
>
|
||||
<div
|
||||
className="overlay"
|
||||
onClick={() => {
|
||||
if (window.innerWidth < 996) {
|
||||
clearDropdown(true);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (window.innerWidth > 996) {
|
||||
clearDropdown(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<AnimatedHeight
|
||||
animate={{
|
||||
transition: { duration: 0.5, ease: cubicBezier(0.4, 0, 0.2, 1) },
|
||||
}}
|
||||
className="overflow-clip relative"
|
||||
exit={{
|
||||
height: 0,
|
||||
transition: { duration: 0.3, ease: cubicBezier(0.4, 0, 0.2, 1) },
|
||||
}}
|
||||
initial={{ height: 0 }}
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
<motion.div
|
||||
className="bg-background-base hide-scrollbar relative overflow-x-clip overflow-y-auto"
|
||||
key={dropdownKey}
|
||||
style={{
|
||||
maxHeight: `calc(100vh - ${headerTop.current + headerHeight.current + 1}px)`,
|
||||
}}
|
||||
onMouseEnter={resetDropdownTimeout}
|
||||
onMouseLeave={() => {
|
||||
if (window.innerWidth < 996) return;
|
||||
clearDropdown();
|
||||
}}
|
||||
>
|
||||
<div className="cmw-[1112px] absolute h-full pointer-events-none top-0 border-x border-border-faint">
|
||||
<Connector className="absolute -left-[11.5px] -top-11" />
|
||||
<Connector className="absolute -right-[11.5px] -top-11" />
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0, pointerEvents: "none" }}
|
||||
initial={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
ease: cubicBezier(0.4, 0, 0.2, 1),
|
||||
}}
|
||||
>
|
||||
{dropdownContent}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</AnimatedHeight>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import Button from "@/components/ui/shadcn/button";
|
||||
import GithubIcon from "./_svg/GithubIcon";
|
||||
|
||||
export default function HeaderGithubClient() {
|
||||
return (
|
||||
<a
|
||||
className="contents"
|
||||
href="https://github.com/firecrawl/firecrawl"
|
||||
target="_blank"
|
||||
>
|
||||
<Button variant="tertiary">
|
||||
<GithubIcon />
|
||||
21.5k
|
||||
</Button>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export default function GithubIcon() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M9.97616 2C5.56555 2 2 5.59184 2 10.0354C2 13.5874 4.28457 16.5941 7.45388 17.6583C7.85012 17.7383 7.99527 17.4854 7.99527 17.2727C7.99527 17.0864 7.9822 16.4478 7.9822 15.7825C5.76343 16.2616 5.30139 14.8247 5.30139 14.8247C4.94482 13.8934 4.41649 13.654 4.41649 13.654C3.69029 13.1618 4.46939 13.1618 4.46939 13.1618C5.27494 13.215 5.69763 13.9866 5.69763 13.9866C6.41061 15.2104 7.55951 14.8647 8.02171 14.6518C8.08767 14.1329 8.2991 13.7737 8.52359 13.5742C6.75396 13.3879 4.89208 12.6962 4.89208 9.60963C4.89208 8.73159 5.20882 8.01322 5.71069 7.45453C5.63151 7.25502 5.35412 6.43004 5.79004 5.32588C5.79004 5.32588 6.46351 5.11298 7.98204 6.15069C8.63218 5.9748 9.30265 5.88532 9.97616 5.88457C10.6496 5.88457 11.3362 5.9778 11.9701 6.15069C13.4888 5.11298 14.1623 5.32588 14.1623 5.32588C14.5982 6.43004 14.3207 7.25502 14.2415 7.45453C14.7566 8.01322 15.0602 8.73159 15.0602 9.60963C15.0602 12.6962 13.1984 13.3745 11.4155 13.5742C11.7061 13.8269 11.9569 14.3058 11.9569 15.0642C11.9569 16.1417 11.9438 17.0065 11.9438 17.2725C11.9438 17.4854 12.0891 17.7383 12.4852 17.6584C15.6545 16.594 17.9391 13.5874 17.9391 10.0354C17.9522 5.59184 14.3736 2 9.97616 2Z"
|
||||
fill="#262626"
|
||||
fillOpacity="0.48"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
interface HeaderContextType {
|
||||
dropdownContent: React.ReactNode;
|
||||
setDropdownContent: (content: React.ReactNode) => void;
|
||||
clearDropdown: (force?: boolean) => void;
|
||||
resetDropdownTimeout: () => void;
|
||||
dropdownKey: number;
|
||||
headerHeight: React.RefObject<number>;
|
||||
headerTop: React.RefObject<number>;
|
||||
}
|
||||
|
||||
const HeaderContext = createContext<HeaderContextType | undefined>(undefined);
|
||||
|
||||
export const HeaderProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [dropdownContent, setDropdownContent] = useState<React.ReactNode>(null);
|
||||
const [dropdownKey, setDropdownKey] = useState(0);
|
||||
const headerHeight = useRef(0);
|
||||
const headerTop = useRef(0);
|
||||
const pathname = usePathname();
|
||||
const timeout = useRef<number | null>(null);
|
||||
|
||||
const clearDropdown = (force?: boolean) => {
|
||||
if (force) {
|
||||
setDropdownContent(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current);
|
||||
}
|
||||
|
||||
timeout.current = window.setTimeout(() => {
|
||||
setDropdownContent(null);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const resetDropdownTimeout = () => {
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const header = document.querySelector(".header") as HTMLElement;
|
||||
|
||||
if (header) {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
headerHeight.current = entry.contentRect.height;
|
||||
}
|
||||
});
|
||||
|
||||
resizeObserver.observe(header);
|
||||
headerHeight.current = header.clientHeight;
|
||||
headerTop.current = header.getBoundingClientRect().top;
|
||||
|
||||
const onScroll = () => {
|
||||
headerTop.current = header.getBoundingClientRect().top;
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<HeaderContext.Provider
|
||||
value={{
|
||||
dropdownContent,
|
||||
setDropdownContent: (content) => {
|
||||
resetDropdownTimeout();
|
||||
|
||||
if (content === dropdownContent) return;
|
||||
setDropdownKey((prev) => prev + 1);
|
||||
setDropdownContent(content);
|
||||
},
|
||||
clearDropdown,
|
||||
resetDropdownTimeout,
|
||||
dropdownKey,
|
||||
headerHeight,
|
||||
headerTop,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</HeaderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useHeaderContext = () => {
|
||||
const context = useContext(HeaderContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useHeaderContext must be used within a HeaderProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export const useHeaderHeight = () => {
|
||||
const [headerHeight, setHeaderHeight] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const header = document.querySelector(".header");
|
||||
|
||||
if (header) {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
setHeaderHeight(entry.contentRect.height);
|
||||
}
|
||||
});
|
||||
|
||||
resizeObserver.observe(header);
|
||||
setHeaderHeight(header.clientHeight);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { headerHeight };
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import { JSX } from "react";
|
||||
|
||||
import { useHeaderContext } from "@/components/shared/header/HeaderContext";
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
import ChevronDown from "./_svg/ChevronDown";
|
||||
|
||||
export default function HeaderNavItem({
|
||||
label,
|
||||
href,
|
||||
dropdown,
|
||||
}: {
|
||||
label: string;
|
||||
href: string;
|
||||
dropdown?: JSX.Element;
|
||||
}) {
|
||||
const { dropdownContent, setDropdownContent, clearDropdown } =
|
||||
useHeaderContext();
|
||||
|
||||
const active = dropdownContent === dropdown;
|
||||
|
||||
return (
|
||||
<a
|
||||
className="p-6 relative flex h-32 group rounded-8 active:scale-[0.98] transition-all duration-[50ms] active:duration-[100ms]"
|
||||
href={href}
|
||||
onMouseEnter={() => {
|
||||
if (dropdown) {
|
||||
setDropdownContent(dropdown);
|
||||
} else {
|
||||
clearDropdown(true);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (!dropdown) return;
|
||||
|
||||
clearDropdown();
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"overlay pointer-events-none group-hover:bg-black-alpha-4 transition-all scale-95 group-active:duration-[100ms] duration-[150ms] group-hover:scale-100 group-active:bg-black-alpha-7",
|
||||
active && "!scale-100 !bg-black-alpha-4",
|
||||
)}
|
||||
/>
|
||||
|
||||
<span className="px-4 text-label-medium text-accent-black">{label}</span>
|
||||
|
||||
{dropdown && <ChevronDown />}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default function ChevronDown() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 18 20"
|
||||
width="18"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 9.5L9 12.5L12 9.5"
|
||||
stroke="#262626"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeOpacity="0.56"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
import EndpointsCrawl from "@/components/app/(home)/sections/endpoints/EndpointsCrawl/EndpointsCrawl";
|
||||
import EndpointsScrape from "@/components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape";
|
||||
import EndpointsSearch from "@/components/app/(home)/sections/endpoints/EndpointsSearch/EndpointsSearch";
|
||||
import EndpointsExtract from "@/components/app/(home)/sections/endpoints/Extract/Extract";
|
||||
import EndpointsMcp from "@/components/app/(home)/sections/endpoints/Mcp/Mcp";
|
||||
import { RenderEndpointIcon } from "@/components/shared/header/Nav/RenderEndpointIcon";
|
||||
import HeaderDropdownContent from "@/components/shared/header/Dropdown/Content/Content";
|
||||
import HeaderDropdownGithub from "@/components/shared/header/Dropdown/Github/Github";
|
||||
import HeaderDropdownStories from "@/components/shared/header/Dropdown/Stories/Stories";
|
||||
|
||||
import Affiliate from "./_svg/Affiliate";
|
||||
import Api from "./_svg/Api";
|
||||
import ArrowRight from "./_svg/ArrowRight";
|
||||
import Careers from "./_svg/Careers";
|
||||
import Changelog from "./_svg/Changelog";
|
||||
import Chats from "./_svg/Chats";
|
||||
import Lead from "./_svg/Lead";
|
||||
import Platforms from "./_svg/Platforms";
|
||||
import Research from "./_svg/Research";
|
||||
import Student from "./_svg/Student";
|
||||
import Templates from "./_svg/Templates";
|
||||
import HeaderNavItem from "./Item/Item";
|
||||
import MCPIcon from "./_svg/MCP";
|
||||
import Image from "@/components/shared/image/Image";
|
||||
import GithubFlame from "@/components/shared/header/Dropdown/Github/Flame/Flame";
|
||||
import EndpointsMap from "@/components/app/(home)/sections/endpoints/EndpointsMap/EndpointsMap";
|
||||
|
||||
export default function HeaderNav() {
|
||||
return (
|
||||
<div className="flex gap-8 relative lg-max:hidden select-none">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<HeaderNavItem key={item.label} {...item} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const NAV_ITEMS = [
|
||||
{
|
||||
label: "Products",
|
||||
href: "#",
|
||||
dropdown: (
|
||||
<HeaderDropdownContent
|
||||
navigationItems={[
|
||||
{
|
||||
label: "Endpoints",
|
||||
items: [
|
||||
{
|
||||
icon: (
|
||||
<RenderEndpointIcon
|
||||
icon={EndpointsScrape}
|
||||
alwaysHeat
|
||||
triggerOnHover
|
||||
/>
|
||||
),
|
||||
label: "Scrape",
|
||||
description: "Turn any url into clean data",
|
||||
href: "https://docs.firecrawl.dev/features/scrape",
|
||||
iconClassName: "-mt-1",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<RenderEndpointIcon
|
||||
icon={EndpointsCrawl}
|
||||
alwaysHeat
|
||||
triggerOnHover
|
||||
/>
|
||||
),
|
||||
label: "Crawl",
|
||||
description: "Crawl entire websites",
|
||||
href: "https://docs.firecrawl.dev/features/crawl",
|
||||
iconClassName: "-mt-1",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<RenderEndpointIcon
|
||||
icon={EndpointsSearch}
|
||||
alwaysHeat
|
||||
triggerOnHover
|
||||
/>
|
||||
),
|
||||
label: "Search",
|
||||
description: "Search and get page content",
|
||||
href: "https://docs.firecrawl.dev/features/search",
|
||||
iconClassName: "-mt-1",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<RenderEndpointIcon
|
||||
icon={EndpointsMap}
|
||||
alwaysHeat
|
||||
triggerOnHover
|
||||
/>
|
||||
),
|
||||
label: "Map",
|
||||
description: "Get all links from a website",
|
||||
href: "https://docs.firecrawl.dev/features/map",
|
||||
iconClassName: "-mt-1",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<RenderEndpointIcon
|
||||
icon={EndpointsMcp}
|
||||
alwaysHeat
|
||||
triggerOnHover
|
||||
/>
|
||||
),
|
||||
label: "MCP",
|
||||
description: "Connect Firecrawl to agents",
|
||||
href: "https://docs.firecrawl.dev/features/mcp",
|
||||
iconClassName: "-mt-1",
|
||||
},
|
||||
|
||||
// Extract section in the same column, highlighted as a separate product
|
||||
// {
|
||||
// icon: <RenderEndpointIcon icon={EndpointsExtract} alwaysHeat triggerOnHover />,
|
||||
// label: 'Extract',
|
||||
// description: 'Get structured data from single pages or entire websites with AI.',
|
||||
// href: '/extract',
|
||||
// big: true,
|
||||
// sectionLabel: 'Extract Product',
|
||||
// iconClassName: 'mt-4',
|
||||
// ctas: [
|
||||
// { label: 'View Extract', href: '/extract' },
|
||||
// { label: 'Try it now', href: '/playground?mode=extract' }
|
||||
// ]
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Use Cases",
|
||||
items: [
|
||||
{
|
||||
icon: <Chats />,
|
||||
label: "AI Platforms",
|
||||
description: "Let customers build AI apps",
|
||||
href: "https://docs.firecrawl.dev/use-cases/ai-platforms",
|
||||
target: "_blank",
|
||||
},
|
||||
{
|
||||
icon: <Lead />,
|
||||
label: "Lead Enrichment",
|
||||
description: "Enhance sales data",
|
||||
href: "https://docs.firecrawl.dev/use-cases/lead-enrichment",
|
||||
target: "_blank",
|
||||
},
|
||||
{
|
||||
icon: <Platforms />,
|
||||
label: "SEO Platforms",
|
||||
description: "Power SEO/GEO tools",
|
||||
href: "https://docs.firecrawl.dev/use-cases/seo-platforms",
|
||||
target: "_blank",
|
||||
},
|
||||
{
|
||||
icon: <Research />,
|
||||
label: "Deep Research",
|
||||
description: "Build research agents",
|
||||
href: "https://docs.firecrawl.dev/use-cases/deep-research",
|
||||
target: "_blank",
|
||||
},
|
||||
{
|
||||
icon: <ArrowRight />,
|
||||
label: "View more",
|
||||
description: "Explore all use cases",
|
||||
href: "https://docs.firecrawl.dev/use-cases/overview",
|
||||
target: "_blank",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
sideContent={<HeaderDropdownStories />}
|
||||
sideItem={{
|
||||
icon: <ArrowRight />,
|
||||
label: "Customer stories",
|
||||
description: "Browse Firecrawl success stories",
|
||||
href: "/blog/category/customer-stories",
|
||||
}}
|
||||
sideLabel="Customer Stories"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "Playground",
|
||||
href: "/playground",
|
||||
},
|
||||
{
|
||||
label: "Docs",
|
||||
href: "https://docs.firecrawl.dev",
|
||||
},
|
||||
{
|
||||
label: "Pricing",
|
||||
href: "/pricing",
|
||||
},
|
||||
{
|
||||
label: "Blog",
|
||||
href: "/blog",
|
||||
},
|
||||
{
|
||||
label: "Extract",
|
||||
href: "#",
|
||||
dropdown: (
|
||||
<HeaderDropdownContent
|
||||
navigationItems={[
|
||||
{
|
||||
label: "Extract API",
|
||||
items: [
|
||||
// { icon: <Templates />, label: 'Templates', description: 'Jumpstart your web scraping', href: '/templates' },
|
||||
{
|
||||
icon: (
|
||||
<RenderEndpointIcon
|
||||
icon={EndpointsExtract}
|
||||
alwaysHeat
|
||||
triggerOnHover
|
||||
/>
|
||||
),
|
||||
label: "Extract",
|
||||
description: "Get structured data from entire websites",
|
||||
href: "/extract",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<div className="text-heat-100">
|
||||
<Platforms />
|
||||
</div>
|
||||
),
|
||||
label: "Playground",
|
||||
description: "Try it out in the /extract playground",
|
||||
href: "/app/extract-playground",
|
||||
},
|
||||
// { icon: <ArrowRight/>, label: 'Docs', description: 'Read the docs.', href: 'https://docs.firecrawl.dev/features/extract', target: '_blank' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
sideContent={
|
||||
<div className="py-24 px-44 border-b border-border-faint relative overflow-clip">
|
||||
{/* <div className="size-40 relative mb-17"> */}
|
||||
{/* */}
|
||||
{/* </div> */}
|
||||
|
||||
<div className="text-label-large">
|
||||
Get web data with a prompt. <br />
|
||||
Collect structured data from any number of URLs or entire domains.
|
||||
</div>
|
||||
|
||||
<GithubFlame />
|
||||
</div>
|
||||
}
|
||||
sideItem={{
|
||||
icon: <ArrowRight />,
|
||||
label: "See Docs",
|
||||
description: "Read the docs.",
|
||||
href: "https://docs.firecrawl.dev/features/extract",
|
||||
}}
|
||||
sideLabel="/extract"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "Resources",
|
||||
href: "#",
|
||||
dropdown: (
|
||||
<HeaderDropdownContent
|
||||
navigationItems={[
|
||||
{
|
||||
label: "Resources",
|
||||
items: [
|
||||
// { icon: <Templates />, label: 'Templates', description: 'Jumpstart your web scraping', href: '/templates' },
|
||||
{
|
||||
icon: <Changelog />,
|
||||
label: "Changelog",
|
||||
description: "Latest APl updates for Firecrawl",
|
||||
href: "/changelog",
|
||||
},
|
||||
{
|
||||
icon: <Api />,
|
||||
label: "API Status",
|
||||
description: "See maintenance, uptime and more",
|
||||
href: "https://firecrawl.betteruptime.dev/",
|
||||
target: "_blank",
|
||||
},
|
||||
{
|
||||
icon: <Careers />,
|
||||
label: "Careers",
|
||||
description: "Join our team, we're hiring!",
|
||||
href: "/careers",
|
||||
},
|
||||
{
|
||||
icon: <Affiliate />,
|
||||
label: "Creator & OSS Program",
|
||||
description: "Earn rewards for referring customers",
|
||||
href: "/creator-oss-program",
|
||||
},
|
||||
{
|
||||
icon: <Student />,
|
||||
label: "Student Program",
|
||||
description: "Get free credits for your projects",
|
||||
href: "/student-program",
|
||||
},
|
||||
{
|
||||
icon: <MCPIcon />,
|
||||
label: "MCP",
|
||||
description: "Connect Firecrawl to agents",
|
||||
href: "https://docs.firecrawl.dev/features/mcp",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
sideContent={<HeaderDropdownGithub />}
|
||||
sideItem={{
|
||||
icon: <ArrowRight />,
|
||||
label: "See Github",
|
||||
description: "View the repository",
|
||||
href: "https://github.com/firecrawl/firecrawl",
|
||||
}}
|
||||
sideLabel="Open Source"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import EndpointsScrape from "@/components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape";
|
||||
import { ComponentProps } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
export const RenderEndpointIcon = ({
|
||||
icon: Icon,
|
||||
...props
|
||||
}: { icon: typeof EndpointsScrape } & ComponentProps<
|
||||
typeof EndpointsScrape
|
||||
>) => {
|
||||
const isMobile = useMediaQuery("(max-width: 996px)");
|
||||
|
||||
return <Icon {...props} size={isMobile ? 24 : 20} />;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
export default function Affiliate() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.3332 6.6665L6.6665 13.3332M17.7082 9.99984C17.7082 14.257 14.257 17.7082 9.99984 17.7082C5.74265 17.7082 2.2915 14.257 2.2915 9.99984C2.2915 5.74265 5.74265 2.2915 9.99984 2.2915C14.257 2.2915 17.7082 5.74265 17.7082 9.99984Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M7.5 6.875C7.84517 6.875 8.125 7.15483 8.125 7.5C8.125 7.84517 7.84517 8.125 7.5 8.125C7.15483 8.125 6.875 7.84517 6.875 7.5C6.875 7.15483 7.15483 6.875 7.5 6.875Z"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M12.5 11.875C12.8452 11.875 13.125 12.1548 13.125 12.5C13.125 12.8452 12.8452 13.125 12.5 13.125C12.1548 13.125 11.875 12.8452 11.875 12.5C11.875 12.1548 12.1548 11.875 12.5 11.875Z"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export default function Api() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.125 4.79167C3.125 3.87119 3.87119 3.125 4.79167 3.125H15.2083C16.1288 3.125 16.875 3.87119 16.875 4.79167V15.2083C16.875 16.1288 16.1288 16.875 15.2083 16.875H4.79167C3.87119 16.875 3.125 16.1288 3.125 15.2083V4.79167Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M3.125 10.0002H6.45833L8.33333 13.5418L11.6667 6.4585L13.5417 10.0002H16.875"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default function ArrowRight() {
|
||||
return (
|
||||
<svg
|
||||
className="text-black-alpha-48 group-hover:text-heat-100 transition-all"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.6667 4.7915L16.875 9.99982M16.875 9.99982L11.6667 15.2082M16.875 9.99982H3.125"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export default function Careers() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.4998 10.625H9.99984M9.99984 10.625H2.49984M9.99984 10.625L10.0017 12.7083M6.66834 6.45833V4.375C6.66834 3.68464 7.22798 3.125 7.91834 3.125H12.085C12.7753 3.125 13.335 3.68464 13.335 4.375V6.45833M16.0415 16.875H3.95817C3.0377 16.875 2.2915 16.1288 2.2915 15.2083V8.125C2.2915 7.20452 3.0377 6.45833 3.95817 6.45833H16.0415C16.962 6.45833 17.7082 7.20452 17.7082 8.125V15.2083C17.7082 16.1288 16.962 16.875 16.0415 16.875Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export default function Changelog() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.2915 2.7085H5.20817C4.2877 2.7085 3.5415 3.45469 3.5415 4.37516V15.6252C3.5415 16.5457 4.2877 17.2918 5.20817 17.2918H7.2915M7.2915 2.7085H14.7915C15.712 2.7085 16.4582 3.45469 16.4582 4.37516V15.6252C16.4582 16.5457 15.712 17.2918 14.7915 17.2918H7.2915M7.2915 2.7085V17.2918M10.6248 6.4585H13.1248M10.6248 9.79183H13.1248"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export default function Ai() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M1.45825 7.50016C5.65385 7.50016 7.49992 5.6541 7.49992 1.4585C7.49992 5.6541 9.34598 7.50016 13.5416 7.50016C9.34598 7.50016 7.49992 9.34623 7.49992 13.5418C7.49992 9.34623 5.65385 7.50016 1.45825 7.50016Z"
|
||||
fillRule="evenodd"
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M10.6249 14.5835C13.3738 14.5835 14.5833 13.374 14.5833 10.6252C14.5833 13.374 15.7927 14.5835 18.5416 14.5835C15.7927 14.5835 14.5833 15.793 14.5833 18.5418C14.5833 15.793 13.3738 14.5835 10.6249 14.5835Z"
|
||||
fillRule="evenodd"
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function Lead() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.8333 16.0417H16.25C17.1705 16.0417 17.9167 15.2955 17.9167 14.375V7.29167C17.9167 6.37119 17.1705 5.625 16.25 5.625H11.1003C10.543 5.625 10.0227 5.3465 9.71355 4.88283L9.03644 3.86717C8.72733 3.4035 8.20695 3.125 7.64969 3.125H4.16666C3.24619 3.125 2.5 3.87119 2.5 4.79167V6.875M6.45833 10.4167C6.45833 11.2221 5.80541 11.875 5 11.875C4.19458 11.875 3.54166 11.2221 3.54166 10.4167C3.54166 9.61125 4.19458 8.95833 5 8.95833C5.80541 8.95833 6.45833 9.61125 6.45833 10.4167ZM1.93082 15.8928C2.5458 14.7359 3.69002 13.9583 5 13.9583C6.30997 13.9583 7.45419 14.7359 8.06918 15.8928C8.32136 16.3672 7.92297 16.875 7.38572 16.875H2.61427C2.07702 16.875 1.67864 16.3672 1.93082 15.8928Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export default function MCPIcon() {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>ModelContextProtocol</title>
|
||||
<path d="M15.688 2.343a2.588 2.588 0 00-3.61 0l-9.626 9.44a.863.863 0 01-1.203 0 .823.823 0 010-1.18l9.626-9.44a4.313 4.313 0 016.016 0 4.116 4.116 0 011.204 3.54 4.3 4.3 0 013.609 1.18l.05.05a4.115 4.115 0 010 5.9l-8.706 8.537a.274.274 0 000 .393l1.788 1.754a.823.823 0 010 1.18.863.863 0 01-1.203 0l-1.788-1.753a1.92 1.92 0 010-2.754l8.706-8.538a2.47 2.47 0 000-3.54l-.05-.049a2.588 2.588 0 00-3.607-.003l-7.172 7.034-.002.002-.098.097a.863.863 0 01-1.204 0 .823.823 0 010-1.18l7.273-7.133a2.47 2.47 0 00-.003-3.537z"></path>
|
||||
<path d="M14.485 4.703a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a4.115 4.115 0 000 5.9 4.314 4.314 0 006.016 0l7.12-6.982a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a2.588 2.588 0 01-3.61 0 2.47 2.47 0 010-3.54l7.12-6.982z"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
export default function Platforms() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.41667 7.49984C5.18655 7.49984 5 7.31329 5 7.08317C5 6.85305 5.18655 6.6665 5.41667 6.6665C5.64679 6.6665 5.83333 6.85305 5.83333 7.08317C5.83333 7.31329 5.64679 7.49984 5.41667 7.49984Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8.33333 7.49984C8.10321 7.49984 7.91667 7.31329 7.91667 7.08317C7.91667 6.85305 8.10321 6.6665 8.33333 6.6665C8.56345 6.6665 8.75 6.85305 8.75 7.08317C8.75 7.31329 8.56345 7.49984 8.33333 7.49984Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.25 7.49984C11.0199 7.49984 10.8333 7.31329 10.8333 7.08317C10.8333 6.85305 11.0199 6.6665 11.25 6.6665C11.4801 6.6665 11.6667 6.85305 11.6667 7.08317C11.6667 7.31329 11.4801 7.49984 11.25 7.49984Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17.7084 10.2085V5.62516C17.7084 4.70469 16.9622 3.9585 16.0417 3.9585H3.95841C3.03794 3.9585 2.29175 4.70469 2.29175 5.62516V15.2085C2.29175 16.129 3.03794 16.8752 3.95841 16.8752H10.2084M18.3334 14.295L12.5001 12.5002L14.295 18.3335L15.6411 15.6412L18.3334 14.295Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
export default function Research() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.8956 8.9541L2.05646 9.98744C1.40773 10.2236 1.07325 10.9409 1.30937 11.5896L1.66563 12.5684C1.90175 13.2172 2.61906 13.5517 3.26778 13.3155L6.10693 12.2822"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M9.40608 6.36865L6.15568 7.55169C5.29072 7.86651 4.84474 8.82292 5.15956 9.68784L5.83647 11.5477C6.15129 12.4127 7.1077 12.8586 7.97266 12.5438L11.223 11.3608"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M11.062 4.82461L15.546 3.19257C15.9785 3.03516 16.4567 3.25815 16.6142 3.69063L18.4667 8.78066C18.6242 9.21316 18.4012 9.69132 17.9687 9.84874L13.4847 11.4807C12.4035 11.8743 11.208 11.3168 10.8144 10.2357L9.81691 7.49485C9.42332 6.41365 9.98082 5.21814 11.062 4.82461Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M8.95825 16.8748V12.2915"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M6.45825 16.875H11.4583"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export default function Student() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.375 7.50417L10 11.8833L0.625 7.50417L10 3.125L19.375 7.50417ZM19.375 7.50417V12.7175M3.95835 9.14633V12.8756C3.95835 13.4922 4.29811 14.0585 4.84186 14.3482L9.21683 16.6793C9.70642 16.9403 10.2936 16.9403 10.7832 16.6793L15.1582 14.3482C15.7019 14.0585 16.0417 13.4922 16.0417 12.8756V9.14633"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export default function Templates() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.95817 16.0417C3.0377 16.0417 2.2915 15.2955 2.2915 14.375V7.29167C2.2915 6.37119 3.0377 5.625 3.95817 5.625H16.0415C16.962 5.625 17.7082 6.37119 17.7082 7.29167V14.375C17.7082 15.2955 16.962 16.0417 16.0415 16.0417H3.95817Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
<path
|
||||
d="M3.9585 3.125H16.0418"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import Button from "@/components/ui/shadcn/button";
|
||||
import { useHeaderContext } from "@/components/shared/header/HeaderContext";
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
export default function HeaderToggle({
|
||||
dropdownContent,
|
||||
}: {
|
||||
dropdownContent: React.ReactNode;
|
||||
}) {
|
||||
const {
|
||||
dropdownContent: headerDropdownContent,
|
||||
clearDropdown,
|
||||
setDropdownContent,
|
||||
} = useHeaderContext();
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="lg:hidden"
|
||||
variant="tertiary"
|
||||
onClick={() => {
|
||||
if (dropdownContent === headerDropdownContent) {
|
||||
clearDropdown(true);
|
||||
} else {
|
||||
setDropdownContent(dropdownContent);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="!size-20"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className={cn("transition-all origin-center", {
|
||||
"rotate-45 -translate-y-4": headerDropdownContent,
|
||||
})}
|
||||
d="M2.28906 13.9609H17.7057"
|
||||
stroke="#262626"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
style={{
|
||||
transformBox: "fill-box",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
className={cn("transition-all origin-center", {
|
||||
"-rotate-45 translate-y-3 translate-x-[2.5px]":
|
||||
headerDropdownContent,
|
||||
})}
|
||||
d="M2.28906 6.03906H17.7057"
|
||||
stroke="#262626"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.25"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
export default function HeaderWrapper({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [shouldShrink, setShouldShrink] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const heroContentHeight =
|
||||
document.getElementById("hero-content")?.clientHeight;
|
||||
const triggerTop = heroContentHeight ? heroContentHeight : 100;
|
||||
|
||||
const onScroll = () => {
|
||||
setShouldShrink(window.scrollY > triggerTop);
|
||||
};
|
||||
|
||||
onScroll();
|
||||
|
||||
window.addEventListener("scroll", onScroll);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"container lg:px-56 px-16 flex justify-between transition-[padding] duration-[200ms] items-center",
|
||||
shouldShrink ? "py-20" : "py-20 lg:py-34",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export default function Logo() {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="15"
|
||||
viewBox="0 0 79 15"
|
||||
width="79"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0.599609 14.4311V0.576888H9.45474V2.59564H2.87778V6.61335H8.30575V8.57272H2.87778V14.4311H0.599609Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M11.9737 2.87272C11.2407 2.87272 10.6663 2.33835 10.6663 1.58626C10.6663 0.83418 11.2407 0.299805 11.9737 0.299805C12.7067 0.299805 13.2812 0.83418 13.2812 1.58626C13.2812 2.33835 12.7067 2.87272 11.9737 2.87272ZM10.8842 14.4311V4.29772H13.0237V14.4311H10.8842Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M20.1527 4.29772H20.6281V6.29668H19.6772C17.7755 6.29668 17.1613 7.78105 17.1613 9.3446V14.4311H15.0219V4.29772H16.9236L17.1613 5.82168C17.6764 4.97064 18.4886 4.29772 20.1527 4.29772Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M26.1788 14.5498C22.9894 14.5498 20.9886 12.4915 20.9886 9.38418C20.9886 6.2571 22.9894 4.17897 25.9807 4.17897C28.9126 4.17897 30.8738 6.03939 30.9333 9.00814C30.9333 9.26543 30.9135 9.54251 30.8738 9.8196H23.2271V9.95814C23.2865 11.68 24.3761 12.8081 26.06 12.8081C27.3674 12.8081 28.3183 12.155 28.6155 11.0269H30.755C30.3984 13.0258 28.6947 14.5498 26.1788 14.5498ZM23.3064 8.25605H28.7145C28.5362 6.75189 27.4863 5.90085 26.0005 5.90085C24.6336 5.90085 23.4648 6.81126 23.3064 8.25605Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M37.2193 14.5498C34.1487 14.5498 32.1875 12.5508 32.1875 9.38418C32.1875 6.2571 34.2081 4.17897 37.2787 4.17897C39.8936 4.17897 41.5181 5.62376 41.9341 7.9196H39.6955C39.4182 6.7321 38.5664 5.9998 37.2391 5.9998C35.5156 5.9998 34.3864 7.38522 34.3864 9.38418C34.3864 11.3633 35.5156 12.729 37.2391 12.729C38.5465 12.729 39.4182 11.9769 39.6757 10.8092H41.9341C41.5379 13.105 39.8144 14.5498 37.2193 14.5498Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M48.6034 4.29772H49.0789V6.29668H48.128C46.2262 6.29668 45.6121 7.78105 45.6121 9.3446V14.4311H43.4726V4.29772H45.3744L45.6121 5.82168C46.1272 4.97064 46.9394 4.29772 48.6034 4.29772Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M54.3679 4.17897C57.0621 4.17897 58.6073 5.46543 58.6073 7.86022V14.4311H56.7451L56.5668 12.9863C55.8735 13.8967 54.9028 14.5498 53.2981 14.5498C51.0794 14.5498 49.5936 13.4613 49.5936 11.5811C49.5936 9.50293 51.0992 8.33522 53.9519 8.33522H56.4876V7.72168C56.4876 6.59355 55.6754 5.90085 54.2688 5.90085C53.001 5.90085 52.1491 6.4946 51.9907 7.38522H49.8908C50.1087 5.40605 51.8124 4.17897 54.3679 4.17897ZM53.6547 12.8873C55.4376 12.8873 56.4678 11.8383 56.4876 10.2748V9.91855H53.833C52.5057 9.91855 51.7728 10.4133 51.7728 11.4425C51.7728 12.2936 52.4859 12.8873 53.6547 12.8873Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M62.7912 14.4311L59.4829 4.29772H61.7413L64.0591 12.0561L66.3768 4.29772H68.3381L70.5568 12.0561L72.9538 4.29772H75.1329L71.7652 14.4311H69.4672L67.3277 7.54355L65.109 14.4311H62.7912Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
<path
|
||||
d="M76.1601 14.4311V0.576888H78.2996V14.4311H76.1601Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user