diff options
| author | 2023-04-22 19:52:26 +0800 | |
|---|---|---|
| committer | 2023-04-22 19:52:26 +0800 | |
| commit | 4838df315931bb883f704ec3e1abe2685f296cdf (patch) | |
| tree | 57a8550c4cd5338f1126364bb518c6cde8d96e7d /docs/components/pages | |
| parent | db74ade0234a40c2120ad5f2a41bee50ce13de02 (diff) | |
| download | HydroRoll-4838df315931bb883f704ec3e1abe2685f296cdf.tar.gz HydroRoll-4838df315931bb883f704ec3e1abe2685f296cdf.zip | |
😀
Diffstat (limited to 'docs/components/pages')
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'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's time for a new beginning in compiler infrastructure for + the entire web ecosystem. Webpack has been downloaded over 3 billion + times. It's become an integral part of building for the web. + But just like Babel and Terser, it's time to go all-in on + native. I joined Vercel and assembled a team of world class + engineers to build the web'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's Bazel, and invented an architecture ready to withstand + the next 10 years. + </p> + <br /> + <p> + With that, we'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'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'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're serving multiple applications, you might reach for a + monorepo. They're incredible for productivity, especially on + the frontend, but the tooling can be a nightmare. There's a lot + of stuff to do (and things to mess up). Nothing “just + works.” It'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't + require PhD to learn or a staff to maintain. + </p> + <br /> + <p>With Turborepo, we're doing just that.</p> + <br /> + <p> + We're building a build system that can keep up with your team. + You'll see your CI get faster, duplicated work get cut, and + your NPM scripts get simpler. You'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> + ); +} |
