mirror of
https://github.com/Dokploy/website
synced 2025-06-26 18:16:01 +00:00
feat: add landing page finished
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
172
apps/website/components/stats.tsx
Normal file
172
apps/website/components/stats.tsx
Normal 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 Didn’t 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 we’re not *completely* making this up.
|
||||
Turns out, Dokploy has actually helped a few people—who 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>
|
||||
);
|
||||
}
|
||||
150
apps/website/components/ui/animated-grid-pattern.tsx
Normal file
150
apps/website/components/ui/animated-grid-pattern.tsx
Normal 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;
|
||||
58
apps/website/components/ui/number-ticker.tsx
Normal file
58
apps/website/components/ui/number-ticker.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
30
apps/website/components/ui/tooltip.tsx
Normal file
30
apps/website/components/ui/tooltip.tsx
Normal 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 };
|
||||
@@ -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",
|
||||
|
||||
BIN
apps/website/public/supafort.png
Normal file
BIN
apps/website/public/supafort.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
85
pnpm-lock.yaml
generated
85
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user