aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/docs/components
diff options
context:
space:
mode:
Diffstat (limited to 'docs/components')
-rw-r--r--docs/components/Authors.tsx22
-rw-r--r--docs/components/Avatar.tsx37
-rw-r--r--docs/components/Badge.tsx24
-rw-r--r--docs/components/Callout.tsx55
-rw-r--r--docs/components/Container.tsx9
-rw-r--r--docs/components/ExamplesArea.tsx30
-rw-r--r--docs/components/ExtraContent.tsx10
-rw-r--r--docs/components/Feature.tsx88
-rw-r--r--docs/components/Features.tsx46
-rw-r--r--docs/components/Footer.tsx252
-rw-r--r--docs/components/FullTurboCTA.tsx41
-rw-r--r--docs/components/HeaderLogo.tsx36
-rw-r--r--docs/components/Icons.tsx165
-rw-r--r--docs/components/Logo.tsx119
-rw-r--r--docs/components/LogoContext/icons.tsx228
-rw-r--r--docs/components/LogoContext/index.tsx169
-rw-r--r--docs/components/LogoContext/items.tsx91
-rw-r--r--docs/components/LogoContext/types.ts25
-rw-r--r--docs/components/MonorepoHandbook.tsx142
-rw-r--r--docs/components/Navigation.tsx47
-rw-r--r--docs/components/QuickStart.tsx89
-rw-r--r--docs/components/RemoteCacheCounter.tsx47
-rw-r--r--docs/components/SiteSwitcher.tsx66
-rw-r--r--docs/components/Social.tsx32
-rw-r--r--docs/components/Tabs.tsx37
-rw-r--r--docs/components/TurbopackFeatures.tsx106
-rw-r--r--docs/components/TurbopackQuickstart.tsx28
-rw-r--r--docs/components/Tweet.tsx46
-rw-r--r--docs/components/blog/Date.tsx21
-rw-r--r--docs/components/clients/Clients.tsx61
-rw-r--r--docs/components/clients/Filters.tsx42
-rw-r--r--docs/components/clients/Logo.tsx67
-rw-r--r--docs/components/clients/Marquee.tsx14
-rw-r--r--docs/components/clients/users.ts556
-rw-r--r--docs/components/counters.module.css6
-rw-r--r--docs/components/counters.tsx24
-rw-r--r--docs/components/header-logo.module.css20
-rw-r--r--docs/components/image/ImageFigure.tsx40
-rw-r--r--docs/components/image/ThemedImage.tsx45
-rw-r--r--docs/components/image/ThemedImageFigure.tsx47
-rw-r--r--docs/components/logos/PackLogo.tsx16
-rw-r--r--docs/components/logos/RepoLogo.tsx16
-rw-r--r--docs/components/logos/Turbo.tsx62
-rw-r--r--docs/components/logos/TurboAnimated.tsx157
-rw-r--r--docs/components/logos/Vercel.tsx11
-rw-r--r--docs/components/logos/og/PackLogo.tsx72
-rw-r--r--docs/components/logos/og/RepoLogo.tsx70
-rw-r--r--docs/components/logos/og/TurboLogo.tsx54
-rw-r--r--docs/components/logos/og/VercelLogo.tsx16
-rw-r--r--docs/components/output-mode-table.mdx7
-rw-r--r--docs/components/pages/confirm.tsx36
-rw-r--r--docs/components/pages/home-shared/CTAButton.tsx43
-rw-r--r--docs/components/pages/home-shared/FadeIn.tsx50
-rw-r--r--docs/components/pages/home-shared/FeatureBox.tsx42
-rw-r--r--docs/components/pages/home-shared/FeaturesBento.tsx38
-rw-r--r--docs/components/pages/home-shared/GlobalStyles.tsx20
-rw-r--r--docs/components/pages/home-shared/Gradient.tsx47
-rw-r--r--docs/components/pages/home-shared/GradientSectionBorder.tsx37
-rw-r--r--docs/components/pages/home-shared/Headings.tsx56
-rw-r--r--docs/components/pages/home-shared/gradients.module.css231
-rw-r--r--docs/components/pages/landing/TurboHeroBackground.tsx33
-rw-r--r--docs/components/pages/landing/Turbopack.tsx27
-rw-r--r--docs/components/pages/landing/Turborepo.tsx27
-rw-r--r--docs/components/pages/landing/index.module.css184
-rw-r--r--docs/components/pages/landing/index.tsx199
-rw-r--r--docs/components/pages/landing/turbohero-background.module.css108
-rw-r--r--docs/components/pages/pack-home/DocsBenchmarkStat.tsx53
-rw-r--r--docs/components/pages/pack-home/DocsBenchmarksGraph.tsx31
-rw-r--r--docs/components/pages/pack-home/PackBenchmarkTabs.tsx149
-rw-r--r--docs/components/pages/pack-home/PackBenchmarks.tsx97
-rw-r--r--docs/components/pages/pack-home/PackBenchmarksGraph.tsx333
-rw-r--r--docs/components/pages/pack-home/PackBenchmarksPicker.tsx20
-rw-r--r--docs/components/pages/pack-home/PackDropdown.tsx117
-rw-r--r--docs/components/pages/pack-home/PackFeatures.tsx12
-rw-r--r--docs/components/pages/pack-home/PackHero.tsx115
-rw-r--r--docs/components/pages/pack-home/PackLetter.tsx114
-rw-r--r--docs/components/pages/pack-home/benchmark-data/README.md7
-rw-r--r--docs/components/pages/pack-home/benchmark-data/data.json54
-rw-r--r--docs/components/pages/pack-home/index.tsx24
-rw-r--r--docs/components/pages/repo-home/RepoFeatures.tsx15
-rw-r--r--docs/components/pages/repo-home/RepoHero.tsx114
-rw-r--r--docs/components/pages/repo-home/RepoLetter.tsx118
-rw-r--r--docs/components/pages/repo-home/index.tsx22
-rw-r--r--docs/components/pages/showcase.tsx47
-rw-r--r--docs/components/useIsomorphicLayoutEffect.tsx7
-rw-r--r--docs/components/usePrefersReducedMotion.tsx44
86 files changed, 6152 insertions, 30 deletions
diff --git a/docs/components/Authors.tsx b/docs/components/Authors.tsx
new file mode 100644
index 0000000..634bd90
--- /dev/null
+++ b/docs/components/Authors.tsx
@@ -0,0 +1,22 @@
+import { Avatar } from "./Avatar";
+import cn from "classnames";
+import TURBO_TEAM from "../content/team";
+import type { Author } from "../content/team";
+
+export function Authors({ authors }: { authors: Array<Author> }) {
+ const validAuthors = authors.filter((author) => TURBO_TEAM[author]);
+ return (
+ <div className="w-full border-b border-gray-400 authors border-opacity-20">
+ <div
+ className={cn(
+ "flex flex-wrap justify-center py-8 mx-auto gap-7",
+ authors.length > 4 && "max-w-3xl"
+ )}
+ >
+ {validAuthors.map((username) => (
+ <Avatar key={username} {...TURBO_TEAM[username]} />
+ ))}
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/Avatar.tsx b/docs/components/Avatar.tsx
new file mode 100644
index 0000000..8826daf
--- /dev/null
+++ b/docs/components/Avatar.tsx
@@ -0,0 +1,37 @@
+import Image from "next/image";
+import type { AuthorDetails } from "../content/team";
+
+export const Avatar = ({ name, picture, twitterUsername }: AuthorDetails) => (
+ <div className="flex items-center flex-shrink-0 md:justify-start">
+ <div className="w-[32px] h-[32px]">
+ <Image
+ src={picture}
+ height={32}
+ width={32}
+ title={name}
+ className="w-full rounded-full"
+ alt={name}
+ priority
+ />
+ </div>
+ <dl className="ml-2 text-sm font-medium leading-4 text-left whitespace-no-wrap">
+ <dt className="sr-only">Name</dt>
+ <dd className="text-gray-900 dark:text-white">{name}</dd>
+ {twitterUsername && (
+ <>
+ <dt className="sr-only">Twitter</dt>
+ <dd>
+ <a
+ href={`https://twitter.com/${twitterUsername}`}
+ className="text-xs text-blue-500 no-underline betterhover:hover:text-blue-600 betterhover:hover:underline"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {`@${twitterUsername}`}
+ </a>
+ </dd>
+ </>
+ )}
+ </dl>
+ </div>
+);
diff --git a/docs/components/Badge.tsx b/docs/components/Badge.tsx
new file mode 100644
index 0000000..45e5a0c
--- /dev/null
+++ b/docs/components/Badge.tsx
@@ -0,0 +1,24 @@
+import cn from "classnames";
+
+import type { ReactNode } from "react";
+
+export type BadgeProps = React.ComponentProps<"span"> & {
+ children: ReactNode;
+ className?: string;
+};
+
+export default function Badge(props: BadgeProps) {
+ const { children, className, ...rest } = props;
+
+ return (
+ <span
+ className={cn(
+ "dark:text-black text-white inline-flex items-center justify-center shrink-0 box-border rounded-lg capitalize whitespace-nowrap font-bold tabular-nums h-5 px-2 text-xs bg-gradient-to-r from-[#d74a41] to-[#407aeb] align-middle",
+ className
+ )}
+ {...rest}
+ >
+ {children}
+ </span>
+ );
+}
diff --git a/docs/components/Callout.tsx b/docs/components/Callout.tsx
new file mode 100644
index 0000000..d284bae
--- /dev/null
+++ b/docs/components/Callout.tsx
@@ -0,0 +1,55 @@
+import React, { ReactElement, ReactNode } from "react";
+import {
+ LightBulbIcon,
+ ExclamationIcon,
+ ExclamationCircleIcon,
+ InformationCircleIcon,
+} from "@heroicons/react/solid";
+
+const THEMES = {
+ info: {
+ classes:
+ "bg-blue-100 text-blue-800 dark:text-blue-300 dark:bg-blue-200 dark:bg-opacity-10",
+ icon: <InformationCircleIcon className="w-5 h-5 mt-1" />,
+ },
+ idea: {
+ classes:
+ "bg-gray-100 text-gray-800 dark:text-gray-300 dark:bg-gray-200 dark:bg-opacity-10",
+ icon: <LightBulbIcon className="w-5 h-5 mt-1" />,
+ },
+ error: {
+ classes:
+ "bg-red-200 text-red-900 dark:text-red-200 dark:bg-red-600 dark:bg-opacity-30",
+ icon: <ExclamationCircleIcon className="w-5 h-5 mt-1" />,
+ },
+ default: {
+ classes:
+ "bg-orange-100 text-orange-800 dark:text-orange-300 dark:bg-orange-200 dark:bg-opacity-10",
+ icon: <ExclamationIcon className="w-5 h-5 mt-1" />,
+ },
+};
+
+export default function Callout({
+ children,
+ type = "default",
+ icon,
+}: {
+ children: ReactNode;
+ type: keyof typeof THEMES;
+ icon?: ReactElement;
+}) {
+ return (
+ <div className={`${THEMES[type].classes} flex rounded-lg callout mt-6`}>
+ <div
+ className="py-2 pl-3 pr-2 text-xl select-none"
+ style={{
+ fontFamily:
+ '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
+ }}
+ >
+ {icon || THEMES[type].icon}
+ </div>
+ <div className="py-2 pr-4 overflow-auto">{children}</div>
+ </div>
+ );
+}
diff --git a/docs/components/Container.tsx b/docs/components/Container.tsx
new file mode 100644
index 0000000..a6db4d8
--- /dev/null
+++ b/docs/components/Container.tsx
@@ -0,0 +1,9 @@
+import type { ReactNode } from "react";
+
+type Props = {
+ children?: ReactNode;
+};
+
+export const Container = ({ children }: Props) => {
+ return <div className="mx-auto max-w-7xl sm:px-6 lg:px-8">{children}</div>;
+};
diff --git a/docs/components/ExamplesArea.tsx b/docs/components/ExamplesArea.tsx
new file mode 100644
index 0000000..8b38263
--- /dev/null
+++ b/docs/components/ExamplesArea.tsx
@@ -0,0 +1,30 @@
+import { useSSG } from "nextra/ssg";
+import { DetailedFeatureLink } from "./Feature";
+import { GitHubIcon } from "./Icons";
+
+export const ExamplesArea = ({
+ filter = "featured",
+}: {
+ filter: "featured" | "all";
+}) => {
+ const { examples } = useSSG();
+
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:gap-x-8 lg:gap-y-12">
+ {examples
+ .filter(({ featured }) => (filter === "featured" ? featured : true))
+ .map(({ name, description, slug }) => (
+ <DetailedFeatureLink
+ key={name}
+ feature={{
+ Icon: GitHubIcon,
+ description,
+ name,
+ }}
+ target="_blank"
+ href={`https://github.com/vercel/turbo/tree/main/examples/${slug}`}
+ />
+ ))}
+ </div>
+ );
+};
diff --git a/docs/components/ExtraContent.tsx b/docs/components/ExtraContent.tsx
new file mode 100644
index 0000000..f489e36
--- /dev/null
+++ b/docs/components/ExtraContent.tsx
@@ -0,0 +1,10 @@
+import RemoteCacheCounter from "./RemoteCacheCounter";
+import { useTurboSite } from "./SiteSwitcher";
+
+export default function ExtraContent() {
+ const site = useTurboSite();
+
+ if (site === "repo") {
+ return <RemoteCacheCounter />;
+ }
+}
diff --git a/docs/components/Feature.tsx b/docs/components/Feature.tsx
new file mode 100644
index 0000000..1a8a8b2
--- /dev/null
+++ b/docs/components/Feature.tsx
@@ -0,0 +1,88 @@
+import classNames from "classnames";
+import Link from "next/link";
+import type { Feature } from "../content/legacy-features";
+
+type FeatureProps = {
+ feature: Omit<Feature, "page">;
+ // include feature description
+ detailed?: boolean;
+};
+
+const DetailedFeatureInner = (props: { feature: FeatureProps["feature"] }) => {
+ const { Icon, name, description } = props.feature;
+ return (
+ <>
+ <div className="inline-flex items-center space-x-3">
+ <div className="flex items-center justify-center bg-black rounded-full bg-opacity-5 w-9 h-9 icon-circle">
+ <Icon
+ className={classNames(
+ "h-8 w-8 dark:text-white flex-shrink-0 p-1.5 text-black block dark:stroke-[url(#pink-gradient)]",
+ Icon.requiresFill && "dark:fill-[url(#pink-gradient)]"
+ )}
+ aria-hidden="true"
+ />
+ </div>
+ <h3 className="m-0 text-lg font-semibold leading-6 tracking-tight text-gray-900 dark:text-white">
+ {name}
+ </h3>
+ </div>
+ <div>
+ <p className="mt-2 text-base font-medium leading-7 text-gray-500 dark:text-gray-400">
+ {description}
+ </p>
+ </div>
+ <style jsx global>{`
+ html.dark .icon-circle {
+ background: linear-gradient(
+ 180deg,
+ rgba(50, 134, 241, 0.2) 0%,
+ rgba(195, 58, 195, 0.2) 100%
+ );
+ }
+ `}</style>
+ </>
+ );
+};
+
+const featureWrapperClasses = `relative block overflow-hidden p-10 bg-white shadow-lg rounded-xl dark:bg-opacity-5 no-underline text-black dark:text-white`;
+
+export const DetailedFeatureLink = (props: {
+ href: string;
+ feature: FeatureProps["feature"];
+ target?: string;
+}) => {
+ const { href, feature, ...rest } = props;
+ return (
+ <Link href={href} className={featureWrapperClasses} {...rest}>
+ <DetailedFeatureInner feature={feature}></DetailedFeatureInner>
+ </Link>
+ );
+};
+
+export default function Feature(props: FeatureProps) {
+ const { feature, detailed = false } = props;
+ const { Icon, name } = feature;
+
+ if (detailed) {
+ return (
+ <div className={featureWrapperClasses}>
+ <DetailedFeatureInner feature={feature} />
+ </div>
+ );
+ }
+
+ return (
+ <div className="flex items-center space-x-4">
+ <div>
+ <Icon
+ className="block w-8 h-8 text-black dark:text-white"
+ style={{ height: 24, width: 24 }}
+ aria-hidden="true"
+ />
+ </div>
+ <div>
+ <div className="my-0 font-medium dark:text-white">{name}</div>
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/Features.tsx b/docs/components/Features.tsx
new file mode 100644
index 0000000..c8a6eed
--- /dev/null
+++ b/docs/components/Features.tsx
@@ -0,0 +1,46 @@
+import React from "react";
+import {
+ LEGACY_REPO_DOCS_FEATURES,
+ LEGACY_REPO_HOME_FEATURES,
+} from "../content/legacy-features";
+import Feature from "./Feature";
+
+export function HomeFeatures() {
+ return (
+ <DetailedFeaturesGrid>
+ {LEGACY_REPO_HOME_FEATURES.map((feature) => (
+ <Feature
+ key={feature.name.split(" ").join("-")}
+ feature={feature}
+ detailed
+ />
+ ))}
+ </DetailedFeaturesGrid>
+ );
+}
+
+export function DocsFeatures({ detailed = true }: { detailed?: boolean }) {
+ return (
+ <div className="grid grid-cols-2 gap-6 my-12 sm:grid-cols-3 ">
+ {LEGACY_REPO_DOCS_FEATURES.map((feature) => (
+ <Feature
+ key={feature.name.split(" ").join("-")}
+ feature={feature}
+ detailed={detailed}
+ />
+ ))}
+ </div>
+ );
+}
+
+export function DetailedFeaturesGrid({
+ children,
+}: {
+ children?: React.ReactNode;
+}) {
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:grid-cols-3 lg:gap-x-8 lg:gap-y-12">
+ {children}
+ </div>
+ );
+}
diff --git a/docs/components/Footer.tsx b/docs/components/Footer.tsx
new file mode 100644
index 0000000..e27c848
--- /dev/null
+++ b/docs/components/Footer.tsx
@@ -0,0 +1,252 @@
+import { useRouter } from "next/router";
+import Link from "next/link";
+import { useState, ReactNode, ReactElement } from "react";
+import cn from "classnames";
+import { ThemeSwitch } from "nextra-theme-docs";
+import VercelLogo from "./logos/Vercel";
+import { useTurboSite, TurboSite } from "./SiteSwitcher";
+
+function FooterLink({ href, children }: { href: string; children: ReactNode }) {
+ const classes =
+ "text-sm text-[#666666] dark:text-[#888888] no-underline betterhover:hover:text-gray-700 betterhover:hover:dark:text-white transition";
+ if (href.startsWith("http")) {
+ return (
+ <a href={href} className={classes}>
+ {children}
+ </a>
+ );
+ }
+ return (
+ <Link href={href} className={classes}>
+ {children}
+ </Link>
+ );
+}
+
+function FooterHeader({ children }: { children: ReactNode }) {
+ return <h3 className="text-sm text-black dark:text-white">{children}</h3>;
+}
+
+const navigation = {
+ general: [
+ { name: "Blog", href: "/blog" },
+ { name: "Releases", href: "https://github.com/vercel/turbo/releases" },
+ ],
+ repo: [
+ { name: "Documentation", href: "/repo/docs" },
+ {
+ name: "API Reference",
+ href: "/repo/docs/reference/command-line-reference",
+ },
+ { name: "FAQ", href: "/repo/docs/faq" },
+ ],
+ pack: [
+ { name: "Documentation", href: "/pack/docs" },
+ { name: "Features", href: "/pack/docs/features" },
+ ],
+ support: [
+ {
+ name: "GitHub",
+ href: "https://github.com/vercel/turbo",
+ },
+ {
+ name: "Discord",
+ href: "https://turbo.build/discord",
+ },
+ ],
+ company: (site: TurboSite) => [
+ { name: "Vercel", href: "https://vercel.com" },
+ {
+ name: "Open Source Software",
+ href: "https://vercel.com/oss?utm_source=turbo.build&utm_medium=referral&utm_campaign=footer-ossLink",
+ },
+ {
+ name: "Contact Sales",
+ href: `https://vercel.com/${
+ site === "repo" ? "solutions/turborepo" : "contact/sales"
+ }?utm_source=turbo.build&utm_medium=referral&utm_campaign=footer-enterpriseLink`,
+ },
+ { name: "Twitter", href: "https://twitter.com/vercel" },
+ ],
+ legal: [
+ { name: "Privacy Policy", href: "/privacy" },
+ { name: "Terms of Service", href: "/terms" },
+ ],
+};
+
+export function FooterContent() {
+ const site = useTurboSite();
+ return (
+ <div className="w-full" aria-labelledby="footer-heading">
+ <h2 id="footer-heading" className="sr-only">
+ Footer
+ </h2>
+ <div className="w-full py-8 mx-auto">
+ <div className="xl:grid xl:grid-cols-3 xl:gap-8">
+ <div className="grid grid-cols-1 gap-8 xl:col-span-2">
+ <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 md:gap-8">
+ <div className="mt-12 md:!mt-0">
+ <FooterHeader>Resources</FooterHeader>
+ <ul role="list" className="mt-4 space-y-1.5 list-none ml-0">
+ {navigation.general.map((item) => (
+ <li key={item.name}>
+ <FooterLink href={item.href}>{item.name}</FooterLink>
+ </li>
+ ))}
+ </ul>
+ </div>
+ <div className="mt-12 md:!mt-0">
+ <FooterHeader>Turborepo</FooterHeader>
+ <ul role="list" className="mt-4 space-y-1.5 list-none ml-0">
+ {navigation.repo.map((item) => (
+ <li key={item.name}>
+ <FooterLink href={item.href}>{item.name}</FooterLink>
+ </li>
+ ))}
+ </ul>
+ </div>
+ <div className="mt-12 md:!mt-0">
+ <FooterHeader>Turbopack</FooterHeader>
+ <ul role="list" className="mt-4 space-y-1.5 list-none ml-0">
+ {navigation.pack.map((item) => (
+ <li key={item.name}>
+ <FooterLink href={item.href}>{item.name}</FooterLink>
+ </li>
+ ))}
+ </ul>
+ </div>
+ <div className="mt-12 md:!mt-0">
+ <FooterHeader>Company</FooterHeader>
+ <ul role="list" className="mt-4 space-y-1.5 list-none ml-0">
+ {navigation.company(site).map((item) => (
+ <li key={item.name}>
+ <FooterLink href={item.href}>{item.name}</FooterLink>
+ </li>
+ ))}
+ </ul>
+ </div>
+ <div className="mt-12 md:!mt-0">
+ <FooterHeader>Legal</FooterHeader>
+ <ul role="list" className="mt-4 space-y-1.5 list-none ml-0">
+ {navigation.legal.map((item) => (
+ <li key={item.name}>
+ <FooterLink href={item.href}>{item.name}</FooterLink>
+ </li>
+ ))}
+ </ul>
+ </div>
+ <div className="mt-12 md:!mt-0">
+ <FooterHeader>Support</FooterHeader>
+ <ul role="list" className="mt-4 space-y-1.5 list-none ml-0">
+ {navigation.support.map((item) => (
+ <li key={item.name}>
+ <FooterLink href={item.href}>{item.name}</FooterLink>
+ </li>
+ ))}
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div className="mt-12 xl:!mt-0">
+ <FooterHeader>Subscribe to our newsletter</FooterHeader>
+ <p className="mt-4 text-sm text-gray-600 dark:text-[#888888]">
+ Subscribe to the Turbo newsletter and stay updated on new releases
+ and features, guides, and case studies.
+ </p>
+ <SubmitForm />
+ </div>
+ </div>
+
+ <div className="pt-8 mt-8 sm:flex sm:items-center sm:justify-between">
+ <div>
+ <a
+ className="text-current"
+ target="_blank"
+ rel="noopener noreferrer"
+ title="vercel.com homepage"
+ href="https://vercel.com?utm_source=turbo.build&utm_medium=referral&utm_campaign=footer-logoLink"
+ >
+ <VercelLogo />
+ </a>
+ <p className="mt-4 text-xs text-gray-500 dark:text-[#888888]">
+ &copy; {new Date().getFullYear()} Vercel, Inc. All rights
+ reserved.
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+function SubmitForm() {
+ const [email, setEmail] = useState("");
+ const router = useRouter();
+ return (
+ <form
+ className="mt-4 sm:flex sm:max-w-md"
+ onSubmit={(e) => {
+ fetch("/api/signup", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ email }),
+ })
+ .then((res) => res.json())
+ .then((res) => {
+ return router.push("/confirm");
+ });
+ e.preventDefault();
+ }}
+ >
+ <label htmlFor="email-address" className="sr-only">
+ Email address
+ </label>
+ <input
+ type="email"
+ name="email-address"
+ id="email-address"
+ autoComplete="email"
+ required
+ value={email}
+ onChange={(e) => setEmail(e.target.value)}
+ className="border-[#666666] dark:border-[#888888] w-full min-w-0 px-4 py-2 text-base text-gray-900 placeholder-gray-500 bg-white border rounded-md appearance-none dark:text-white sm:text-sm dark:bg-transparent focus:outline-none focus:ring-2 focus:ring-gray-800 dark:focus:border-white focus:placeholder-gray-400"
+ placeholder="you@example.com"
+ />
+ <div className="mt-3 rounded-md sm:mt-0 sm:ml-3 sm:flex-shrink-0">
+ <button
+ type="submit"
+ className="flex items-center justify-center w-full px-4 py-2 text-base font-medium text-white bg-black border border-transparent rounded-md dark:bg-white dark:text-black sm:text-sm betterhover:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-800 dark:focus:ring-white dark:betterhover:hover:bg-gray-300"
+ >
+ Subscribe
+ </button>
+ </div>
+ </form>
+ );
+}
+
+export function Footer({ menu }: { menu?: boolean }): ReactElement {
+ return (
+ <footer className="bg-[#FAFAFA] pb-[env(safe-area-inset-bottom)] relative dark:bg-[#111111]">
+ <div className="absolute top-0 h-12 w-full -translate-y-full bg-gradient-to-t from-[#FAFAFA] to-transparent dark:from-black pointer-events-none" />
+ <div
+ className={cn(
+ "mx-auto max-w-[90rem] py-2 px-4 flex gap-2",
+ menu ? "flex" : "hidden"
+ )}
+ >
+ <ThemeSwitch />
+ </div>
+ <hr className="dark:border-neutral-800" />
+ <div
+ className={cn(
+ "mx-auto max-w-[90rem] py-12 flex justify-center md:justify-center text-black dark:text-white",
+ "pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)]"
+ )}
+ >
+ <FooterContent />
+ </div>
+ </footer>
+ );
+}
diff --git a/docs/components/FullTurboCTA.tsx b/docs/components/FullTurboCTA.tsx
new file mode 100644
index 0000000..0c3a44b
--- /dev/null
+++ b/docs/components/FullTurboCTA.tsx
@@ -0,0 +1,41 @@
+import { Container } from "./Container";
+import Callout from "./Callout";
+import Link from "next/link";
+
+function FullTurboCTA() {
+ return (
+ <div className="flex flex-col items-start w-full gap-4 p-6 mt-8 bg-white shadow-lg md:items-center md:flex-row rounded-xl dark:bg-opacity-5">
+ <div className="justify-start flex-1">
+ <h3 className="font-semibold leading-6 tracking-tight">
+ Ready to go
+ <span className="m-2 font-mono full-turbo">{">>>"} FULL TURBO</span>
+ at your organization?
+ </h3>
+ <div className="text-base font-medium leading-7 text-gray-500 dark:text-gray-400">
+ Vercel&apos;s Experts can bring your entire team up to speed quickly
+ </div>
+ </div>
+ <div className="flex-none">
+ <Link
+ href="https://vercel.com/contact/sales?utm_source=turbo.build&utm_medium=referral&utm_campaign=turborepo_side_banner"
+ className="justify-center block px-4 py-2 text-black no-underline bg-white rounded-full dark:bg-opacity-5 dark:text-white"
+ >
+ Talk to an Expert
+ </Link>
+ </div>
+ <style jsx global>{`
+ .full-turbo {
+ background-image: linear-gradient(
+ 60deg,
+ rgba(50, 134, 241, 1) 0%,
+ rgba(255, 30, 86, 1) 100%
+ );
+ background-clip: text;
+ color: transparent;
+ }
+ `}</style>
+ </div>
+ );
+}
+
+export default FullTurboCTA;
diff --git a/docs/components/HeaderLogo.tsx b/docs/components/HeaderLogo.tsx
new file mode 100644
index 0000000..a1fc395
--- /dev/null
+++ b/docs/components/HeaderLogo.tsx
@@ -0,0 +1,36 @@
+import SiteSwitcher from "./SiteSwitcher";
+import Link from "next/link";
+import styles from "./header-logo.module.css";
+import TurboAnimated from "./logos/TurboAnimated";
+import { LogoContext } from "./LogoContext";
+
+function HeaderLogo() {
+ return (
+ <>
+ <LogoContext />
+ <svg
+ data-testid="geist-icon"
+ fill="none"
+ height={24}
+ shapeRendering="geometricPrecision"
+ stroke="currentColor"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="1.5"
+ viewBox="0 0 24 24"
+ className="dark:text-[#333] text-[#eaeaea] ml-2 mr-1"
+ >
+ <path d="M16.88 3.549L7.12 20.451" />
+ </svg>
+
+ <Link href="/" title="Home" className="hover:opacity-75">
+ <TurboAnimated height={32} />
+ </Link>
+ <div className={styles.siteSwitcher}>
+ <SiteSwitcher />
+ </div>
+ </>
+ );
+}
+
+export default HeaderLogo;
diff --git a/docs/components/Icons.tsx b/docs/components/Icons.tsx
new file mode 100644
index 0000000..c203b3d
--- /dev/null
+++ b/docs/components/Icons.tsx
@@ -0,0 +1,165 @@
+import React, { ComponentProps } from "react";
+
+export type IconType = ((props: ComponentProps<"svg">) => JSX.Element) & {
+ requiresFill?: boolean;
+};
+
+export const TailwindIcon: IconType = (props) => {
+ return (
+ <svg
+ width="30"
+ height="18"
+ viewBox="0 0 30 18"
+ fill="currentColor"
+ aria-hidden="true"
+ {...props}
+ >
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M15 0c-4 0-6.5 2-7.5 6 1.5-2 3.25-2.75 5.25-2.25 1.141.285 1.957 1.113 2.86 2.03C17.08 7.271 18.782 9 22.5 9c4 0 6.5-2 7.5-6-1.5 2-3.25 2.75-5.25 2.25-1.141-.285-1.957-1.113-2.86-2.03C20.42 1.728 18.718 0 15 0ZM7.5 9C3.5 9 1 11 0 15c1.5-2 3.25-2.75 5.25-2.25 1.141.285 1.957 1.113 2.86 2.03C9.58 16.271 11.282 18 15 18c4 0 6.5-2 7.5-6-1.5 2-3.25 2.75-5.25 2.25-1.141-.285-1.957-1.113-2.86-2.03C12.92 10.729 11.218 9 7.5 9Z"
+ ></path>
+ </svg>
+ );
+};
+
+export const GitHubIcon: IconType = ({ height = 28, ...props }) => {
+ return (
+ <svg
+ height={height}
+ viewBox="2 2 20 20"
+ fill="currentColor"
+ aria-hidden="true"
+ >
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ fill="currentColor"
+ d="M12 3C7.0275 3 3 7.12937 3 12.2276C3 16.3109 5.57625 19.7597 9.15374 20.9824C9.60374 21.0631 9.77249 20.7863 9.77249 20.5441C9.77249 20.3249 9.76125 19.5982 9.76125 18.8254C7.5 19.2522 6.915 18.2602 6.735 17.7412C6.63375 17.4759 6.19499 16.6569 5.8125 16.4378C5.4975 16.2647 5.0475 15.838 5.80124 15.8264C6.51 15.8149 7.01625 16.4954 7.18499 16.7723C7.99499 18.1679 9.28875 17.7758 9.80625 17.5335C9.885 16.9337 10.1212 16.53 10.38 16.2993C8.3775 16.0687 6.285 15.2728 6.285 11.7432C6.285 10.7397 6.63375 9.9092 7.20749 9.26326C7.1175 9.03257 6.8025 8.08674 7.2975 6.81794C7.2975 6.81794 8.05125 6.57571 9.77249 7.76377C10.4925 7.55615 11.2575 7.45234 12.0225 7.45234C12.7875 7.45234 13.5525 7.55615 14.2725 7.76377C15.9937 6.56418 16.7475 6.81794 16.7475 6.81794C17.2424 8.08674 16.9275 9.03257 16.8375 9.26326C17.4113 9.9092 17.76 10.7281 17.76 11.7432C17.76 15.2843 15.6563 16.0687 13.6537 16.2993C13.98 16.5877 14.2613 17.1414 14.2613 18.0065C14.2613 19.2407 14.25 20.2326 14.25 20.5441C14.25 20.7863 14.4188 21.0746 14.8688 20.9824C16.6554 20.364 18.2079 19.1866 19.3078 17.6162C20.4077 16.0457 20.9995 14.1611 21 12.2276C21 7.12937 16.9725 3 12 3Z"
+ />
+ </svg>
+ );
+};
+
+export const DockerIcon: IconType = ({ height = 28, ...props }) => {
+ return (
+ <svg viewBox="0 0 122.88 88.17" {...props}>
+ <g>
+ <path
+ fill="transparent"
+ strokeWidth={6}
+ d="M121.68,33.34c-0.34-0.28-3.42-2.62-10.03-2.62c-1.71,0-3.48,0.17-5.19,0.46c-1.25-8.72-8.49-12.94-8.78-13.16 l-1.77-1.03l-1.14,1.65c-1.42,2.22-2.51,4.73-3.13,7.29c-1.2,4.96-0.46,9.63,2.05,13.62c-3.02,1.71-7.92,2.11-8.95,2.17l-80.93,0 c-2.11,0-3.82,1.71-3.82,3.82c-0.11,7.07,1.08,14.13,3.53,20.8c2.79,7.29,6.95,12.71,12.31,16.01c6.04,3.7,15.9,5.81,27.01,5.81 c5.01,0,10.03-0.46,14.99-1.37c6.9-1.25,13.51-3.65,19.6-7.12c5.02-2.91,9.52-6.61,13.34-10.94c6.44-7.24,10.26-15.33,13.05-22.51 c0.4,0,0.74,0,1.14,0c7.01,0,11.34-2.79,13.73-5.19c1.6-1.48,2.79-3.31,3.65-5.36l0.51-1.48L121.68,33.34L121.68,33.34z M71.59,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C70.68,38.98,71.08,39.38,71.59,39.38L71.59,39.38z M56.49,11.63h10.83c0.51,0,0.97-0.4,0.97-0.97V0.97c0-0.51-0.46-0.97-0.97-0.97 L56.49,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C55.52,11.17,55.97,11.63,56.49,11.63L56.49,11.63z M56.49,25.53h10.83 c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.46-0.97-0.97-0.97H56.49c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C55.52,25.08,55.97,25.53,56.49,25.53L56.49,25.53z M41.5,25.53h10.83c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97 l0,0H41.5c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C40.53,25.08,40.93,25.53,41.5,25.53L41.5,25.53z M26.28,25.53h10.83 c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0H26.28c-0.51,0-0.97,0.4-0.97,0.97v9.69 C25.37,25.08,25.77,25.53,26.28,25.53L26.28,25.53z M56.49,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97 l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C55.52,38.98,55.97,39.38,56.49,39.38L56.49,39.38L56.49,39.38z M41.5,39.38 h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C40.53,38.98,40.93,39.38,41.5,39.38L41.5,39.38L41.5,39.38z M26.28,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69 c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97v9.69C25.37,38.98,25.77,39.38,26.28,39.38L26.28,39.38z M11.35,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0 v9.69C10.44,38.98,10.84,39.38,11.35,39.38L11.35,39.38L11.35,39.38z"
+ />
+ </g>
+ </svg>
+ );
+};
+
+export const RectangleGroupIcon: IconType = (props) => {
+ return (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ {...props}
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z"
+ />
+ </svg>
+ );
+};
+
+export const FaceSmileIcon: IconType = (props) => {
+ return (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ {...props}
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M15.182 15.182a4.5 4.5 0 01-6.364 0M21 12a9 9 0 11-18 0 9 9 0 0118 0zM9.75 9.75c0 .414-.168.75-.375.75S9 10.164 9 9.75 9.168 9 9.375 9s.375.336.375.75zm-.375 0h.008v.015h-.008V9.75zm5.625 0c0 .414-.168.75-.375.75s-.375-.336-.375-.75.168-.75.375-.75.375.336.375.75zm-.375 0h.008v.015h-.008V9.75z"
+ />
+ </svg>
+ );
+};
+
+export const RectangleStackIcon: IconType = (props) => {
+ return (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ {...props}
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M6 6.878V6a2.25 2.25 0 012.25-2.25h7.5A2.25 2.25 0 0118 6v.878m-12 0c.235-.083.487-.128.75-.128h10.5c.263 0 .515.045.75.128m-12 0A2.25 2.25 0 004.5 9v.878m13.5-3A2.25 2.25 0 0119.5 9v.878m0 0a2.246 2.246 0 00-.75-.128H5.25c-.263 0-.515.045-.75.128m15 0A2.25 2.25 0 0121 12v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6c0-.98.626-1.813 1.5-2.122"
+ />
+ </svg>
+ );
+};
+
+export const JSIcon: IconType = (props) => {
+ return (
+ <svg viewBox="0 0 1024 1024" stroke="currentColor" {...props}>
+ <path d="M416 176.002h-160v424.996c0 105.16-36.064 134.522-98.824 134.522-29.41 0-55.896-5.042-76.5-12.126L64 847.808C93.4 857.932 138.518 864 173.814 864 317.91 864 416 796.258 416 602.04V176.002zM764.926 160C610.04 160 512 247.996 512 364.308c0 100.166 75.502 162.88 185.282 203.33 79.4 28.316 110.784 53.616 110.784 95.078 0 45.512-36.278 74.85-104.896 74.85-63.726 0-121.578-21.28-160.788-42.51v-0.042L512 821.454c37.278 21.276 106.882 42.51 182.334 42.51C875.708 863.96 960 766.86 960 652.568c0-97.1-53.916-159.8-170.556-204.326-86.278-34.382-122.54-53.59-122.54-97.084 0-34.4 31.376-65.738 96.086-65.738 63.692 0 107.488 21.414 133.01 34.582l38.25-128C894.25 174.44 840.376 160 764.926 160z" />
+ </svg>
+ );
+};
+
+JSIcon.requiresFill = true;
+
+export const TSIcon: IconType = (props) => {
+ return (
+ <svg
+ viewBox="0 0 350 350"
+ stroke="currentColor"
+ fill="currentColor"
+ {...props}
+ >
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M198 228.56V278.56C206.1 282.76 216 285.86 227 287.96C238 290.06 250 291.06 262 291.06C274 291.06 285 289.96 296 287.66C307 285.36 316 281.56 324 276.66C332.1 271.36 339 264.66 343 255.66C347 246.66 350.1 236.66 350.1 223.66C350.1 214.56 348.7 206.66 346 199.66C343.3 192.66 339.4 186.66 334 181.66C328.9 176.36 323 171.66 316 167.66C309 163.66 301 159.46 292 155.66C285.4 152.96 280 150.36 274 147.76C268.8 145.16 264.3 142.56 261 139.96C257.3 137.26 254.5 134.46 252.5 131.56C250.5 128.56 249.5 125.26 249.5 121.56C249.5 118.16 250.39 115.06 252.2 112.26C254.01 109.46 256.5 107.16 259.7 105.16C262.9 103.16 266.9 101.66 271.7 100.56C276.4 99.46 281.6 98.96 287.7 98.96C291.9 98.96 296.3 99.27 300.7 99.9C305.3 100.53 310 101.5 314.7 102.8C319.4 104.1 324 105.7 328.7 107.7C333.1 109.7 337.2 112 340.7 114.6V67.6C333.1 64.7 324.7 62.5 315.7 61.1C306.7 59.7 296.7 59 284.7 59C272.7 59 261.7 60.3 250.7 62.8C239.7 65.3 230.7 69.3 222.7 74.8C214.6 80.2 208.7 86.8 203.7 95.8C199 104.2 196.7 113.8 196.7 125.8C196.7 140.8 201 153.8 209.7 163.8C218.3 174.8 231.7 182.8 248.7 190.8C255.6 193.6 261.7 196.4 267.7 199.1C273.7 201.8 278.7 204.6 282.7 207.5C287 210.4 290.4 213.6 292.7 217C295.2 220.4 296.5 224.4 296.5 229C296.5 232.2 295.72 235.2 294.2 238C292.68 240.8 290.3 243.2 287.1 245.2C283.9 247.2 280 248.8 275.1 250C270.4 251.1 265.1 251.7 258.1 251.7C247.1 251.7 236.1 249.8 226.1 246C215.1 242.2 205.1 236.5 196.1 229L198 228.56ZM114 105.56H178V64.56H-1V105.56H63V288.56H114V105.56Z"
+ />
+ </svg>
+ );
+};
+
+TSIcon.requiresFill = true;
+
+export const CSSIcon: IconType = (props) => {
+ return (
+ <svg viewBox="0 0 470.699 470.699" stroke="currentColor" {...props}>
+ <path
+ d="M426.981,0H43.701C34.52,0,27.632,7.769,28.442,16.949L63.45,409.254c0.811,9.173,8.745,18.774,17.644,21.253
+ l138.006,38.335c8.887,2.463,23.413,2.479,32.313,0.032l138.177-38.281c8.901-2.472,16.835-11.986,17.645-21.175l35.023-392.469
+ C443.068,7.769,436.209,0,426.981,0z M360.51,141.611c-0.006,0.06-0.053,0.107-0.112,0.117c-0.06,0.01-0.118-0.021-0.144-0.077
+ L252.13,185.96c-1.54,0.631-2.418,2.264-2.098,3.897c0.322,1.633,1.754,2.811,3.419,2.811h84.103c4.474,0,8.736,1.9,11.728,5.227
+ c2.991,3.327,4.429,7.768,3.954,12.216l-13.141,123.273c-0.645,6.048-4.709,11.186-10.447,13.205l-89.269,31.41
+ c-3.362,1.184-7.027,1.193-10.397,0.025l-88.852-30.778c-5.773-2-9.871-7.153-10.52-13.228l-5.957-55.828
+ c-0.313-2.931,0.634-5.857,2.604-8.048c1.971-2.192,4.779-3.444,7.727-3.444h24.725c5.313,0,9.769,4.007,10.331,9.289l3.655,34.316
+ l61.521,21.385l61.803-21.58l7.559-71.17H129.835c-5.297,0-9.746-3.985-10.327-9.25l-3.327-30.164
+ c-0.508-4.601,2.088-8.982,6.366-10.745l111.837-46.109c1.269-0.523,1.99-1.868,1.724-3.214c-0.267-1.345-1.446-2.314-2.817-2.314
+ H115.542c-3.545,0-6.518-2.677-6.888-6.201l-3.406-32.421c-0.205-1.951,0.428-3.898,1.741-5.357
+ c1.313-1.458,3.184-2.291,5.146-2.291h246.379c1.973,0,3.852,0.842,5.166,2.313c1.314,1.472,1.938,3.434,1.715,5.394L360.51,141.611
+ z"
+ />
+ </svg>
+ );
+};
+
+CSSIcon.requiresFill = true;
diff --git a/docs/components/Logo.tsx b/docs/components/Logo.tsx
new file mode 100644
index 0000000..4c697ea
--- /dev/null
+++ b/docs/components/Logo.tsx
@@ -0,0 +1,119 @@
+export const Logo = () => (
+ <svg
+ height={35}
+ width={120}
+ viewBox="0 0 333 75"
+ className="dark:text-white text-gray-900"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M297.282 0.0221394C288.721 -0.273052 280.161 2.38367 273.076 7.99231L277.799 10.649C283.408 6.51635 290.492 4.7452 297.282 5.04039V0.0221394Z"
+ fill="url(#paint0_linear)"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M322.373 11.2394C316.469 5.04038 308.794 1.20289 300.529 0.317316V5.33557C307.614 6.51634 313.813 9.76344 318.831 14.7817L322.373 11.2394Z"
+ fill="url(#paint1_linear)"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M333 35.4451C332.705 27.7701 329.753 20.0951 324.735 13.6009L321.192 17.1432C325.325 22.7519 327.687 28.9509 327.982 35.4451H333Z"
+ fill="url(#paint2_linear)"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M324.735 60.8315C329.753 54.3373 332.705 46.6624 333 38.9874H327.982C327.687 45.4816 325.325 51.6806 321.192 57.2892L324.735 60.8315Z"
+ fill="url(#paint3_linear)"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M300.529 74.1152C308.499 73.2296 316.469 69.3921 322.373 63.1931L318.831 59.6508C313.813 64.9642 307.318 68.2113 300.529 69.0969V74.1152Z"
+ fill="url(#paint4_linear)"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M273.076 66.4402C280.161 72.0488 288.721 74.7055 297.282 74.4104V69.3921C290.492 69.6873 283.703 67.9161 277.799 63.7835L273.076 66.4402Z"
+ fill="url(#paint5_linear)"
+ />
+ <path
+ d="M19.0886 26.424V57H8.89658V26.424H0.524582V17.792H27.4606V26.424H19.0886ZM39.5859 17.792V39.112C39.5859 40.256 39.6206 41.4347 39.6899 42.648C39.7939 43.8267 40.0539 44.9013 40.4699 45.872C40.9206 46.8427 41.6139 47.64 42.5499 48.264C43.4859 48.8533 44.8033 49.148 46.5019 49.148C48.2006 49.148 49.5006 48.8533 50.4019 48.264C51.3379 47.64 52.0313 46.8427 52.4819 45.872C52.9326 44.9013 53.1926 43.8267 53.2619 42.648C53.3659 41.4347 53.4179 40.256 53.4179 39.112V17.792H63.5579V40.516C63.5579 46.6173 62.1539 51.072 59.3459 53.88C56.5726 56.688 52.2913 58.092 46.5019 58.092C40.7126 58.092 36.4139 56.688 33.6059 53.88C30.7979 51.072 29.3939 46.6173 29.3939 40.516V17.792H39.5859ZM79.3883 35.316H81.3123C83.3229 35.316 84.8656 34.9 85.9403 34.068C87.0149 33.236 87.5523 32.04 87.5523 30.48C87.5523 28.92 87.0149 27.724 85.9403 26.892C84.8656 26.06 83.3229 25.644 81.3123 25.644H79.3883V35.316ZM101.8 57H89.1123L79.3883 41.92V57H69.1963V17.792H85.0563C87.2403 17.792 89.1469 18.1213 90.7763 18.78C92.4056 19.404 93.7403 20.2707 94.7803 21.38C95.8549 22.4893 96.6523 23.772 97.1723 25.228C97.7269 26.684 98.0043 28.244 98.0043 29.908C98.0043 32.8893 97.2763 35.316 95.8203 37.188C94.3989 39.0253 92.2843 40.2733 89.4763 40.932L101.8 57ZM113.655 49.096H115.891C118.457 49.096 120.294 48.7667 121.403 48.108C122.513 47.4493 123.067 46.392 123.067 44.936C123.067 43.48 122.513 42.4227 121.403 41.764C120.294 41.1053 118.457 40.776 115.891 40.776H113.655V49.096ZM113.655 33.184H115.527C118.717 33.184 120.311 31.9187 120.311 29.388C120.311 26.8573 118.717 25.592 115.527 25.592H113.655V33.184ZM103.463 17.792H118.647C122.253 17.792 124.991 18.6587 126.863 20.392C128.735 22.1253 129.671 24.6213 129.671 27.88C129.671 29.856 129.307 31.5027 128.579 32.82C127.886 34.1027 126.811 35.1947 125.355 36.096C126.811 36.3733 128.042 36.8067 129.047 37.396C130.087 37.9507 130.919 38.644 131.543 39.476C132.202 40.308 132.67 41.244 132.947 42.284C133.225 43.324 133.363 44.4333 133.363 45.612C133.363 47.4493 133.034 49.0787 132.375 50.5C131.751 51.9213 130.85 53.1173 129.671 54.088C128.527 55.0587 127.123 55.7867 125.459 56.272C123.795 56.7573 121.923 57 119.843 57H103.463V17.792ZM145.455 37.396C145.455 38.956 145.749 40.3947 146.339 41.712C146.928 43.0293 147.725 44.1733 148.731 45.144C149.736 46.1147 150.897 46.8773 152.215 47.432C153.567 47.952 154.988 48.212 156.479 48.212C157.969 48.212 159.373 47.952 160.691 47.432C162.043 46.8773 163.221 46.1147 164.227 45.144C165.267 44.1733 166.081 43.0293 166.671 41.712C167.26 40.3947 167.555 38.956 167.555 37.396C167.555 35.836 167.26 34.3973 166.671 33.08C166.081 31.7627 165.267 30.6187 164.227 29.648C163.221 28.6773 162.043 27.932 160.691 27.412C159.373 26.8573 157.969 26.58 156.479 26.58C154.988 26.58 153.567 26.8573 152.215 27.412C150.897 27.932 149.736 28.6773 148.731 29.648C147.725 30.6187 146.928 31.7627 146.339 33.08C145.749 34.3973 145.455 35.836 145.455 37.396ZM134.795 37.396C134.795 34.484 135.332 31.78 136.407 29.284C137.481 26.7533 138.972 24.552 140.879 22.68C142.785 20.808 145.056 19.352 147.691 18.312C150.36 17.2373 153.289 16.7 156.479 16.7C159.633 16.7 162.545 17.2373 165.215 18.312C167.884 19.352 170.172 20.808 172.079 22.68C174.02 24.552 175.528 26.7533 176.603 29.284C177.677 31.78 178.215 34.484 178.215 37.396C178.215 40.308 177.677 43.0293 176.603 45.56C175.528 48.056 174.02 50.24 172.079 52.112C170.172 53.984 167.884 55.4573 165.215 56.532C162.545 57.572 159.633 58.092 156.479 58.092C153.289 58.092 150.36 57.572 147.691 56.532C145.056 55.4573 142.785 53.984 140.879 52.112C138.972 50.24 137.481 48.056 136.407 45.56C135.332 43.0293 134.795 40.308 134.795 37.396ZM192.245 35.316H194.169C196.179 35.316 197.722 34.9 198.797 34.068C199.871 33.236 200.409 32.04 200.409 30.48C200.409 28.92 199.871 27.724 198.797 26.892C197.722 26.06 196.179 25.644 194.169 25.644H192.245V35.316ZM214.657 57H201.969L192.245 41.92V57H182.053V17.792H197.913C200.097 17.792 202.003 18.1213 203.633 18.78C205.262 19.404 206.597 20.2707 207.637 21.38C208.711 22.4893 209.509 23.772 210.029 25.228C210.583 26.684 210.861 28.244 210.861 29.908C210.861 32.8893 210.133 35.316 208.677 37.188C207.255 39.0253 205.141 40.2733 202.333 40.932L214.657 57ZM238.628 26.424H226.512V32.976H237.952V41.608H226.512V48.368H238.628V57H216.32V17.792H238.628V26.424ZM253.568 35.784H256.948C260.692 35.784 262.564 34.1547 262.564 30.896C262.564 27.6373 260.692 26.008 256.948 26.008H253.568V35.784ZM253.568 57H243.376V17.792H259.6C264.003 17.792 267.365 18.936 269.688 21.224C272.045 23.512 273.224 26.736 273.224 30.896C273.224 35.056 272.045 38.28 269.688 40.568C267.365 42.856 264.003 44 259.6 44H253.568V57ZM284.91 37.396C284.91 38.956 285.205 40.3947 285.794 41.712C286.383 43.0293 287.181 44.1733 288.186 45.144C289.191 46.1147 290.353 46.8773 291.67 47.432C293.022 47.952 294.443 48.212 295.934 48.212C297.425 48.212 298.829 47.952 300.146 47.432C301.498 46.8773 302.677 46.1147 303.682 45.144C304.722 44.1733 305.537 43.0293 306.126 41.712C306.715 40.3947 307.01 38.956 307.01 37.396C307.01 35.836 306.715 34.3973 306.126 33.08C305.537 31.7627 304.722 30.6187 303.682 29.648C302.677 28.6773 301.498 27.932 300.146 27.412C298.829 26.8573 297.425 26.58 295.934 26.58C294.443 26.58 293.022 26.8573 291.67 27.412C290.353 27.932 289.191 28.6773 288.186 29.648C287.181 30.6187 286.383 31.7627 285.794 33.08C285.205 34.3973 284.91 35.836 284.91 37.396ZM274.25 37.396C274.25 34.484 274.787 31.78 275.862 29.284C276.937 26.7533 278.427 24.552 280.334 22.68C282.241 20.808 284.511 19.352 287.146 18.312C289.815 17.2373 292.745 16.7 295.934 16.7C299.089 16.7 302.001 17.2373 304.67 18.312C307.339 19.352 309.627 20.808 311.534 22.68C313.475 24.552 314.983 26.7533 316.058 29.284C317.133 31.78 317.67 34.484 317.67 37.396C317.67 40.308 317.133 43.0293 316.058 45.56C314.983 48.056 313.475 50.24 311.534 52.112C309.627 53.984 307.339 55.4573 304.67 56.532C302.001 57.572 299.089 58.092 295.934 58.092C292.745 58.092 289.815 57.572 287.146 56.532C284.511 55.4573 282.241 53.984 280.334 52.112C278.427 50.24 276.937 48.056 275.862 45.56C274.787 43.0293 274.25 40.308 274.25 37.396Z"
+ fill="currentColor"
+ />
+ <defs>
+ <linearGradient
+ id="paint0_linear"
+ x1="303.038"
+ y1={0}
+ x2="303.038"
+ y2="74.4325"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#1E90FF" />
+ <stop offset={1} stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient
+ id="paint1_linear"
+ x1="303.038"
+ y1={0}
+ x2="303.038"
+ y2="74.4325"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#1E90FF" />
+ <stop offset={1} stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient
+ id="paint2_linear"
+ x1="303.038"
+ y1={0}
+ x2="303.038"
+ y2="74.4325"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#1E90FF" />
+ <stop offset={1} stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient
+ id="paint3_linear"
+ x1="303.038"
+ y1={0}
+ x2="303.038"
+ y2="74.4325"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#1E90FF" />
+ <stop offset={1} stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient
+ id="paint4_linear"
+ x1="303.038"
+ y1={0}
+ x2="303.038"
+ y2="74.4325"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#1E90FF" />
+ <stop offset={1} stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient
+ id="paint5_linear"
+ x1="303.038"
+ y1={0}
+ x2="303.038"
+ y2="74.4325"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#1E90FF" />
+ <stop offset={1} stopColor="#FF1E56" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
diff --git a/docs/components/LogoContext/icons.tsx b/docs/components/LogoContext/icons.tsx
new file mode 100644
index 0000000..dabefc2
--- /dev/null
+++ b/docs/components/LogoContext/icons.tsx
@@ -0,0 +1,228 @@
+import classNames from "classnames";
+
+export const VercelLogo = ({ className }: { className?: string }) => (
+ <svg
+ height={22}
+ viewBox="0 0 235 203"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ className={classNames(className, "dark:fill-white fill-black")}
+ >
+ <path d="M117.082 0L234.164 202.794H0L117.082 0Z" fill="currentColor" />
+ </svg>
+);
+
+export const TurborepoLogo = ({ className }: { className?: string }) => (
+ <svg
+ width="100"
+ height="100"
+ viewBox="0 0 100 100"
+ fill="none"
+ className={className}
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M49.5423 17.3221C31.7763 17.3221 17.3223 31.7761 17.3223 49.5421C17.3223 67.3081 31.7763 81.7621 49.5423 81.7621C67.3083 81.7621 81.7623 67.3081 81.7623 49.5421C81.7623 31.7761 67.3083 17.3221 49.5423 17.3221ZM49.5423 66.2161C40.3323 66.2161 32.8683 58.7521 32.8683 49.5421C32.8683 40.3321 40.3323 32.8681 49.5423 32.8681C58.7523 32.8681 66.2163 40.3321 66.2163 49.5421C66.2163 58.7521 58.7523 66.2161 49.5423 66.2161Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M52.2419 12.03V0C78.3899 1.398 99.162 23.046 99.162 49.542C99.162 76.038 78.3899 97.68 52.2419 99.084V87.054C71.7299 85.662 87.1619 69.378 87.1619 49.542C87.1619 29.706 71.7299 13.422 52.2419 12.03ZM21.126 74.1419C15.96 68.1779 12.63 60.5819 12.036 52.2419H0C0.624 63.9119 5.292 74.5019 12.606 82.6559L21.12 74.1419H21.126ZM46.8421 99.084V87.054C38.4961 86.46 30.9001 83.136 24.9361 77.964L16.4221 86.478C24.5821 93.798 35.1721 98.46 46.8361 99.084H46.8421Z"
+ fill="url(#paint0_linear_2758_13788)"
+ />
+ <defs>
+ <linearGradient
+ id="paint0_linear_2758_13788"
+ x1="54.1863"
+ y1="6.9667"
+ x2="5.4184"
+ y2="55.7346"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
+
+export const TurbopackLogo = ({ className }: { className?: string }) => (
+ <svg
+ width="100"
+ height="100"
+ viewBox="0 0 100 100"
+ fill="none"
+ className={className}
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M38.6868 36.6983C37.5559 36.6983 36.6391 37.6151 36.6391 38.7459V61.5202C36.6391 62.651 37.5559 63.5678 38.6868 63.5678H61.461C62.5919 63.5678 63.5086 62.651 63.5086 61.5202V38.7459C63.5086 37.6151 62.5919 36.6983 61.461 36.6983H38.6868ZM22.259 20.4426C21.2232 20.4426 20.3834 21.2823 20.3834 22.3182V77.9479C20.3834 78.9838 21.2232 79.8235 22.259 79.8235H77.8887C78.9246 79.8235 79.7643 78.9838 79.7643 77.9479V22.3182C79.7643 21.2823 78.9246 20.4426 77.8887 20.4426H22.259Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M0 51.0638V87.234C0 89.6609 0.677167 91.9295 1.85286 93.8614L14.8936 80.8206V51.0638H0Z"
+ fill="url(#paint0_linear_2758_13848)"
+ />
+ <path
+ d="M5.94644 98.0278L18.8765 85.0978C18.9666 85.1035 19.0574 85.1064 19.1489 85.1064H47.6402V100H12.766C10.2582 100 7.91931 99.2769 5.94644 98.0278Z"
+ fill="url(#paint1_linear_2758_13848)"
+ />
+ <path
+ d="M53.481 100H87.234C94.2845 100 100 94.2845 100 87.234V12.766C100 5.71551 94.2845 0 87.234 0H51.0638V14.8936H80.8511C83.2012 14.8936 85.1064 16.7988 85.1064 19.1489V80.8511C85.1064 83.2012 83.2012 85.1064 80.8511 85.1064H53.481V100Z"
+ fill="url(#paint2_linear_2758_13848)"
+ />
+ <defs>
+ <linearGradient
+ id="paint0_linear_2758_13848"
+ x1="54.9167"
+ y1="7.03125"
+ x2="5.69936"
+ y2="55.9148"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient
+ id="paint1_linear_2758_13848"
+ x1="54.9167"
+ y1="7.03125"
+ x2="5.69936"
+ y2="55.9148"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient
+ id="paint2_linear_2758_13848"
+ x1="54.9167"
+ y1="7.03125"
+ x2="5.69936"
+ y2="55.9148"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
+
+export const NextJSLogo = ({ className }: { className?: string }) => (
+ <svg
+ width="180"
+ height="180"
+ viewBox="0 0 180 180"
+ fill="none"
+ className={className}
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <mask
+ id="mask0_408_139"
+ style={{ maskType: "alpha" }}
+ maskUnits="userSpaceOnUse"
+ x="0"
+ y="0"
+ width="180"
+ height="180"
+ >
+ <circle cx="90" cy="90" r="90" fill="black" />
+ </mask>
+ <g mask="url(#mask0_408_139)">
+ <circle
+ cx="90"
+ cy="90"
+ r="87"
+ fill="black"
+ stroke="white"
+ strokeWidth="6"
+ />
+ <path
+ d="M149.508 157.52L69.142 54H54V125.97H66.1136V69.3836L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z"
+ fill="url(#paint0_linear_408_139)"
+ />
+ <rect
+ x="115"
+ y="54"
+ width="12"
+ height="72"
+ fill="url(#paint1_linear_408_139)"
+ />
+ </g>
+ <defs>
+ <linearGradient
+ id="paint0_linear_408_139"
+ x1="109"
+ y1="116.5"
+ x2="144.5"
+ y2="160.5"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="white" />
+ <stop offset="1" stopColor="white" stopOpacity="0" />
+ </linearGradient>
+ <linearGradient
+ id="paint1_linear_408_139"
+ x1="121"
+ y1="54"
+ x2="120.799"
+ y2="106.875"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="white" />
+ <stop offset="1" stopColor="white" stopOpacity="0" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
+
+export const DesignSystemLogo = ({ className }: { className?: string }) => (
+ <svg
+ data-testid="geist-icon"
+ fill="none"
+ height="24"
+ shapeRendering="geometricPrecision"
+ stroke="currentColor"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="1.5"
+ viewBox="0 0 24 24"
+ className={className}
+ width="24"
+ >
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M9.67025 6.612L9.09631 7.63233L10.4037 8.36772L10.9772 7.34818C10.4872 7.1987 10.0429 6.94464 9.67025 6.612ZM13.0228 7.34818L13.5963 8.36772L14.9037 7.63233L14.3297 6.612C13.9571 6.94464 13.5128 7.1987 13.0228 7.34818ZM6.41944 20.75C6.4722 20.5084 6.49999 20.2574 6.49999 20C6.49999 19.7426 6.4722 19.4916 6.41945 19.25H7.49999V20.75H6.41944ZM5.32976 17.388L5.90367 16.3677L4.59631 15.6323L4.02284 16.6518C4.51277 16.8013 4.95708 17.0554 5.32976 17.388ZM17.5805 19.25C17.5278 19.4916 17.5 19.7426 17.5 20C17.5 20.2574 17.5278 20.5084 17.5805 20.75H16.5V19.25H17.5805ZM19.9771 16.6518C19.4872 16.8013 19.0429 17.0554 18.6702 17.388L18.0963 16.3677L19.4037 15.6323L19.9771 16.6518ZM9.50367 9.96772L8.60367 11.5677L7.29631 10.8323L8.19631 9.23233L9.50367 9.96772ZM7.70367 13.1677L6.80367 14.7677L5.49631 14.0323L6.39631 12.4323L7.70367 13.1677ZM15.3963 11.5677L14.4963 9.96772L15.8037 9.23233L16.7037 10.8323L15.3963 11.5677ZM17.1963 14.7677L16.2963 13.1677L17.6037 12.4323L18.5037 14.0323L17.1963 14.7677ZM12.9 20.75H14.7V19.25H12.9V20.75ZM9.29999 20.75H11.1V19.25H9.29999V20.75Z"
+ fill="currentColor"
+ strokeWidth="0"
+ />
+ <circle cx="12" cy="4" r="2" stroke="currentColor" strokeWidth="1.5" />
+ <circle cx="3" cy="20" r="2" stroke="currentColor" strokeWidth="1.5" />
+ <circle cx="21" cy="20" r="2" stroke="currentColor" strokeWidth="1.5" />
+ </svg>
+);
+
+export const IconType = ({ className }: { className?: string }) => (
+ <svg
+ data-testid="geist-icon"
+ fill="none"
+ height="24"
+ shapeRendering="geometricPrecision"
+ stroke="currentColor"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="1.5"
+ viewBox="0 0 24 24"
+ width="24"
+ className={className}
+ >
+ <path d="M4 7V4h16v3" />
+ <path d="M9 20h6" />
+ <path d="M12 4v16" />
+ </svg>
+);
diff --git a/docs/components/LogoContext/index.tsx b/docs/components/LogoContext/index.tsx
new file mode 100644
index 0000000..3f03740
--- /dev/null
+++ b/docs/components/LogoContext/index.tsx
@@ -0,0 +1,169 @@
+import { useEffect, useCallback, useState, useRef } from "react";
+import { useTheme } from "nextra-theme-docs";
+import Link from "next/link";
+import classNames from "classnames";
+import { VercelLogo } from "./icons";
+import { PRODUCT_MENU_ITEMS, PLATFORM_MENU_ITEMS } from "./items";
+import type { MenuItemProps } from "./types";
+import { MouseEvent } from "react";
+import { useTurboSite } from "../SiteSwitcher";
+
+function MenuDivider({ children, ...other }: { children: string }) {
+ return (
+ <h3
+ className={classNames(
+ "group flex items-center px-4 py-2 text-xs dark:text-gray-600 text-gray-500 font-bold"
+ )}
+ {...other}
+ >
+ {children}
+ </h3>
+ );
+}
+
+function MenuItem({
+ children,
+ prefix,
+ className,
+ type,
+ href,
+ onClick,
+ closeMenu,
+ disabled,
+ ...other
+}: MenuItemProps) {
+ const [copied, setCopied] = useState(false);
+
+ const handleClick = () => {
+ if (onClick) {
+ onClick();
+ }
+ if (type === "copy") {
+ setCopied(true);
+ } else {
+ closeMenu();
+ }
+ };
+
+ useEffect(() => {
+ if (copied) {
+ const timeout = setTimeout(() => {
+ setCopied(false);
+ closeMenu();
+ }, 2000);
+ return () => clearTimeout(timeout);
+ }
+ }, [copied, closeMenu]);
+
+ const classes = classNames(
+ className,
+ "group flex items-center px-4 py-2 text-sm dark:hover:bg-gray-800 hover:bg-gray-200 w-full rounded-md"
+ );
+ if (type === "internal") {
+ return (
+ <Link className={classes} href={href} onClick={handleClick} {...other}>
+ {prefix}
+ {children}
+ </Link>
+ );
+ }
+ if (type === "external") {
+ return (
+ <a
+ href={href}
+ onClick={handleClick}
+ target="_blank"
+ rel="noopener noreferrer"
+ className={classes}
+ {...other}
+ >
+ {prefix}
+ {children}
+ </a>
+ );
+ }
+
+ if (type === "copy") {
+ return (
+ <button
+ onClick={handleClick}
+ className={classes}
+ disabled={disabled}
+ {...other}
+ >
+ {prefix}
+ {copied ? "Copied to clipboard!" : children}
+ </button>
+ );
+ }
+}
+
+export function LogoContext() {
+ const [open, setOpen] = useState(false);
+ const site = useTurboSite();
+ const menu = useRef(null);
+ const { theme = "dark" } = useTheme();
+
+ const toggleMenu = (e: MouseEvent<HTMLButtonElement>) => {
+ e.preventDefault();
+ if (e.type === "contextmenu") {
+ setOpen((prev) => !prev);
+ } else {
+ setOpen(false);
+ window.open(`https://vercel.com`, "_blank");
+ }
+ };
+
+ const onClickOutside: EventListener = useCallback(
+ (e) => {
+ if (menu.current && open && !menu.current.contains(e.target)) {
+ setOpen(false);
+ }
+ },
+ [open]
+ );
+
+ useEffect(() => {
+ document.addEventListener("click", onClickOutside, true);
+ return () => {
+ document.removeEventListener("click", onClickOutside, true);
+ };
+ }, [onClickOutside]);
+
+ return (
+ <div className="block relative">
+ <button onClick={toggleMenu} onContextMenu={toggleMenu} className="flex">
+ <VercelLogo />
+ </button>
+ {open && (
+ <div
+ ref={menu}
+ className="absolute border dark:border-gray-700 left-6 z-10 mt-2 w-60 origin-top-right divide-y divide-gray-100 rounded-md bg-white dark:bg-black shadow-sm focus:outline-none"
+ >
+ <div className="p-2">
+ <MenuDivider>Platform</MenuDivider>
+ {PLATFORM_MENU_ITEMS({ theme, site }).map((item) => (
+ <MenuItem
+ key={item.name}
+ closeMenu={() => setOpen(false)}
+ {...item}
+ >
+ {item.children}
+ </MenuItem>
+ ))}
+ <MenuDivider>Products</MenuDivider>
+ {PRODUCT_MENU_ITEMS({ theme, site }).map((item) => (
+ <MenuItem
+ key={item.name}
+ closeMenu={() => setOpen(false)}
+ {...item}
+ >
+ {item.children}
+ </MenuItem>
+ ))}
+ </div>
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/docs/components/LogoContext/items.tsx b/docs/components/LogoContext/items.tsx
new file mode 100644
index 0000000..6ce1fe6
--- /dev/null
+++ b/docs/components/LogoContext/items.tsx
@@ -0,0 +1,91 @@
+import {
+ VercelLogo,
+ TurborepoLogo,
+ TurbopackLogo,
+ IconType,
+ NextJSLogo,
+ DesignSystemLogo,
+} from "./icons";
+import type { ContextItem, ContextList } from "./types";
+import copy from "copy-to-clipboard";
+
+export const PLATFORM_MENU_ITEMS = ({
+ theme,
+}: ContextList): Array<ContextItem> => [
+ {
+ name: "copy-logo",
+ "aria-label": "Copy Logo as SVG to Clipboard",
+ children: "Copy Logo as SVG",
+ prefix: <VercelLogo className="mr-3 h-4 w-4" />,
+ type: "copy",
+ onClick: () => {
+ copy(
+ `<svg
+ width="76"
+ height="65"
+ viewBox="0 0 76 65"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path d="M37.5274 0L75.0548 65H0L37.5274 0Z" fill="${
+ theme === "dark" ? "#ffffff" : "#000000"
+ }" />
+ </svg>`
+ );
+ },
+ },
+ {
+ name: "copy-wordmark",
+ "aria-label": "Copy Wordmark as SVG to Clipboard",
+ children: "Copy Wordmark as SVG",
+ prefix: <IconType className="mr-3 h-4 w-4" />,
+ type: "copy",
+ onClick: () => {
+ copy(
+ // NOTE: We include `xmlns` as this is required when the SVG isn't inlined.
+ `<svg xmlns="http://www.w3.org/2000/svg" fill="${
+ theme === "dark" ? "#ffffff" : "#000000"
+ }" viewBox="0 0 284 65"><path d="M141.68 16.25c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm117.14-14.5c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm-39.03 3.5c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9v-46h9zM37.59.25l36.95 64H.64l36.95-64zm92.38 5l-27.71 48-27.71-48h10.39l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10v14.8h-9v-34h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" /><!-- With love, the ▲ team --></svg>`
+ );
+ },
+ },
+ {
+ name: "brand-guidelines",
+ "aria-label": "Open Brand Guidelines in New Tab",
+ children: "Brand Guidelines",
+ prefix: <DesignSystemLogo className="mr-3 h-4 w-4" />,
+ type: "external",
+ href: "https://vercel.com/design/brands",
+ },
+];
+
+export const PRODUCT_MENU_ITEMS = ({
+ site,
+}: ContextList): Array<ContextItem> => [
+ {
+ name: "next-js",
+ "aria-label": "Open Next.js Home in New Tab",
+ children: "Next.js",
+ prefix: <NextJSLogo className="mr-3 h-4 w-4" />,
+ type: "external",
+ href: "https://nextjs.org",
+ },
+ {
+ name: "turborepo",
+ "aria-label": "Open Turborepo Home in New Tab",
+ disabled: site === "repo",
+ children: "Turborepo",
+ prefix: <TurborepoLogo className="mr-3 h-4 w-4" />,
+ type: "internal",
+ href: "/repo",
+ },
+ {
+ name: "turbopack",
+ "aria-label": "Open Turbopack Home in New Tab",
+ disabled: site === "pack",
+ children: "Turbopack",
+ prefix: <TurbopackLogo className="mr-3 h-4 w-4" />,
+ type: "internal",
+ href: "/pack",
+ },
+];
diff --git a/docs/components/LogoContext/types.ts b/docs/components/LogoContext/types.ts
new file mode 100644
index 0000000..ff7f644
--- /dev/null
+++ b/docs/components/LogoContext/types.ts
@@ -0,0 +1,25 @@
+import type { ReactNode } from "react";
+import { TurboSite } from "../SiteSwitcher";
+
+type MenuItemType = "internal" | "external" | "copy";
+
+export interface MenuItemProps extends ContextItem {
+ closeMenu?: () => void;
+ className?: string;
+}
+
+export interface ContextList {
+ theme: string;
+ site: TurboSite;
+}
+
+export interface ContextItem {
+ name: string;
+ "aria-label": string;
+ disabled?: boolean;
+ type: MenuItemType;
+ children: ReactNode;
+ prefix: ReactNode;
+ href?: string;
+ onClick?: () => void;
+}
diff --git a/docs/components/MonorepoHandbook.tsx b/docs/components/MonorepoHandbook.tsx
new file mode 100644
index 0000000..d7e2fe5
--- /dev/null
+++ b/docs/components/MonorepoHandbook.tsx
@@ -0,0 +1,142 @@
+import {
+ BanIcon,
+ ChatAlt2Icon,
+ CloudDownloadIcon,
+ CloudUploadIcon,
+ CodeIcon,
+ CubeIcon,
+ LibraryIcon,
+ PencilAltIcon,
+ ShareIcon,
+ ShieldExclamationIcon,
+ StarIcon,
+} from "@heroicons/react/outline";
+import React from "react";
+import { DetailedFeatureLink } from "./Feature";
+import { DockerIcon } from "./Icons";
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => {
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:gap-x-8 lg:gap-y-12">
+ {children}
+ </div>
+ );
+};
+
+export const FundamentalsArea = () => {
+ return (
+ <Wrapper>
+ <DetailedFeatureLink
+ feature={{
+ Icon: CubeIcon,
+ description: `Understand how a monorepo compares to a polyrepo, and what problems it solves.`,
+ name: "What is a Monorepo?",
+ }}
+ href="/docs/handbook/what-is-a-monorepo"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: CloudDownloadIcon,
+ description: `Learn how to install and manage packages in your monorepo.`,
+ name: "Package Installation",
+ }}
+ href="/docs/handbook/package-installation"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: ChatAlt2Icon,
+ description:
+ "Understand how workspaces help you develop packages locally.",
+ name: "Workspaces",
+ }}
+ href="/docs/handbook/workspaces"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: LibraryIcon,
+ description:
+ "Step-by-step guide on migrating from a multi-repo to a monorepo.",
+ name: "Migrating to a Monorepo",
+ }}
+ href="/docs/handbook/migrating-to-a-monorepo"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: ShareIcon,
+ description:
+ "Learn how to share code easily using either internal or external packages.",
+ name: "Sharing Code",
+ }}
+ href="/docs/handbook/sharing-code"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: BanIcon,
+ description:
+ "Learn the common monorepo pain points, and how to fix them.",
+ name: "Troubleshooting",
+ }}
+ href="/docs/handbook/troubleshooting"
+ ></DetailedFeatureLink>
+ </Wrapper>
+ );
+};
+
+export const TasksArea = () => {
+ return (
+ <Wrapper>
+ <DetailedFeatureLink
+ feature={{
+ Icon: PencilAltIcon,
+ description: `Learn how to set up your dev scripts using Turborepo.`,
+ name: "Development Tasks",
+ }}
+ href="/docs/handbook/dev"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: CodeIcon,
+ description:
+ "Get framework-specific guides for building your apps with Turborepo.",
+ name: "Building your App",
+ }}
+ href="/docs/handbook/building-your-app"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: ShieldExclamationIcon,
+ description:
+ "Learn how to share linting configs and co-ordinate tasks across your repo.",
+ name: "Linting",
+ }}
+ href="/docs/handbook/linting"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: StarIcon,
+ description: "Configure your integration or end-to-end tests easily.",
+ name: "Testing",
+ }}
+ href="/docs/handbook/testing"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: DockerIcon,
+ description:
+ "Make use of Turborepo's prune command to keep your Docker deploys fast.",
+ name: "Deploying with Docker",
+ }}
+ href="/docs/handbook/deploying-with-docker"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: CloudUploadIcon,
+ description:
+ "Bundle, version and publish packages to npm from your monorepo.",
+ name: "Publishing Packages",
+ }}
+ href="/docs/handbook/publishing-packages"
+ ></DetailedFeatureLink>
+ </Wrapper>
+ );
+};
diff --git a/docs/components/Navigation.tsx b/docs/components/Navigation.tsx
new file mode 100644
index 0000000..d72b714
--- /dev/null
+++ b/docs/components/Navigation.tsx
@@ -0,0 +1,47 @@
+import { Navbar } from "nextra-theme-docs";
+import { useTurboSite } from "./SiteSwitcher";
+
+function Navigation(props) {
+ const site = useTurboSite();
+
+ /*
+ Inject a dynamic docs link when NOT on root
+ 1. Points to /repo/docs when on /repo
+ 2. Points to /pack/docs when on /pack
+ */
+ const leadingItem = props.items[0];
+ if (leadingItem?.id !== "contextual-docs" && site) {
+ props.items.unshift({
+ title: "Docs",
+ type: "page",
+ route: `/${site}/docs`,
+ id: "contextual-docs",
+ key: "contextual-docs",
+ });
+ }
+
+ const lastItem = props.items[props.items.length - 1];
+ if (lastItem?.id !== "contextual-enterprise") {
+ props.items.push({
+ title: "Enterprise",
+ newWindow: true,
+ // https://github.com/shuding/nextra/issues/1028
+ route: "enterprise",
+ href: `https://vercel.com/${
+ site === "repo" ? "solutions/turborepo" : "contact/sales"
+ }?utm_source=turbo.build&utm_medium=referral&utm_campaign=header-enterpriseLink`,
+ id: "contextual-enterprise",
+ key: "contextual-enterprise",
+ });
+ }
+
+ // remove the top level repo and pack links
+ const headerItems = props.items.filter((item) => {
+ return item.name !== "repo" && item.name !== "pack";
+ });
+
+ // items last to override the default
+ return <Navbar {...props} items={headerItems} />;
+}
+
+export default Navigation;
diff --git a/docs/components/QuickStart.tsx b/docs/components/QuickStart.tsx
new file mode 100644
index 0000000..040871e
--- /dev/null
+++ b/docs/components/QuickStart.tsx
@@ -0,0 +1,89 @@
+import {
+ BookOpenIcon,
+ CloudDownloadIcon,
+ CloudUploadIcon,
+ LightBulbIcon,
+ LightningBoltIcon,
+ PencilIcon,
+ ServerIcon,
+ SparklesIcon,
+} from "@heroicons/react/outline";
+import { DetailedFeatureLink } from "./Feature";
+import Turbo from "./logos/Turbo";
+
+export const QuickStartArea = () => {
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:gap-x-8 lg:gap-y-12">
+ <DetailedFeatureLink
+ feature={{
+ Icon: PencilIcon,
+ description: `Add Turborepo to any JavaScript or TypeScript project in minutes.`,
+ name: "Add to existing project",
+ }}
+ href="/repo/docs/getting-started/add-to-project"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: SparklesIcon,
+ description: `Build a brand-new monorepo with shared packages powered by Turborepo.`,
+ name: "Create a new monorepo",
+ }}
+ href="/repo/docs/getting-started/create-new"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: ServerIcon,
+ description: `Incrementally add Turborepo to your existing monorepo codebase.`,
+ name: "Add to existing monorepo",
+ }}
+ href="/repo/docs/getting-started/existing-monorepo"
+ ></DetailedFeatureLink>
+ </div>
+ );
+};
+
+export const MonoreposArea = () => {
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:gap-x-8 lg:gap-y-12">
+ <DetailedFeatureLink
+ feature={{
+ Icon: LightBulbIcon,
+ description: `Understand why monorepos don't scale - and why Turborepo is the solution.`,
+ name: "Why Turborepo?",
+ }}
+ href="/repo/docs/core-concepts/monorepos"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: BookOpenIcon,
+ description: `Learn the basics of monorepos before you dive in to Turborepo.`,
+ name: "Read the Monorepo Handbook",
+ }}
+ href="/docs/handbook"
+ ></DetailedFeatureLink>
+ </div>
+ );
+};
+
+export const LearnMoreArea = () => {
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:gap-x-8 lg:gap-y-12">
+ <DetailedFeatureLink
+ feature={{
+ Icon: CloudUploadIcon,
+ description: `Turborepo remembers the output of any task you run - and can skip work that's already been done.`,
+ name: "Never do the same work twice",
+ }}
+ href="/repo/docs/core-concepts/caching"
+ />
+ <DetailedFeatureLink
+ feature={{
+ Icon: LightningBoltIcon,
+ description: `The way you run your tasks is probably not optimized. Turborepo speeds them up with smart scheduling, minimising idle CPU's.`,
+ name: "Maximum Multitasking",
+ }}
+ href="/repo/docs/core-concepts/monorepos/running-tasks"
+ ></DetailedFeatureLink>
+ </div>
+ );
+};
diff --git a/docs/components/RemoteCacheCounter.tsx b/docs/components/RemoteCacheCounter.tsx
new file mode 100644
index 0000000..ef4dc6e
--- /dev/null
+++ b/docs/components/RemoteCacheCounter.tsx
@@ -0,0 +1,47 @@
+import cn from "classnames";
+import { useState, useEffect } from "react";
+import { animated, useSpring, config } from "@react-spring/web";
+import useTurborepoMinutesSaved from "../lib/useTurborepoMinutesSaved";
+import Link from "next/link";
+
+const counterFormatter = Intl.NumberFormat(undefined, {
+ minimumIntegerDigits: 7,
+ maximumFractionDigits: 0,
+});
+
+export default function RemoteCacheCounter() {
+ const [targetMinutes, setTargetMinutes] = useState(0);
+ const timeSaved = useTurborepoMinutesSaved();
+ useEffect(() => {
+ if (timeSaved) {
+ setTargetMinutes(
+ timeSaved.local_cache_minutes_saved +
+ timeSaved.remote_cache_minutes_saved
+ );
+ }
+ }, [timeSaved]);
+
+ const spring = useSpring({
+ from: { minutesSaved: 0 },
+ minutesSaved: targetMinutes,
+ config: config.molasses,
+ });
+
+ return (
+ <Link
+ href="/repo/docs/core-concepts/remote-caching"
+ className="group mt-4 rounded-lg border border-transparent overflow-hidden bg-origin-border bg-gradient-to-r from-red-500 to-blue-500 dark:text-[#9ca3af] text-[#6b7280]"
+ >
+ <div className="p-4 dark:bg-[#111111] bg-white">
+ <animated.p className="inline-block text-xl bg-gradient-to-r from-red-500 to-blue-500 bg-clip-text text-transparent tabular-nums">
+ {spring.minutesSaved.to((t) => counterFormatter.format(t))}
+ </animated.p>
+ <div className="text-xs">Total Compute Minutes Saved</div>
+
+ <div className="text-xs mt-4 group-hover:underline">
+ Get Started With Remote Caching →
+ </div>
+ </div>
+ </Link>
+ );
+}
diff --git a/docs/components/SiteSwitcher.tsx b/docs/components/SiteSwitcher.tsx
new file mode 100644
index 0000000..e5d58d2
--- /dev/null
+++ b/docs/components/SiteSwitcher.tsx
@@ -0,0 +1,66 @@
+import cn from "classnames";
+import { useRouter } from "next/router";
+import Link from "next/link";
+
+export type TurboSite = "pack" | "repo";
+
+export function useTurboSite(): TurboSite | undefined {
+ const { pathname } = useRouter();
+
+ if (pathname.startsWith("/repo")) {
+ return "repo";
+ }
+
+ if (pathname.startsWith("/pack")) {
+ return "pack";
+ }
+
+ return undefined;
+}
+
+function SiteSwitcherLink({ href, text, isActive }) {
+ const classes =
+ "py-1 transition-colors duration-300 inline-block w-[50px] cursor-pointer hover:text-black dark:hover:text-white";
+
+ const conditionalClasses = {
+ "text-black dark:text-white": !!isActive,
+ };
+
+ return (
+ <Link href={href} className={cn(classes, conditionalClasses)}>
+ {text}
+ </Link>
+ );
+}
+
+function SiteSwitcher() {
+ const site = useTurboSite();
+
+ return (
+ <div className="relative flex items-center justify-between p-2 text-xl group">
+ <span
+ className={cn(
+ "flex h-[34px] w-[100px] flex-shrink-0 items-center rounded-[8px] border border-[#dedfde] dark:border-[#333333] p-1 duration-300 ease-in-out",
+ "after:h-[24px] after:w-[44px] after:rounded-md dark:after:bg-[#333333] after:shadow-sm after:duration-300 after:border dark:after:border-[#333333] after:border-[#666666]/100 after:bg-gradient-to-b after:from-[#3286F1] after:to-[#C33AC3] after:opacity-20 dark:after:opacity-100 dark:after:bg-none",
+ "indeterminate:after:hidden",
+ {
+ "after:hidden": !site,
+ "after:translate-x-[46px]": site === "pack",
+ }
+ )}
+ />
+
+ <span
+ className={cn(
+ "z-50 absolute p-1 text-sm flex justify-between text-center w-[100px] text-[#666666] dark:text-[#888888]",
+ { "hover:text-black dark:hover:text-white": site }
+ )}
+ >
+ <SiteSwitcherLink href="/repo" text="Repo" isActive={site === "repo"} />
+ <SiteSwitcherLink href="/pack" text="Pack" isActive={site === "pack"} />
+ </span>
+ </div>
+ );
+}
+
+export default SiteSwitcher;
diff --git a/docs/components/Social.tsx b/docs/components/Social.tsx
new file mode 100644
index 0000000..2ed0df0
--- /dev/null
+++ b/docs/components/Social.tsx
@@ -0,0 +1,32 @@
+import { DiscordIcon, GitHubIcon } from "nextra/icons";
+
+function Github() {
+ return (
+ <a
+ href="https://github.com/vercel/turbo"
+ className="hidden p-2 text-current sm:flex hover:opacity-75"
+ title="Turbo GitHub repo"
+ target="_blank"
+ rel="noreferrer"
+ >
+ {/* Nextra icons have a <title> attribute providing alt text */}
+ <GitHubIcon />
+ </a>
+ );
+}
+
+function Discord() {
+ return (
+ <a
+ href="https://turbo.build/discord"
+ className="hidden p-2 text-current sm:flex hover:opacity-75"
+ title="Turbo Discord server"
+ target="_blank"
+ rel="noreferrer"
+ >
+ <DiscordIcon />
+ </a>
+ );
+}
+
+export { Github, Discord };
diff --git a/docs/components/Tabs.tsx b/docs/components/Tabs.tsx
new file mode 100644
index 0000000..f75019a
--- /dev/null
+++ b/docs/components/Tabs.tsx
@@ -0,0 +1,37 @@
+import type { FC, ReactElement } from "react";
+
+import { Tabs as NextraTabs, Tab } from "nextra-theme-docs";
+import useSWR from "swr";
+
+export { Tab };
+
+export const Tabs: FC<{
+ storageKey?: string;
+ items: string[];
+ children: ReactElement;
+}> = function ({ storageKey = "tab-index", items, children = null, ...props }) {
+ // Use SWR so all tabs with the same key can sync their states.
+ const { data, mutate } = useSWR(storageKey, (key) => {
+ try {
+ return JSON.parse(localStorage.getItem(key));
+ } catch (e) {
+ return null;
+ }
+ });
+
+ const selectedIndex = items.indexOf(data);
+
+ return (
+ <NextraTabs
+ onChange={(index) => {
+ localStorage.setItem(storageKey, JSON.stringify(items[index]));
+ mutate(items[index], false);
+ }}
+ selectedIndex={selectedIndex === -1 ? undefined : selectedIndex}
+ items={items}
+ {...props}
+ >
+ {children}
+ </NextraTabs>
+ );
+};
diff --git a/docs/components/TurbopackFeatures.tsx b/docs/components/TurbopackFeatures.tsx
new file mode 100644
index 0000000..5570a52
--- /dev/null
+++ b/docs/components/TurbopackFeatures.tsx
@@ -0,0 +1,106 @@
+import {
+ AdjustmentsIcon,
+ ArchiveIcon,
+ DesktopComputerIcon,
+ DownloadIcon,
+ ServerIcon,
+} from "@heroicons/react/outline";
+import { DetailedFeatureLink } from "./Feature";
+import { CSSIcon, JSIcon, TSIcon } from "./Icons";
+
+export const TurbopackFeatures = () => {
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:gap-x-8 lg:gap-y-12">
+ <DetailedFeatureLink
+ feature={{
+ Icon: JSIcon,
+ description: `Supports all ESNext features, Browserslist and top-level await.`,
+ name: "JavaScript",
+ }}
+ href="/pack/docs/features/javascript"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: TSIcon,
+ description: (
+ <>
+ Supports TypeScript out of the box, including resolving{" "}
+ <code>paths</code> and <code>baseUrl</code>.
+ </>
+ ),
+ name: "TypeScript",
+ }}
+ href="/pack/docs/features/typescript"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: DownloadIcon,
+ description: (
+ <>
+ Supports <code>require</code>, <code>import</code>, dynamic
+ imports and more.
+ </>
+ ),
+ name: "Imports",
+ }}
+ href="/pack/docs/features/imports"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: DesktopComputerIcon,
+ description: `Our optimized dev server supports Hot Module Reloading (HMR) and Fast Refresh.`,
+ name: "Dev Server",
+ }}
+ href="/pack/docs/features/dev-server"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: CSSIcon,
+ description: (
+ <>
+ Supports Global CSS, CSS Modules, postcss-nested and{" "}
+ <code>@import</code>.
+ </>
+ ),
+ name: "CSS",
+ }}
+ href="/pack/docs/features/css"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: ArchiveIcon,
+ description:
+ "Learn about Next.js, Svelte, Vue and React Server Components support.",
+ name: "Frameworks",
+ }}
+ href="/pack/docs/features/frameworks"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: ServerIcon,
+ description: (
+ <>
+ Supports the <code>/public</code> directory, JSON imports, and
+ importing assets via ESM.
+ </>
+ ),
+ name: "Static Assets",
+ }}
+ href="/pack/docs/features/static-assets"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: AdjustmentsIcon,
+ description: (
+ <>
+ Supports environment variables via <code>.env</code>,{" "}
+ <code>.env.local</code>, and more.
+ </>
+ ),
+ name: "Environment Variables",
+ }}
+ href="/pack/docs/features/environment-variables"
+ ></DetailedFeatureLink>
+ </div>
+ );
+};
diff --git a/docs/components/TurbopackQuickstart.tsx b/docs/components/TurbopackQuickstart.tsx
new file mode 100644
index 0000000..695a947
--- /dev/null
+++ b/docs/components/TurbopackQuickstart.tsx
@@ -0,0 +1,28 @@
+import {
+ LightBulbIcon,
+ QuestionMarkCircleIcon,
+} from "@heroicons/react/outline";
+import { DetailedFeatureLink } from "./Feature";
+
+export const TurbopackQuickstartArea = () => {
+ return (
+ <div className="grid grid-cols-1 mt-12 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:gap-x-8 lg:gap-y-12">
+ <DetailedFeatureLink
+ feature={{
+ Icon: QuestionMarkCircleIcon,
+ description: `Learn why we created Turbopack, and why we think it’s the future of bundling for the web.`,
+ name: "Why Turbopack?",
+ }}
+ href="/pack/docs/why-turbopack"
+ ></DetailedFeatureLink>
+ <DetailedFeatureLink
+ feature={{
+ Icon: LightBulbIcon,
+ description: `Learn about the innovative architecture that powers Turbopack’s speed improvements.`,
+ name: "Core Concepts",
+ }}
+ href="/pack/docs/core-concepts"
+ ></DetailedFeatureLink>
+ </div>
+ );
+};
diff --git a/docs/components/Tweet.tsx b/docs/components/Tweet.tsx
new file mode 100644
index 0000000..e5ac388
--- /dev/null
+++ b/docs/components/Tweet.tsx
@@ -0,0 +1,46 @@
+import Image from "next/image";
+
+function TweetLink({ href, children }) {
+ return (
+ <a
+ href={href}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="inline-block text-[#35ACDF]"
+ >
+ {children}
+ </a>
+ );
+}
+
+export function Mention({ children }) {
+ return (
+ <TweetLink href={`https://twitter.com/${children.replace("@", "")}`}>
+ {children}
+ </TweetLink>
+ );
+}
+
+export default function Tweet({ url, username, name, avatar, date, children }) {
+ return (
+ <div className="flex p-4 bg-white rounded-md shadow-xl dark:bg-opacity-10">
+ <div className="flex-shrink-0 mr-4">
+ <Image
+ className="w-12 h-12 rounded-full"
+ width={42}
+ height={42}
+ src={avatar}
+ alt={`${name} twitter avatar`}
+ />
+ </div>
+ <div>
+ <div className="flex items-center space-x-1 text-sm">
+ <h4 className="font-medium dark:text-white">{name}</h4>
+ <div className="truncate dark:text-gray-400">@{username}</div>
+ <div className="dark:text-gray-500 md:hidden xl:block">• {date}</div>
+ </div>
+ <div className="mt-1 text-sm dark:text-gray-200">{children}</div>
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/blog/Date.tsx b/docs/components/blog/Date.tsx
new file mode 100644
index 0000000..506eecc
--- /dev/null
+++ b/docs/components/blog/Date.tsx
@@ -0,0 +1,21 @@
+import { ReactNode } from "react";
+
+function Date({
+ children,
+ update = null,
+}: {
+ children: ReactNode;
+ update?: string;
+}) {
+ return (
+ <div className="text-sm mt-2 text-center text-gray-500 dark:text-gray-400 font-space-grotesk">
+ {children}
+
+ {update != null && (
+ <div className="text-xs mt-1 text-center">Last updated {update}</div>
+ )}
+ </div>
+ );
+}
+
+export default Date;
diff --git a/docs/components/clients/Clients.tsx b/docs/components/clients/Clients.tsx
new file mode 100644
index 0000000..199794e
--- /dev/null
+++ b/docs/components/clients/Clients.tsx
@@ -0,0 +1,61 @@
+import React from "react";
+import cn from "classnames";
+import { users } from "./users";
+import { Logo } from "./Logo";
+
+export function Clients({
+ linked,
+ staticWidth,
+ companyList,
+}: {
+ linked?: boolean;
+ staticWidth?: boolean;
+ companyList?: string[];
+}) {
+ const showcaseDark = [];
+ const showcaseLight = [];
+
+ const LogoWrapper = ({ className, children }) => {
+ if (!staticWidth) return children;
+ return (
+ <div
+ className={cn(
+ "w-48 lg:w-40 flex items-center justify-center",
+ className
+ )}
+ >
+ {children}
+ </div>
+ );
+ };
+
+ users
+ .filter((i) => (companyList ? companyList.includes(i.caption) : true))
+ .forEach((user) => {
+ if (user.pinned) {
+ showcaseDark.push(
+ <LogoWrapper
+ key={`${user.caption}-dark`}
+ className="flex dark:hidden"
+ >
+ <Logo user={user} theme={"dark"} isLink={linked} />
+ </LogoWrapper>
+ );
+ showcaseLight.push(
+ <LogoWrapper
+ key={`${user.caption}-light`}
+ className="hidden dark:flex"
+ >
+ <Logo user={user} theme={"light"} isLink={linked} />
+ </LogoWrapper>
+ );
+ }
+ });
+
+ return (
+ <>
+ {showcaseDark}
+ {showcaseLight}
+ </>
+ );
+}
diff --git a/docs/components/clients/Filters.tsx b/docs/components/clients/Filters.tsx
new file mode 100644
index 0000000..3c22edc
--- /dev/null
+++ b/docs/components/clients/Filters.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+export const Filters: React.FC<{}> = () => (
+ <>
+ <svg width={0} height={0}>
+ <defs>
+ <filter id="high-threshold">
+ <feColorMatrix type="saturate" values="0" />
+ <feComponentTransfer>
+ <feFuncR type="discrete" tableValues="0" />
+ <feFuncG type="discrete" tableValues="0" />
+ <feFuncB type="discrete" tableValues="0" />
+ </feComponentTransfer>
+ </filter>
+ </defs>
+ </svg>
+ <svg width={0} height={0}>
+ <defs>
+ <filter id="medium-threshold">
+ <feColorMatrix type="saturate" values="0" />
+ <feComponentTransfer>
+ <feFuncR type="discrete" tableValues="0 1" />
+ <feFuncG type="discrete" tableValues="0 1" />
+ <feFuncB type="discrete" tableValues="0 1" />
+ </feComponentTransfer>
+ </filter>
+ </defs>
+ </svg>
+ <svg width={0} height={0}>
+ <defs>
+ <filter id="low-threshold">
+ <feColorMatrix type="saturate" values="0" />
+ <feComponentTransfer>
+ <feFuncR type="discrete" tableValues="0 0 0 0 1" />
+ <feFuncG type="discrete" tableValues="0 0 0 0 1" />
+ <feFuncB type="discrete" tableValues="0 0 0 0 1" />
+ </feComponentTransfer>
+ </filter>
+ </defs>
+ </svg>
+ </>
+);
diff --git a/docs/components/clients/Logo.tsx b/docs/components/clients/Logo.tsx
new file mode 100644
index 0000000..79ab9c2
--- /dev/null
+++ b/docs/components/clients/Logo.tsx
@@ -0,0 +1,67 @@
+import React from "react";
+import cn from "classnames";
+import Image from "next/image";
+import { TurboUser } from "./users";
+
+const DEFAULT_SIZE = {
+ width: 100,
+ height: 75,
+};
+
+export function Logo({
+ user,
+ theme,
+ isLink,
+}: {
+ user: TurboUser;
+ theme: "dark" | "light";
+ isLink: boolean;
+}) {
+ const styles = {
+ ...DEFAULT_SIZE,
+ ...user.style,
+ };
+ let numericWidth: number;
+ let numericHeight: number;
+ if (typeof styles.width === "number") {
+ numericWidth = styles.width;
+ }
+ if (typeof styles.height === "number") {
+ numericHeight = styles.height;
+ }
+ const logo = (
+ <Image
+ src={user.image.replace(
+ "/logos",
+ theme === "light" ? "/logos/white" : "/logos/color"
+ )}
+ alt={`${user.caption}'s Logo`}
+ width={numericWidth}
+ height={numericHeight}
+ priority={true}
+ style={styles}
+ className={cn("mx-8", {
+ "hidden dark:inline": theme !== "dark",
+ "dark:hidden inline": theme === "dark",
+ })}
+ />
+ );
+
+ if (isLink) {
+ return (
+ <a
+ href={user.infoLink}
+ target="_blank"
+ rel="noopener noreferrer"
+ className={cn("flex justify-center item-center", {
+ "hidden dark:flex": theme !== "dark",
+ "dark:hidden flex": theme === "dark",
+ })}
+ >
+ {logo}
+ </a>
+ );
+ }
+
+ return logo;
+}
diff --git a/docs/components/clients/Marquee.tsx b/docs/components/clients/Marquee.tsx
new file mode 100644
index 0000000..0e0fc6f
--- /dev/null
+++ b/docs/components/clients/Marquee.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+
+export function Marquee({ children, ...props }) {
+ return (
+ <div className="overflow-x-hidden">
+ <div className="sr-only">
+ These are the logos of some but not all of our users.
+ </div>
+ <div className="relative">
+ <div className="inline-block wrapper">{children}</div>
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/clients/users.ts b/docs/components/clients/users.ts
new file mode 100644
index 0000000..efd2172
--- /dev/null
+++ b/docs/components/clients/users.ts
@@ -0,0 +1,556 @@
+import { CSSProperties } from "react";
+
+export type TurboUser = {
+ caption: string;
+ image: string;
+ infoLink: string;
+ pinned?: boolean;
+ style?: CSSProperties;
+};
+
+export const users: Array<TurboUser> = [
+ {
+ caption: "Vercel",
+ image: "/images/logos/vercel.svg",
+ infoLink: "https://vercel.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "AWS",
+ image: "/images/logos/aws.svg",
+ infoLink: "https://aws.amazon.com/",
+ pinned: true,
+ style: {
+ width: 75,
+ },
+ },
+ {
+ caption: "Microsoft",
+ image: "/images/logos/microsoft.svg",
+ infoLink: "https://www.microsoft.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Netflix",
+ image: "/images/logos/netflix.svg",
+ infoLink: "https://netflix.com/",
+ pinned: true,
+ style: {
+ width: 110,
+ },
+ },
+ {
+ caption: "Disney",
+ image: "/images/logos/disney.svg",
+ infoLink: "https://www.disney.com/",
+ pinned: true,
+ },
+ {
+ caption: "Github",
+ image: "/images/logos/github.svg",
+ infoLink: "https://www.github.com/",
+ pinned: true,
+ style: {
+ width: 110,
+ },
+ },
+ {
+ caption: "Alibaba",
+ image: "/images/logos/alibaba.svg",
+ infoLink: "https://www.alibaba.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Ant Group",
+ image: "/images/logos/ant.svg",
+ infoLink: "https://antgroup.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Adobe",
+ image: "/images/logos/adobe.svg",
+ infoLink: "https://www.adobe.com/",
+ pinned: true,
+ },
+ {
+ caption: "PayPal",
+ image: "/images/logos/paypal.svg",
+ infoLink: "https://www.paypal.com/",
+ pinned: true,
+ },
+
+ {
+ caption: "Snap",
+ image: "/images/logos/snap.svg",
+ infoLink: "https://snap.com/",
+ pinned: true,
+ },
+ {
+ caption: "SAP",
+ image: "/images/logos/sap.svg",
+ infoLink: "https://www.sap.com/",
+ pinned: true,
+ style: {
+ width: 75,
+ },
+ },
+
+ {
+ caption: "Shopify",
+ image: "/images/logos/shopify.svg",
+ infoLink: "https://www.shopify.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+
+ {
+ caption: "Datadog",
+ image: "/images/logos/datadog.svg",
+ infoLink: "https://www.datadoghq.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Twilio",
+ image: "/images/logos/twilio.svg",
+ infoLink: "https://www.twilio.com/",
+ pinned: true,
+ },
+ {
+ caption: "Segment",
+ image: "/images/logos/segment.svg",
+ infoLink: "https://segment.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Twitch",
+ image: "/images/logos/twitch.svg",
+ infoLink: "https://www.twitch.tv/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Xiaomi",
+ image: "/images/logos/xiaomi.svg",
+ infoLink: "https://www.mi.com/",
+ pinned: true,
+ style: {
+ width: 50,
+ },
+ },
+ {
+ caption: "Line",
+ image: "/images/logos/line.svg",
+ infoLink: "https://line.me/",
+ pinned: true,
+ style: {
+ width: 75,
+ },
+ },
+ {
+ caption: "ESPN",
+ image: "/images/logos/espn.svg",
+ infoLink: "https://www.espn.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Volvo",
+ image: "/images/logos/volvo.svg",
+ infoLink: "https://www.volvo.com/",
+ pinned: true,
+ style: {
+ width: 60,
+ },
+ },
+ {
+ caption: "Hearst",
+ image: "/images/logos/hearst.svg",
+ infoLink: "https://www.hearst.com/",
+ pinned: true,
+ style: {
+ width: 175,
+ },
+ },
+ {
+ caption: "The Washington Post",
+ image: "/images/logos/washingtonpost.svg",
+ infoLink: "https://www.washingtonpost.com/",
+ pinned: true,
+ style: {
+ width: 175,
+ },
+ },
+ {
+ caption: "Wayfair",
+ image: "/images/logos/wayfair.svg",
+ infoLink: "https://www.wayfair.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Hulu",
+ image: "/images/logos/hulu.svg",
+ infoLink: "https://www.hulu.com/",
+ pinned: true,
+ },
+ {
+ caption: "CrowdStrike",
+ image: "/images/logos/crowdstrike.svg",
+ infoLink: "https://www.crowdstrike.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ marginTop: 20,
+ },
+ },
+ {
+ caption: "Binance",
+ image: "/images/logos/binance.svg",
+ infoLink: "https://www.binance.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Plex",
+ image: "/images/logos/plex.svg",
+ infoLink: "https://www.plex.tv/",
+ pinned: true,
+ },
+ {
+ caption: "Groupon",
+ image: "/images/logos/groupon.svg",
+ infoLink: "https://groupon.com/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Vimeo",
+ image: "/images/logos/vimeo.svg",
+ infoLink: "https://vimeo.com/",
+ pinned: true,
+ },
+ {
+ caption: "GoodRx",
+ image: "/images/logos/goodrx.svg",
+ infoLink: "https://www.goodrx.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Tripadvisor",
+ image: "/images/logos/tripadvisor.svg",
+ infoLink: "https://www.tripadvisor.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "RapidAPI",
+ image: "/images/logos/rapidapi.svg",
+ infoLink: "https://rapidapi.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Miro",
+ image: "/images/logos/miro.svg",
+ infoLink: "https://miro.com/",
+ pinned: true,
+ },
+ {
+ caption: "Lattice",
+ image: "/images/logos/lattice.svg",
+ infoLink: "https://lattice.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Watershed",
+ image: "/images/logos/watershed.svg",
+ infoLink: "https://watershed.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "N26",
+ image: "/images/logos/n26.svg",
+ infoLink: "https://n26.com/",
+ pinned: true,
+ style: {
+ width: 75,
+ },
+ },
+ {
+ caption: "Sourcegraph",
+ image: "/images/logos/sourcegraph.svg",
+ infoLink: "https://sourcegraph.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Big Commerce",
+ image: "/images/logos/bigcommerce.svg",
+ infoLink: "https://www.bigcommerce.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Stedi",
+ image: "/images/logos/stedi.svg",
+ infoLink: "https://www.stedi.com/",
+ pinned: true,
+ style: {
+ width: 75,
+ },
+ },
+ {
+ caption: "Framer",
+ image: "/images/logos/framer.svg",
+ infoLink: "https://www.framer.com/",
+ pinned: true,
+ },
+ {
+ caption: "Maze",
+ image: "/images/logos/maze.svg",
+ infoLink: "https://maze.co/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Builder.io",
+ image: "/images/logos/builderio.svg",
+ infoLink: "https://www.builder.io/",
+ pinned: true,
+ style: {
+ width: 125,
+ },
+ },
+ {
+ caption: "Contentful",
+ image: "/images/logos/contentful.svg",
+ infoLink: "https://www.contentful.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Xata",
+ image: "/images/logos/xata.svg",
+ infoLink: "https://xata.io/",
+ pinned: true,
+ },
+ {
+ caption: "Cal.com",
+ image: "/images/logos/calcom.svg",
+ infoLink: "https://cal.com/",
+ pinned: true,
+ },
+ {
+ caption: "Codesandbox",
+ image: "/images/logos/codesandbox.svg",
+ infoLink: "https://codesandbox.io/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "WooCommerce",
+ image: "/images/logos/woocommerce.svg",
+ infoLink: "https://woocommerce.com/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Expo",
+ image: "/images/logos/expo.svg",
+ infoLink: "https://expo.dev/",
+ pinned: true,
+ },
+ {
+ caption: "TeeSpring",
+ image: "/images/logos/teespring.svg",
+ infoLink: "https://www.spri.ng/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Endear",
+ image: "/images/logos/endear.svg",
+ infoLink: "https://endearhq.com/",
+ pinned: true,
+ },
+ {
+ caption: "Makeswift",
+ image: "/images/logos/makeswift.svg",
+ infoLink: "https://www.makeswift.com/",
+ pinned: true,
+ },
+ {
+ caption: "Fandom",
+ image: "/images/logos/fandom.svg",
+ infoLink: "https://www.fandom.com/",
+ pinned: true,
+ },
+ {
+ caption: "Waggel",
+ image: "/images/logos/waggel.svg",
+ infoLink: "https://www.waggel.co.uk/",
+ pinned: true,
+ },
+ {
+ caption: "n8n",
+ image: "/images/logos/n8n.svg",
+ infoLink: "https://n8n.io/",
+ pinned: true,
+ },
+ {
+ caption: "React Flow",
+ image: "/images/logos/reactflow.svg",
+ infoLink: "https://reactflow.dev/",
+ pinned: true,
+ },
+ {
+ caption: "Agrotoken",
+ image: "/images/logos/agrotoken.svg",
+ infoLink: "https://agrotoken.io/",
+ pinned: true,
+ },
+ {
+ caption: "Rocket.Chat",
+ image: "/images/logos/rocketchat.svg",
+ infoLink: "https://www.rocket.chat/",
+ pinned: true,
+ },
+ {
+ caption: "Backpack",
+ image: "/images/logos/backpack.svg",
+ infoLink: "https://www.backpack.app/",
+ pinned: true,
+ },
+ {
+ caption: "Supernova.io",
+ image: "/images/logos/supernova.svg",
+ infoLink: "https://www.supernova.io/",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "BuildPass",
+ image: "/images/logos/buildpass.svg",
+ infoLink: "https://www.buildpass.com.au/",
+ pinned: true,
+ style: {
+ width: 175,
+ },
+ },
+ {
+ caption: "SolanaFM",
+ image: "/images/logos/solanafm.svg",
+ infoLink: "https://solana.fm/",
+ pinned: true,
+ },
+ {
+ caption: "Authdog",
+ image: "/images/logos/authdog.svg",
+ infoLink: "https://www.authdog.com/",
+ pinned: true,
+ style: {
+ width: 175,
+ },
+ },
+ {
+ caption: "Nhost",
+ image: "/images/logos/nhost.svg",
+ infoLink: "https://nhost.io/",
+ pinned: true,
+ },
+ {
+ caption: "LG U+",
+ image: "/images/logos/lguplus.svg",
+ infoLink: "https://www.lguplus.com/about/en",
+ pinned: true,
+ style: {
+ width: 150,
+ },
+ },
+ {
+ caption: "Comparastore",
+ image: "/images/logos/comparastore.svg",
+ infoLink: "https://www.comparastore.com",
+ pinned: true,
+ style: {
+ width: 175,
+ },
+ },
+ {
+ caption: "Block Protocol",
+ image: "/images/logos/blockprotocol.svg",
+ infoLink: "https://blockprotocol.org/",
+ pinned: true,
+ },
+ {
+ caption: "HASH",
+ image: "/images/logos/hash.svg",
+ infoLink: "https://hash.dev/",
+ pinned: true,
+ },
+ {
+ caption: "Chợ Tốt",
+ image: "/images/logos/chotot.svg",
+ infoLink: "https://chotot.com/",
+ pinned: true,
+ },
+];
diff --git a/docs/components/counters.module.css b/docs/components/counters.module.css
deleted file mode 100644
index 4a5d0c8..0000000
--- a/docs/components/counters.module.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.counter {
- border: 1px solid #ccc;
- border-radius: 5px;
- padding: 2px 6px;
- margin: 12px 0 0;
-}
diff --git a/docs/components/counters.tsx b/docs/components/counters.tsx
deleted file mode 100644
index b78f12d..0000000
--- a/docs/components/counters.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-// Example from https://beta.reactjs.org/learn
-
-import { useState } from 'react'
-import styles from './counters.module.css'
-
-function MyButton() {
- const [count, setCount] = useState(0)
-
- function handleClick() {
- setCount(count + 1)
- }
-
- return (
- <div>
- <button onClick={handleClick} className={styles.counter}>
- Clicked {count} times
- </button>
- </div>
- )
-}
-
-export default function MyApp() {
- return <MyButton />
-}
diff --git a/docs/components/header-logo.module.css b/docs/components/header-logo.module.css
new file mode 100644
index 0000000..515486e
--- /dev/null
+++ b/docs/components/header-logo.module.css
@@ -0,0 +1,20 @@
+.desktopLogo {
+ display: none;
+
+ @media (min-width: 768px) {
+ display: block;
+ }
+}
+
+.siteSwitcher {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+
+ @media (min-width: 768px) {
+ margin-left: 12px;
+ position: relative;
+ left: 0;
+ transform: none;
+ }
+}
diff --git a/docs/components/image/ImageFigure.tsx b/docs/components/image/ImageFigure.tsx
new file mode 100644
index 0000000..b90237e
--- /dev/null
+++ b/docs/components/image/ImageFigure.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+import Image from "next/image";
+
+type ImageProps = Parameters<typeof Image>[0];
+
+export type ImageFigureProps = ImageProps & {
+ caption?: string;
+ margin?: number;
+ captionSpacing?: number;
+ shadow?: boolean;
+ borderRadius?: boolean;
+};
+
+export function ImageFigure(props: ImageFigureProps): React.ReactNode {
+ const {
+ caption,
+ margin = 40,
+ captionSpacing = null,
+ shadow = false,
+ borderRadius = false,
+ ...rest
+ } = props;
+
+ return (
+ <figure className="block text-center" style={{ margin: `${margin}px 0` }}>
+ <div className="relative inline-block w-full max-w-full overflow-hidden border-box text-[0px]">
+ {/* eslint-disable-next-line jsx-a11y/alt-text */}
+ <Image {...rest} />
+ </div>
+ {caption && (
+ <figcaption
+ className="m-0 text-xs text-center text-gray-500"
+ style={captionSpacing ? { marginTop: captionSpacing } : {}}
+ >
+ {caption}
+ </figcaption>
+ )}
+ </figure>
+ );
+}
diff --git a/docs/components/image/ThemedImage.tsx b/docs/components/image/ThemedImage.tsx
new file mode 100644
index 0000000..0416bd9
--- /dev/null
+++ b/docs/components/image/ThemedImage.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import Image from "next/image";
+
+export interface Image {
+ height: number;
+ width: number;
+ source: string;
+}
+
+export interface ThemedImageProps {
+ title?: string;
+ dark?: Image;
+ light?: Image;
+ priority?: boolean;
+}
+
+export function ThemedImage({
+ title,
+ light,
+ dark,
+ priority = false,
+}: ThemedImageProps) {
+ return (
+ <>
+ <div className="block w-full dark:hidden">
+ <Image
+ alt={title}
+ src={light.source}
+ width={light.width}
+ height={light.height}
+ priority={priority}
+ />
+ </div>
+ <div className="hidden w-full dark:block">
+ <Image
+ alt={title}
+ src={dark.source}
+ width={dark.width}
+ height={dark.height}
+ priority={priority}
+ />
+ </div>
+ </>
+ );
+}
diff --git a/docs/components/image/ThemedImageFigure.tsx b/docs/components/image/ThemedImageFigure.tsx
new file mode 100644
index 0000000..9c50dc7
--- /dev/null
+++ b/docs/components/image/ThemedImageFigure.tsx
@@ -0,0 +1,47 @@
+import React from "react";
+import { ImageFigureProps } from "./ImageFigure";
+import { ThemedImage, ThemedImageProps } from "./ThemedImage";
+import cn from "classnames";
+export type ThemedImageFigureProps = Omit<ImageFigureProps, "src"> &
+ ThemedImageProps;
+
+export function ThemedImageFigure(
+ props: ThemedImageFigureProps
+): React.ReactNode {
+ const {
+ caption,
+ margin = 40,
+ captionSpacing = null,
+ shadow = false,
+ borderRadius = false,
+ ...rest
+ } = props;
+
+ return (
+ <figure
+ className="block -mx-4 text-center sm:-mx-4 md:-mx-7 lg:-mx-12"
+ style={{ marginTop: `${margin}px`, marginBottom: `${margin}px` }}
+ >
+ <div
+ className={cn(
+ "relative inline-block max-w-full overflow-hidden border-box text-[0px]",
+ {
+ "rounded-md": borderRadius,
+ "shadow-lg": shadow,
+ }
+ )}
+ >
+ {/* eslint-disable-next-line jsx-a11y/alt-text */}
+ <ThemedImage {...rest} />
+ </div>
+ {caption && (
+ <figcaption
+ className="m-0 text-xs text-center text-gray-500"
+ style={captionSpacing ? { marginTop: captionSpacing } : {}}
+ >
+ {caption}
+ </figcaption>
+ )}
+ </figure>
+ );
+}
diff --git a/docs/components/logos/PackLogo.tsx b/docs/components/logos/PackLogo.tsx
new file mode 100644
index 0000000..ddb1d36
--- /dev/null
+++ b/docs/components/logos/PackLogo.tsx
@@ -0,0 +1,16 @@
+const PackLogo = (props) => (
+ <svg {...props} viewBox="0 0 485 49" xmlns="http://www.w3.org/2000/svg">
+ <title>Turbopack logo</title>
+ <path d="M51.1928 12.1277V1.56075H0.267822V12.1277H19.492V47.2659H31.9686V12.1277H51.1928Z" />
+ <path d="M79.966 47.9662C95.6254 47.9662 104.219 40.0091 104.219 26.8959V1.56075H91.7424V25.6865C91.7424 33.0069 87.923 37.1446 79.966 37.1446C72.0089 37.1446 68.1895 33.0069 68.1895 25.6865V1.56075H55.7129V26.8959C55.7129 40.0091 64.3065 47.9662 79.966 47.9662Z" />
+ <path d="M123.318 32.1157H135.731L145.534 47.2659H159.857L148.78 30.779C155.019 28.551 158.838 23.5858 158.838 16.8382C158.838 7.03519 151.518 1.56075 140.378 1.56075H110.841V47.2659H123.318V32.1157ZM123.318 22.249V12.0004H139.741C144.133 12.0004 146.552 13.9101 146.552 17.1565C146.552 20.212 144.133 22.249 139.741 22.249H123.318Z" />
+ <path d="M164.643 47.2659H197.299C207.484 47.2659 213.34 42.4281 213.34 34.3437C213.34 28.9329 209.903 25.2409 205.829 23.5858C208.63 22.249 212.067 19.0662 212.067 14.0374C212.067 5.95303 206.338 1.56075 196.217 1.56075H164.643V47.2659ZM176.611 19.4482V11.6821H194.944C198.381 11.6821 200.291 13.0189 200.291 15.5651C200.291 18.1114 198.381 19.4482 194.944 19.4482H176.611ZM176.611 28.8056H196.089C199.463 28.8056 201.309 30.4607 201.309 32.9433C201.309 35.4259 199.463 37.0809 196.089 37.0809H176.611V28.8056Z" />
+ <path d="M243.845 0.796875C227.868 0.796875 216.346 10.7909 216.346 24.4133C216.346 38.0358 227.868 48.0298 243.845 48.0298C259.823 48.0298 271.281 38.0358 271.281 24.4133C271.281 10.7909 259.823 0.796875 243.845 0.796875ZM243.845 11.6184C252.248 11.6184 258.55 16.5836 258.55 24.4133C258.55 32.2431 252.248 37.2083 243.845 37.2083C235.443 37.2083 229.141 32.2431 229.141 24.4133C229.141 16.5836 235.443 11.6184 243.845 11.6184Z" />
+ <path d="M289.262 32.6887H305.876C317.016 32.6887 324.336 27.3415 324.336 17.1565C324.336 6.90788 317.016 1.56075 305.876 1.56075H276.785V47.2659H289.262V32.6887ZM289.262 22.3127V12.0004H305.176C309.632 12.0004 312.051 13.9101 312.051 17.1565C312.051 20.3393 309.632 22.3127 305.176 22.3127H289.262Z" />
+ <path d="M341.036 1.56075L318.565 47.2659H331.806L335.943 38.6723H359.942L364.079 47.2659H377.765L355.358 1.56075H341.036ZM348.038 13.7827L355.167 28.6783H340.845L348.038 13.7827Z" />
+ <path d="M375.141 24.4133C375.141 38.0358 386.535 48.0298 402.45 48.0298C415.181 48.0298 424.029 42.3007 427.53 33.0706L416.136 27.9781C414.353 33.3252 409.706 37.2083 402.45 37.2083C394.302 37.2083 387.872 32.2431 387.872 24.4133C387.872 16.5836 394.302 11.6184 402.45 11.6184C409.706 11.6184 414.353 15.5015 416.136 20.8486L427.53 15.7561C424.029 6.52594 415.181 0.796875 402.45 0.796875C386.535 0.796875 375.141 10.7909 375.141 24.4133Z" />
+ <path d="M444.513 1.56075H432.037V47.2659H444.513V35.8715L452.661 28.551L468.512 47.2659H484.362L461.637 20.5303L482.771 1.56075H465.775L444.513 20.8486V1.56075Z" />
+ </svg>
+);
+
+export default PackLogo;
diff --git a/docs/components/logos/RepoLogo.tsx b/docs/components/logos/RepoLogo.tsx
new file mode 100644
index 0000000..2751733
--- /dev/null
+++ b/docs/components/logos/RepoLogo.tsx
@@ -0,0 +1,16 @@
+const RepoLogo = (props) => (
+ <svg {...props} viewBox="0 0 506 50" xmlns="http://www.w3.org/2000/svg">
+ <title>Turborepo logo</title>
+ <path d="M53.7187 12.0038V1.05332H0.945312V12.0038H20.8673V48.4175H33.7968V12.0038H53.7187Z" />
+ <path d="M83.5362 49.1431C99.764 49.1431 108.67 40.8972 108.67 27.3081V1.05332H95.7401V26.0547C95.7401 33.6409 91.7821 37.9287 83.5362 37.9287C75.2904 37.9287 71.3324 33.6409 71.3324 26.0547V1.05332H58.4029V27.3081C58.4029 40.8972 67.3084 49.1431 83.5362 49.1431Z" />
+ <path d="M128.462 32.7174H141.325L151.484 48.4175H166.327L154.848 31.3321C161.313 29.0232 165.271 23.8778 165.271 16.8853C165.271 6.72646 157.685 1.05332 146.141 1.05332H115.532V48.4175H128.462V32.7174ZM128.462 22.4925V11.8719H145.481C150.033 11.8719 152.54 13.8509 152.54 17.2152C152.54 20.3816 150.033 22.4925 145.481 22.4925H128.462Z" />
+ <path d="M171.287 48.4175H205.128C215.683 48.4175 221.752 43.404 221.752 35.0262C221.752 29.419 218.189 25.593 213.967 23.8778C216.87 22.4925 220.432 19.1942 220.432 13.9828C220.432 5.60502 214.495 1.05332 204.006 1.05332H171.287V48.4175ZM183.689 19.59V11.542H202.687C206.249 11.542 208.228 12.9273 208.228 15.566C208.228 18.2047 206.249 19.59 202.687 19.59H183.689ZM183.689 29.2871H203.875C207.371 29.2871 209.284 31.0022 209.284 33.5749C209.284 36.1476 207.371 37.8628 203.875 37.8628H183.689V29.2871Z" />
+ <path d="M253.364 0.261719C236.806 0.261719 224.866 10.6185 224.866 24.7354C224.866 38.8523 236.806 49.2091 253.364 49.2091C269.922 49.2091 281.796 38.8523 281.796 24.7354C281.796 10.6185 269.922 0.261719 253.364 0.261719ZM253.364 11.4761C262.072 11.4761 268.602 16.6215 268.602 24.7354C268.602 32.8493 262.072 37.9947 253.364 37.9947C244.656 37.9947 238.126 32.8493 238.126 24.7354C238.126 16.6215 244.656 11.4761 253.364 11.4761Z" />
+ <path d="M300.429 32.7174H313.292L323.451 48.4175H338.294L326.815 31.3321C333.28 29.0232 337.238 23.8778 337.238 16.8853C337.238 6.72646 329.652 1.05332 318.108 1.05332H287.499V48.4175H300.429V32.7174ZM300.429 22.4925V11.8719H317.448C322 11.8719 324.507 13.8509 324.507 17.2152C324.507 20.3816 322 22.4925 317.448 22.4925H300.429Z" />
+ <path d="M343.254 1.05332V48.4175H389.299V37.467H355.92V29.7489H385.539V19.0622H355.92V12.0038H389.299V1.05332H343.254Z" />
+ <path d="M408.46 33.3111H425.677C437.221 33.3111 444.807 27.7699 444.807 17.2152C444.807 6.59453 437.221 1.05332 425.677 1.05332H395.53V48.4175H408.46V33.3111ZM408.46 22.5585V11.8719H424.951C429.569 11.8719 432.076 13.8509 432.076 17.2152C432.076 20.5135 429.569 22.5585 424.951 22.5585H408.46Z" />
+ <path d="M476.899 0.261719C460.341 0.261719 448.401 10.6185 448.401 24.7354C448.401 38.8523 460.341 49.2091 476.899 49.2091C493.456 49.2091 505.33 38.8523 505.33 24.7354C505.33 10.6185 493.456 0.261719 476.899 0.261719ZM476.899 11.4761C485.606 11.4761 492.137 16.6215 492.137 24.7354C492.137 32.8493 485.606 37.9947 476.899 37.9947C468.191 37.9947 461.66 32.8493 461.66 24.7354C461.66 16.6215 468.191 11.4761 476.899 11.4761Z" />
+ </svg>
+);
+
+export default RepoLogo;
diff --git a/docs/components/logos/Turbo.tsx b/docs/components/logos/Turbo.tsx
new file mode 100644
index 0000000..d873e5f
--- /dev/null
+++ b/docs/components/logos/Turbo.tsx
@@ -0,0 +1,62 @@
+type LogoProps = {
+ className?: string;
+ height?: number;
+};
+
+const Turbo = ({ height = 32, className = "" }: LogoProps) => (
+ <svg
+ className={className}
+ width="112"
+ height={height}
+ viewBox="0 0 112 28"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <title>Turborepo</title>
+ <path
+ d="M48.2623 11.2944V8.24418H33.5623V11.2944H39.1115V21.4374H42.713V11.2944H48.2623Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M56.5679 21.6396C61.0882 21.6396 63.5688 19.3427 63.5688 15.5574V8.24418H59.9673V15.2083C59.9673 17.3214 58.8648 18.5158 56.5679 18.5158C54.271 18.5158 53.1685 17.3214 53.1685 15.2083V8.24418H49.567V15.5574C49.567 19.3427 52.0476 21.6396 56.5679 21.6396Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M69.0819 17.0642H72.665L75.4947 21.4374H79.6291L76.4319 16.6783C78.2326 16.0352 79.3351 14.6019 79.3351 12.6542C79.3351 9.82443 77.222 8.24418 74.0064 8.24418H65.4804V21.4374H69.0819V17.0642ZM69.0819 14.2161V11.2577H73.8226C75.0905 11.2577 75.7887 11.8089 75.7887 12.7461C75.7887 13.6281 75.0905 14.2161 73.8226 14.2161H69.0819Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M81.0108 21.4374H90.4372C93.3772 21.4374 95.0677 20.0409 95.0677 17.7073C95.0677 16.1454 94.0754 15.0797 92.8994 14.6019C93.7079 14.2161 94.7002 13.2973 94.7002 11.8457C94.7002 9.51206 93.0464 8.24418 90.1248 8.24418H81.0108V21.4374ZM84.4653 13.4076V11.1658H89.7573C90.7496 11.1658 91.3008 11.5517 91.3008 12.2867C91.3008 13.0217 90.7496 13.4076 89.7573 13.4076H84.4653ZM84.4653 16.1087H90.0881C91.0619 16.1087 91.5948 16.5864 91.5948 17.3031C91.5948 18.0197 91.0619 18.4974 90.0881 18.4974H84.4653V16.1087Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M103.873 8.02368C99.2612 8.02368 95.9353 10.9086 95.9353 14.8408C95.9353 18.7731 99.2612 21.6579 103.873 21.6579C108.485 21.6579 111.793 18.7731 111.793 14.8408C111.793 10.9086 108.485 8.02368 103.873 8.02368ZM103.873 11.1474C106.299 11.1474 108.118 12.5807 108.118 14.8408C108.118 17.1009 106.299 18.5342 103.873 18.5342C101.448 18.5342 99.6287 17.1009 99.6287 14.8408C99.6287 12.5807 101.448 11.1474 103.873 11.1474Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M13.9396 6.42181C9.79423 6.42181 6.42163 9.79441 6.42163 13.9398C6.42163 18.0852 9.79423 21.4578 13.9396 21.4578C18.085 21.4578 21.4576 18.0852 21.4576 13.9398C21.4576 9.79441 18.085 6.42181 13.9396 6.42181ZM13.9396 17.8304C11.7906 17.8304 10.049 16.0888 10.049 13.9398C10.049 11.7908 11.7906 10.0492 13.9396 10.0492C16.0886 10.0492 17.8302 11.7908 17.8302 13.9398C17.8302 16.0888 16.0886 17.8304 13.9396 17.8304Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M14.5697 5.187V2.38C20.6709 2.7062 25.5177 7.7574 25.5177 13.9398C25.5177 20.1222 20.6709 25.172 14.5697 25.4996V22.6926C19.1169 22.3678 22.7177 18.5682 22.7177 13.9398C22.7177 9.3114 19.1169 5.5118 14.5697 5.187ZM7.30928 19.6798C6.10388 18.2882 5.32688 16.5158 5.18828 14.5698H2.37988C2.52548 17.2928 3.61468 19.7638 5.32128 21.6664L7.30788 19.6798H7.30928ZM13.3097 25.4996V22.6926C11.3623 22.554 9.5899 21.7784 8.1983 20.5716L6.2117 22.5582C8.1157 24.2662 10.5867 25.354 13.3083 25.4996H13.3097Z"
+ fill="url(#paint0_linear_531_5968)"
+ />
+ <defs>
+ <linearGradient
+ id="paint0_linear_531_5968"
+ x1="15.0234"
+ y1="4.00556"
+ x2="3.64419"
+ y2="15.3847"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
+
+export default Turbo;
diff --git a/docs/components/logos/TurboAnimated.tsx b/docs/components/logos/TurboAnimated.tsx
new file mode 100644
index 0000000..c70f42d
--- /dev/null
+++ b/docs/components/logos/TurboAnimated.tsx
@@ -0,0 +1,157 @@
+import { AnimatePresence, motion, Variants } from "framer-motion";
+import { useTurboSite } from "../SiteSwitcher";
+import cn from "classnames";
+import styles from "../header-logo.module.css";
+
+type LogoProps = {
+ className?: string;
+ height?: number;
+};
+
+const LEFT_PADDING = 8;
+const RIGHT_PADDING = 12;
+// The width of the logo + wordmark. This does not include the "invisible" padding.
+const VISUAL_WIDTH = 112;
+
+const TurboAnimated = ({ height = 32, className = "" }: LogoProps) => {
+ const site = useTurboSite();
+
+ return (
+ <svg
+ className={className}
+ width={LEFT_PADDING + VISUAL_WIDTH}
+ height={height}
+ viewBox={`0 0 ${LEFT_PADDING + VISUAL_WIDTH} 28`}
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <title>Turborepo</title>
+
+ <defs>
+ <linearGradient
+ id="logo-ring-gradient"
+ x1="15.0234"
+ y1="4.00556"
+ x2="3.64419"
+ y2="15.3847"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ <linearGradient id="gradient">
+ <stop offset="0%" stopColor="#000000" />
+ <stop offset="25%" stopColor="#ffffff" />
+ <stop offset="85%" stopColor="#ffffff" />
+ <stop offset="100%" stopColor="#000000" />
+ </linearGradient>
+ <mask id="logo-mask">
+ <rect
+ x="0"
+ y="0"
+ width={`${26 + LEFT_PADDING + RIGHT_PADDING}`}
+ height="26"
+ fill="url(#gradient)"
+ transform="translate(-8,0)"
+ />
+ </mask>
+ </defs>
+
+ <g mask="url(#logo-mask)" transform={`translate(${LEFT_PADDING},0)`}>
+ <AnimatePresence mode="sync" initial={false}>
+ {site === "repo" || site === undefined ? (
+ <motion.g
+ key="turborepo"
+ variants={variants}
+ initial="hidden"
+ animate="visible"
+ exit="hidden"
+ custom={24}
+ className="z-0 relative"
+ >
+ <path
+ d="M13.9396 6.42181C9.79423 6.42181 6.42163 9.79441 6.42163 13.9398C6.42163 18.0852 9.79423 21.4578 13.9396 21.4578C18.085 21.4578 21.4576 18.0852 21.4576 13.9398C21.4576 9.79441 18.085 6.42181 13.9396 6.42181ZM13.9396 17.8304C11.7906 17.8304 10.049 16.0888 10.049 13.9398C10.049 11.7908 11.7906 10.0492 13.9396 10.0492C16.0886 10.0492 17.8302 11.7908 17.8302 13.9398C17.8302 16.0888 16.0886 17.8304 13.9396 17.8304Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M14.5697 5.187V2.38C20.6709 2.7062 25.5177 7.7574 25.5177 13.9398C25.5177 20.1222 20.6709 25.172 14.5697 25.4996V22.6926C19.1169 22.3678 22.7177 18.5682 22.7177 13.9398C22.7177 9.3114 19.1169 5.5118 14.5697 5.187ZM7.30928 19.6798C6.10388 18.2882 5.32688 16.5158 5.18828 14.5698H2.37988C2.52548 17.2928 3.61468 19.7638 5.32128 21.6664L7.30788 19.6798H7.30928ZM13.3097 25.4996V22.6926C11.3623 22.554 9.5899 21.7784 8.1983 20.5716L6.2117 22.5582C8.1157 24.2662 10.5867 25.354 13.3083 25.4996H13.3097Z"
+ fill="url(#logo-ring-gradient)"
+ />
+ </motion.g>
+ ) : (
+ <motion.g
+ key="turbopack"
+ variants={variants}
+ initial="hidden"
+ animate="visible"
+ exit="hidden"
+ custom={-24}
+ className="z-0 relative"
+ >
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M10.9443 10.4846C10.6829 10.4846 10.4709 10.6966 10.4709 10.958V16.2234C10.4709 16.4849 10.6829 16.6968 10.9443 16.6968H16.2097C16.4712 16.6968 16.6831 16.4849 16.6831 16.2234V10.958C16.6831 10.6966 16.4712 10.4846 16.2097 10.4846H10.9443ZM7.14628 6.72631C6.90676 6.72631 6.71263 6.92045 6.71263 7.15994V20.0215C6.71263 20.261 6.90676 20.4551 7.14628 20.4551H20.0078C20.2473 20.4551 20.4414 20.261 20.4414 20.0215V7.15994C20.4414 6.92045 20.2473 6.72631 20.0078 6.72631H7.14628Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M2 13.8059V22.1684C2 22.7295 2.15656 23.254 2.42838 23.7007L5.44339 20.6857V13.8059H2ZM3.37481 24.664L6.36423 21.6745C6.38505 21.6758 6.40605 21.6765 6.42723 21.6765H13.0144V25.12H4.95147C4.37169 25.12 3.83094 24.9528 3.37481 24.664ZM14.3648 25.12H22.1684C23.7985 25.12 25.12 23.7985 25.12 22.1684V4.95147C25.12 3.32142 23.7985 2 22.1684 2H13.8059V5.44339H20.6927C21.236 5.44339 21.6765 5.88386 21.6765 6.42723V20.6927C21.6765 21.2361 21.236 21.6765 20.6927 21.6765H14.3648V25.12Z"
+ fill="url(#logo-ring-gradient)"
+ />
+ </motion.g>
+ )}
+ </AnimatePresence>
+ </g>
+
+ {/* Turbo Wordmark */}
+ <g
+ className={cn("z-10 relative", styles.desktopLogo)}
+ transform={`translate(${LEFT_PADDING},0)`}
+ >
+ <path
+ d="M48.2623 11.2944V8.24418H33.5623V11.2944H39.1115V21.4374H42.713V11.2944H48.2623Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M56.5679 21.6396C61.0882 21.6396 63.5688 19.3427 63.5688 15.5574V8.24418H59.9673V15.2083C59.9673 17.3214 58.8648 18.5158 56.5679 18.5158C54.271 18.5158 53.1685 17.3214 53.1685 15.2083V8.24418H49.567V15.5574C49.567 19.3427 52.0476 21.6396 56.5679 21.6396Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M69.0819 17.0642H72.665L75.4947 21.4374H79.6291L76.4319 16.6783C78.2326 16.0352 79.3351 14.6019 79.3351 12.6542C79.3351 9.82443 77.222 8.24418 74.0064 8.24418H65.4804V21.4374H69.0819V17.0642ZM69.0819 14.2161V11.2577H73.8226C75.0905 11.2577 75.7887 11.8089 75.7887 12.7461C75.7887 13.6281 75.0905 14.2161 73.8226 14.2161H69.0819Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M81.0108 21.4374H90.4372C93.3772 21.4374 95.0677 20.0409 95.0677 17.7073C95.0677 16.1454 94.0754 15.0797 92.8994 14.6019C93.7079 14.2161 94.7002 13.2973 94.7002 11.8457C94.7002 9.51206 93.0464 8.24418 90.1248 8.24418H81.0108V21.4374ZM84.4653 13.4076V11.1658H89.7573C90.7496 11.1658 91.3008 11.5517 91.3008 12.2867C91.3008 13.0217 90.7496 13.4076 89.7573 13.4076H84.4653ZM84.4653 16.1087H90.0881C91.0619 16.1087 91.5948 16.5864 91.5948 17.3031C91.5948 18.0197 91.0619 18.4974 90.0881 18.4974H84.4653V16.1087Z"
+ className="dark:fill-white fill-black"
+ />
+ <path
+ d="M103.873 8.02368C99.2612 8.02368 95.9353 10.9086 95.9353 14.8408C95.9353 18.7731 99.2612 21.6579 103.873 21.6579C108.485 21.6579 111.793 18.7731 111.793 14.8408C111.793 10.9086 108.485 8.02368 103.873 8.02368ZM103.873 11.1474C106.299 11.1474 108.118 12.5807 108.118 14.8408C108.118 17.1009 106.299 18.5342 103.873 18.5342C101.448 18.5342 99.6287 17.1009 99.6287 14.8408C99.6287 12.5807 101.448 11.1474 103.873 11.1474Z"
+ className="dark:fill-white fill-black"
+ />
+ </g>
+ </svg>
+ );
+};
+
+export default TurboAnimated;
+
+const variants: Variants = {
+ visible: {
+ opacity: 1,
+ x: 0,
+ transition: {
+ duration: 0.3,
+ },
+ },
+ hidden: (distance) => ({
+ opacity: 0,
+ x: distance,
+ transition: {
+ duration: 0.3,
+ },
+ }),
+};
diff --git a/docs/components/logos/Vercel.tsx b/docs/components/logos/Vercel.tsx
new file mode 100644
index 0000000..36b5e81
--- /dev/null
+++ b/docs/components/logos/Vercel.tsx
@@ -0,0 +1,11 @@
+const Vercel = ({ height = 20 }: { height?: number }) => (
+ <svg height={height} viewBox="0 0 283 64" fill="none">
+ <title>Vercel</title>
+ <path
+ fill="currentColor"
+ d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z"
+ />
+ </svg>
+);
+
+export default Vercel;
diff --git a/docs/components/logos/og/PackLogo.tsx b/docs/components/logos/og/PackLogo.tsx
new file mode 100644
index 0000000..a2bc95a
--- /dev/null
+++ b/docs/components/logos/og/PackLogo.tsx
@@ -0,0 +1,72 @@
+const PackLogo = (props) => (
+ <svg
+ {...props}
+ viewBox="0 0 697 103"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ d="M39.8024 37.699C38.6407 37.699 37.699 38.6407 37.699 39.8024V63.1976C37.699 64.3593 38.6407 65.301 39.8024 65.301H63.1976C64.3593 65.301 65.301 64.3593 65.301 63.1976V39.8024C65.301 38.6407 64.3593 37.699 63.1976 37.699H39.8024ZM22.9267 21C21.8626 21 21 21.8626 21 22.9267V80.0733C21 81.1374 21.8626 82 22.9267 82H80.0733C81.1374 82 82 81.1374 82 80.0733V22.9267C82 21.8626 81.1374 21 80.0733 21H22.9267Z"
+ fill="white"
+ />
+ <path
+ d="M200.915 36.6177V24.5219H142.622V36.6177H164.628V76.8399H178.909V36.6177H200.915Z"
+ fill="white"
+ />
+ <path
+ d="M233.851 77.6414C251.776 77.6414 261.613 68.5331 261.613 53.5227V24.5219H247.331V52.1382C247.331 60.5178 242.959 65.2541 233.851 65.2541C224.743 65.2541 220.371 60.5178 220.371 52.1382V24.5219H206.089V53.5227C206.089 68.5331 215.926 77.6414 233.851 77.6414Z"
+ fill="white"
+ />
+ <path
+ d="M283.475 59.4977H297.684L308.906 76.8399H325.301L312.622 57.9675C319.763 55.4172 324.135 49.7336 324.135 42.0098C324.135 30.7884 315.755 24.5219 303.003 24.5219H269.194V76.8399H283.475V59.4977ZM283.475 48.2034V36.472H302.275C307.303 36.472 310.071 38.6579 310.071 42.3741C310.071 45.8717 307.303 48.2034 302.275 48.2034H283.475Z"
+ fill="white"
+ />
+ <path
+ d="M330.78 76.8399H368.16C379.819 76.8399 386.522 71.302 386.522 62.048C386.522 55.8544 382.588 51.6281 377.924 49.7336C381.13 48.2034 385.065 44.5601 385.065 38.8037C385.065 29.5497 378.507 24.5219 366.921 24.5219H330.78V76.8399ZM344.479 44.9973V36.1076H365.464C369.399 36.1076 371.585 37.6378 371.585 40.5525C371.585 43.4671 369.399 44.9973 365.464 44.9973H344.479ZM344.479 55.7086H366.776C370.638 55.7086 372.751 57.6032 372.751 60.445C372.751 63.2867 370.638 65.1813 366.776 65.1813H344.479V55.7086Z"
+ fill="white"
+ />
+ <path
+ d="M421.441 23.6475C403.152 23.6475 389.963 35.0875 389.963 50.6809C389.963 66.2743 403.152 77.7143 421.441 77.7143C439.731 77.7143 452.847 66.2743 452.847 50.6809C452.847 35.0875 439.731 23.6475 421.441 23.6475ZM421.441 36.0348C431.06 36.0348 438.273 41.7183 438.273 50.6809C438.273 59.6434 431.06 65.327 421.441 65.327C411.823 65.327 404.609 59.6434 404.609 50.6809C404.609 41.7183 411.823 36.0348 421.441 36.0348Z"
+ fill="white"
+ />
+ <path
+ d="M473.428 60.1535H492.447C505.198 60.1535 513.578 54.0327 513.578 42.3741C513.578 30.6427 505.198 24.5219 492.447 24.5219H459.147V76.8399H473.428V60.1535ZM473.428 48.2763V36.472H491.645C496.746 36.472 499.515 38.6579 499.515 42.3741C499.515 46.0174 496.746 48.2763 491.645 48.2763H473.428Z"
+ fill="white"
+ />
+ <path
+ d="M532.693 24.5219L506.971 76.8399H522.128L526.864 67.0029H554.335L559.071 76.8399H574.737L549.088 24.5219H532.693ZM540.709 38.5122L548.87 55.5629H532.475L540.709 38.5122Z"
+ fill="white"
+ />
+ <path
+ d="M571.733 50.6809C571.733 66.2743 584.776 77.7143 602.993 77.7143C617.566 77.7143 627.694 71.1563 631.702 60.5907L618.659 54.7614C616.619 60.8822 611.299 65.327 602.993 65.327C593.666 65.327 586.306 59.6434 586.306 50.6809C586.306 41.7183 593.666 36.0348 602.993 36.0348C611.299 36.0348 616.619 40.4796 618.659 46.6004L631.702 40.7711C627.694 30.2055 617.566 23.6475 602.993 23.6475C584.776 23.6475 571.733 35.0875 571.733 50.6809Z"
+ fill="white"
+ />
+ <path
+ d="M651.143 24.5219H636.861V76.8399H651.143V63.7968L660.469 55.4172L678.613 76.8399H696.757L670.744 46.236L694.935 24.5219H675.48L651.143 46.6004V24.5219Z"
+ fill="white"
+ />
+ <path
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ d="M0.060791 52.4562V89.6126C0.060791 92.1056 0.75642 94.4361 1.96416 96.4206L15.3605 83.0243V52.4562H0.060791ZM6.16937 100.701L19.452 87.4181C19.5445 87.4239 19.6378 87.4269 19.7318 87.4269H49V102.727H13.1748C10.5987 102.727 8.19603 101.984 6.16937 100.701ZM55 102.727H89.6734C96.9161 102.727 102.787 96.8553 102.787 89.6126V13.114C102.787 5.87136 96.9161 0 89.6734 0H52.5169V15.2997H83.1164C85.5306 15.2997 87.4877 17.2568 87.4877 19.6711V83.0556C87.4877 85.4698 85.5306 87.4269 83.1164 87.4269H55V102.727Z"
+ fill="url(#paint0_linear_1910_4887)"
+ />
+ <defs>
+ <linearGradient
+ id="paint0_linear_1910_4887"
+ x1="56.4749"
+ y1="7.22297"
+ x2="5.91555"
+ y2="57.4394"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
+
+export default PackLogo;
diff --git a/docs/components/logos/og/RepoLogo.tsx b/docs/components/logos/og/RepoLogo.tsx
new file mode 100644
index 0000000..f09a1ff
--- /dev/null
+++ b/docs/components/logos/og/RepoLogo.tsx
@@ -0,0 +1,70 @@
+const RepoLogo = (props) => (
+ <svg
+ {...props}
+ viewBox="0 0 617 83"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M41.4998 14.5103C26.6177 14.5103 14.51 26.618 14.51 41.5001C14.51 56.3822 26.6177 68.4899 41.4998 68.4899C56.3819 68.4899 68.4897 56.3822 68.4897 41.5001C68.4897 26.618 56.3819 14.5103 41.4998 14.5103ZM41.4998 55.4674C33.7849 55.4674 27.5325 49.2151 27.5325 41.5001C27.5325 33.7851 33.7849 27.5327 41.4998 27.5327C49.2148 27.5327 55.4672 33.7851 55.4672 41.5001C55.4672 49.2151 49.2148 55.4674 41.4998 55.4674Z"
+ fill="white"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M43.7617 10.0772V0C65.6652 1.17107 83.0653 19.305 83.0653 41.5C83.0653 63.695 65.6652 81.8239 43.7617 83V72.9228C60.0863 71.7567 73.0133 58.1161 73.0133 41.5C73.0133 24.8839 60.0863 11.2432 43.7617 10.0772ZM17.6967 62.1068C13.3693 57.1109 10.5798 50.7479 10.0822 43.7617H0C0.522708 53.5374 4.43297 62.4083 10.5597 69.2387L17.6917 62.1068H17.6967ZM39.2382 82.9997V72.9225C32.247 72.4249 25.884 69.6405 20.8881 65.308L13.7562 72.44C20.5916 78.5718 29.4626 82.477 39.2332 82.9997H39.2382Z"
+ fill="url(#paint0_linear_1179_4996)"
+ />
+ <path
+ d="M164.719 32.0037V21.0532H111.945V32.0037H131.867V68.4173H144.797V32.0037H164.719Z"
+ fill="white"
+ />
+ <path
+ d="M194.536 69.143C210.764 69.143 219.67 60.8971 219.67 47.308V21.0532H206.74V46.0546C206.74 53.6408 202.782 57.9286 194.536 57.9286C186.29 57.9286 182.332 53.6408 182.332 46.0546V21.0532H169.403V47.308C169.403 60.8971 178.308 69.143 194.536 69.143Z"
+ fill="white"
+ />
+ <path
+ d="M239.462 52.7172H252.325L262.484 68.4173H277.327L265.848 51.3319C272.313 49.0231 276.271 43.8777 276.271 36.8852C276.271 26.7263 268.685 21.0532 257.141 21.0532H226.532V68.4173H239.462V52.7172ZM239.462 42.4924V31.8717H256.481C261.033 31.8717 263.54 33.8507 263.54 37.2151C263.54 40.3815 261.033 42.4924 256.481 42.4924H239.462Z"
+ fill="white"
+ />
+ <path
+ d="M282.287 68.4173H316.128C326.683 68.4173 332.752 63.4039 332.752 55.0261C332.752 49.4189 329.189 45.5928 324.967 43.8777C327.87 42.4924 331.432 39.1941 331.432 33.9827C331.432 25.6049 325.495 21.0532 315.006 21.0532H282.287V68.4173ZM294.689 39.5899V31.5419H313.687C317.249 31.5419 319.228 32.9272 319.228 35.5659C319.228 38.2046 317.249 39.5899 313.687 39.5899H294.689ZM294.689 49.287H314.875C318.371 49.287 320.284 51.0021 320.284 53.5748C320.284 56.1475 318.371 57.8626 314.875 57.8626H294.689V49.287Z"
+ fill="white"
+ />
+ <path
+ d="M364.364 20.2616C347.806 20.2616 335.866 30.6184 335.866 44.7353C335.866 58.8521 347.806 69.2089 364.364 69.2089C380.922 69.2089 392.796 58.8521 392.796 44.7353C392.796 30.6184 380.922 20.2616 364.364 20.2616ZM364.364 31.4759C373.072 31.4759 379.602 36.6214 379.602 44.7353C379.602 52.8492 373.072 57.9946 364.364 57.9946C355.656 57.9946 349.126 52.8492 349.126 44.7353C349.126 36.6214 355.656 31.4759 364.364 31.4759Z"
+ fill="white"
+ />
+ <path
+ d="M411.429 52.7172H424.292L434.451 68.4173H449.294L437.815 51.3319C444.28 49.0231 448.238 43.8777 448.238 36.8852C448.238 26.7263 440.652 21.0532 429.108 21.0532H398.499V68.4173H411.429V52.7172ZM411.429 42.4924V31.8717H428.448C433 31.8717 435.507 33.8507 435.507 37.2151C435.507 40.3815 433 42.4924 428.448 42.4924H411.429Z"
+ fill="white"
+ />
+ <path
+ d="M454.254 21.0532V68.4173H500.299V57.4668H466.92V49.7487H496.539V39.0621H466.92V32.0037H500.299V21.0532H454.254Z"
+ fill="white"
+ />
+ <path
+ d="M519.46 53.3109H536.677C548.221 53.3109 555.807 47.7697 555.807 37.2151C555.807 26.5944 548.221 21.0532 536.677 21.0532H506.53V68.4173H519.46V53.3109ZM519.46 42.5584V31.8717H535.951C540.569 31.8717 543.076 33.8507 543.076 37.2151C543.076 40.5134 540.569 42.5584 535.951 42.5584H519.46Z"
+ fill="white"
+ />
+ <path
+ d="M587.899 20.2616C571.341 20.2616 559.401 30.6184 559.401 44.7353C559.401 58.8521 571.341 69.2089 587.899 69.2089C604.456 69.2089 616.33 58.8521 616.33 44.7353C616.33 30.6184 604.456 20.2616 587.899 20.2616ZM587.899 31.4759C596.606 31.4759 603.137 36.6214 603.137 44.7353C603.137 52.8492 596.606 57.9946 587.899 57.9946C579.191 57.9946 572.66 52.8492 572.66 44.7353C572.66 36.6214 579.191 31.4759 587.899 31.4759Z"
+ fill="white"
+ />
+ <defs>
+ <linearGradient
+ id="paint0_linear_1179_4996"
+ x1="45.3904"
+ y1="5.8358"
+ x2="4.53889"
+ y2="46.6874"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
+
+export default RepoLogo;
diff --git a/docs/components/logos/og/TurboLogo.tsx b/docs/components/logos/og/TurboLogo.tsx
new file mode 100644
index 0000000..5646ce2
--- /dev/null
+++ b/docs/components/logos/og/TurboLogo.tsx
@@ -0,0 +1,54 @@
+const TurboLogo = (props) => (
+ <svg
+ {...props}
+ viewBox="0 0 460 97"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M48.5003 16.9576C31.108 16.9576 16.958 31.1076 16.958 48.5C16.958 65.8923 31.108 80.0423 48.5003 80.0423C65.8927 80.0423 80.0427 65.8923 80.0427 48.5C80.0427 31.1076 65.8927 16.9576 48.5003 16.9576ZM48.5003 64.8233C39.484 64.8233 32.177 57.5163 32.177 48.5C32.177 39.4837 39.484 32.1767 48.5003 32.1767C57.5166 32.1767 64.8236 39.4837 64.8236 48.5C64.8236 57.5163 57.5166 64.8233 48.5003 64.8233Z"
+ fill="white"
+ />
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M51.143 11.777V0C76.7411 1.3686 97.0762 22.5613 97.0762 48.5C97.0762 74.4387 76.7411 95.6255 51.143 97V85.223C70.2212 83.8603 85.3286 67.9188 85.3286 48.5C85.3286 29.0812 70.2212 13.1397 51.143 11.777ZM20.6817 72.5824C15.6243 66.7439 12.3644 59.3076 11.7829 51.143H0C0.610876 62.5676 5.1807 72.9348 12.3409 80.9173L20.6758 72.5824H20.6817ZM45.8571 97V85.223C37.6866 84.6415 30.2504 81.3875 24.4118 76.3242L16.0769 84.6592C24.0652 91.8252 34.4325 96.3891 45.8512 97H45.8571Z"
+ fill="url(#paint0_linear_1368_4681)"
+ />
+ <path
+ d="M192.503 37.4015V24.604H130.828V37.4015H154.111V79.9572H169.221V37.4015H192.503Z"
+ fill="white"
+ />
+ <path
+ d="M227.35 80.8053C246.315 80.8053 256.723 71.1685 256.723 55.2872V24.604H241.613V53.8225C241.613 62.6882 236.987 67.6993 227.35 67.6993C217.714 67.6993 213.088 62.6882 213.088 53.8225V24.604H197.978V55.2872C197.978 71.1685 208.385 80.8053 227.35 80.8053Z"
+ fill="white"
+ />
+ <path
+ d="M279.853 61.6089H294.887L306.759 79.9572H324.105L310.691 59.99C318.246 57.2917 322.872 51.2784 322.872 43.1064C322.872 31.234 314.006 24.604 300.515 24.604H264.743V79.9572H279.853V61.6089ZM279.853 49.6594V37.2473H299.744C305.063 37.2473 307.993 39.5601 307.993 43.4919C307.993 47.1924 305.063 49.6594 299.744 49.6594H279.853Z"
+ fill="white"
+ />
+ <path
+ d="M329.902 79.9572H369.451C381.786 79.9572 388.879 74.0981 388.879 64.3072C388.879 57.7542 384.716 53.2828 379.782 51.2784C383.174 49.6594 387.337 45.8047 387.337 39.7143C387.337 29.9234 380.399 24.604 368.141 24.604H329.902V79.9572ZM344.396 46.2673V36.8619H366.599C370.762 36.8619 373.075 38.4808 373.075 41.5646C373.075 44.6483 370.762 46.2673 366.599 46.2673H344.396ZM344.396 57.6001H367.987C372.073 57.6001 374.308 59.6045 374.308 62.6111C374.308 65.6178 372.073 67.6222 367.987 67.6222H344.396V57.6001Z"
+ fill="white"
+ />
+ <path
+ d="M425.824 23.6788C406.473 23.6788 392.519 35.7825 392.519 52.2806C392.519 68.7786 406.473 80.8823 425.824 80.8823C445.174 80.8823 459.051 68.7786 459.051 52.2806C459.051 35.7825 445.174 23.6788 425.824 23.6788ZM425.824 36.7848C436 36.7848 443.632 42.7981 443.632 52.2806C443.632 61.7631 436 67.7764 425.824 67.7764C415.647 67.7764 408.015 61.7631 408.015 52.2806C408.015 42.7981 415.647 36.7848 425.824 36.7848Z"
+ fill="white"
+ />
+ <defs>
+ <linearGradient
+ id="paint0_linear_1368_4681"
+ x1="53.0466"
+ y1="6.82015"
+ x2="5.30449"
+ y2="54.5623"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop stopColor="#0096FF" />
+ <stop offset="1" stopColor="#FF1E56" />
+ </linearGradient>
+ </defs>
+ </svg>
+);
+
+export default TurboLogo;
diff --git a/docs/components/logos/og/VercelLogo.tsx b/docs/components/logos/og/VercelLogo.tsx
new file mode 100644
index 0000000..020f271
--- /dev/null
+++ b/docs/components/logos/og/VercelLogo.tsx
@@ -0,0 +1,16 @@
+const VercelLogo = ({
+ fill = "none",
+ height = 20,
+}: {
+ fill?: string;
+ height?: number;
+}) => (
+ <svg height={height} viewBox="0 0 283 64" fill={fill}>
+ <path
+ fill={fill}
+ d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z"
+ />
+ </svg>
+);
+
+export default VercelLogo;
diff --git a/docs/components/output-mode-table.mdx b/docs/components/output-mode-table.mdx
new file mode 100644
index 0000000..56fd80a
--- /dev/null
+++ b/docs/components/output-mode-table.mdx
@@ -0,0 +1,7 @@
+| option | description |
+| ----------- | ---------------------------------------- |
+| full | This is the default. Displays all output |
+| hash-only | Show only the hashes of the tasks |
+| new-only | Only show output from cache misses |
+| errors-only | Only show output from task failures |
+| none | Hides all task output |
diff --git a/docs/components/pages/confirm.tsx b/docs/components/pages/confirm.tsx
new file mode 100644
index 0000000..b9295cf
--- /dev/null
+++ b/docs/components/pages/confirm.tsx
@@ -0,0 +1,36 @@
+/* eslint-disable react/no-unescaped-entities */
+import Head from "next/head";
+import { Container } from "../Container";
+
+export default function Confirm() {
+ return (
+ <>
+ <Head>
+ <title>Confirm</title>
+ <meta name="robots" content="noindex" />
+ </Head>
+ <Container>
+ <div className="container mx-auto">
+ <div className="pt-20 mx-auto ">
+ <div className="max-w-md mx-auto rounded-lg shadow-xl dark:bg-gray-900 dark:bg-opacity-80">
+ <div className="p-6 rounded-lg shadow-sm ">
+ <div className="mx-auto space-y-4 dark:text-white">
+ <h2 className="text-xl font-bold">Thanks so much!</h2>
+ <p>
+ Keep an eye on your inbox for product updates and
+ announcements from Turbo and Vercel.
+ </p>{" "}
+ <p>
+ Thanks,
+ <br />
+ The Turbo Team
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </Container>
+ </>
+ );
+}
diff --git a/docs/components/pages/home-shared/CTAButton.tsx b/docs/components/pages/home-shared/CTAButton.tsx
new file mode 100644
index 0000000..57da227
--- /dev/null
+++ b/docs/components/pages/home-shared/CTAButton.tsx
@@ -0,0 +1,43 @@
+import cn from "classnames";
+import { MouseEventHandler } from "react";
+import gradients from "./gradients.module.css";
+
+export function CTAButton({
+ children,
+ outline,
+ onClick,
+ monospace,
+}: {
+ outline?: boolean;
+ children: React.ReactNode;
+ onClick?: MouseEventHandler<HTMLButtonElement>;
+ monospace?: boolean;
+}) {
+ const outlineClasses =
+ "border dark:border-neutral-400 dark:text-neutral-200 dark:hover:border-white dark:hover:text-white border-[#EAEAEA] text-neutral-800 hover:border-black hover:text-black";
+ const filledClasses =
+ "dark:text-black text-white border-transparent bg-black dark:bg-white";
+
+ return (
+ <div className="relative w-full group">
+ <button
+ onClick={onClick}
+ className={`w-full min-w-[120px] text-base font-medium no-underline ${
+ outline ? outlineClasses : filledClasses
+ } rounded md:leading-6 transition-all duration-300 ${
+ monospace ? "font-mono" : ""
+ }`}
+ >
+ {children}
+ </button>
+ {!outline && (
+ <div
+ className={cn(
+ "absolute bg-red-100 w-full h-full top-0 -z-10 rounded-full transition-all duration-300 blur-xl group-hover:opacity-70 opacity-0",
+ gradients.translatingGlow
+ )}
+ />
+ )}
+ </div>
+ );
+}
diff --git a/docs/components/pages/home-shared/FadeIn.tsx b/docs/components/pages/home-shared/FadeIn.tsx
new file mode 100644
index 0000000..826a078
--- /dev/null
+++ b/docs/components/pages/home-shared/FadeIn.tsx
@@ -0,0 +1,50 @@
+import { motion, useInView } from "framer-motion";
+import { useRef } from "react";
+
+export function FadeIn({
+ children,
+ className,
+ noVertical,
+ delay,
+ viewTriggerOffset,
+}: {
+ children: React.ReactNode;
+ className?: string;
+ noVertical?: boolean;
+ delay?: number;
+ viewTriggerOffset?: boolean;
+}) {
+ const ref = useRef(null);
+ const inView = useInView(ref, {
+ once: true,
+ margin: viewTriggerOffset ? "-128px" : "0px",
+ });
+
+ const fadeUpVariants = {
+ initial: {
+ opacity: 0,
+ y: noVertical ? 0 : 24,
+ },
+ animate: {
+ opacity: 1,
+ y: 0,
+ },
+ };
+
+ return (
+ <motion.div
+ ref={ref}
+ animate={inView ? "animate" : "initial"}
+ variants={fadeUpVariants}
+ className={className}
+ initial={false}
+ transition={{
+ duration: 1,
+ delay: delay || 0,
+ ease: [0.21, 0.47, 0.32, 0.98],
+ }}
+ >
+ {children}
+ </motion.div>
+ );
+}
diff --git a/docs/components/pages/home-shared/FeatureBox.tsx b/docs/components/pages/home-shared/FeatureBox.tsx
new file mode 100644
index 0000000..c9d46c1
--- /dev/null
+++ b/docs/components/pages/home-shared/FeatureBox.tsx
@@ -0,0 +1,42 @@
+import Image from "next/image";
+import type { ReactNode } from "react";
+
+export function FeatureBox({
+ name,
+ description,
+ iconDark,
+ iconLight,
+}: {
+ iconDark: Parameters<typeof Image>[0]["src"];
+ iconLight: Parameters<typeof Image>[0]["src"];
+ name: string;
+ description: ReactNode;
+}) {
+ return (
+ <div className="box-border relative flex flex-col gap-5 p-8 overflow-hidden text-black no-underline border dark:text-white rounded-xl dark:border-neutral-800">
+ <Image
+ src={iconDark}
+ width={64}
+ height={64}
+ aria-hidden="true"
+ alt=""
+ className="hidden dark:block"
+ />
+ <Image
+ src={iconLight}
+ width={64}
+ height={64}
+ aria-hidden="true"
+ alt=""
+ className="block dark:hidden"
+ />
+ <div className="flex flex-col gap-2">
+ <h3 className="m-0 font-bold leading-5 text-gray-900 font-space-grotesk dark:text-white">
+ {name}
+ </h3>
+
+ <p className="m-0 leading-6 opacity-70">{description}</p>
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/pages/home-shared/FeaturesBento.tsx b/docs/components/pages/home-shared/FeaturesBento.tsx
new file mode 100644
index 0000000..f2664db
--- /dev/null
+++ b/docs/components/pages/home-shared/FeaturesBento.tsx
@@ -0,0 +1,38 @@
+import type { Features } from "../../../content/features";
+import { FadeIn } from "./FadeIn";
+import { SectionHeader, SectionSubtext } from "./Headings";
+import { FeatureBox } from "./FeatureBox";
+
+export function FeaturesBento({
+ header,
+ body,
+ features,
+}: {
+ header: string;
+ body: string;
+ features: Features;
+}) {
+ return (
+ <section className="relative flex flex-col items-center px-6 pb-16 font-sans md:pb-24 lg:pb-32 gap-9 lg:gap-14">
+ <FadeIn className="flex flex-col items-center gap-5 md:gap-6">
+ <SectionHeader>{header}</SectionHeader>
+ <SectionSubtext>{body}</SectionSubtext>
+ </FadeIn>
+ <div className="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3 lg:gap-x-6 lg:gap-y-6 max-w-[1200px]">
+ {features.map((feature) => (
+ <FadeIn
+ className="flex"
+ key={feature.name.replace(/\s+/g, "-").toLowerCase()}
+ >
+ <FeatureBox
+ name={feature.name}
+ description={feature.description}
+ iconDark={feature.iconDark}
+ iconLight={feature.iconLight}
+ />
+ </FadeIn>
+ ))}
+ </div>
+ </section>
+ );
+}
diff --git a/docs/components/pages/home-shared/GlobalStyles.tsx b/docs/components/pages/home-shared/GlobalStyles.tsx
new file mode 100644
index 0000000..5f695f3
--- /dev/null
+++ b/docs/components/pages/home-shared/GlobalStyles.tsx
@@ -0,0 +1,20 @@
+import Head from "next/head";
+
+export function LandingPageGlobalStyles() {
+ return (
+ <Head>
+ <style>
+ {`
+ .dark footer,
+ .dark body {
+ background-color: black !important;
+ }
+
+ .dark .nextra-nav-container .nextra-nav-container-blur {
+ background-color: rgba(0,0,0,.5) !important;
+ }
+ `}
+ </style>
+ </Head>
+ );
+}
diff --git a/docs/components/pages/home-shared/Gradient.tsx b/docs/components/pages/home-shared/Gradient.tsx
new file mode 100644
index 0000000..9a03a99
--- /dev/null
+++ b/docs/components/pages/home-shared/Gradient.tsx
@@ -0,0 +1,47 @@
+import cn from "classnames";
+import gradients from "./gradients.module.css";
+
+export function Gradient({
+ width = 1000,
+ height = 200,
+ opacity,
+ pink,
+ blue,
+ conic,
+ gray,
+ className,
+ small,
+}: {
+ width?: number | string;
+ height?: number | string;
+ opacity?: number;
+ pink?: boolean;
+ blue?: boolean;
+ conic?: boolean;
+ gray?: boolean;
+ className?: string;
+ small?: boolean;
+}) {
+ return (
+ <span
+ className={cn(
+ "absolute",
+ gradients.glow,
+ {
+ [gradients.glowPink]: pink,
+ [gradients.glowBlue]: blue,
+ [gradients.glowConic]: conic,
+ [gradients.glowSmall]: small,
+ [gradients.glowGray]: gray,
+ },
+ className
+ )}
+ style={{
+ width,
+ height,
+ opacity,
+ borderRadius: "100%",
+ }}
+ />
+ );
+}
diff --git a/docs/components/pages/home-shared/GradientSectionBorder.tsx b/docs/components/pages/home-shared/GradientSectionBorder.tsx
new file mode 100644
index 0000000..ef1e824
--- /dev/null
+++ b/docs/components/pages/home-shared/GradientSectionBorder.tsx
@@ -0,0 +1,37 @@
+import cn from "classnames";
+import { FadeIn } from "./FadeIn";
+import gradients from "../home-shared/gradients.module.css";
+
+export function GradientSectionBorder({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+ <section className={cn("relative overflow-hidden")}>
+ <FadeIn noVertical viewTriggerOffset>
+ <span
+ className={cn(
+ "w-full absolute white h-[1px] top-0 opacity-25",
+ gradients.gradientSectionBorderDivider
+ )}
+ />
+ <span
+ className={cn(
+ gradients.gradientSectionBorder,
+ gradients.gradientSectionBorderLeft,
+ "dark:opacity-35 opacity-[0.15]"
+ )}
+ />
+ <span
+ className={cn(
+ gradients.gradientSectionBorder,
+ gradients.gradientSectionBorderRight,
+ "dark:opacity-35 opacity-[0.15]"
+ )}
+ />
+ </FadeIn>
+ {children}
+ </section>
+ );
+}
diff --git a/docs/components/pages/home-shared/Headings.tsx b/docs/components/pages/home-shared/Headings.tsx
new file mode 100644
index 0000000..43a5e52
--- /dev/null
+++ b/docs/components/pages/home-shared/Headings.tsx
@@ -0,0 +1,56 @@
+import cn from "classnames";
+import gradients from "./gradients.module.css";
+
+export function HeroText({
+ children,
+ className,
+ h1,
+}: {
+ children: React.ReactNode;
+ className?: string;
+ h1?: boolean;
+}) {
+ const combinedClassname = cn(
+ gradients.heroHeading,
+ "font-extrabold tracking-[-0.04em] leading-none text-[40px] md:text-5xl lg:text-[80px] max-w-lg md:max-w-xl lg:max-w-4xl text-center text-transparent",
+ className
+ );
+
+ if (h1) {
+ return <h1 className={combinedClassname}>{children}</h1>;
+ }
+ return <h2 className={combinedClassname}>{children}</h2>;
+}
+
+export function SectionHeader({ children }: { children: React.ReactNode }) {
+ return (
+ <h2
+ className={cn(
+ gradients.heroHeading,
+ "font-bold tracking-[-0.01em] pb-1 text-[32px] md:text-4xl lg:text-[40px] max-w-sm md:max-w-md lg:max-w-2xl text-center text-transparent"
+ )}
+ >
+ {children}
+ </h2>
+ );
+}
+
+export function SectionSubtext({
+ hero,
+ children,
+}: {
+ hero?: boolean;
+ children: React.ReactNode;
+}) {
+ const textClasses = hero
+ ? "text-[20px] lg:text-xl"
+ : "text-[16px] lg:text-[20px]";
+
+ return (
+ <p
+ className={`font-space-grotesk leading-snug dark:text-[#FFFFFFB2] text-[#00000080] ${textClasses} max-w-md md:max-w-xl lg:max-w-[640px] text-center`}
+ >
+ {children}
+ </p>
+ );
+}
diff --git a/docs/components/pages/home-shared/gradients.module.css b/docs/components/pages/home-shared/gradients.module.css
new file mode 100644
index 0000000..b9c50a9
--- /dev/null
+++ b/docs/components/pages/home-shared/gradients.module.css
@@ -0,0 +1,231 @@
+.benchmarkTurbo {
+ background: linear-gradient(288.43deg, #ff1e56 28.29%, #9c51a1 78.78%);
+ box-shadow: 0px 0px 16px #f02662;
+ :global(.light) & {
+ background: linear-gradient(
+ 268.86deg,
+ #ff1e56 -5.68%,
+ #d67fdc 107.63%,
+ #9c51a1 107.64%
+ );
+ box-shadow: none;
+ }
+}
+
+.benchmarkActiveTab {
+ background: radial-gradient(
+ 50% 50% at 50% 100%,
+ rgba(255, 255, 255, 0.2) 0%,
+ rgba(255, 255, 255, 0) 100%
+ ),
+ linear-gradient(0deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12)),
+ radial-gradient(
+ 128.57% 128.57% at 50% 0%,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0) 100%
+ ),
+ radial-gradient(
+ 100% 427.04% at 100% 0%,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0) 100%
+ ),
+ radial-gradient(
+ 100% 462.63% at 0% 0%,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ :global(.light) & {
+ background: linear-gradient(
+ 0deg,
+ rgba(255, 255, 255, 0.8),
+ rgba(255, 255, 255, 0.8)
+ ),
+ radial-gradient(
+ 50% 50% at 49.66% 0%,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ }
+}
+
+.benchmarkTurboLabel {
+ background: linear-gradient(288.43deg, #ff1e56 28.29%, #9c51a1 78.78%);
+ color: transparent;
+ background-clip: text;
+ :global(.light) & {
+ background: linear-gradient(
+ 268.86deg,
+ #ff1e56 -5.68%,
+ #d67fdc 107.63%,
+ #9c51a1 107.64%
+ ),
+ linear-gradient(288.43deg, #ff1e56 28.29%, #9c51a1 78.78%);
+ color: transparent;
+ background-clip: text;
+ }
+}
+
+.benchmark {
+ background: linear-gradient(270deg, #5c5c5c 0%, #1f1f1f 100%);
+ :global(.light) & {
+ background: linear-gradient(89.98deg, #e0e0e0 0.01%, #9c9c9c 99.49%);
+ }
+}
+
+.barBorder {
+ border: rgba(255, 255, 255, 0.4) 1px solid;
+ :global(.light) & {
+ border: rgba(0, 0, 0, 0.6) 1px solid;
+ }
+}
+
+.tooltipArrow {
+ display: block;
+ border-left: 8px solid transparent;
+ border-bottom: 8px solid #333333;
+ border-right: 8px solid transparent;
+ :global(.light) & {
+ border-bottom: 8px solid #f5f5f5;
+ }
+}
+.translatingGlow {
+ background: linear-gradient(32deg, #2a8af6 0%, #a853ba 50%, #e92a67 100%);
+ background-size: 200% 200%;
+ animation: translateGlow 7s linear infinite;
+ will-change: filter;
+}
+
+@keyframes translateGlow {
+ 0% {
+ background-position: -20% -20%;
+ }
+ 25% {
+ background-position: 30% 80%;
+ }
+ 50% {
+ background-position: 110% 110%;
+ }
+ 75% {
+ background-position: 80% 30%;
+ }
+ 100% {
+ background-position: -20% -20%;
+ }
+}
+
+.turbopackHeaderText {
+ background: linear-gradient(
+ 90deg,
+ rgba(200, 221, 255, 0.75) 0%,
+ rgba(255, 202, 222, 0.75) 100%
+ ),
+ linear-gradient(0deg, #ffffff, #ffffff);
+
+ :global(.light) & {
+ background: linear-gradient(
+ 90deg,
+ rgba(200, 221, 255, 0.1) 0%,
+ rgba(255, 202, 222, 0.1) 100%
+ ),
+ #000000;
+ background-clip: text;
+ }
+ background-clip: text;
+}
+
+.heroHeading {
+ background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%), #ffffff;
+ :global(.light) & {
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, #000000 100%);
+ background-clip: text;
+ }
+ background-clip: text;
+}
+
+.letterLine {
+ opacity: 0.2;
+ background: linear-gradient(
+ 90deg,
+ #000000 0%,
+ #ffffff 20%,
+ #ffffff 80%,
+ #000000 100%
+ );
+ :global(.light) & {
+ background: linear-gradient(
+ 90deg,
+ #ffffff 0%,
+ #000000 20%,
+ #000000 80%,
+ #ffffff 100%
+ );
+ }
+}
+
+.glow {
+ mix-blend-mode: normal;
+ filter: blur(75px);
+ will-change: filter;
+}
+
+.glowSmall {
+ filter: blur(32px);
+}
+
+.glowBlue {
+ background: linear-gradient(180deg, #58a5ff 0%, #a67af4 100%);
+}
+
+.glowPink {
+ background: linear-gradient(180deg, #ff3358 0%, #ff4fd8 100%);
+}
+
+.glowConic {
+ background: conic-gradient(
+ from 180deg at 50% 50%,
+ #2a8af6 0deg,
+ #a853ba 180deg,
+ #e92a67 360deg
+ );
+}
+
+.glowGray {
+ background: rgba(255, 255, 255, 0.15);
+}
+
+.gradientSectionBorder {
+ --gradient-y-offset: -200px;
+ --gradient-x-offset: -200px;
+ --height: 255px;
+ position: relative;
+ overflow: hidden;
+ will-change: filter;
+}
+
+.gradientSectionBorderLeft {
+ position: absolute;
+ width: 60vw;
+ height: var(--height);
+ left: var(--gradient-x-offset);
+ top: var(--gradient-y-offset);
+ background: linear-gradient(180deg, #58a5ff 0%, #a67af4 100%);
+ border-radius: 100%;
+ mix-blend-mode: normal;
+ filter: blur(50px);
+}
+
+.gradientSectionBorderRight {
+ width: 60vw;
+ position: absolute;
+ height: var(--height);
+ right: var(--gradient-x-offset);
+ top: var(--gradient-y-offset);
+ background: linear-gradient(180deg, #ff3358 0%, #ff4fd8 100%);
+ border-radius: 100%;
+ mix-blend-mode: normal;
+ filter: blur(50px);
+}
+
+.gradientSectionBorderDivider {
+ background: linear-gradient(90deg, #288cf9 0%, #e32c6b 100%);
+}
diff --git a/docs/components/pages/landing/TurboHeroBackground.tsx b/docs/components/pages/landing/TurboHeroBackground.tsx
new file mode 100644
index 0000000..dffa5b6
--- /dev/null
+++ b/docs/components/pages/landing/TurboHeroBackground.tsx
@@ -0,0 +1,33 @@
+import cn from "classnames";
+import styles from "./turbohero-background.module.css";
+
+export function TurboheroBackground(): JSX.Element {
+ return (
+ <div
+ className={cn(
+ "![perspective:1000px] sm:![perspective:1000px] md:![perspective:1000px] lg:![perspective:1000px]",
+ styles.container
+ )}
+ >
+ <div
+ className="z-[100] absolute inset-0 [--gradient-stop-1:0px] [--gradient-stop-2:50%]"
+ style={{
+ background:
+ "linear-gradient(to top, rgba(0,0,0,0) 0px, var(--geist-foreground) 50%)",
+ }}
+ />
+ <div
+ style={{
+ transform: "rotateX(75deg)",
+ position: "absolute",
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ }}
+ >
+ <div className={styles.lines} />
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/pages/landing/Turbopack.tsx b/docs/components/pages/landing/Turbopack.tsx
new file mode 100644
index 0000000..1db1076
--- /dev/null
+++ b/docs/components/pages/landing/Turbopack.tsx
@@ -0,0 +1,27 @@
+import Image from "next/image";
+
+export function Turbopack() {
+ return (
+ <div className="relative w-24 h-24">
+ <div className="pointer-events-none absolute w-[261px] h-[261px] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gradient-to-b from-[#4EBFFF] to-[#BD69FF] mix-blend-normal opacity-5 dark:opacity-[0.15] blur-[60px]" />
+ <div className="contents dark:hidden">
+ <Image
+ alt=""
+ src={`/images/docs/pack/turbopack-hero-logo-light.svg`}
+ width={120}
+ height={136.15}
+ className="absolute w-[84px] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
+ />
+ </div>
+ <div className="dark:contents hidden">
+ <Image
+ alt=""
+ src={`/images/docs/pack/turbopack-hero-logo-dark.svg`}
+ width={120}
+ height={136.15}
+ className="hidden dark:block absolute w-[84px] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
+ />
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/pages/landing/Turborepo.tsx b/docs/components/pages/landing/Turborepo.tsx
new file mode 100644
index 0000000..022f37f
--- /dev/null
+++ b/docs/components/pages/landing/Turborepo.tsx
@@ -0,0 +1,27 @@
+import Image from "next/image";
+
+export function Turborepo() {
+ return (
+ <div className="relative w-24 h-24">
+ <div className="pointer-events-none absolute w-[261px] h-[261px] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gradient-to-b from-[#FF3358] to-[#FF4FD8] mix-blend-normal opacity-5 dark:opacity-[0.15] blur-[60px]" />
+ <div className="contents dark:hidden">
+ <Image
+ alt="Turborepo Logo"
+ src={`/images/docs/repo/repo-hero-logo-light.svg`}
+ width={120}
+ height={120}
+ className="absolute w-[84px] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
+ />
+ </div>
+ <div className="dark:contents hidden">
+ <Image
+ alt="Turborepo Logo"
+ src={`/images/docs/repo/repo-hero-logo-dark.svg`}
+ width={120}
+ height={120}
+ className="hidden dark:block absolute w-[84px] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
+ />
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/pages/landing/index.module.css b/docs/components/pages/landing/index.module.css
new file mode 100644
index 0000000..ac18d80
--- /dev/null
+++ b/docs/components/pages/landing/index.module.css
@@ -0,0 +1,184 @@
+.leftLights::before {
+ content: "";
+ position: absolute;
+ pointer-events: none;
+ width: 25%;
+ height: 900px;
+ left: -12.5%;
+ top: calc(50% - 900px / 2 + 151px);
+ opacity: 0.2;
+ background: linear-gradient(180deg, #77b8ff 0%, rgba(42, 138, 246, 0.4) 100%);
+ filter: blur(125px);
+ transform: rotate(-15deg);
+ border-bottom-left-radius: 25% 25%;
+ border-bottom-right-radius: 25% 25%;
+ border-top-left-radius: 100% 100%;
+ border-top-right-radius: 100% 100%;
+ z-index: 200;
+ will-change: filter;
+ mix-blend-mode: normal;
+}
+
+.leftLights::after {
+ content: "";
+ position: absolute;
+ pointer-events: none;
+ width: 40%;
+ height: 422px;
+ left: 0px;
+ top: calc(50% - 422px / 2 + 298px);
+ opacity: 0.5;
+ background: linear-gradient(
+ 180deg,
+ rgba(29, 92, 162, 0.2) 0%,
+ rgba(42, 138, 246, 0.4) 100%
+ );
+ filter: blur(125px);
+ will-change: filter;
+ mix-blend-mode: normal;
+}
+
+.rightLights::before {
+ z-index: 200;
+ content: "";
+ position: absolute;
+ pointer-events: none;
+ width: 25%;
+ height: 900px;
+ right: -12.5%;
+ top: calc(50% - 900px / 2 + 151px);
+ background-image: linear-gradient(
+ 180deg,
+ rgba(236, 151, 207, 0.4) 0%,
+ rgba(233, 42, 103, 1) 100%
+ );
+ filter: blur(125px);
+ transform: rotate(15deg);
+ border-bottom-left-radius: 25% 25%;
+ border-bottom-right-radius: 25% 25%;
+ border-top-left-radius: 100% 100%;
+ border-top-right-radius: 100% 100%;
+ opacity: 0.2;
+ overflow: hidden;
+ will-change: filter;
+ mix-blend-mode: normal;
+}
+
+.rightLights::after {
+ content: "";
+ position: absolute;
+ pointer-events: none;
+ width: 40%;
+ height: 422px;
+ right: 0px;
+ top: calc(50% - 422px / 2 + 298px);
+ opacity: 0.25;
+
+ background: linear-gradient(
+ 180deg,
+ rgba(236, 151, 207, 0.4) 0%,
+ rgba(233, 42, 103, 1) 100%
+ );
+ transform: matrix(-1, 0, 0, 1, 0, 0);
+ filter: blur(125px);
+ will-change: filter;
+ mix-blend-mode: normal;
+}
+
+.counter-border {
+ --border-radius: 12px;
+ --border-size: 1px;
+ --padding: 1px;
+ --border-bg: conic-gradient(
+ from 180deg at 50% 50%,
+ #e92a67 0deg,
+ #a853ba 112.5deg,
+ #2a8af6 228.75deg,
+ rgba(42, 138, 246, 0) 360deg
+ );
+ position: relative;
+ overflow: hidden;
+ font-size: 2rem;
+ padding: calc(var(--padding) + var(--border-size));
+ border-radius: var(--border-radius);
+ display: inline-block;
+ z-index: 0;
+ backface-visibility: hidden;
+ perspective: 1000;
+ transform: translate3d(0, 0, 0);
+}
+
+.counter-border:hover {
+ cursor: pointer;
+}
+
+.counter-border i {
+ content: "";
+ position: absolute;
+ top: var(--border-size);
+ right: var(--border-size);
+ bottom: var(--border-size);
+ left: var(--border-size);
+ padding: var(--border-size);
+ mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+ mask-composite: exclude;
+ z-index: -1;
+ border-radius: calc(var(--border-radius) + var(--border-size));
+}
+
+.counter-border i::before {
+ content: "";
+ display: block;
+ background: var(--border-bg);
+ box-shadow: 0px 0px 40px 20px --var(--border-bg);
+ width: calc(100% * 1.41421356237);
+ padding-bottom: calc(100% * 1.41421356237);
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ border-radius: 100%;
+ z-index: -2;
+ animation: spin 5s linear infinite;
+}
+
+@media (prefers-reduced-motion) {
+ .counter-border i::before {
+ animation: none;
+ }
+}
+
+@keyframes spin {
+ from {
+ transform: translate(-50%, -50%) rotate(360deg);
+ }
+ to {
+ transform: translate(-50%, -50%) rotate(0);
+ }
+}
+
+.leftBottomLights {
+ position: absolute;
+ width: 387px;
+ height: 404px;
+ left: calc(50% - 387px / 2 - 80px);
+ bottom: -32px;
+ background: linear-gradient(180deg, #58a5ff 0%, #a67af4 100%);
+ mix-blend-mode: normal;
+ opacity: 0.15;
+ filter: blur(50px);
+ will-change: filter;
+}
+
+.rightBottomLights {
+ position: absolute;
+ width: 387px;
+ height: 404px;
+ left: calc(50% - 387px / 2 + 81px);
+ bottom: -32px;
+ background: linear-gradient(180deg, #ff3358 0%, #ff4fd8 100%);
+ mix-blend-mode: normal;
+ opacity: 0.15;
+ filter: blur(50px);
+ will-change: filter;
+}
diff --git a/docs/components/pages/landing/index.tsx b/docs/components/pages/landing/index.tsx
new file mode 100644
index 0000000..deb63f3
--- /dev/null
+++ b/docs/components/pages/landing/index.tsx
@@ -0,0 +1,199 @@
+import React from "react";
+import Head from "next/head";
+import cn from "classnames";
+import Link from "next/link";
+import { motion } from "framer-motion";
+import { Clients } from "../../clients/Clients";
+import { Marquee } from "../../clients/Marquee";
+import { TurboheroBackground } from "./TurboHeroBackground";
+import { Turborepo } from "./Turborepo";
+import { Turbopack } from "./Turbopack";
+import { FadeIn } from "../home-shared/FadeIn";
+import { LandingPageGlobalStyles } from "../home-shared/GlobalStyles";
+import styles from "./index.module.css";
+import PackLogo from "../../logos/PackLogo";
+import RepoLogo from "../../logos/RepoLogo";
+
+function Background() {
+ return (
+ <div className="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none">
+ <div
+ className={cn(
+ "z-[-1] absolute w-full h-full [--gradient-stop-1:60%] [--gradient-stop-2:85%] lg:[--gradient-stop-1:50%] lg:[--gradient-stop-2:90%]",
+ "[--gradient-color-1=rgba(0,0,0,1)] [--gradient-color-2=rgba(0,0,0,0.8)] [--gradient-color-3=rgba(0,0,0,0)]",
+ "dark:[--gradient-color-1=rgba(255,255,255,1)] dark:[--gradient-color-2=rgba(255,255,255,0.8)] dark:[--gradient-color-3=rgba(255,255,255,0)]"
+ )}
+ style={{
+ background:
+ "linear-gradient(180deg, var(--gradient-color-1) 0%, var(--gradient-color-2) var(--gradient-stop-1), var(--gradient-color-3) var(--gradient-stop-2), 100% transparent)",
+ }}
+ />
+ <span className={cn(styles.leftLights, "opacity-50 dark:opacity-100")} />
+ <span className={cn(styles.rightLights, "opacity-50 dark:opacity-100")} />
+ <span className="absolute bottom-0 left-0 w-full h-48 bg-gradient-to-t dark:from-black from-white to-transparent" />
+ <span className="bg-gradient-to-b dark:from-black from-white to-transparent absolute top-[20vh] left-0 w-full h-[50vh]" />
+ <TurboheroBackground />
+ </div>
+ );
+}
+
+export function CardBadge({ children }: { children: React.ReactNode }) {
+ return (
+ <div className="font-mono font-bold text-xs text-black/50 dark:text-white/50 px-[6px] py-[3.25px] tracking-[-0.01em] rounded-[6px] uppercase flex justify-center items-center bg-black/5 dark:bg-white/[0.15] border border-black/[0.1] dark:border-white/[0.1]">
+ {children}
+ </div>
+ );
+}
+
+const variants = {
+ hidden: { opacity: 0 },
+ active: { opacity: 1 },
+};
+
+function Card({
+ alt,
+ href,
+ title,
+ icon: Icon,
+ className,
+ children,
+}: {
+ href: string;
+ icon: React.ElementType;
+ title: "repo" | "pack";
+ alt?: string;
+ className?: string;
+ children: React.ReactNode;
+}) {
+ const [hovering, setHovering] = React.useState(false);
+ return (
+ <Link
+ href={href}
+ className={cn(
+ styles["counter-border"],
+ "w-[calc(100%_-_0px)] h-[304]px sm:!w-[488px] sm:h-[352px]"
+ )}
+ onMouseEnter={() => setHovering(true)}
+ onMouseLeave={() => setHovering(false)}
+ >
+ <motion.i
+ initial="hidden"
+ animate={hovering ? "active" : "hidden"}
+ variants={variants}
+ aria-hidden="true"
+ ></motion.i>
+ <div
+ className={cn(
+ "relative w-full h-full max-w-full !pb-12 pt-8 md:!pb-4 md:!pt-4 p-3 rounded-xl overflow-hidden flex flex-col items-center justify-center border border-[rgba(255,255,255,0.05)]",
+ className
+ )}
+ >
+ <div className="flex items-center justify-center flex-1 mb-7 md:mb-0">
+ <Icon />
+ </div>
+
+ <div className="flex flex-col items-center flex-1">
+ {title == "pack" ? (
+ <PackLogo
+ alt={alt}
+ className="w-[160px] md:w-[220px] mb-3 fill-black dark:fill-white"
+ />
+ ) : (
+ <RepoLogo
+ alt={alt}
+ className="w-[160px] md:w-[220px] mb-3 fill-black dark:fill-white"
+ />
+ )}
+ {children}
+ </div>
+ </div>
+ </Link>
+ );
+}
+
+function SiteCards() {
+ return (
+ <div className="flex w-full container items-center justify-center gap-6 px-6 sm:mx-0 mt-8 md:!mt-14 lg:!mt-15 md:mb-0 flex-col lg:!flex-row z-10 lg:!translate-y-0">
+ <FadeIn delay={0.1}>
+ <Card
+ title="repo"
+ alt="Turborepo"
+ icon={Turborepo}
+ href="/repo"
+ className="turborepoCardBg"
+ >
+ <p className="text-lg !w-[280px] md:!w-[340px] font-space-grotesk text-center opacity-50 dark:opacity-70">
+ High-performance build system for JavaScript and TypeScript
+ codebases.
+ </p>
+ </Card>
+ </FadeIn>
+ <FadeIn delay={0.2}>
+ <Card
+ title="pack"
+ alt="Turbopack"
+ icon={Turbopack}
+ href="/pack"
+ className="turbopackCardBg"
+ >
+ <div className="absolute top-3 left-3">
+ <CardBadge>alpha</CardBadge>
+ </div>
+ <p className="text-lg !w-[280px] md:!w-[340px] font-space-grotesk text-center opacity-50 dark:opacity-70 ">
+ Introducing the Rust-powered successor to Webpack.
+ </p>
+ </Card>
+ </FadeIn>
+ </div>
+ );
+}
+
+function Teams() {
+ return (
+ <div className="mx-auto ">
+ <p className="bg-contain mb-2 md:!mb-4 text-sm font-semibold tracking-wide text-center text-[#666666] dark:text-[#888888] uppercase">
+ Trusted by teams from
+ <br className="inline md:hidden" /> around the world
+ </p>
+ <div className="z-50 grid grid-flow-col grid-rows-6 sm:grid-rows-3 md:grid-rows-2 lg:grid-rows-1">
+ <Clients
+ companyList={[
+ "Vercel",
+ "AWS",
+ "Microsoft",
+ "Adobe",
+ "Disney",
+ "Netflix",
+ ]}
+ staticWidth
+ />
+ </div>
+ </div>
+ );
+}
+
+function LandingPage() {
+ return (
+ <>
+ <LandingPageGlobalStyles />
+ <main className="relative flex flex-col items-center justify-center w-full h-full overflow-hidden [--geist-foreground:#fff] dark:[--geist-foreground:#000] [--gradient-stop-1:0px] [--gradient-stop-2:120px] sm:[--gradient-stop-1:0px] sm:[--gradient-stop-2:120px]">
+ <Background />
+ <FadeIn className="z-10 flex flex-col items-center justify-center w-full h-full">
+ <h1 className="mt-12 lg:!mt-20 mx-6 w-[300px] md:!w-full font-extrabold text-5xl lg:text-6xl leading-tight text-center mb-4 bg-clip-text text-transparent bg-gradient-to-b from-black/80 to-black dark:from-white dark:to-[#AAAAAA]">
+ Make Ship Happen
+ </h1>
+ <p className="mx-6 text-xl max-h-[112px] md:max-h-[96px] w-[315px] md:w-[660px] md:text-2xl font-space-grotesk text-center text-[#666666] dark:text-[#888888]">
+ Turbo is an incremental bundler and build system optimized for
+ JavaScript and TypeScript, written in Rust.
+ </p>
+ </FadeIn>
+ <SiteCards />
+ <FadeIn delay={0.3} className="z-10 py-16">
+ <Teams />
+ </FadeIn>
+ </main>
+ </>
+ );
+}
+
+export default LandingPage;
diff --git a/docs/components/pages/landing/turbohero-background.module.css b/docs/components/pages/landing/turbohero-background.module.css
new file mode 100644
index 0000000..8f157e1
--- /dev/null
+++ b/docs/components/pages/landing/turbohero-background.module.css
@@ -0,0 +1,108 @@
+.container {
+ position: absolute;
+ z-index: -6;
+ overflow: hidden;
+ inset: 0;
+ transition: perspective 3000ms ease 0s;
+}
+
+.lines {
+ --right: #f8cde8;
+ --left: #b9ddff;
+ position: absolute;
+ width: 200vw;
+ margin-left: -50%;
+ transform: translateY(0);
+ background-image: linear-gradient(
+ to right,
+ var(--left) 45%,
+ rgba(0, 0, 0, 0) 50%,
+ var(--right) 55%
+ );
+ mask-image: linear-gradient(
+ to right,
+ rgba(0, 0, 0, 1) 2px,
+ rgba(0, 0, 0, 0) 1px
+ ),
+ linear-gradient(to bottom, rgba(0, 0, 0, 1) 2px, rgba(0, 0, 0, 0) 1px);
+ mask-size: 60px 60px;
+ overflow: hidden;
+ mask-repeat: repeat repeat;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ inset: -100% 0px;
+ background-position-y: 100%;
+ mask-position: 50% 0px;
+ animation: go-up 60s linear infinite;
+}
+
+@media (min-width: 1024px) {
+ .lines {
+ animation-duration: 30s;
+ mask-size: 80px 80px;
+ }
+}
+
+:global(.dark) .lines {
+ --right: #4c2638;
+ --left: #223b67;
+}
+
+@keyframes go-up {
+ 0% {
+ transform: translateY(0);
+ }
+
+ 100% {
+ transform: translateY(calc(50% + 28px));
+ }
+}
+
+.pulse::before {
+ content: "";
+ position: absolute;
+ inset: 0px;
+ animation: pulse-frames ease-out 8s infinite;
+ animation-delay: 0s;
+ background: rgba(0, 0, 0, 0)
+ linear-gradient(
+ to top,
+ rgba(0, 0, 0, 0) 45%,
+ var(--pulse-color) 50%,
+ rgba(0, 0, 0, 0) 90%
+ )
+ no-repeat;
+ z-index: 211;
+ animation-delay: var(--delay);
+}
+
+@keyframes pulse-frames {
+ 0% {
+ transform: translateY(0%);
+ }
+ 50% {
+ transform: translateY(200%);
+ }
+ 100% {
+ transform: translateY(200%);
+ }
+}
+
+@media (prefers-reduced-motion) {
+ .lines {
+ animation: none;
+ }
+ .pulse::before {
+ animation: none;
+ }
+}
+
+@media (prefers-reduced-motion) {
+ .lines {
+ animation: none;
+ }
+ .pulse::before {
+ animation: none;
+ }
+}
diff --git a/docs/components/pages/pack-home/DocsBenchmarkStat.tsx b/docs/components/pages/pack-home/DocsBenchmarkStat.tsx
new file mode 100644
index 0000000..5b8dcf6
--- /dev/null
+++ b/docs/components/pages/pack-home/DocsBenchmarkStat.tsx
@@ -0,0 +1,53 @@
+import benchmarkData from "./benchmark-data/data.json";
+
+type StatFunc = (data: typeof benchmarkData) => string;
+
+/**
+ * Replace with satisfies keyword when TS 4.9 drops
+ */
+const satisfies =
+ <T,>() =>
+ <U extends T>(t: U) =>
+ t;
+
+const formatToSeconds = (seconds: number) => `${seconds.toFixed(1)}s`;
+const formatPercentage = (percentage: number) => `${percentage.toFixed(1)}x`;
+
+const stats = satisfies<Record<string, StatFunc>>()({
+ "next12-cold-1000": (data) => formatToSeconds(data.cold[1000].next12),
+ "turbopack-cold-1000": (data) => formatToSeconds(data.cold[1000].next13),
+ "turbopack-cold-vs-next12": (data) =>
+ formatPercentage(data.cold[1000].next12 / data.cold[1000].next13),
+ "turbopack-cold-vs-next12-30000": (data) =>
+ formatPercentage(data.cold[30000].next12 / data.cold[30000].next13),
+ "turbopack-update-vs-next12": (data) =>
+ formatPercentage(
+ data.file_change[1000].next12 / data.file_change[1000].next13
+ ),
+ "turbopack-update-vs-next12-30000": (data) =>
+ formatPercentage(
+ data.file_change[30000].next12 / data.file_change[30000].next13
+ ),
+ "vite-cold-1000": (data) => formatToSeconds(data.cold[1000].vite),
+ "turbopack-cold-vs-vite": (data) =>
+ formatPercentage(data.cold[1000].vite / data.cold[1000].next13),
+ "turbopack-cold-vs-vite-30000": (data) =>
+ formatPercentage(data.cold[30000].vite / data.cold[30000].next13),
+ "turbopack-update-vs-vite": (data) =>
+ formatPercentage(
+ data.file_change[1000].vite / data.file_change[1000].next13
+ ),
+ "turbopack-update-vs-vite-30000": (data) =>
+ formatPercentage(
+ data.file_change[30000].vite / data.file_change[30000].next13
+ ),
+});
+
+type Stat = keyof typeof stats;
+
+export function DocsBenchmarkStat(props: { stat: Stat }) {
+ if (!stats[props.stat]) {
+ throw new Error(`Invalid stat: ${props.stat}`);
+ }
+ return stats[props.stat](benchmarkData);
+}
diff --git a/docs/components/pages/pack-home/DocsBenchmarksGraph.tsx b/docs/components/pages/pack-home/DocsBenchmarksGraph.tsx
new file mode 100644
index 0000000..7fb9d55
--- /dev/null
+++ b/docs/components/pages/pack-home/DocsBenchmarksGraph.tsx
@@ -0,0 +1,31 @@
+import { useState } from "react";
+import {
+ BenchmarkBar,
+ BenchmarkCategory,
+ BenchmarkNumberOfModules,
+} from "./PackBenchmarks";
+import { BenchmarksGraph } from "./PackBenchmarksGraph";
+import { PackBenchmarksPicker } from "./PackBenchmarksPicker";
+
+export function DocsBenchmarksGraph(props: {
+ bars: BenchmarkBar[];
+ category: BenchmarkCategory;
+}) {
+ const [numberOfModules, setNumberOfModules] =
+ useState<BenchmarkNumberOfModules>("1000");
+ return (
+ <div className="my-10">
+ <BenchmarksGraph
+ bars={props.bars}
+ category={props.category}
+ numberOfModules={numberOfModules}
+ pinTime
+ />
+ <div className="flex justify-center mt-6">
+ <PackBenchmarksPicker
+ setNumberOfModules={setNumberOfModules}
+ ></PackBenchmarksPicker>
+ </div>
+ </div>
+ );
+}
diff --git a/docs/components/pages/pack-home/PackBenchmarkTabs.tsx b/docs/components/pages/pack-home/PackBenchmarkTabs.tsx
new file mode 100644
index 0000000..f771fb1
--- /dev/null
+++ b/docs/components/pages/pack-home/PackBenchmarkTabs.tsx
@@ -0,0 +1,149 @@
+import { useRef, useState } from "react";
+import { AnimatePresence, motion } from "framer-motion";
+import { BenchmarkCategory } from "./PackBenchmarks";
+import classNames from "classnames";
+import gradients from "../home-shared/gradients.module.css";
+
+const TABS: {
+ id: BenchmarkCategory;
+ title: string;
+ soon: boolean;
+ tooltip: string;
+}[] = [
+ {
+ id: "cold",
+ title: "Cold Start",
+ soon: false,
+ tooltip: "First run",
+ },
+ {
+ id: "file_change",
+ title: "File Change",
+ soon: false,
+ tooltip: "Hot Reload (HMR)",
+ },
+ {
+ id: "code_build",
+ title: "Code Build",
+ soon: true,
+ tooltip: "First Build",
+ },
+ {
+ id: "build_from_cache",
+ title: "Build from Cache",
+ soon: true,
+ tooltip: "Second Build",
+ },
+];
+
+const TRANSITION = {
+ duration: 0.3,
+ ease: [0.59, 0.15, 0.18, 0.93],
+};
+
+function SoonBadge() {
+ return (
+ <span className="inline-flex items-center h-5 px-2 rounded-full text-xs font-medium dark:text-[#888888] dark:bg-[#333333] text-[#666666] bg-[#EAEAEA] ">
+ Soon
+ </span>
+ );
+}
+
+export function PackBenchmarkTabs({
+ onTabChange,
+}: {
+ onTabChange: (tab: BenchmarkCategory) => void;
+}) {
+ const [activeTab, setActiveTab] = useState(0);
+
+ const onTabClick = (index: number) => {
+ if (TABS[index].soon) return;
+ setActiveTab(index);
+ onTabChange(TABS[index].id);
+ };
+
+ return (
+ <div className="flex items-center justify-center w-full">
+ <div className="relative flex items-center justify-start pb-12 overflow-x-scroll overflow-y-clip no-scrollbar">
+ <AnimatePresence>
+ <div className="flex flex-row items-center rounded-full p-1 dark:bg-[#ffffff0d] bg-[#00000005] mx-5">
+ {TABS.map((tab, index) => (
+ <button
+ className="relative px-5 py-3"
+ key={tab.id}
+ onClick={() => onTabClick(index)}
+ disabled={tab.soon}
+ >
+ {TABS[activeTab].id === tab.id && (
+ <motion.div
+ className={classNames(
+ gradients.benchmarkActiveTab,
+ "absolute w-full rounded-full h-full top-0 left-0 border border-neutral-200 dark:border-neutral-800"
+ )}
+ layoutId="tabSwitcher"
+ style={{ borderRadius: 36 }}
+ transition={TRANSITION}
+ />
+ )}
+ <ToolTip text={tab.tooltip}>
+ <motion.div
+ animate={{ opacity: activeTab === index ? 1 : 0.4 }}
+ className="flex flex-row items-center justify-center gap-2 whitespace-nowrap"
+ transition={{ ...TRANSITION, duration: 0.2 }}
+ style={{ cursor: tab.soon ? "not-allowed" : "pointer" }}
+ >
+ <span
+ className="z-10 m-0 font-medium"
+ style={{ opacity: tab.soon ? 0.6 : 1 }}
+ >
+ {tab.title}
+ </span>
+ {tab.soon && <SoonBadge />}
+ </motion.div>
+ </ToolTip>
+ </button>
+ ))}
+ </div>
+ </AnimatePresence>
+ </div>
+ </div>
+ );
+}
+
+function ToolTip({ text, children }: { text; children: React.ReactNode }) {
+ const [show, setShow] = useState(false);
+ const timeout = useRef<NodeJS.Timeout>();
+
+ const onMouseEnter = () => {
+ timeout.current = setTimeout(() => {
+ setShow(true);
+ }, 800);
+ };
+
+ const onMouseLeave = () => {
+ clearTimeout(timeout.current);
+ setShow(false);
+ };
+
+ return (
+ <div
+ className="relative"
+ onMouseEnter={onMouseEnter}
+ onMouseLeave={onMouseLeave}
+ >
+ <motion.div
+ animate={show ? { opacity: 1, y: 0 } : { opacity: 0, y: -4 }}
+ transition={{ duration: 0.2, ease: [0.59, 0.15, 0.18, 0.93] }}
+ className={
+ "absolute top-[100%] mt-4 w-full flex flex-col items-center justify-center z-50"
+ }
+ >
+ <div className={gradients.tooltipArrow} />
+ <div className="dark:bg-[#333333] bg-neutral-100 rounded-lg px-4 py-1 whitespace-nowrap">
+ <p className="font-sans text-sm text-[#888888]">{text}</p>
+ </div>
+ </motion.div>
+ <div>{children}</div>
+ </div>
+ );
+}
diff --git a/docs/components/pages/pack-home/PackBenchmarks.tsx b/docs/components/pages/pack-home/PackBenchmarks.tsx
new file mode 100644
index 0000000..9bd1db8
--- /dev/null
+++ b/docs/components/pages/pack-home/PackBenchmarks.tsx
@@ -0,0 +1,97 @@
+import { useState } from "react";
+import { FadeIn } from "../home-shared/FadeIn";
+import { SectionHeader, SectionSubtext } from "../home-shared/Headings";
+import { BenchmarksGraph } from "./PackBenchmarksGraph";
+import { PackBenchmarksPicker } from "./PackBenchmarksPicker";
+import { PackBenchmarkTabs } from "./PackBenchmarkTabs";
+
+export type BenchmarkNumberOfModules = "1000" | "5000" | "10000" | "30000";
+export type BenchmarkCategory =
+ | "cold"
+ | "from_cache"
+ | "file_change"
+ | "code_build"
+ | "build_from_cache";
+export interface BenchmarkData {
+ next13: number;
+ next12: number;
+ vite: number;
+ next11: number;
+}
+
+export interface BenchmarkBar {
+ label: string;
+ key: keyof BenchmarkData;
+ turbo?: true;
+ swc?: true;
+}
+
+export const DEFAULT_BARS: BenchmarkBar[] = [
+ {
+ key: "next13",
+ label: "Next.js 13",
+ turbo: true,
+ },
+ {
+ key: "next12",
+ label: "Next.js 12",
+ },
+ {
+ key: "vite",
+ label: "Vite",
+ swc: true,
+ },
+ {
+ key: "next11",
+ label: "Next.js 11",
+ },
+];
+export const HMR_BARS: BenchmarkBar[] = [
+ {
+ key: "next13",
+ label: "Next.js 13",
+ turbo: true,
+ },
+ {
+ key: "vite",
+ label: "Vite",
+ swc: true,
+ },
+ {
+ key: "next12",
+ label: "Next.js 12",
+ },
+ {
+ key: "next11",
+ label: "Next.js 11",
+ },
+];
+
+export function PackBenchmarks() {
+ const [numberOfModules, setNumberOfModules] =
+ useState<BenchmarkNumberOfModules>("1000");
+ const [category, setCategory] = useState<BenchmarkCategory>("cold");
+
+ return (
+ <FadeIn className="relative flex flex-col items-center justify-center w-full gap-10 py-16 font-sans md:py-24 lg:py-32">
+ <div className="flex flex-col items-center gap-5 md:gap-6">
+ <SectionHeader>Faster Than Fast</SectionHeader>
+ <SectionSubtext>
+ Crafted by the creators of Webpack, Turbopack delivers unparalleled
+ performance at scale.
+ </SectionSubtext>
+ </div>
+ <div className="flex flex-col items-center w-full">
+ <PackBenchmarkTabs onTabChange={setCategory} />
+ <BenchmarksGraph
+ category={category}
+ numberOfModules={numberOfModules}
+ bars={DEFAULT_BARS}
+ />
+ </div>
+ <PackBenchmarksPicker
+ setNumberOfModules={setNumberOfModules}
+ ></PackBenchmarksPicker>
+ </FadeIn>
+ );
+}
diff --git a/docs/components/pages/pack-home/PackBenchmarksGraph.tsx b/docs/components/pages/pack-home/PackBenchmarksGraph.tsx
new file mode 100644
index 0000000..a4792b8
--- /dev/null
+++ b/docs/components/pages/pack-home/PackBenchmarksGraph.tsx
@@ -0,0 +1,333 @@
+import cn from "classnames";
+import {
+ animate,
+ motion,
+ useInView,
+ useAnimation,
+ AnimationPlaybackControls,
+} from "framer-motion";
+import Image from "next/image";
+import { useEffect, useRef, useState } from "react";
+import benchmarkData from "./benchmark-data/data.json";
+import { Gradient } from "../home-shared/Gradient";
+import gradients from "../home-shared/gradients.module.css";
+import {
+ BenchmarkBar,
+ BenchmarkCategory,
+ BenchmarkData,
+ BenchmarkNumberOfModules,
+} from "./PackBenchmarks";
+
+interface BenchmarksGraphProps {
+ category: BenchmarkCategory;
+ numberOfModules: BenchmarkNumberOfModules;
+ bars: BenchmarkBar[];
+ pinTime?: true;
+}
+
+export function BenchmarksGraph({
+ category,
+ numberOfModules,
+ bars,
+ pinTime,
+}: BenchmarksGraphProps) {
+ const data: BenchmarkData = benchmarkData[category][numberOfModules];
+ const keys = bars.map((bar) => bar.key);
+ const longestTime = Math.max(...keys.map((key) => data[key])) * 1000;
+ const longestTimeWithPadding = longestTime * 1.15;
+ const graphRef = useRef(null);
+ const graphInView = useInView(graphRef, { once: true, margin: "-128px" });
+
+ return (
+ <div className="flex w-full max-w-[1248px] relative px-6">
+ <div className="absolute top-0 flex items-center justify-center flex-1 w-full h-full">
+ <Gradient
+ gray
+ width="100%"
+ height="100%"
+ className="dark:opacity-0 dark:md:opacity-25 opacity-10"
+ />
+ </div>
+ <div
+ ref={graphRef}
+ className="relative flex flex-col flex-1 gap-6 md:gap-10"
+ >
+ {bars.map((bar) => {
+ return (
+ <GraphBar
+ key={bar.key}
+ turbo={bar.turbo}
+ Label={
+ <GraphLabel label={bar.label} turbo={bar.turbo} swc={bar.swc} />
+ }
+ duration={data[bar.key] * 1000}
+ longestTime={longestTimeWithPadding}
+ inView={graphInView}
+ pinTime={pinTime}
+ ></GraphBar>
+ );
+ })}
+ </div>
+ </div>
+ );
+}
+
+const START_DELAY = 0.0;
+
+const graphBarVariants = {
+ initial: {
+ width: 0,
+ },
+ progress: {
+ width: "100%",
+ },
+};
+
+const graphBarWrapperVariants = {
+ hidden: {
+ opacity: 0,
+ },
+ show: {
+ opacity: 1,
+ },
+};
+
+function GraphBar({
+ turbo,
+ duration,
+ longestTime,
+ inView,
+ Label,
+ pinTime,
+}: {
+ turbo?: boolean;
+ duration: number;
+ longestTime: number;
+ Label: JSX.Element;
+ inView?: boolean;
+ // Pin the time
+ pinTime?: true;
+}) {
+ const controls = useAnimation();
+ const [timer, setTimer] = useState(0);
+ const [timerAnimation, setTimerAnimation] =
+ useState<AnimationPlaybackControls>();
+ const [barWidth, setBarWidth] = useState(0);
+ const [, setFinished] = useState(false);
+
+ async function stopAnimation() {
+ timerAnimation && timerAnimation.stop();
+ controls.stop();
+ }
+
+ async function resetAnimation() {
+ setTimer(0);
+ setFinished(false);
+ await controls.start("initial");
+ }
+
+ async function startAnimation() {
+ const transition = {
+ duration: duration / 1000,
+ delay: START_DELAY,
+ };
+ setBarWidth((duration / longestTime) * 100);
+ await controls.start("show");
+ controls
+ .start("progress", {
+ ...transition,
+ ease: "linear",
+ })
+ .then(() => {
+ setFinished(true);
+ });
+ const timerAnimationRef = animate(0, duration, {
+ ...transition,
+ ease: "linear",
+ onUpdate(value) {
+ setTimer(value);
+ },
+ });
+ setTimerAnimation(timerAnimationRef);
+ }
+
+ async function playFullAnimation() {
+ await stopAnimation();
+ await controls.start("hidden");
+ await resetAnimation();
+ await startAnimation();
+ }
+
+ useEffect(() => {
+ if (inView) {
+ void startAnimation();
+ } else {
+ void stopAnimation();
+ void resetAnimation();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [inView]);
+
+ useEffect(() => {
+ if (!inView) return;
+ void playFullAnimation();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [duration, longestTime]);
+
+ return (
+ <div className="justify-center w-full gap-1 md:flex-row md:flex align-center">
+ <div className="flex items-center w-48">{Label}</div>
+ <div className="flex w-full items-center justify-between gap-4 z-10 border dark:border-[#333333] rounded-lg p-1">
+ <motion.div
+ animate={controls}
+ variants={graphBarWrapperVariants}
+ style={{ width: `${barWidth}%` }}
+ transition={{ duration: 0.1 }}
+ initial="hidden"
+ className={cn(
+ "flex items-center h-full rounded relative dark:bg-[#383838] bg-[#E6E6E6]"
+ )}
+ >
+ <motion.div
+ className={cn(
+ "h-12 rounded w-0 relative",
+ turbo ? gradients.benchmarkTurbo : gradients.benchmark,
+ { [gradients.barBorder]: !turbo }
+ )}
+ variants={graphBarVariants}
+ animate={controls}
+ transition={{ duration: 0.1 }}
+ />
+ </motion.div>
+ <motion.div
+ animate={controls}
+ variants={graphBarWrapperVariants}
+ className="pr-2"
+ transition={{ duration: 0.1 }}
+ >
+ <GraphTimer
+ turbo={turbo}
+ timer={pinTime ? duration : timer}
+ duration={duration}
+ />
+ </motion.div>
+ </div>
+ </div>
+ );
+}
+
+const GraphTimer = ({
+ turbo,
+ timer,
+ duration,
+}: {
+ turbo: boolean;
+ timer: number;
+ duration: number;
+}) => {
+ return (
+ <div className={`flex flex-row gap-2 w-24 justify-end items-center z-10`}>
+ {turbo && (
+ <div className="relative flex w-6 h-6">
+ <Image
+ alt="Turbopack"
+ src="/images/docs/pack/turbo-benchmark-icon-light.svg"
+ width={32}
+ height={32}
+ className="block dark:hidden"
+ />
+ <Image
+ alt="Turbopack"
+ src="/images/docs/pack/turbo-benchmark-icon-dark.svg"
+ width={32}
+ height={32}
+ className="hidden dark:block"
+ />
+ <Gradient
+ pink
+ width="100%"
+ height="100%"
+ small
+ className="opacity-0 dark:opacity-60"
+ />
+ </div>
+ )}
+ <p className="font-mono">
+ <Time value={timer} maxValue={duration} />
+ </p>
+ </div>
+ );
+};
+
+function roundTo(num: number, decimals: number) {
+ const factor = Math.pow(10, decimals);
+ return Math.round(num * factor) / factor;
+}
+
+const Time = ({
+ value,
+ maxValue,
+}: {
+ value: number;
+ maxValue: number;
+}): JSX.Element => {
+ let unitValue: string;
+ let unit: string;
+ if (maxValue < 1000) {
+ unitValue = Math.round(value).toFixed(0);
+ unit = "ms";
+ } else {
+ const roundedValue = roundTo(value / 1000, 1);
+ unitValue = roundedValue.toFixed(1);
+ unit = "s";
+ }
+
+ return (
+ <>
+ {unitValue}
+ {unit}
+ </>
+ );
+};
+
+function GraphLabel({
+ label,
+ turbo,
+ swc,
+ mobileOnly,
+ esbuild,
+}: {
+ label: string;
+ turbo?: boolean;
+ swc?: boolean;
+ mobileOnly?: boolean;
+ esbuild?: boolean;
+}) {
+ return (
+ <div
+ className={`flex items-center h-12 whitespace-nowrap font-bold gap-y-1 gap-x-2 ${
+ mobileOnly && "md:hidden"
+ }`}
+ >
+ <p>{label}</p>
+ {turbo && (
+ <p
+ className={cn(
+ "font-space-grotesk m-0",
+ gradients.benchmarkTurboLabel
+ )}
+ >
+ turbo
+ </p>
+ )}
+ {swc && (
+ <p className="font-space-grotesk m-0 font-light text-[#666666]">
+ with SWC
+ </p>
+ )}
+ {esbuild && (
+ <p className="font-space-grotesk m-0 text-[#666666]">esbuild</p>
+ )}
+ </div>
+ );
+}
diff --git a/docs/components/pages/pack-home/PackBenchmarksPicker.tsx b/docs/components/pages/pack-home/PackBenchmarksPicker.tsx
new file mode 100644
index 0000000..dd3fc15
--- /dev/null
+++ b/docs/components/pages/pack-home/PackBenchmarksPicker.tsx
@@ -0,0 +1,20 @@
+import { BenchmarkNumberOfModules } from "./PackBenchmarks";
+import { PackDropdown } from "./PackDropdown";
+
+export function PackBenchmarksPicker(props: {
+ setNumberOfModules: (num: BenchmarkNumberOfModules) => void;
+}) {
+ return (
+ <div className="flex items-center gap-3">
+ <a
+ className="dark:text-[#888888] hover:underline underline-offset-4 text-[#666666] text-sm"
+ href="https://github.com/vercel/turbo/blob/main/docs/components/pages/pack-home/benchmark-data"
+ >
+ React Components
+ </a>
+ <PackDropdown
+ onOptionSelected={(value) => props.setNumberOfModules(value)}
+ />
+ </div>
+ );
+}
diff --git a/docs/components/pages/pack-home/PackDropdown.tsx b/docs/components/pages/pack-home/PackDropdown.tsx
new file mode 100644
index 0000000..7ff5d76
--- /dev/null
+++ b/docs/components/pages/pack-home/PackDropdown.tsx
@@ -0,0 +1,117 @@
+import { useState, Fragment } from "react";
+import { Listbox, Transition } from "@headlessui/react";
+import { BenchmarkNumberOfModules } from "./PackBenchmarks";
+
+export function PackDropdown({
+ onOptionSelected,
+}: {
+ onOptionSelected: (option: BenchmarkNumberOfModules) => void;
+}) {
+ const [selectedOption, setSelectedOption] =
+ useState<BenchmarkNumberOfModules>("1000");
+
+ const onSelect = (option: BenchmarkNumberOfModules) => {
+ onOptionSelected(option);
+ setSelectedOption(option);
+ };
+
+ return (
+ <div className="relative">
+ <Listbox value={selectedOption} onChange={onSelect}>
+ <Listbox.Button className="flex w-24 pl-3 pr-2 py-2 gap-3 rounded !bg-[#fafafa] dark:!bg-[#111111] dark:hover:text-white hover:text-black dark:text-[#888888] text-[#666666] items-center justify-between transition-all text-sm leading-none font-medium m-0">
+ {Number(selectedOption).toLocaleString()}
+ <Arrow />
+ </Listbox.Button>
+
+ <Transition
+ as={Fragment}
+ leave="transition ease-in duration-100"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0"
+ >
+ <Listbox.Options className="absolute left-0 mt-2 w-full dark:bg-[#111111] bg-[#FAFAFA] rounded py-1 z-50 list">
+ <Listbox.Option
+ value="1000"
+ className={({ active }) =>
+ `relative cursor-default select-none py-1 text-sm pl-3 text-gray-400 ${
+ active ? "bg-gray-800 text-gray-100" : "text-gray-900"
+ }`
+ }
+ >
+ 1000
+ </Listbox.Option>
+ <Listbox.Option
+ className={({ active }) =>
+ `relative cursor-default select-none py-1 text-sm pl-3 text-gray-400 ${
+ active ? "bg-gray-800 text-gray-100" : "text-gray-900"
+ }`
+ }
+ value="5000"
+ >
+ 5000
+ </Listbox.Option>
+ <Listbox.Option
+ className={({ active }) =>
+ `relative cursor-default select-none py-1 text-sm pl-3 text-gray-400 ${
+ active ? "bg-gray-800 text-gray-100" : "text-gray-900"
+ }`
+ }
+ value="10000"
+ >
+ 10000
+ </Listbox.Option>
+ <Listbox.Option
+ className={({ active }) =>
+ `relative cursor-default select-none py-1 text-sm pl-3 text-gray-400 ${
+ active ? "bg-gray-800 text-gray-100" : "text-gray-900"
+ }`
+ }
+ value="30000"
+ >
+ 30000
+ </Listbox.Option>
+ </Listbox.Options>
+ </Transition>
+ </Listbox>
+ </div>
+ );
+}
+
+function BenchmarkOption({
+ value,
+ onSelect,
+}: {
+ value: BenchmarkNumberOfModules;
+ onSelect: (value: string) => void;
+}) {
+ return (
+ <div
+ className="flex pl-3 py-2 items-center justify-between cursor-pointer transition-all dark:text-[#888888] dark:hover:text-white text-[#666666] hover:text-[#000]"
+ onClick={() => onSelect(value)}
+ >
+ <p className="text-sm leading-none font-medium m-0">
+ {Number(value).toLocaleString()}
+ </p>
+ </div>
+ );
+}
+
+function Arrow() {
+ return (
+ <svg
+ width="16"
+ height="16"
+ viewBox="0 0 16 16"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M4 6L8 10L12 6"
+ stroke="#666666"
+ strokeWidth="1.5"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ />
+ </svg>
+ );
+}
diff --git a/docs/components/pages/pack-home/PackFeatures.tsx b/docs/components/pages/pack-home/PackFeatures.tsx
new file mode 100644
index 0000000..b668153
--- /dev/null
+++ b/docs/components/pages/pack-home/PackFeatures.tsx
@@ -0,0 +1,12 @@
+import { PACK_HOME_FEATURES } from "../../../content/features";
+import { FeaturesBento } from "../home-shared/FeaturesBento";
+
+export function PackFeatures() {
+ return (
+ <FeaturesBento
+ header="Why Turbopack?"
+ body="With incremental behavior and adaptable bundling strategies, Turbopack provides a fast and flexible development experience for apps of any size."
+ features={PACK_HOME_FEATURES}
+ />
+ );
+}
diff --git a/docs/components/pages/pack-home/PackHero.tsx b/docs/components/pages/pack-home/PackHero.tsx
new file mode 100644
index 0000000..eb5b348
--- /dev/null
+++ b/docs/components/pages/pack-home/PackHero.tsx
@@ -0,0 +1,115 @@
+import cn from "classnames";
+import Image from "next/image";
+import Link from "next/link";
+// import { Marquee } from "../../clients/Marquee";
+// import { Clients } from "../../clients/Clients";
+import gradients from "../home-shared/gradients.module.css";
+import { HeroText, SectionSubtext } from "../home-shared/Headings";
+import { Gradient } from "../home-shared/Gradient";
+import { FadeIn } from "../home-shared/FadeIn";
+import { CTAButton } from "../home-shared/CTAButton";
+import PackLogo from "../../logos/PackLogo";
+
+export function PackHero() {
+ return (
+ <>
+ <FadeIn
+ noVertical
+ className="font-sans w-auto pb-16 pt-[48px] md:pb-24 lg:pb-32 md:pt-16 lg:pt-20 flex justify-between gap-8 items-center flex-col relative z-0"
+ >
+ <FadeIn className="z-50 flex items-center justify-center w-full">
+ <div className="absolute min-w-[614px] min-h-[614px]">
+ <Image
+ alt="Turbopack"
+ src="/images/docs/pack/turbopack-hero-hexagons-dark.svg"
+ width={614}
+ height={614}
+ className="hidden dark:block"
+ />
+ <Image
+ alt="Turbopack"
+ src="/images/docs/pack/turbopack-hero-hexagons-light.svg"
+ width={614}
+ height={614}
+ className="block dark:hidden"
+ />
+ </div>
+ <div className="absolute z-50 flex items-center justify-center w-64 h-64">
+ <Gradient
+ small
+ width={120}
+ height={120}
+ conic
+ className="dark:opacity-100 opacity-40"
+ />
+ </div>
+
+ <div className="w-[120px] z-50 mt-[-8.075px] mb-[-8.075px]">
+ <Image
+ alt=""
+ src={`/images/docs/pack/turbopack-hero-logo-dark.svg`}
+ width={120}
+ height={136.15}
+ className="hidden dark:block"
+ />
+ <Image
+ alt=""
+ src={`/images/docs/pack/turbopack-hero-logo-light.svg`}
+ width={120}
+ height={136.15}
+ className="block dark:hidden"
+ />
+ </div>
+ </FadeIn>
+ <Gradient
+ width={1000}
+ height={1000}
+ className="top-[-500px] dark:opacity-20 opacity-[0.15]"
+ conic
+ />
+ <div className="absolute top-0 z-10 w-full h-48 dark:from-black from-white to-transparent bg-gradient-to-b" />
+ <FadeIn
+ delay={0.15}
+ className="z-50 flex flex-col items-center justify-center gap-5 px-6 text-center lg:gap-6"
+ >
+ <PackLogo
+ alt="Turbopack"
+ width="200"
+ className="w-[160px] md:w-[200px] fill-black dark:fill-white"
+ />
+ <HeroText h1>The Rust-powered successor to Webpack</HeroText>
+ <SectionSubtext hero>
+ Turbopack is an incremental bundler optimized for JavaScript and
+ TypeScript, written in Rust.
+ </SectionSubtext>
+ </FadeIn>
+ <FadeIn
+ delay={0.3}
+ className="z-50 flex flex-col items-center w-full max-w-md gap-5 px-6"
+ >
+ <div className="flex flex-col w-full gap-3 md:!flex-row">
+ <CTAButton>
+ <Link href="/pack/docs" className="block py-3">
+ Get Started
+ </Link>
+ </CTAButton>
+ <CTAButton outline>
+ <a
+ target="_blank"
+ rel="noreferrer"
+ href="https://github.com/vercel/turbo"
+ className="block py-3"
+ >
+ GitHub
+ </a>
+ </CTAButton>
+ </div>
+ <p className="text-sm text-[#666666]">License: MPL-2.0</p>
+ </FadeIn>
+ <FadeIn delay={0.5} className="relative w-full">
+ <div className="absolute bottom-0 w-full dark:from-black from-white to-transparent h-72 bg-gradient-to-t" />
+ </FadeIn>
+ </FadeIn>
+ </>
+ );
+}
diff --git a/docs/components/pages/pack-home/PackLetter.tsx b/docs/components/pages/pack-home/PackLetter.tsx
new file mode 100644
index 0000000..d4b0f2f
--- /dev/null
+++ b/docs/components/pages/pack-home/PackLetter.tsx
@@ -0,0 +1,114 @@
+import { HeroText } from "../home-shared/Headings";
+import Image from "next/image";
+import cn from "classnames";
+import gradients from "../home-shared/gradients.module.css";
+import { FadeIn } from "../home-shared/FadeIn";
+import { CTAButton } from "../home-shared/CTAButton";
+import Link from "next/link";
+import { Gradient } from "../home-shared/Gradient";
+
+export function PackLetter() {
+ return (
+ <section className="relative flex flex-col items-center px-6 py-16 font-sans md:py-24 lg:py-32 gap-14">
+ <FadeIn>
+ <HeroText>
+ Let&apos;s move
+ <br />
+ the web forward
+ </HeroText>
+ </FadeIn>
+ <div className="flex flex-col max-w-xl leading-6 md:text-lg lg:text-lg">
+ <FadeIn className="opacity-70">
+ <p>
+ It&apos;s time for a new beginning in compiler infrastructure for
+ the entire web ecosystem. Webpack has been downloaded over 3 billion
+ times. It&apos;s become an integral part of building for the web.
+ But just like Babel and Terser, it&apos;s time to go all-in on
+ native. I joined Vercel and assembled a team of world class
+ engineers to build the web&apos;s next generation bundler.
+ </p>
+ <br />
+ <p>
+ This team has taken lessons from 10 years of Webpack, combined with
+ the innovations in incremental computation from Turborepo and
+ Google&apos;s Bazel, and invented an architecture ready to withstand
+ the next 10 years.
+ </p>
+ <br />
+ <p>
+ With that, we&apos;re excited to introduce Turbopack, our
+ Rust-powered successor to Webpack. It will harness the power of our
+ build system, Turborepo, for massive performance improvements.
+ Turbopack is the new foundation of high-performance bare-metal
+ tooling and is now open source—we&apos;re excited to share it with
+ you.
+ </p>
+ </FadeIn>
+ <FadeIn
+ noVertical
+ viewTriggerOffset
+ className="relative h-2 md:h-12 lg:h-12"
+ >
+ <span
+ className={cn(
+ "w-full h-[1px] -bottom-8 md:-bottom-4 lg:-bottom-4 absolute",
+ gradients.letterLine
+ )}
+ />
+ </FadeIn>
+ <FadeIn
+ viewTriggerOffset
+ noVertical
+ className="flex items-end justify-center gap-3 md:self-start md:-ml-4 lg:self-start lg:-ml-4 min-w-[300px]"
+ >
+ <div className="w-24 h-24 min-w-[96px] min-h-[96px] rounded-full border dark:border-white/10 border-black/10 flex items-center justify-center ">
+ <Image
+ alt="Image of Tobias Koopers"
+ src="/images/people/tobiaskoppers.jpg"
+ width={64}
+ height={64}
+ className="rounded-full"
+ />
+ </div>
+ <div className="flex flex-col gap-3 pb-2">
+ <Image
+ alt="Tobias Koppers hand written signature"
+ src="/images/docs/pack/tobias-signature-light.svg"
+ // 16 px added and offset to account for the glow
+ width={173 + 16}
+ height={91 + 16}
+ className="block -mb-3 -ml-3 dark:hidden"
+ />
+ <Image
+ alt="Tobias Koppers hand written signature"
+ src="/images/docs/pack/tobias-signature-dark.svg"
+ // 16 px added and offset to account for the glow
+ width={173 + 16}
+ height={91 + 16}
+ className="hidden -mb-3 -ml-3 dark:block"
+ />
+ <div className="flex gap-2 flex-wrap text-sm leading-none text-[#888888] max-w-[156px] md:max-w-xl lg:max-w-xl">
+ <p className="font-bold">Tobias Koppers</p>
+ <p>Creator of Webpack</p>
+ </div>
+ </div>
+ </FadeIn>
+ </div>
+ <FadeIn noVertical className="relative flex justify-center w-full mt-16">
+ <div className="max-w-[180px] w-full">
+ <CTAButton>
+ <Link href="/pack/docs" className="block py-3 font-sans">
+ Start Building
+ </Link>
+ </CTAButton>
+ </div>
+ <Gradient
+ width={1200}
+ height={300}
+ className="bottom-[-200px] -z-10 opacity-20"
+ conic
+ />
+ </FadeIn>
+ </section>
+ );
+}
diff --git a/docs/components/pages/pack-home/benchmark-data/README.md b/docs/components/pages/pack-home/benchmark-data/README.md
new file mode 100644
index 0000000..9b53481
--- /dev/null
+++ b/docs/components/pages/pack-home/benchmark-data/README.md
@@ -0,0 +1,7 @@
+# `turbopack` Benchmark Data
+
+- `bench_startup`: Time from cold start of the bundler to the browser successfully retrieving bundled scripts. This does not include react hydration time.
+- `bench_hydration`: Time from cold start of the bundler to the browser successfully retrieving bundled scripts. This does wait until react hydration has completed.
+- `bench_restart`: Before measuring: warms up any available persistent cache (we don’t have one yet) by performing the equivalent of the bench_hydration benchmark, shuts down the server. Then, times another bench_hydration.
+- `bench_hmr_to_eval`: Measures the time it takes from an incremental change to be made, bundled, sent over hmr, and evaluated by the browser.
+- `bench_hmr_to_commit`: Measures the time it takes from an incremental change to be made, bundled, sent over hmr, evaluated by the browser, and committed by React (runs a useEffect).
diff --git a/docs/components/pages/pack-home/benchmark-data/data.json b/docs/components/pages/pack-home/benchmark-data/data.json
new file mode 100644
index 0000000..f88ae6f
--- /dev/null
+++ b/docs/components/pages/pack-home/benchmark-data/data.json
@@ -0,0 +1,54 @@
+{
+ "cold": {
+ "1000": {
+ "next13": 1.38187759,
+ "vite": 4.19890847,
+ "next12": 3.64327949,
+ "next11": 9.19035540
+ },
+ "5000": {
+ "next13": 3.99792562,
+ "vite": 16.59615430,
+ "next12": 12.14057345,
+ "next11": 32.89712268
+ },
+ "10000": {
+ "next13": 7.34248178,
+ "vite": 32.25177941,
+ "next12": 23.27525035,
+ "next11": 71.80680350
+ },
+ "30000": {
+ "next13": 21.97034306,
+ "vite": 97.74466099,
+ "next12": 89.07274544,
+ "next11": 237.61188540
+ }
+ },
+ "file_change": {
+ "1000": {
+ "next13": 0.01890358,
+ "vite": 0.10476515,
+ "next12": 0.14617346,
+ "next11": 0.21155549
+ },
+ "5000": {
+ "next13": 0.02379283,
+ "vite": 0.10963156,
+ "next12": 0.49470051,
+ "next11": 0.86600602
+ },
+ "10000": {
+ "next13": 0.02302405,
+ "vite": 0.11295908,
+ "next12": 1.15193035,
+ "next11": 2.35675312
+ },
+ "30000": {
+ "next13": 0.02246753,
+ "vite": 0.13328557,
+ "next12": 6.40370549,
+ "next11": 9.50431942
+ }
+ }
+}
diff --git a/docs/components/pages/pack-home/index.tsx b/docs/components/pages/pack-home/index.tsx
new file mode 100644
index 0000000..db73b25
--- /dev/null
+++ b/docs/components/pages/pack-home/index.tsx
@@ -0,0 +1,24 @@
+import { PackBenchmarks } from "./PackBenchmarks";
+import { PackHero } from "./PackHero";
+import { PackLetter } from "./PackLetter";
+import { PackFeatures } from "./PackFeatures";
+import { GradientSectionBorder } from "../home-shared/GradientSectionBorder";
+import { LandingPageGlobalStyles } from "../home-shared/GlobalStyles";
+
+export default function Home() {
+ return (
+ <>
+ <LandingPageGlobalStyles />
+ <main className="relative">
+ <PackHero />
+ <GradientSectionBorder>
+ <PackBenchmarks />
+ <PackFeatures />
+ </GradientSectionBorder>
+ <GradientSectionBorder>
+ <PackLetter />
+ </GradientSectionBorder>
+ </main>
+ </>
+ );
+}
diff --git a/docs/components/pages/repo-home/RepoFeatures.tsx b/docs/components/pages/repo-home/RepoFeatures.tsx
new file mode 100644
index 0000000..4499725
--- /dev/null
+++ b/docs/components/pages/repo-home/RepoFeatures.tsx
@@ -0,0 +1,15 @@
+import { REPO_HOME_FEATURES } from "../../../content/features";
+import { FadeIn } from "../home-shared/FadeIn";
+import { FeaturesBento } from "../home-shared/FeaturesBento";
+
+export function RepoFeatures() {
+ return (
+ <FadeIn className="py-16 md:py-24 lg:py-32">
+ <FeaturesBento
+ header="Why Turborepo?"
+ body="Turborepo reimagines build system techniques used by Facebook and Google to remove maintenance burden and overhead."
+ features={REPO_HOME_FEATURES}
+ />
+ </FadeIn>
+ );
+}
diff --git a/docs/components/pages/repo-home/RepoHero.tsx b/docs/components/pages/repo-home/RepoHero.tsx
new file mode 100644
index 0000000..a63777a
--- /dev/null
+++ b/docs/components/pages/repo-home/RepoHero.tsx
@@ -0,0 +1,114 @@
+import cn from "classnames";
+import Image from "next/image";
+import Link from "next/link";
+import gradients from "../home-shared/gradients.module.css";
+import { HeroText, SectionSubtext } from "../home-shared/Headings";
+import { Gradient } from "../home-shared/Gradient";
+import { FadeIn } from "../home-shared/FadeIn";
+import { CTAButton } from "../home-shared/CTAButton";
+import RepoLogo from "../../logos/RepoLogo";
+
+export function RepoHero() {
+ return (
+ <>
+ <FadeIn
+ noVertical
+ className="font-sans w-auto pb-16 pt-[48px] md:pb-24 lg:pb-32 md:pt-16 lg:pt-20 flex justify-between gap-8 items-center flex-col relative z-0"
+ >
+ <FadeIn className="z-50 flex items-center justify-center w-full ">
+ <div className="absolute min-w-[614px] min-h-[614px]">
+ {/* TODO: On dark mode, there should be a "breathing" gradient inside the inner circle */}
+ <Image
+ alt="Turborepo"
+ src="/images/docs/repo/repo-hero-circles-dark.svg"
+ width={614}
+ height={614}
+ className="hidden dark:block"
+ />
+ <Image
+ alt="Turborepo"
+ src="/images/docs/repo/repo-hero-circles-light.svg"
+ width={614}
+ height={614}
+ className="block dark:hidden"
+ />
+ </div>
+ <div className="absolute z-50 flex items-center justify-center w-64 h-64">
+ <Gradient
+ small
+ width={120}
+ height={120}
+ conic
+ className="dark:opacity-100 opacity-40"
+ />
+ </div>
+
+ <div className="w-[120px] h-[120px] z-50">
+ <Image
+ alt=""
+ src={`/images/docs/repo/repo-hero-logo-dark.svg`}
+ width={120}
+ height={120}
+ className="hidden dark:block"
+ />
+ <Image
+ alt=""
+ src={`/images/docs/repo/repo-hero-logo-light.svg`}
+ width={120}
+ height={120}
+ className="block dark:hidden"
+ />
+ </div>
+ </FadeIn>
+ <Gradient
+ width={1000}
+ height={1000}
+ className="top-[-500px] dark:opacity-20 opacity-[0.15]"
+ conic
+ />
+ <div className="absolute top-0 z-10 w-full h-48 dark:from-black from-white to-transparent bg-gradient-to-b" />
+ <FadeIn
+ delay={0.15}
+ className="z-50 flex flex-col items-center justify-center gap-5 px-6 text-center lg:gap-6"
+ >
+ <RepoLogo
+ alt="Turborepo"
+ width="200"
+ className="w-[160px] md:w-[200px] fill-black dark:fill-white"
+ />
+ <HeroText h1>The build system that makes ship happen</HeroText>
+ <SectionSubtext hero>
+ Turborepo is a high-performance build system for JavaScript and
+ TypeScript codebases.
+ </SectionSubtext>
+ </FadeIn>
+ <FadeIn
+ delay={0.3}
+ className="z-50 flex flex-col items-center w-full max-w-md gap-5 px-6"
+ >
+ <div className="flex flex-col w-full gap-3 md:!flex-row">
+ <CTAButton>
+ <Link href="/repo/docs" className="block py-3">
+ Get Started
+ </Link>
+ </CTAButton>
+ <CTAButton outline>
+ <a
+ target="_blank"
+ rel="noreferrer"
+ href="https://github.com/vercel/turbo"
+ className="block py-3"
+ >
+ GitHub
+ </a>
+ </CTAButton>
+ </div>
+ <p className="text-sm text-[#666666]">License: MPL-2.0</p>
+ </FadeIn>
+ <FadeIn delay={0.5} className="relative w-full">
+ <div className="absolute bottom-0 w-full dark:from-black from-white to-transparent h-72 bg-gradient-to-t" />
+ </FadeIn>
+ </FadeIn>
+ </>
+ );
+}
diff --git a/docs/components/pages/repo-home/RepoLetter.tsx b/docs/components/pages/repo-home/RepoLetter.tsx
new file mode 100644
index 0000000..7093a3a
--- /dev/null
+++ b/docs/components/pages/repo-home/RepoLetter.tsx
@@ -0,0 +1,118 @@
+import { HeroText } from "../home-shared/Headings";
+import Image from "next/image";
+import cn from "classnames";
+import gradients from "../home-shared/gradients.module.css";
+import { FadeIn } from "../home-shared/FadeIn";
+import { CTAButton } from "../home-shared/CTAButton";
+import Link from "next/link";
+import { Gradient } from "../home-shared/Gradient";
+
+export function RepoLetter() {
+ return (
+ <section className="relative flex flex-col items-center px-6 py-16 font-sans md:py-24 lg:py-32 gap-14">
+ <FadeIn>
+ <HeroText className="lg:text-[65px]">
+ Scaling your Codebase
+ <br />
+ shouldn&apos;t be so difficult
+ </HeroText>
+ </FadeIn>
+ <div className="flex flex-col max-w-xl leading-6 md:text-lg lg:text-lg">
+ <FadeIn className="opacity-70">
+ <p>
+ The bigger your project grows, the slower it gets. Tasks like
+ linting, testing, and building begin to take enormous amounts of
+ time.
+ </p>
+ <br />
+ <p>
+ If you&apos;re serving multiple applications, you might reach for a
+ monorepo. They&apos;re incredible for productivity, especially on
+ the frontend, but the tooling can be a nightmare. There&apos;s a lot
+ of stuff to do (and things to mess up). Nothing &ldquo;just
+ works.&rdquo; It&apos;s become completely normal to waste entire
+ days or weeks on plumbing—tweaking configs, writing one-off scripts,
+ and stitching stuff together.
+ </p>
+ <br />
+ <p>We need something else.</p>
+ <br></br>
+ <p>
+ A fresh take on the whole setup. Designed to glue everything
+ together. A toolchain that works for you and not against you. With
+ sensible defaults, but even better escape hatches. Built with the
+ same techniques used by the big guys, but in a way that doesn&apos;t
+ require PhD to learn or a staff to maintain.
+ </p>
+ <br />
+ <p>With Turborepo, we&apos;re doing just that.</p>
+ <br />
+ <p>
+ We&apos;re building a build system that can keep up with your team.
+ You&apos;ll see your CI get faster, duplicated work get cut, and
+ your NPM scripts get simpler. You&apos;ll get a world-class
+ development environment, without the maintenance burden.
+ </p>
+ </FadeIn>
+ <FadeIn noVertical viewTriggerOffset className="relative h-2 md:h-12">
+ <span
+ className={cn(
+ "w-full h-[1px] -bottom-8 md:-bottom-4 lg:-bottom-4 absolute",
+ gradients.letterLine
+ )}
+ />
+ </FadeIn>
+ <FadeIn
+ viewTriggerOffset
+ noVertical
+ className="flex items-end justify-center gap-3 md:self-start md:-ml-4 lg:self-start lg:-ml-4 min-w-[300px]"
+ >
+ <div className="w-24 h-24 min-w-[96px] min-h-[96px] rounded-full border dark:border-white/10 border-black/10 flex items-center justify-center ">
+ <Image
+ alt="Image of Jared Palmer"
+ src="/images/people/jaredpalmer.jpeg"
+ width={64}
+ height={64}
+ className="rounded-full grayscale"
+ />
+ </div>
+ <div className="flex flex-col">
+ <Image
+ alt="Jared Palmer's hand written signature"
+ src="/images/docs/repo/jared-signature-light.svg"
+ width={190}
+ height={90}
+ className="block mt-3 mb-4 ml-3 dark:hidden"
+ />
+ <Image
+ alt="Jared Palmer's hand written signature"
+ src="/images/docs/repo/jared-signature-dark.svg"
+ width={209}
+ height={116}
+ className="hidden -mt-2 dark:block"
+ />
+ <div className="flex gap-2 flex-wrap text-sm leading-none text-[#888888] max-w-[156px] md:max-w-xl lg:max-w-xl">
+ <p className="font-bold">Jared Palmer</p>
+ <p>Founder of Turborepo</p>
+ </div>
+ </div>
+ </FadeIn>
+ </div>
+ <FadeIn noVertical className="relative flex justify-center w-full mt-16">
+ <div className="max-w-[180px] w-full">
+ <CTAButton>
+ <Link href="/repo/docs" className="block py-3 font-sans">
+ Start Building
+ </Link>
+ </CTAButton>
+ </div>
+ <Gradient
+ width={1200}
+ height={300}
+ className="bottom-[-200px] -z-10 opacity-20"
+ conic
+ />
+ </FadeIn>
+ </section>
+ );
+}
diff --git a/docs/components/pages/repo-home/index.tsx b/docs/components/pages/repo-home/index.tsx
new file mode 100644
index 0000000..992a614
--- /dev/null
+++ b/docs/components/pages/repo-home/index.tsx
@@ -0,0 +1,22 @@
+import { RepoHero } from "./RepoHero";
+import { RepoFeatures } from "./RepoFeatures";
+import { RepoLetter } from "./RepoLetter";
+import { GradientSectionBorder } from "../home-shared/GradientSectionBorder";
+import { LandingPageGlobalStyles } from "../home-shared/GlobalStyles";
+
+export default function Home() {
+ return (
+ <>
+ <LandingPageGlobalStyles />
+ <main className="relative">
+ <RepoHero />
+ <GradientSectionBorder>
+ <RepoFeatures />
+ </GradientSectionBorder>
+ <GradientSectionBorder>
+ <RepoLetter />
+ </GradientSectionBorder>
+ </main>
+ </>
+ );
+}
diff --git a/docs/components/pages/showcase.tsx b/docs/components/pages/showcase.tsx
new file mode 100644
index 0000000..62c8f50
--- /dev/null
+++ b/docs/components/pages/showcase.tsx
@@ -0,0 +1,47 @@
+/* eslint-disable react/no-unescaped-entities */
+import { Container } from "../Container";
+import { Clients } from "../clients/Clients";
+
+export default function Showcase() {
+ return (
+ <main className="relative">
+ <div className="mx-auto">
+ <div className="py-16 lg:text-center">
+ <p className="text-base font-semibold leading-6 tracking-wide text-blue-600 uppercase dark:text-gray-400 font-space-grotesk">
+ Showcase
+ </p>
+ <h1 className="mt-2 text-3xl font-extrabold leading-8 tracking-tight text-gray-900 md:text-5xl dark:text-white sm:text-4xl sm:leading-10">
+ Who's using Turbo?
+ </h1>
+ <p className="max-w-3xl mt-4 text-xl leading-7 text-gray-500 dark:text-gray-400 lg:mx-auto font-space-grotesk">
+ Turbo is the one of the fastest growing toolchains in the frontend
+ ecosystem. It's trusted by thousands of developers in production
+ including teams at Vercel, AWS, Netflix, Microsoft, Disney, and
+ more.
+ </p>
+ </div>
+ </div>
+
+ <div className="grid items-center grid-cols-3 gap-16 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-7 ">
+ <Clients linked />
+ </div>
+ <Container>
+ <div className="max-w-xl pt-20 pb-24 mx-auto space-y-6 text-center">
+ <div className="mt-2 text-2xl font-extrabold leading-8 tracking-tight text-gray-900 dark:text-white sm:text-4xl sm:leading-10">
+ Are you using Turbo?
+ </div>
+ <div className="mx-auto rounded-md">
+ <a
+ href="https://github.com/vercel/turbo/edit/main/docs/components/clients/users.ts"
+ target="_blank"
+ rel="noopener noreferrer"
+ className="inline-flex items-center justify-center w-auto px-8 py-3 text-base font-medium text-white no-underline bg-black border border-transparent rounded-md dark:bg-white dark:text-black betterhover:dark:hover:bg-gray-300 betterhover:hover:bg-gray-700 md:py-3 md:text-lg md:px-10 md:leading-6"
+ >
+ Add Your Company
+ </a>
+ </div>
+ </div>
+ </Container>
+ </main>
+ );
+}
diff --git a/docs/components/useIsomorphicLayoutEffect.tsx b/docs/components/useIsomorphicLayoutEffect.tsx
new file mode 100644
index 0000000..c685670
--- /dev/null
+++ b/docs/components/useIsomorphicLayoutEffect.tsx
@@ -0,0 +1,7 @@
+import * as React from "react";
+
+const useIsomorphicLayoutEffect =
+ typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
+
+/* eslint-disable-next-line import/no-default-export -- TODO: Fix ESLint Error (#13355) */
+export default useIsomorphicLayoutEffect;
diff --git a/docs/components/usePrefersReducedMotion.tsx b/docs/components/usePrefersReducedMotion.tsx
new file mode 100644
index 0000000..dd9d82c
--- /dev/null
+++ b/docs/components/usePrefersReducedMotion.tsx
@@ -0,0 +1,44 @@
+import { useState, useEffect } from "react";
+
+const QUERY = "(prefers-reduced-motion: no-preference)";
+const isRenderingOnServer = typeof window === "undefined";
+/**
+ * All code here from https://www.joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion/
+ */
+const getInitialState = () => {
+ // For our initial server render, we won't know if the user
+ // prefers reduced motion, but it doesn't matter. This value
+ // will be overwritten on the client, before any animations
+ // occur.
+ return isRenderingOnServer ? true : !window.matchMedia(QUERY).matches;
+};
+
+/**
+ * Checks the user's device setting for `prefers-reduced-motion`.
+ * Use this if you can't use a media query in CSS.
+ *
+ * From https://www.joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion/
+ */
+export function usePrefersReducedMotion(): boolean {
+ const [prefersReducedMotion, setPrefersReducedMotion] =
+ useState(getInitialState);
+ useEffect(() => {
+ const mediaQueryList = window.matchMedia(QUERY);
+ const listener = (event: MediaQueryListEvent) => {
+ setPrefersReducedMotion(!event.matches);
+ };
+ if (mediaQueryList.addEventListener) {
+ mediaQueryList.addEventListener("change", listener);
+ } else {
+ mediaQueryList.addListener(listener);
+ }
+ return () => {
+ if (mediaQueryList.removeEventListener) {
+ mediaQueryList.removeEventListener("change", listener);
+ } else {
+ mediaQueryList.removeListener(listener);
+ }
+ };
+ }, []);
+ return prefersReducedMotion;
+}