feat: add landing page finished

This commit is contained in:
Mauricio Siu
2024-11-01 23:16:02 -06:00
parent 7ebe7814de
commit f4f524a16e
13 changed files with 915 additions and 190 deletions

View File

@@ -6,6 +6,7 @@ import { Testimonials } from "@/components/Testimonials";
import { FeaturesSectionDemo } from "@/components/features";
import { Pricing } from "@/components/pricing";
import { RippleDemo } from "@/components/sponsors";
import { StatsSection } from "@/components/stats";
export default function Home() {
return (
@@ -14,12 +15,12 @@ export default function Home() {
<Hero />
<FeaturesSectionDemo />
<PrimaryFeatures />
<StatsSection />
<Testimonials />
<div className="w-full relative">
<Pricing />
</div>
<Faqs />
<RippleDemo />
<CallToAction />
</main>

View File

@@ -9,7 +9,7 @@ export function CallToAction() {
return (
<section
id="get-started-today"
className="relative overflow-hidden border-y border-border/30 bg-black py-10"
className="relative overflow-hidden border-y border-border/30 bg-black py-10 mt-20"
>
<svg
viewBox="0 0 2000 1000"

View File

@@ -125,6 +125,20 @@ function MobileNavigation() {
<MobileNavLink href={linkT("docs.intro")} target="_blank">
{t("navigation.docs")}
</MobileNavLink>
<MobileNavLink href={linkT("docs.intro")} target="_blank">
<Button className=" w-full" asChild>
<Link
href="https://app.dokploy.com/register"
aria-label="Sign In Dokploy Cloud"
target="_blank"
>
<div className="group flex-row relative mx-auto flex max-w-fit items-center justify-center rounded-2xl text-sm font-medium w-full">
<span>{t("navigation.dashboard")}</span>
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
</div>
</Link>
</Button>
</MobileNavLink>
</Popover.Panel>
</Transition.Child>
</Transition.Root>
@@ -152,7 +166,7 @@ export function Header() {
</NavLink>
</div>
</div>
<div className="flex items-center gap-x-2 md:gap-x-5">
<div className="flex items-center gap-x-4 md:gap-x-5">
<Link href="https://x.com/getdokploy" target="_blank">
<svg
stroke="currentColor"
@@ -180,7 +194,7 @@ export function Header() {
<HeartIcon className="animate-heartbeat size-4 fill-red-600 text-red-500 " />
</Link> */}
<Button className="rounded-full" asChild>
<Button className="rounded-full max-md:hidden" asChild>
<Link
href="https://app.dokploy.com/register"
aria-label="Sign In Dokploy Cloud"

View File

@@ -14,6 +14,7 @@ import { useEffect, useState } from "react";
import { Container } from "./Container";
import Hero12 from "./hero/Hi";
import AnimatedGradientText from "./ui/animated-gradient-text";
import AnimatedGridPattern from "./ui/animated-grid-pattern";
import AnimatedShinyText from "./ui/animated-shiny-text";
import { Button, buttonVariants } from "./ui/button";
import HeroVideoDialog from "./ui/hero-video-dialog";
@@ -62,8 +63,8 @@ export function Hero() {
return () => clearTimeout(timer);
}, [isCopied]);
return (
<Container className="bg-black pb-16 pt-20 text-center lg:pt-32">
<div className="absolute inset-0">
<div className="bg-black pt-20 lg:pt-32 h-[1100px] sm:h-[1100px]">
{/* <div className="absolute inset-0">
<svg viewBox="0 0 2000 1000" xmlns="http://www.w3.org/2000/svg">
<mask id="b" x="0" y="0" width="2000" height="1000">
<path fill="url(#a)" d="M0 0h2000v1000H0z" />
@@ -79,146 +80,161 @@ export function Hero() {
</radialGradient>
</defs>
</svg>
</div>
<div className="relative">
<div className="text-center">
<motion.a
href="/pricing"
className="relative z-10 mb-4 inline-block"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<div className="z-10 flex items-center justify-center">
<AnimatedGradientText>
🎉 <hr className="mx-2 h-4 w-px shrink-0 bg-gray-300" />{" "}
<span
className={cn(
"inline animate-gradient bg-gradient-to-r from-[#ffaa40] via-[#9c40ff] to-[#ffaa40] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent",
)}
>
Introducing Dokploy Cloud
</span>
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
</AnimatedGradientText>
</div>
</motion.a>
<motion.h1
className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{t("hero.deploy")}{" "}
<span className="relative whitespace-nowrap text-primary">
<svg
aria-hidden="true"
viewBox="0 0 418 42"
className="absolute left-0 top-2/3 h-[0.58em] w-full fill-primary"
preserveAspectRatio="none"
>
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
</svg>
<span className="relative"> {t("hero.anywhere")}</span>
</span>{" "}
{t("hero.with")}
</motion.h1>
<motion.p
className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-muted-foreground"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
{t("hero.des")}
</motion.p>
<motion.div
className="flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<div className="flex flex-col gap-6">
<div className="mt-6 flex flex-wrap items-center justify-center gap-6 md:flex-nowrap">
<code className="flex flex-row items-center gap-4 rounded-xl border p-3 font-sans">
curl -sSL https://dokploy.com/install.sh | sh
<button
type="button"
onClick={() =>
navigator.clipboard
.writeText(
"curl -sSL https://dokploy.com/install.sh | sh",
)
.then(() => setIsCopied(true))
.catch(() => setIsCopied(false))
}
>
{isCopied ? (
<Check className="h-4 w-4 text-muted-foreground" />
) : (
<Copy className="h-4 w-4 text-muted-foreground" />
</div> */}
<div className=" bottom-0 flex w-full items-center justify-center overflow-hidden rounded-lg bg-background md:shadow-xl">
<div className="relative px-4">
<div className="text-center">
<motion.a
href="#pricing"
className="relative z-10 mb-4 inline-block"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<div className="z-10 flex items-center justify-center">
<AnimatedGradientText>
🎉 <hr className="mx-2 h-4 w-px shrink-0 bg-gray-300" />{" "}
<span
className={cn(
"inline animate-gradient bg-gradient-to-r from-[#ffaa40] via-[#9c40ff] to-[#ffaa40] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent",
)}
</button>
</code>
</div>
<div className="mx-auto flex w-full max-w-sm flex-wrap items-center justify-center gap-3 md:flex-nowrap">
<Button className="w-full rounded-full" asChild>
<Link
href="https://github.com/dokploy/dokploy"
aria-label="Dokploy on GitHub"
target="_blank"
className="flex flex-row items-center gap-2"
>
<svg aria-hidden="true" className="h-6 w-6 fill-black">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
</svg>
Github
</Link>
</Button>
<Button
className="w-full rounded-full bg-[#5965F2] hover:bg-[#4A55E0]"
asChild
Introducing Dokploy Cloud
</span>
<ChevronRight className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
</AnimatedGradientText>
</div>
</motion.a>
<motion.h1
className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{t("hero.deploy")}{" "}
<span className="relative whitespace-nowrap text-primary">
<svg
aria-hidden="true"
viewBox="0 0 418 42"
className="absolute left-0 top-2/3 h-[0.58em] w-full fill-primary"
preserveAspectRatio="none"
>
<Link
href="https://discord.gg/2tBnJ3jDJc"
aria-label="Dokploy on GitHub"
target="_blank"
className="flex flex-row items-center gap-2 text-white"
>
<svg
role="img"
className="h-6 w-6 fill-white"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
</svg>
<span className="relative"> {t("hero.anywhere")}</span>
</span>{" "}
{t("hero.with")}
</motion.h1>
<motion.p
className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-muted-foreground"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
{t("hero.des")}
</motion.p>
<motion.div
className="flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<div className="flex flex-col gap-6">
<div className="mt-6 flex flex-wrap items-center justify-center gap-6 md:flex-nowrap">
<code className="flex flex-row items-center gap-4 rounded-xl border p-3 font-sans">
curl -sSL https://dokploy.com/install.sh | sh
<button
type="button"
onClick={() =>
navigator.clipboard
.writeText(
"curl -sSL https://dokploy.com/install.sh | sh",
)
.then(() => setIsCopied(true))
.catch(() => setIsCopied(false))
}
>
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
</svg>
{t("navigation.discord")}
</Link>
</Button>
{isCopied ? (
<Check className="h-4 w-4 text-muted-foreground" />
) : (
<Copy className="h-4 w-4 text-muted-foreground" />
)}
</button>
</code>
</div>
<div className="mx-auto flex w-full max-w-sm flex-wrap items-center justify-center gap-3 md:flex-nowrap">
<Button className="w-full rounded-full" asChild>
<Link
href="https://github.com/dokploy/dokploy"
aria-label="Dokploy on GitHub"
target="_blank"
className="flex flex-row items-center gap-2"
>
<svg aria-hidden="true" className="h-6 w-6 fill-black">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
</svg>
Github
</Link>
</Button>
<Button
className="w-full rounded-full bg-[#5965F2] hover:bg-[#4A55E0]"
asChild
>
<Link
href="https://discord.gg/2tBnJ3jDJc"
aria-label="Dokploy on GitHub"
target="_blank"
className="flex flex-row items-center gap-2 text-white"
>
<svg
role="img"
className="h-6 w-6 fill-white"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
</svg>
{t("navigation.discord")}
</Link>
</Button>
</div>
</div>
</motion.div>
</div>
<motion.div
className="mt-10 max-w-2xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.6 }}
>
<div className="mt-10 flex flex-row justify-center gap-x-8 rounded-lg sm:gap-x-0 sm:gap-y-10 xl:gap-x-12 xl:gap-y-0">
<HeroVideoDialog
className="block max-w-md w-full rounded-xl"
animationStyle="top-in-bottom-out"
videoSrc="https://www.youtube-nocookie.com/embed/mznYKPvhcfw?si=vHvqP3HKy0V3XkOZ"
thumbnailSrc="https://dokploy.com/banner.webp"
thumbnailAlt="Hero Video"
/>
</div>
</motion.div>
</div>
<motion.div
className="mt-10 max-w-2xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.6 }}
>
<div className="mt-10 flex flex-row justify-center gap-x-8 rounded-lg sm:gap-x-0 sm:gap-y-10 xl:gap-x-12 xl:gap-y-0">
<HeroVideoDialog
className="block max-w-md w-full rounded-xl"
animationStyle="top-in-bottom-out"
videoSrc="https://www.youtube-nocookie.com/embed/mznYKPvhcfw?si=vHvqP3HKy0V3XkOZ"
thumbnailSrc="https://dokploy.com/banner.webp"
thumbnailAlt="Hero Video"
/>
</div>
</motion.div>
<AnimatedGridPattern
numSquares={30}
maxOpacity={0.1}
height={40}
width={40}
duration={3}
repeatDelay={1}
className={cn(
"[mask-image:radial-gradient(800px_circle_at_center,white,transparent)]",
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12 absolute",
)}
/>
</div>
</Container>
{/* */}
</div>
);
}

View File

@@ -1,12 +1,20 @@
"use client";
import { PlusCircleIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { Button } from "./ui/button";
import Link from "next/link";
import { buttonVariants } from "./ui/button";
import Ripple from "./ui/ripple";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "./ui/tooltip";
export const RippleDemo = () => {
const t = useTranslations("HomePage");
return (
<div className="mt-20 flex flex-col justify-center gap-y-10 w-full">
<div className="mt-20 flex flex-col justify-center gap-y-10 w-full ">
<div className="flex flex-col justify-start gap-4 px-4">
<h1 className="mx-auto max-w-2xl font-display text-3xl font-medium tracking-tight text-primary sm:text-5xl text-center">
{t("hero.sponsors.title")}
@@ -15,13 +23,27 @@ export const RippleDemo = () => {
{t("hero.sponsors.description")}
</p>
</div>
<div className="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg border bg-background md:shadow-xl">
<p className="z-10 whitespace-pre-wrap text-center text-5xl font-medium tracking-tighter text-white">
<Button variant="secondary" className="rounded-full p-0 m-0">
<PlusCircleIcon className="size-10 text-muted-foreground hover:text-primary transition-colors" />
</Button>
{/* <PlusCircleIcon className="size-10 " /> */}
</p>
<div className="relative flex h-[700px] w-full flex-col items-center justify-center overflow-hidden bg-background md:shadow-xl">
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger className="z-10 m-0 p-0">
<Link
href={"https://opencollective.com/dokploy"}
target="_blank"
className={buttonVariants({
variant: "secondary",
size: "sm",
className: "bg-transparent !rounded-full w-fit !p-0 m-0",
})}
>
<PlusCircleIcon className="size-10 text-muted-foreground hover:text-primary transition-colors" />
</Link>
</TooltipTrigger>
<TooltipContent className="bg-black rounded-lg border-0 text-center w-[200px] z-[200] text-white font-semibold">
Become a sponsor 🤑
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Ripple />
</div>
</div>

View File

@@ -0,0 +1,172 @@
import { GithubIcon, HandCoins, Users } from "lucide-react";
import React from "react";
import { useId } from "react";
import NumberTicker from "./ui/number-ticker";
export function StatsSection() {
return (
<div className="py-20 lg:py-40 flex flex-col gap-10 px-4 ">
<div className="mx-auto max-w-2xl md:text-center">
<h2 className="font-display text-3xl tracking-tight sm:text-4xl text-center">
Stats You Didnt Ask For (But Secretly Love to See)
</h2>
<p className="mt-4 text-lg tracking-tight text-muted-foreground text-center">
Just a few numbers to show were not *completely* making this up.
Turns out, Dokploy has actually helped a few peoplewho knew?
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-10 md:gap-2 max-w-7xl mx-auto">
{grid.map((feature, index) => (
<div
key={feature.title}
className="relative bg-gradient-to-b from-neutral-900 to-neutral-950 p-6 rounded-3xl overflow-hidden"
>
<Grid size={20} />
<p className="text-base font-bold text-white relative z-20 flex flex-row gap-4 items-center">
{feature.title}
{feature.icon}
</p>
<p className="text-neutral-400 mt-4 text-base font-normal relative z-20">
{feature.description}
</p>
{feature.component}
</div>
))}
</div>
</div>
);
}
const grid = [
{
title: "GitHub Stars",
description:
"With over 8.3k stars on GitHub, Dokploy is trusted by developers worldwide. Explore our repositories and join our community!",
icon: (
<svg aria-hidden="true" className="h-6 w-6 fill-white">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
</svg>
),
component: (
<p className="whitespace-pre-wrap text-2xl !font-semibold tracking-tighter mt-4">
<NumberTicker value={8300} />+
</p>
),
},
{
title: "DockerHub Downloads",
description:
"Downloaded over 700,000 times, Dokploy has become a go-to solution for seamless deployments. Discover our presence on DockerHub.",
icon: (
<svg
stroke="currentColor"
fill="currentColor"
strokeWidth="0"
viewBox="0 0 640 512"
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 fill-white"
>
<path d="M349.9 236.3h-66.1v-59.4h66.1v59.4zm0-204.3h-66.1v60.7h66.1V32zm78.2 144.8H362v59.4h66.1v-59.4zm-156.3-72.1h-66.1v60.1h66.1v-60.1zm78.1 0h-66.1v60.1h66.1v-60.1zm276.8 100c-14.4-9.7-47.6-13.2-73.1-8.4-3.3-24-16.7-44.9-41.1-63.7l-14-9.3-9.3 14c-18.4 27.8-23.4 73.6-3.7 103.8-8.7 4.7-25.8 11.1-48.4 10.7H2.4c-8.7 50.8 5.8 116.8 44 162.1 37.1 43.9 92.7 66.2 165.4 66.2 157.4 0 273.9-72.5 328.4-204.2 21.4.4 67.6.1 91.3-45.2 1.5-2.5 6.6-13.2 8.5-17.1l-13.3-8.9zm-511.1-27.9h-66v59.4h66.1v-59.4zm78.1 0h-66.1v59.4h66.1v-59.4zm78.1 0h-66.1v59.4h66.1v-59.4zm-78.1-72.1h-66.1v60.1h66.1v-60.1z" />
</svg>
),
component: (
<p className="whitespace-pre-wrap text-2xl !font-semibold tracking-tighter mt-4">
<NumberTicker value={700000} />+
</p>
),
},
{
title: "Community Contributors",
description:
"Thanks to a growing base of over 65 contributors, Dokploy continues to thrive with valuable contributions from developers around the world.",
icon: <Users className="h-6 w-6 stroke-white" />,
component: (
<p className="whitespace-pre-wrap text-2xl !font-semibold tracking-tighter mt-4">
<NumberTicker value={65} />+
</p>
),
},
{
title: "Sponsors",
description:
"More than 35 companies/individuals have sponsored Dokploy, ensuring a steady flow of support and resources. Join our community!",
icon: <HandCoins className="h-6 w-6 stroke-white" />,
component: (
<p className="whitespace-pre-wrap text-2xl !font-semibold tracking-tighter mt-4">
<NumberTicker value={35} />+
</p>
),
},
];
export const Grid = ({
pattern,
size,
}: {
pattern?: number[][];
size?: number;
}) => {
const p = pattern ?? [
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
];
return (
<div className="pointer-events-none absolute left-1/2 top-0 -ml-20 -mt-2 h-full w-full [mask-image:linear-gradient(white,transparent)]">
<div className="absolute inset-0 bg-gradient-to-r [mask-image:radial-gradient(farthest-side_at_top,white,transparent)] from-zinc-900/30 to-zinc-900/30 opacity-100">
<GridPattern
width={size ?? 20}
height={size ?? 20}
x="-12"
y="4"
squares={p}
className="absolute inset-0 h-full w-full mix-blend-overlay fill-white/10 stroke-white/10 "
/>
</div>
</div>
);
};
export function GridPattern({ width, height, x, y, squares, ...props }: any) {
const patternId = useId();
return (
<svg aria-hidden="true" {...props}>
<defs>
<pattern
id={patternId}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path d={`M.5 ${height}V.5H${width}`} fill="none" />
</pattern>
</defs>
<rect
width="100%"
height="100%"
strokeWidth={0}
fill={`url(#${patternId})`}
/>
{squares && (
<svg x={x} y={y} className="overflow-visible">
{squares.map(([x, y]: any) => (
<rect
strokeWidth="0"
key={`${x}-${y}`}
width={width + 1}
height={height + 1}
x={x * width}
y={y * height}
/>
))}
</svg>
)}
</svg>
);
}

View File

@@ -0,0 +1,150 @@
"use client";
import { motion } from "framer-motion";
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "@/lib/utils";
interface GridPatternProps {
width?: number;
height?: number;
x?: number;
y?: number;
strokeDasharray?: any;
numSquares?: number;
className?: string;
maxOpacity?: number;
duration?: number;
repeatDelay?: number;
}
export function GridPattern({
width = 40,
height = 40,
x = -1,
y = -1,
strokeDasharray = 0,
numSquares = 50,
className,
maxOpacity = 0.5,
duration = 4,
repeatDelay = 0.5,
...props
}: GridPatternProps) {
const id = useId();
const containerRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [squares, setSquares] = useState(() => generateSquares(numSquares));
function getPos() {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
];
}
// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}));
}
// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq,
),
);
};
// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares));
}
}, [dimensions, numSquares]);
// Resize observer to update container dimensions
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
setDimensions({
width: entry.contentRect.width,
height: entry.contentRect.height,
});
}
});
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [containerRef]);
return (
<svg
ref={containerRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/10",
className,
)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path
d={`M.5 ${height}V.5H${width}`}
fill="none"
strokeDasharray={strokeDasharray}
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
transition={{
duration,
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
fill="currentColor"
strokeWidth="0"
/>
))}
</svg>
</svg>
);
}
export default GridPattern;

View File

@@ -0,0 +1,58 @@
"use client";
import { useInView, useMotionValue, useSpring } from "framer-motion";
import { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
export default function NumberTicker({
value,
direction = "up",
delay = 0,
className,
decimalPlaces = 0,
}: {
value: number;
direction?: "up" | "down";
className?: string;
delay?: number; // delay in s
decimalPlaces?: number;
}) {
const ref = useRef<HTMLSpanElement>(null);
const motionValue = useMotionValue(direction === "down" ? value : 0);
const springValue = useSpring(motionValue, {
damping: 60,
stiffness: 100,
});
const isInView = useInView(ref, { once: true, margin: "0px" });
useEffect(() => {
isInView &&
setTimeout(() => {
motionValue.set(direction === "down" ? 0 : value);
}, delay * 1000);
}, [motionValue, isInView, delay, value, direction]);
useEffect(
() =>
springValue.on("change", (latest) => {
if (ref.current) {
ref.current.textContent = Intl.NumberFormat("en-US", {
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
}).format(Number(latest.toFixed(decimalPlaces)));
}
}),
[springValue, decimalPlaces],
);
return (
<span
className={cn(
"inline-block tabular-nums text-white tracking-wider",
className,
)}
ref={ref}
/>
);
}

View File

@@ -1,8 +1,15 @@
"use client";
import React, { type CSSProperties } from "react";
import React from "react";
import { cn } from "@/lib/utils";
import Link from "next/link";
import { Avatar, AvatarFallback, AvatarImage } from "./avatar";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "./tooltip";
interface RippleProps {
mainCircleSize?: number;
@@ -11,37 +18,79 @@ interface RippleProps {
className?: string;
}
type AvatarItem = {
name: string;
image: string;
link: string;
type: "hero" | "premium" | "elite" | "supporting" | "community";
};
const Ripple = React.memo(function Ripple({
mainCircleSize = 210,
mainCircleOpacity = 0.24,
numCircles = 8,
className,
}: RippleProps) {
const avatarsFirstRing = [
"https://github.com/shadcn.png", // URLs de los avatares del primer aro
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
const heroSponsors: AvatarItem[] = [
{
name: "Hostinger",
image: "https://avatars.githubusercontent.com/u/2630767?s=200&v=4",
link: "https://www.hostinger.com/vps-hosting?ref=dokploy",
type: "hero",
},
{
name: "Lxaer",
image:
"https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/lxaer.png",
link: "https://www.lxaer.com?ref=dokploy",
type: "hero",
},
];
const avatarsSecondRing = [
"https://github.com/shadcn.png", // URLs de los avatares del segundo aro
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
const premiumSponsors = [
{
name: "Supafort",
image: "supafort.png",
link: "https://supafort.com/?ref=dokploy",
type: "premium",
},
];
const avatarsThirdRing = [
"https://github.com/shadcn.png", // URLs de los avatares del tercer aro
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
"https://github.com/shadcn.png",
const eliteSponsors = [];
const supportingSponsors = [
{
name: "Lightspeed Run",
image: "https://github.com/lightspeedrun.png",
link: "https://lightspeed.run/?ref=dokploy",
type: "supporting",
},
{
name: "Cloudblast",
image: "https://cloudblast.io/img/logo-icon.193cf13e.svg",
link: "https://cloudblast.io/?ref=dokploy",
type: "supporting",
},
];
const communitySponsors = [
{
name: "Steamsets",
image: "https://avatars.githubusercontent.com/u/111978405?s=200&v=4",
link: "https://steamsets.com/?ref=dokploy",
type: "premium",
},
{
name: "Rivo GG",
image: "https://avatars.githubusercontent.com/u/126797452?s=200&v=4",
link: "https://rivo.gg/?ref=dokploy",
type: "premium",
},
{
name: "Photoquest",
image: "https://photoquest.wedding/favicon/android-chrome-512x512.png",
link: "https://photoquest.wedding/?ref=dokploy",
type: "premium",
},
];
return (
@@ -75,14 +124,40 @@ const Ripple = React.memo(function Ripple({
left: "50%",
transform: "translate(-50%, -50%) scale(1)",
}}
/>
);
})}
{Array.from({ length: numCircles }, (_, i) => {
const size = mainCircleSize + i * 70;
const opacity = mainCircleOpacity - i * 0.03;
const animationDelay = `${i * 0.06}s`;
const borderStyle = i === numCircles - 1 ? "dashed" : "solid";
const borderOpacity = 5 + i * 5;
return (
<div
key={i}
className={`absolute z-30 animate-ripple rounded-full shadow-xl border [--i:${i}]`}
style={{
animationDelay,
borderStyle,
borderWidth: "1px",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%) scale(1)",
}}
>
{i === 0 && (
<div className="relative w-full h-full flex justify-center items-center">
{avatarsFirstRing.map((src, index) => {
const angle = (360 / avatarsFirstRing.length) * index;
{heroSponsors.map((item, index) => {
const angle = (360 / heroSponsors.length) * index;
const radius = mainCircleSize / 2;
const x = radius * Math.cos((angle * Math.PI) / 180);
const y = radius * Math.sin((angle * Math.PI) / 180);
const initials = item.name
.split(" ")
.map((n) => n[0])
.join("");
return (
<div
@@ -94,10 +169,26 @@ const Ripple = React.memo(function Ripple({
transform: `translate(${x}px, ${y}px) translate(-50%, -50%)`,
}}
>
<Avatar>
<AvatarImage src={src} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<Link href={item.link} target="_blank">
<Avatar className="border-2 border-red-600">
<AvatarImage
src={item.image}
alt={item.name}
/>
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
</Link>
</TooltipTrigger>
<TooltipContent>
<p className="text-xs font-semibold ">
{item.name}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
})}
@@ -106,12 +197,15 @@ const Ripple = React.memo(function Ripple({
{i === 1 && (
<div className="relative w-full h-full flex justify-center items-center">
{avatarsSecondRing.map((src, index) => {
const angle = (360 / avatarsSecondRing.length) * index;
{premiumSponsors.map((item, index) => {
const angle = (360 / premiumSponsors.length) * index;
const radius = mainCircleSize / 2 + 70; // Radio mayor para el segundo aro
const x = radius * Math.cos((angle * Math.PI) / 180);
const y = radius * Math.sin((angle * Math.PI) / 180);
const initials = item.name
.split(" ")
.map((n) => n[0])
.join("");
return (
<div
key={index}
@@ -122,10 +216,26 @@ const Ripple = React.memo(function Ripple({
transform: `translate(${x}px, ${y}px) translate(-50%, -50%)`,
}}
>
<Avatar>
<AvatarImage src={src} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<Link href={item.link} target="_blank">
<Avatar className="border-2 border-yellow-500">
<AvatarImage
src={item.image}
alt={item.name}
/>
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
</Link>
</TooltipTrigger>
<TooltipContent>
<p className="text-xs font-semibold ">
{item.name}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
})}
@@ -134,12 +244,15 @@ const Ripple = React.memo(function Ripple({
{i === 3 && (
<div className="relative w-full h-full flex justify-center items-center">
{avatarsThirdRing.map((src, index) => {
const angle = (360 / avatarsThirdRing.length) * index;
const radius = mainCircleSize / 2 + 140; // Radio mayor para el tercer aro
{supportingSponsors.map((item, index) => {
const angle = (360 / supportingSponsors.length) * index;
const radius = mainCircleSize / 2 + 140;
const x = radius * Math.cos((angle * Math.PI) / 180);
const y = radius * Math.sin((angle * Math.PI) / 180);
const initials = item.name
.split(" ")
.map((n) => n[0])
.join("");
return (
<div
key={index}
@@ -150,10 +263,73 @@ const Ripple = React.memo(function Ripple({
transform: `translate(${x}px, ${y}px) translate(-50%, -50%)`,
}}
>
<Avatar>
<AvatarImage src={src} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<Link href={item.link} target="_blank">
<Avatar className="border-2 border-yellow-900">
<AvatarImage
src={item.image}
alt={item.name}
/>
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
</Link>
</TooltipTrigger>
<TooltipContent>
<p className="text-xs font-semibold ">
{item.name}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
})}
</div>
)}
{i === 4 && (
<div className="relative w-full h-full flex justify-center items-center">
{communitySponsors.map((item, index) => {
const angle = (360 / communitySponsors.length) * index;
const radius = mainCircleSize / 2 + 180;
const x = radius * Math.cos((angle * Math.PI) / 180);
const y = radius * Math.sin((angle * Math.PI) / 180);
const initials = item.name
.split(" ")
.map((n) => n[0])
.join("");
return (
<div
key={index}
className="absolute"
style={{
top: "50%",
left: "50%",
transform: `translate(${x}px, ${y}px) translate(-50%, -50%)`,
}}
>
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<Link href={item.link} target="_blank">
<Avatar className="border-2 border-yellow-500">
<AvatarImage
src={item.image}
alt={item.name}
/>
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
</Link>
</TooltipTrigger>
<TooltipContent>
<p className="text-xs font-semibold ">
{item.name}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
})}

View File

@@ -0,0 +1,30 @@
"use client";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import * as React from "react";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View File

@@ -21,6 +21,7 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "1.1.1",
"@radix-ui/react-tooltip": "^1.1.3",
"@tabler/icons-react": "3.21.0",
"@types/node": "20.4.6",
"autoprefixer": "^10.4.12",

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

85
pnpm-lock.yaml generated
View File

@@ -109,6 +109,9 @@ importers:
'@radix-ui/react-tabs':
specifier: 1.1.1
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-tooltip':
specifier: ^1.1.3
version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tabler/icons-react':
specifier: 3.21.0
version: 3.21.0(react@18.2.0)
@@ -782,6 +785,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-dismissable-layer@1.1.1':
resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
peerDependencies:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-focus-guards@1.0.1':
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies:
@@ -896,6 +912,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-portal@1.1.2':
resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
peerDependencies:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-presence@1.0.1':
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies:
@@ -1070,6 +1099,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-tooltip@1.1.3':
resolution: {integrity: sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==}
peerDependencies:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-use-callback-ref@1.0.1':
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
peerDependencies:
@@ -3702,6 +3744,19 @@ snapshots:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
'@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.2.0)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.2.0)
'@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.5)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
'@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.5)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
@@ -3866,6 +3921,16 @@ snapshots:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
'@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
'@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
@@ -4091,6 +4156,26 @@ snapshots:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
'@radix-ui/react-tooltip@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.2.0)
'@radix-ui/react-context': 1.1.1(@types/react@18.3.5)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.5)(react@18.2.0)
'@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
'@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.5)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0