aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/docs/components/pages
diff options
context:
space:
mode:
authorHsiangNianian <admin@jyunko.cn>2023-04-22 19:52:26 +0800
committerHsiangNianian <admin@jyunko.cn>2023-04-22 19:52:26 +0800
commit4838df315931bb883f704ec3e1abe2685f296cdf (patch)
tree57a8550c4cd5338f1126364bb518c6cde8d96e7d /docs/components/pages
parentdb74ade0234a40c2120ad5f2a41bee50ce13de02 (diff)
downloadHydroRoll-4838df315931bb883f704ec3e1abe2685f296cdf.tar.gz
HydroRoll-4838df315931bb883f704ec3e1abe2685f296cdf.zip
😀
Diffstat (limited to 'docs/components/pages')
-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
34 files changed, 2620 insertions, 0 deletions
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>
+ );
+}