Merge pull request #3 from Dokploy/feat/redesign-website
Feat/redesign website
178
apps/website/app/[locale]/_changelog/page.tsx
Normal file
@@ -1,19 +1,28 @@
|
||||
import { CallToAction } from "@/components/CallToAction";
|
||||
import { Faqs } from "@/components/Faqs";
|
||||
import { Hero } from "@/components/Hero";
|
||||
import { PrimaryFeatures } from "@/components/PrimaryFeatures";
|
||||
import { SecondaryFeatures } from "@/components/SecondaryFeatures";
|
||||
import { Testimonials } from "@/components/Testimonials";
|
||||
import { FirstFeaturesSection } from "@/components/first-features";
|
||||
import { Pricing } from "@/components/pricing";
|
||||
import { SecondaryFeaturesSections } from "@/components/secondary-features";
|
||||
import { Sponsors } from "@/components/sponsors";
|
||||
import { StatsSection } from "@/components/stats";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<main>
|
||||
<Hero />
|
||||
<PrimaryFeatures />
|
||||
<SecondaryFeatures />
|
||||
<CallToAction />
|
||||
{/* <Testimonials /> */}
|
||||
<FirstFeaturesSection />
|
||||
<SecondaryFeaturesSections />
|
||||
<StatsSection />
|
||||
<Testimonials />
|
||||
<div className="w-full relative">
|
||||
<Pricing />
|
||||
</div>
|
||||
<Faqs />
|
||||
<Sponsors />
|
||||
<CallToAction />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Pricing } from "@/components/pricing";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Pricing />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
{
|
||||
url: "https://dokploy.com",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "yearly",
|
||||
changeFrequency: "monthly",
|
||||
priority: 1,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -5,11 +5,10 @@ import { Button } from "./ui/button";
|
||||
|
||||
export function CallToAction() {
|
||||
const t = useTranslations("HomePage");
|
||||
const linkT = useTranslations("Link");
|
||||
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"
|
||||
@@ -41,7 +40,7 @@ export function CallToAction() {
|
||||
|
||||
<Button className="mt-10 rounded-full" asChild>
|
||||
<Link
|
||||
href={linkT("docs.install")}
|
||||
href={"https://app.dokploy.com/register"}
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row items-center gap-2"
|
||||
|
||||
@@ -1,53 +1,81 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Container } from "./Container";
|
||||
|
||||
const faqs = [
|
||||
[
|
||||
{
|
||||
question: "faq.q1",
|
||||
answer: "faq.a1",
|
||||
},
|
||||
{
|
||||
question: "faq.q2",
|
||||
answer: "faq.a2",
|
||||
},
|
||||
{
|
||||
question: "faq.q3",
|
||||
answer: "faq.a3",
|
||||
},
|
||||
{
|
||||
question: "faq.q4",
|
||||
answer: "faq.a4",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question: "faq.q5",
|
||||
answer: "faq.a5",
|
||||
},
|
||||
{
|
||||
question: "faq.q6",
|
||||
answer: "faq.a6",
|
||||
},
|
||||
{
|
||||
question: "faq.q7",
|
||||
answer: "faq.a7",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question: "faq.q8",
|
||||
answer: "faq.a8",
|
||||
},
|
||||
{
|
||||
question: "faq.q9",
|
||||
answer: "faq.a9",
|
||||
},
|
||||
{
|
||||
question: "faq.q10",
|
||||
answer: "faq.a10",
|
||||
},
|
||||
],
|
||||
{
|
||||
question: "faq.q1",
|
||||
answer: "faq.a1",
|
||||
},
|
||||
{
|
||||
question: "faq.q11",
|
||||
answer: "faq.a11",
|
||||
},
|
||||
{
|
||||
question: "faq.q12",
|
||||
answer: "faq.a12",
|
||||
},
|
||||
{
|
||||
question: "faq.q13",
|
||||
answer: "faq.a13",
|
||||
},
|
||||
{
|
||||
question: "faq.q14",
|
||||
answer: "faq.a14",
|
||||
},
|
||||
{
|
||||
question: "faq.q15",
|
||||
answer: "faq.a15",
|
||||
},
|
||||
{
|
||||
question: "faq.q17",
|
||||
answer: "faq.a17",
|
||||
},
|
||||
{
|
||||
question: "faq.q18",
|
||||
answer: "faq.a18",
|
||||
},
|
||||
{
|
||||
question: "faq.q2",
|
||||
answer: "faq.a2",
|
||||
},
|
||||
{
|
||||
question: "faq.q4",
|
||||
answer: "faq.a4",
|
||||
},
|
||||
{
|
||||
question: "faq.q5",
|
||||
answer: "faq.a5",
|
||||
},
|
||||
{
|
||||
question: "faq.q6",
|
||||
answer: "faq.a6",
|
||||
},
|
||||
{
|
||||
question: "faq.q7",
|
||||
answer: "faq.a7",
|
||||
},
|
||||
{
|
||||
question: "faq.q8",
|
||||
answer: "faq.a8",
|
||||
},
|
||||
{
|
||||
question: "faq.q16",
|
||||
answer: "faq.a16",
|
||||
},
|
||||
{
|
||||
question: "faq.q9",
|
||||
answer: "faq.a9",
|
||||
},
|
||||
{
|
||||
question: "faq.q10",
|
||||
answer: "faq.a10",
|
||||
},
|
||||
];
|
||||
|
||||
export function Faqs() {
|
||||
@@ -58,36 +86,33 @@ export function Faqs() {
|
||||
aria-labelledby="faq-title"
|
||||
className="relative overflow-hidden bg-black py-20 sm:py-32"
|
||||
>
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||
<Container className="relative flex flex-col gap-10">
|
||||
<div className="mx-auto lg:mx-0 justify-center w-full">
|
||||
<h2
|
||||
id="faq-title"
|
||||
className="font-display text-3xl tracking-tight text-primary sm:text-4xl"
|
||||
className="font-display text-3xl tracking-tight text-primary sm:text-4xl text-center"
|
||||
>
|
||||
{t("faq.title")}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground text-center">
|
||||
{t("faq.des")}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 lg:max-w-none lg:grid-cols-3">
|
||||
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="w-full max-w-3xl mx-auto"
|
||||
>
|
||||
{faqs.map((column, columnIndex) => (
|
||||
<li key={columnIndex}>
|
||||
<ul className="flex flex-col gap-y-8">
|
||||
{column.map((faq, faqIndex) => (
|
||||
<li key={faqIndex}>
|
||||
<h3 className="font-display text-lg leading-7 text-primary">
|
||||
{t(faq.question)}
|
||||
</h3>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
{t(faq.answer)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
<AccordionItem value={`${columnIndex}`} key={columnIndex}>
|
||||
<AccordionTrigger className="text-left">
|
||||
{t(column.question)}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>{t(column.answer)}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</ul>
|
||||
</Accordion>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -67,10 +67,14 @@ export function Footer() {
|
||||
aria-label="Dokploy on Twitter"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="h-6 w-6 fill-muted-foreground group-hover:fill-muted-foreground/70"
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
strokeWidth="0"
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5 fill-muted-foreground group-hover:fill-muted-foreground/70"
|
||||
>
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0 0 22 5.92a8.19 8.19 0 0 1-2.357.646 4.118 4.118 0 0 0 1.804-2.27 8.224 8.224 0 0 1-2.605.996 4.107 4.107 0 0 0-6.993 3.743 11.65 11.65 0 0 1-8.457-4.287 4.106 4.106 0 0 0 1.27 5.477A4.073 4.073 0 0 1 2.8 9.713v.052a4.105 4.105 0 0 0 3.292 4.022 4.093 4.093 0 0 1-1.853.07 4.108 4.108 0 0 0 3.834 2.85A8.233 8.233 0 0 1 2 18.407a11.615 11.615 0 0 0 6.29 1.84" />
|
||||
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z" />
|
||||
</svg>
|
||||
</Link>
|
||||
<Link
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { HeartIcon } from "lucide-react";
|
||||
import { ChevronRight, HeartIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Fragment, type JSX, type SVGProps } from "react";
|
||||
import { Container } from "./Container";
|
||||
import { NavLink } from "./NavLink";
|
||||
import { trackGAEvent } from "./analitycs";
|
||||
import { Logo } from "./shared/Logo";
|
||||
import AnimatedGradientText from "./ui/animated-gradient-text";
|
||||
import { Button, buttonVariants } from "./ui/button";
|
||||
|
||||
function MobileNavLink({
|
||||
@@ -119,11 +120,25 @@ function MobileNavigation() {
|
||||
as="div"
|
||||
className="absolute inset-x-0 top-full mt-4 flex origin-top flex-col rounded-2xl border border-border bg-background p-4 text-lg tracking-tight text-primary shadow-xl ring-1 ring-border/5"
|
||||
>
|
||||
<MobileNavLink href="/pricing">Pricing</MobileNavLink>
|
||||
<MobileNavLink href="#pricing">Pricing</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">{t("navigation.faqs")}</MobileNavLink>
|
||||
<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>
|
||||
@@ -144,15 +159,28 @@ export function Header() {
|
||||
<Logo className="h-10 w-auto" />
|
||||
</Link>
|
||||
<div className="hidden md:flex md:gap-x-6">
|
||||
<NavLink href="/pricing">{t("navigation.pricing")}</NavLink>
|
||||
<NavLink href="#pricing">{t("navigation.pricing")}</NavLink>
|
||||
<NavLink href="/#faqs">{t("navigation.faqs")}</NavLink>
|
||||
<NavLink href={linkT("docs.intro")} target="_blank">
|
||||
{t("navigation.docs")}
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 md:gap-x-5">
|
||||
<Link
|
||||
<div className="flex items-center gap-x-4 md:gap-x-5">
|
||||
<Link href="https://x.com/getdokploy" target="_blank">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
strokeWidth="0"
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5 fill-muted-foreground group-hover:fill-muted-foreground/70 hover:fill-muted-foreground/80"
|
||||
>
|
||||
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z" />
|
||||
</svg>
|
||||
</Link>
|
||||
|
||||
{/* <Link
|
||||
className={buttonVariants({
|
||||
variant: "outline",
|
||||
className: " flex items-center gap-2 !rounded-full",
|
||||
@@ -164,15 +192,18 @@ export function Header() {
|
||||
{t("navigation.support")}{" "}
|
||||
</span>
|
||||
<HeartIcon className="animate-heartbeat size-4 fill-red-600 text-red-500 " />
|
||||
</Link>
|
||||
<Button className="rounded-xl" asChild>
|
||||
</Link> */}
|
||||
|
||||
<Button className="rounded-full max-md:hidden" asChild>
|
||||
<Link
|
||||
href="https://app.dokploy.com"
|
||||
aria-label="Dokploy on GitHub"
|
||||
href="https://app.dokploy.com/register"
|
||||
aria-label="Sign In Dokploy Cloud"
|
||||
target="_blank"
|
||||
// className="flex flex-row items-center gap-2 text-white"
|
||||
>
|
||||
{t("navigation.dashboard")}
|
||||
<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>
|
||||
<div className="-mr-1 md:hidden">
|
||||
|
||||
@@ -1,134 +1,216 @@
|
||||
import { Container } from "./Container";
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Marquee } from "./ui/marquee";
|
||||
|
||||
const testimonials = [
|
||||
[
|
||||
{
|
||||
content:
|
||||
"This application has revolutionized the way we handle deployments. The integration of Docker and Traefik through such a user-friendly interface has saved us countless hours.",
|
||||
author: {
|
||||
name: "Emily R.",
|
||||
role: "Full Stack Developer",
|
||||
image: "/avatars/avatar-1.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
content:
|
||||
"As a fast-paced startup, efficiency and reliability are paramount. This software delivered on both, allowing us to focus more on development and less on operations.",
|
||||
author: {
|
||||
name: "Mark T.",
|
||||
role: "CTO, Tech Innovations Inc.",
|
||||
image: "/avatars/avatar-2.png",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
content:
|
||||
"The comprehensive monitoring and robust backup solutions have given us the peace of mind we need to operate at our best 24/7. Highly recommended!",
|
||||
author: {
|
||||
name: "Sarah L.",
|
||||
role: "IT Director, Creative Solutions Agency",
|
||||
image: "/avatars/avatar-3.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
content:
|
||||
"Upgrading to this platform was a game-changer for our agency. The user permission controls and real-time updates have greatly enhanced our team's efficiency.",
|
||||
author: {
|
||||
name: "James P.",
|
||||
role: "Lead Developer, Dynamic Web Solutions",
|
||||
image: "/avatars/avatar-4.png",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
content:
|
||||
"Fantastic tool! The direct container access and dynamic Traefik configuration features have made it so easy to manage our services. It's like having a DevOps team in a box!",
|
||||
author: {
|
||||
name: "Ana D.",
|
||||
role: "Full Stack Developer, Independent Contractor",
|
||||
image: "/avatars/avatar-7.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
content:
|
||||
"his tool has been indispensable for managing my client projects. It has streamlined my workflow and dramatically increased my productivity, allowing me to take on more clients without sacrificing quality.",
|
||||
author: {
|
||||
name: "Carlos M.",
|
||||
role: "Freelance Full Stack Developer",
|
||||
image: "/avatars/avatar-6.png",
|
||||
},
|
||||
},
|
||||
],
|
||||
// const testimonials = [
|
||||
// [
|
||||
// {
|
||||
// content:
|
||||
// "This application has revolutionized the way we handle deployments. The integration of Docker and Traefik through such a user-friendly interface has saved us countless hours.",
|
||||
// author: {
|
||||
// name: "Emily R.",
|
||||
// role: "Full Stack Developer",
|
||||
// image: "/avatars/avatar-1.png",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// content:
|
||||
// "As a fast-paced startup, efficiency and reliability are paramount. This software delivered on both, allowing us to focus more on development and less on operations.",
|
||||
// author: {
|
||||
// name: "Mark T.",
|
||||
// role: "CTO, Tech Innovations Inc.",
|
||||
// image: "/avatars/avatar-2.png",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// content:
|
||||
// "The comprehensive monitoring and robust backup solutions have given us the peace of mind we need to operate at our best 24/7. Highly recommended!",
|
||||
// author: {
|
||||
// name: "Sarah L.",
|
||||
// role: "IT Director, Creative Solutions Agency",
|
||||
// image: "/avatars/avatar-3.png",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// content:
|
||||
// "Upgrading to this platform was a game-changer for our agency. The user permission controls and real-time updates have greatly enhanced our team's efficiency.",
|
||||
// author: {
|
||||
// name: "James P.",
|
||||
// role: "Lead Developer, Dynamic Web Solutions",
|
||||
// image: "/avatars/avatar-4.png",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// content:
|
||||
// "Fantastic tool! The direct container access and dynamic Traefik configuration features have made it so easy to manage our services. It's like having a DevOps team in a box!",
|
||||
// author: {
|
||||
// name: "Ana D.",
|
||||
// role: "Full Stack Developer, Independent Contractor",
|
||||
// image: "/avatars/avatar-7.png",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// content:
|
||||
// "his tool has been indispensable for managing my client projects. It has streamlined my workflow and dramatically increased my productivity, allowing me to take on more clients without sacrificing quality.",
|
||||
// author: {
|
||||
// name: "Carlos M.",
|
||||
// role: "Freelance Full Stack Developer",
|
||||
// image: "/avatars/avatar-6.png",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// ];
|
||||
|
||||
// function QuoteIcon(props: React.ComponentPropsWithoutRef<"svg">) {
|
||||
// return (
|
||||
// <svg aria-hidden="true" width={105} height={78} {...props}>
|
||||
// <path d="M25.086 77.292c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622C1.054 58.534 0 53.411 0 47.686c0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C28.325 3.917 33.599 1.507 39.324 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Zm54.24 0c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622-2.11-4.52-3.164-9.643-3.164-15.368 0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C82.565 3.917 87.839 1.507 93.564 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Z" />
|
||||
// </svg>
|
||||
// );
|
||||
// }
|
||||
|
||||
const reviews = [
|
||||
{
|
||||
name: "Duras",
|
||||
username: "@duras",
|
||||
body: "This app convinced me to try something beyond pure Docker Compose. It’s a pleasure to contribute to such an awesome project!",
|
||||
img: "https://avatar.vercel.sh/duras",
|
||||
},
|
||||
{
|
||||
name: "apis",
|
||||
username: "@apis",
|
||||
body: "I replaced my previous setup with Dokploy today. It’s stable, easy to use, and offers excellent support!",
|
||||
img: "https://avatar.vercel.sh/apis",
|
||||
},
|
||||
{
|
||||
name: "yayza_",
|
||||
username: "@yayza_",
|
||||
body: "Migrated all my services to Dokploy—it worked seamlessly! The level of configuration is perfect for all kinds of projects.",
|
||||
img: "https://avatar.vercel.sh/yayza",
|
||||
},
|
||||
{
|
||||
name: "Vaurion",
|
||||
username: "@vaurion",
|
||||
body: "Dokploy makes my deployments incredibly easy. I just test locally, push a preview to GitHub, and Dokploy takes care of the rest.",
|
||||
img: "https://avatar.vercel.sh/vaurion",
|
||||
},
|
||||
{
|
||||
name: "vinum?",
|
||||
username: "@vinum",
|
||||
body: "Dokploy is everything I wanted in a PaaS. The functionality is impressive, and it's completely free!",
|
||||
img: "https://avatar.vercel.sh/vinum",
|
||||
},
|
||||
{
|
||||
name: "vadzim",
|
||||
username: "@vadzim",
|
||||
body: "Dokploy is fantastic! I rarely encounter any deployment issues, and the community support is top-notch.",
|
||||
img: "https://avatar.vercel.sh/vadzim",
|
||||
},
|
||||
{
|
||||
name: "Slurpy Beckerman",
|
||||
username: "@slurpy",
|
||||
body: "This is exactly what I want in a deployment system. I’ve restructured my dev process around Dokploy!",
|
||||
img: "https://avatar.vercel.sh/slurpy",
|
||||
},
|
||||
{
|
||||
name: "lua",
|
||||
username: "@lua",
|
||||
body: "Dokploy is genuinely so nice to use. The hard work behind it really shows.",
|
||||
img: "https://avatar.vercel.sh/lua",
|
||||
},
|
||||
{
|
||||
name: "johnnygri",
|
||||
username: "@johnnygri",
|
||||
body: "Dokploy is a complete joy to use. I’m running a mix of critical and low-priority services seamlessly across servers.",
|
||||
img: "https://avatar.vercel.sh/johnnygri",
|
||||
},
|
||||
{
|
||||
name: "HiJoe",
|
||||
username: "@hijoe",
|
||||
body: "Setting up Dokploy was great—simple, intuitive, and reliable. Perfect for small to medium-sized businesses.",
|
||||
img: "https://avatar.vercel.sh/hijoe",
|
||||
},
|
||||
{
|
||||
name: "johannes0910",
|
||||
username: "@johannes0910",
|
||||
body: "Dokploy has been a game-changer for my side projects. Solid UI, straightforward Docker abstraction, and great design.",
|
||||
img: "https://avatar.vercel.sh/johannes0910",
|
||||
},
|
||||
];
|
||||
|
||||
function QuoteIcon(props: React.ComponentPropsWithoutRef<"svg">) {
|
||||
const firstRow = reviews.slice(0, reviews.length / 2);
|
||||
const secondRow = reviews.slice(reviews.length / 2);
|
||||
|
||||
const ReviewCard = ({
|
||||
img,
|
||||
name,
|
||||
username,
|
||||
body,
|
||||
}: {
|
||||
img: string;
|
||||
name: string;
|
||||
username: string;
|
||||
body: string;
|
||||
}) => {
|
||||
return (
|
||||
<svg aria-hidden="true" width={105} height={78} {...props}>
|
||||
<path d="M25.086 77.292c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622C1.054 58.534 0 53.411 0 47.686c0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C28.325 3.917 33.599 1.507 39.324 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Zm54.24 0c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622-2.11-4.52-3.164-9.643-3.164-15.368 0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C82.565 3.917 87.839 1.507 93.564 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Z" />
|
||||
</svg>
|
||||
<figure
|
||||
className={cn(
|
||||
"relative w-64 cursor-pointer overflow-hidden rounded-xl border p-4",
|
||||
// light styles
|
||||
// "border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05]",
|
||||
// dark styles
|
||||
"hover:bg-gray-50/[.15]",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<img className="rounded-full" width="32" height="32" alt="" src={img} />
|
||||
<div className="flex flex-col">
|
||||
<figcaption className="text-sm font-medium text-white">
|
||||
{name}
|
||||
</figcaption>
|
||||
<p className="text-xs font-medium text-white/40">{username}</p>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote className="mt-2 text-sm">{body}</blockquote>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export function Testimonials() {
|
||||
return (
|
||||
<section
|
||||
id="testimonials"
|
||||
aria-label="What our customers are saying"
|
||||
className="bg-black py-20 sm:py-32"
|
||||
className=" py-20 sm:py-32"
|
||||
>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl md:text-center">
|
||||
<h2 className="font-display text-3xl tracking-tight sm:text-4xl">
|
||||
What Our Users Say
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground ">
|
||||
Don’t just take our word for it—see what our users across the globe
|
||||
are saying about how our platform has transformed their development
|
||||
workflows and boosted their productivity.
|
||||
</p>
|
||||
</div>
|
||||
<ul className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-6 sm:gap-8 lg:mt-20 lg:max-w-none lg:grid-cols-3">
|
||||
{testimonials.map((column, columnIndex) => (
|
||||
<li key={columnIndex}>
|
||||
<ul className="flex flex-col gap-y-6 sm:gap-y-8">
|
||||
{column.map((testimonial, testimonialIndex) => (
|
||||
<li key={testimonialIndex}>
|
||||
<figure className="relative rounded-2xl bg-muted p-6 shadow-xl shadow-slate-900/10">
|
||||
<QuoteIcon className="absolute left-6 top-6 fill-border" />
|
||||
<blockquote className="relative">
|
||||
<p className="text-lg tracking-tight ">
|
||||
{testimonial.content}
|
||||
</p>
|
||||
</blockquote>
|
||||
<figcaption className="relative mt-6 flex items-center justify-between border-t border-border pt-6">
|
||||
<div>
|
||||
<div className="font-display text-base ">
|
||||
{testimonial.author.name}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-muted-foreground">
|
||||
{testimonial.author.role}
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-full bg-slate-50">
|
||||
<img
|
||||
className="h-14 w-14 object-cover"
|
||||
src={testimonial.author.image}
|
||||
alt=""
|
||||
width={56}
|
||||
height={56}
|
||||
/>
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
<div className="mx-auto max-w-2xl md:text-center px-4">
|
||||
<h2 className="font-display text-3xl tracking-tight sm:text-4xl text-center">
|
||||
Why Developers Love Dokploy
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground text-center">
|
||||
Think we’re bragging? Hear from the devs who once doubted too—until
|
||||
Dokploy made their lives (and deployments) surprisingly easier.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden bg-background md:shadow-xl">
|
||||
<Marquee pauseOnHover className="[--duration:20s]">
|
||||
{firstRow.map((review) => (
|
||||
<ReviewCard key={review.username} {...review} />
|
||||
))}
|
||||
</ul>
|
||||
</Container>
|
||||
</Marquee>
|
||||
<Marquee reverse pauseOnHover className="[--duration:20s]">
|
||||
{secondRow.map((review) => (
|
||||
<ReviewCard key={review.username} {...review} />
|
||||
))}
|
||||
</Marquee>
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 w-1/3 bg-gradient-to-r from-background" />
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 w-1/3 bg-gradient-to-l from-background" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
232
apps/website/components/features-second.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { IconBrandYoutubeFilled } from "@tabler/icons-react";
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import type React from "react";
|
||||
|
||||
export function FeaturesSectionDemo() {
|
||||
const features = [
|
||||
{
|
||||
title: "Track issues effectively",
|
||||
description:
|
||||
"Track and manage your project issues with ease using our intuitive interface.",
|
||||
skeleton: <SkeletonOne />,
|
||||
className:
|
||||
"col-span-1 lg:col-span-4 border-b lg:border-r dark:border-neutral-800",
|
||||
},
|
||||
{
|
||||
title: "Capture pictures with AI",
|
||||
description:
|
||||
"Capture stunning photos effortlessly using our advanced AI technology.",
|
||||
skeleton: <SkeletonTwo />,
|
||||
className: "border-b col-span-1 lg:col-span-2 dark:border-neutral-800",
|
||||
},
|
||||
{
|
||||
title: "Watch our AI on YouTube",
|
||||
description:
|
||||
"Whether its you or Tyler Durden, you can get to know about our product on YouTube",
|
||||
skeleton: <SkeletonThree />,
|
||||
className:
|
||||
"col-span-1 lg:col-span-3 lg:border-r dark:border-neutral-800",
|
||||
},
|
||||
{
|
||||
title: "Deploy in seconds",
|
||||
description:
|
||||
"With our blazing fast, state of the art, cutting edge, we are so back cloud servies (read AWS) - you can deploy your model in seconds.",
|
||||
skeleton: <SkeletonFour />,
|
||||
className: "col-span-1 lg:col-span-3 border-b lg:border-none",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="relative z-20 py-10 lg:py-40 max-w-7xl mx-auto">
|
||||
<div className="px-8">
|
||||
<h4 className="text-3xl lg:text-5xl lg:leading-tight max-w-5xl mx-auto text-center tracking-tight font-medium text-black dark:text-white">
|
||||
Packed with thousands of features
|
||||
</h4>
|
||||
|
||||
<p className="text-sm lg:text-base max-w-2xl my-4 mx-auto text-neutral-500 text-center font-normal dark:text-neutral-300">
|
||||
From Image generation to video generation, Everything AI has APIs for
|
||||
literally everything. It can even create this website copy for you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative ">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-6 mt-12 xl:border rounded-md dark:border-neutral-800">
|
||||
{features.map((feature) => (
|
||||
<FeatureCard key={feature.title} className={feature.className}>
|
||||
<FeatureTitle>{feature.title}</FeatureTitle>
|
||||
<FeatureDescription>{feature.description}</FeatureDescription>
|
||||
<div className=" h-full w-full">{feature.skeleton}</div>
|
||||
</FeatureCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const FeatureCard = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn("p-4 sm:p-8 relative overflow-hidden", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FeatureTitle = ({ children }: { children?: React.ReactNode }) => {
|
||||
return (
|
||||
<p className=" max-w-5xl mx-auto text-left tracking-tight text-black dark:text-white text-xl md:text-2xl md:leading-snug">
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const FeatureDescription = ({ children }: { children?: React.ReactNode }) => {
|
||||
return (
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm md:text-base max-w-4xl text-left mx-auto",
|
||||
"text-neutral-500 text-center font-normal dark:text-neutral-300",
|
||||
"text-left max-w-sm mx-0 md:text-sm my-2",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonOne = () => {
|
||||
return (
|
||||
<div className="relative flex py-8 px-2 gap-10 h-full">
|
||||
<div className="w-full p-5 mx-auto bg-white dark:bg-neutral-900 shadow-2xl group h-full">
|
||||
<div className="flex flex-1 w-full h-full flex-col space-y-2 ">
|
||||
{/* TODO */}
|
||||
<Image
|
||||
src="/linear.webp"
|
||||
alt="header"
|
||||
width={800}
|
||||
height={800}
|
||||
className="h-full w-full aspect-square object-cover object-left-top rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-0 z-40 inset-x-0 h-60 bg-gradient-to-t from-white dark:from-black via-white dark:via-black to-transparent w-full pointer-events-none" />
|
||||
<div className="absolute top-0 z-40 inset-x-0 h-60 bg-gradient-to-b from-white dark:from-black via-transparent to-transparent w-full pointer-events-none" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonThree = () => {
|
||||
return (
|
||||
<Link
|
||||
href="https://www.youtube.com/watch?v=RPa3_AD1_Vs"
|
||||
target="__blank"
|
||||
className="relative flex gap-10 h-full group/image"
|
||||
>
|
||||
<div className="w-full mx-auto bg-transparent dark:bg-transparent group h-full">
|
||||
<div className="flex flex-1 w-full h-full flex-col space-y-2 relative">
|
||||
{/* TODO */}
|
||||
<IconBrandYoutubeFilled className="h-20 w-20 absolute z-10 inset-0 text-red-500 m-auto " />
|
||||
<Image
|
||||
src="https://assets.aceternity.com/fireship.jpg"
|
||||
alt="header"
|
||||
width={800}
|
||||
height={800}
|
||||
className="h-full w-full aspect-square object-cover object-center rounded-sm blur-none group-hover/image:blur-md transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonTwo = () => {
|
||||
const images = [
|
||||
"https://images.unsplash.com/photo-1517322048670-4fba75cbbb62?q=80&w=3000&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"https://images.unsplash.com/photo-1573790387438-4da905039392?q=80&w=3425&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"https://images.unsplash.com/photo-1555400038-63f5ba517a47?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"https://images.unsplash.com/photo-1554931670-4ebfabf6e7a9?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"https://images.unsplash.com/photo-1546484475-7f7bd55792da?q=80&w=2581&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
];
|
||||
|
||||
const imageVariants = {
|
||||
whileHover: {
|
||||
scale: 1.1,
|
||||
rotate: 0,
|
||||
zIndex: 100,
|
||||
},
|
||||
whileTap: {
|
||||
scale: 1.1,
|
||||
rotate: 0,
|
||||
zIndex: 100,
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div className="relative flex flex-col items-start p-8 gap-10 h-full overflow-hidden">
|
||||
{/* TODO */}
|
||||
<div className="flex flex-row -ml-20">
|
||||
{images.map((image, idx) => (
|
||||
<motion.div
|
||||
variants={imageVariants}
|
||||
key={`images-first${idx}`}
|
||||
style={{
|
||||
rotate: Math.random() * 20 - 10,
|
||||
}}
|
||||
whileHover="whileHover"
|
||||
whileTap="whileTap"
|
||||
className="rounded-xl -mr-4 mt-4 p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 border border-neutral-100 flex-shrink-0 overflow-hidden"
|
||||
>
|
||||
<Image
|
||||
src={image}
|
||||
alt="bali images"
|
||||
width="500"
|
||||
height="500"
|
||||
className="rounded-lg h-20 w-20 md:h-40 md:w-40 object-cover flex-shrink-0"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
{images.map((image, idx) => (
|
||||
<motion.div
|
||||
key={`images-second${idx}`}
|
||||
style={{
|
||||
rotate: Math.random() * 20 - 10,
|
||||
}}
|
||||
variants={imageVariants}
|
||||
whileHover="whileHover"
|
||||
whileTap="whileTap"
|
||||
className="rounded-xl -mr-4 mt-4 p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 border border-neutral-100 flex-shrink-0 overflow-hidden"
|
||||
>
|
||||
<Image
|
||||
src={image}
|
||||
alt="bali images"
|
||||
width="500"
|
||||
height="500"
|
||||
className="rounded-lg h-20 w-20 md:h-40 md:w-40 object-cover flex-shrink-0"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="absolute left-0 z-[100] inset-y-0 w-20 bg-gradient-to-r from-white dark:from-black to-transparent h-full pointer-events-none" />
|
||||
<div className="absolute right-0 z-[100] inset-y-0 w-20 bg-gradient-to-l from-white dark:from-black to-transparent h-full pointer-events-none" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonFour = () => {
|
||||
return (
|
||||
<div className="h-60 md:h-60 flex flex-col items-center relative bg-transparent dark:bg-transparent mt-10">
|
||||
{/* <Globe className="absolute -right-10 md:-right-10 -bottom-80 md:-bottom-72" /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
148
apps/website/components/first-features.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
IconActivity,
|
||||
IconCloud,
|
||||
IconDatabase,
|
||||
IconEaseInOut,
|
||||
IconRocket,
|
||||
IconTemplate,
|
||||
IconTerminal,
|
||||
IconTerminal2,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-react";
|
||||
import { Layers, Lock, UnlockIcon } from "lucide-react";
|
||||
|
||||
export function FirstFeaturesSection() {
|
||||
const features = [
|
||||
{
|
||||
title: "Flexible Application Deployment",
|
||||
description:
|
||||
"Deploy any application using Nixpacks, Heroku Buildpacks, or your custom Dockerfile, tailored to your stack.",
|
||||
icon: <IconRocket />,
|
||||
},
|
||||
{
|
||||
title: "Native Docker Compose Support",
|
||||
description:
|
||||
"Deploy complex applications natively with full Docker Compose integration for seamless orchestration.",
|
||||
icon: <Layers />,
|
||||
},
|
||||
{
|
||||
title: "Multi-server Support",
|
||||
description:
|
||||
"Effortlessly deploy your applications on remote servers, with zero configuration hassle.",
|
||||
icon: <IconCloud />,
|
||||
},
|
||||
{
|
||||
title: "Advanced User Management",
|
||||
description:
|
||||
"Control user access with detailed roles and permissions, keeping your deployments secure and organized.",
|
||||
icon: <IconUsers />,
|
||||
},
|
||||
{
|
||||
title: "Database Management with Backups",
|
||||
description:
|
||||
"Manage and back up MySQL, PostgreSQL, MongoDB, MariaDB, Redis directly from Dokploy.",
|
||||
icon: <IconDatabase />,
|
||||
},
|
||||
{
|
||||
title: "API & CLI Access",
|
||||
description:
|
||||
"Need custom functionality? Dokploy offers complete API and CLI access to fit your needs.",
|
||||
icon: <IconTerminal />,
|
||||
},
|
||||
{
|
||||
title: "Docker Swarm Clusters",
|
||||
description:
|
||||
"Scale your deployments seamlessly with built-in Docker Swarm support for robust, multi-node applications.",
|
||||
icon: <IconUsers />,
|
||||
},
|
||||
{
|
||||
title: "Open Source Templates",
|
||||
description:
|
||||
"Get started quickly with pre-configured templates for popular tools like Supabase, Cal.com, and Pocketbase.",
|
||||
icon: <IconTemplate />,
|
||||
},
|
||||
{
|
||||
title: "No Vendor Lock-In",
|
||||
description:
|
||||
"Experience complete freedom to modify, scale, and customize Dokploy to suit your specific needs.",
|
||||
icon: <UnlockIcon />,
|
||||
},
|
||||
{
|
||||
title: "Real-time Monitoring & Alerts",
|
||||
description:
|
||||
"Monitor CPU, memory, and network usage in real-time across your deployments for full visibility.",
|
||||
icon: <IconActivity />,
|
||||
},
|
||||
{
|
||||
title: "Built for developers",
|
||||
description:
|
||||
"Designed specifically for engineers and developers seeking control and flexibility.",
|
||||
icon: <IconTerminal2 />,
|
||||
},
|
||||
{
|
||||
title: "Self-hosted & Open Source",
|
||||
description:
|
||||
"Dokploy provides complete control with self-hosting capabilities and open-source transparency.",
|
||||
icon: <IconEaseInOut />,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center mt-20 px-4">
|
||||
<h2 className="font-display text-3xl tracking-tight text-primary sm:text-4xl text-center">
|
||||
Powerful Deployment, Tailored for You
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-muted-foreground text-center">
|
||||
Unlock seamless multi-server deployments, advanced user control, and
|
||||
flexible database management—all with Dokploy’s developer-focused
|
||||
features.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 relative z-10 py-10 max-w-7xl mx-auto mt-10 max-sm:p-0 max-sm:mx-0 max-sm:w-full">
|
||||
{features.map((feature, index) => (
|
||||
<Feature key={feature.title} {...feature} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Feature = ({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
index,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
index: number;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col lg:border-r py-10 relative group/feature border-neutral-800",
|
||||
(index === 0 || index === 4 || index === 8) &&
|
||||
"lg:border-l dark:border-neutral-800",
|
||||
(index < 4 || index < 8) && "lg:border-b dark:border-neutral-800",
|
||||
)}
|
||||
>
|
||||
{index < 4 && (
|
||||
<div className="opacity-0 group-hover/feature:opacity-100 transition duration-200 absolute inset-0 h-full w-full bg-gradient-to-t from-neutral-800 to-transparent pointer-events-none" />
|
||||
)}
|
||||
{index >= 4 && (
|
||||
<div className="opacity-0 group-hover/feature:opacity-100 transition duration-200 absolute inset-0 h-full w-full bg-gradient-to-b from-neutral-800 to-transparent pointer-events-none" />
|
||||
)}
|
||||
<div className="mb-4 relative z-10 px-10 text-neutral-400">{icon}</div>
|
||||
<div className="text-lg font-bold mb-2 relative z-10 px-10">
|
||||
<div className="absolute left-0 inset-y-0 h-6 group-hover/feature:h-8 w-1 rounded-tr-full rounded-br-full bg-neutral-700 group-hover/feature:bg-white transition-all duration-200 origin-center" />
|
||||
<span className="group-hover/feature:translate-x-2 transition duration-200 inline-block text-neutral-100">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-neutral-300 max-w-xs relative z-10 px-10">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,17 +7,13 @@ import { useEffect, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Container } from "./Container";
|
||||
import Safari from "./ui/safari";
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "primaryFeatures.projects",
|
||||
description: "primaryFeatures.projectsDes",
|
||||
image: "/primary/projects.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.applications",
|
||||
description: "primaryFeatures.applicationsDes",
|
||||
image: "/primary/applications.png",
|
||||
image: "/primary/primary.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.compose",
|
||||
@@ -25,9 +21,9 @@ const features = [
|
||||
image: "/primary/compose.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.multinode",
|
||||
description: "primaryFeatures.multinodeDes",
|
||||
image: "/primary/multinode.png",
|
||||
title: "primaryFeatures.multiserver",
|
||||
description: "primaryFeatures.multiserverDes",
|
||||
image: "/primary/servers.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.monitoring",
|
||||
@@ -39,9 +35,14 @@ const features = [
|
||||
description: "primaryFeatures.backupsDes",
|
||||
image: "/primary/backups.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.traefik",
|
||||
description: "primaryFeatures.traefikDes",
|
||||
image: "/primary/traefik.png",
|
||||
},
|
||||
];
|
||||
|
||||
export function PrimaryFeatures() {
|
||||
export function SecondaryFeaturesSections() {
|
||||
const t = useTranslations("HomePage");
|
||||
const [tabOrientation, setTabOrientation] = useState<
|
||||
"horizontal" | "vertical"
|
||||
@@ -85,7 +86,7 @@ export function PrimaryFeatures() {
|
||||
height={1636}
|
||||
unoptimized
|
||||
/> */}
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-7xl max-lg:px-4 relative">
|
||||
<div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
|
||||
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl md:text-5xl">
|
||||
{t("primaryFeatures.title")}
|
||||
@@ -96,16 +97,16 @@ export function PrimaryFeatures() {
|
||||
</div>
|
||||
<Tab.Group
|
||||
as="div"
|
||||
className="mt-16 grid grid-cols-1 items-center gap-y-2 pt-10 sm:gap-y-6 md:mt-20 lg:grid-cols-12 lg:pt-0"
|
||||
vertical={tabOrientation === "vertical"}
|
||||
className="mt-16 grid grid-cols-1 items-center gap-y-2 pt-10 sm:gap-y-6 md:mt-20"
|
||||
vertical={false}
|
||||
>
|
||||
{({ selectedIndex }) => (
|
||||
<>
|
||||
<div className="-mx-4 flex overflow-x-auto pb-4 sm:mx-0 sm:overflow-visible sm:pb-0 lg:col-span-5">
|
||||
<div className="-mx-4 flex overflow-x-auto pb-4 sm:mx-0 overflow-visible sm:pb-0">
|
||||
<Tab.List
|
||||
aria-description="primary feature tabs"
|
||||
aria-roledescription="primary feature tabs"
|
||||
className="relative z-10 flex gap-x-4 whitespace-nowrap px-4 sm:mx-auto sm:px-0 lg:mx-0 lg:block lg:gap-x-0 lg:gap-y-1 lg:whitespace-normal"
|
||||
className="relative z-10 flex gap-x-4 whitespace-nowrap px-4 sm:mx-auto sm:px-0 "
|
||||
>
|
||||
{features.map((feature, featureIndex) => (
|
||||
<motion.div
|
||||
@@ -113,14 +114,14 @@ export function PrimaryFeatures() {
|
||||
initial={false}
|
||||
key={`feature-${featureIndex}`}
|
||||
className={cn(
|
||||
"group relative rounded-full px-4 py-1 transition-colors lg:rounded-l-xl lg:rounded-r-none lg:p-6 ",
|
||||
"group relative rounded-full px-4 py-1 transition-colors ",
|
||||
)}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{selectedIndex === featureIndex && (
|
||||
<motion.span
|
||||
layoutId="tab"
|
||||
className="absolute inset-0 z-10 rounded-full bg-white/5 mix-blend-difference lg:rounded-l-xl lg:rounded-r-none"
|
||||
className="absolute inset-0 z-10 rounded-full bg-white/5 mix-blend-difference"
|
||||
initial={{ opacity: 1 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
@@ -138,13 +139,13 @@ export function PrimaryFeatures() {
|
||||
"font-display text-lg text-primary ui-not-focus-visible:outline-none",
|
||||
)}
|
||||
>
|
||||
<span className="absolute inset-0 rounded-full lg:rounded-l-xl lg:rounded-r-none" />
|
||||
<span className="absolute inset-0 rounded-full" />
|
||||
{t(feature.title)}
|
||||
</Tab>
|
||||
</h3>
|
||||
<p
|
||||
className={cn(
|
||||
"mt-2 hidden text-sm text-muted-foreground lg:block",
|
||||
"mt-2 hidden text-sm text-muted-foreground ",
|
||||
)}
|
||||
>
|
||||
{t(feature.description)}
|
||||
@@ -153,34 +154,35 @@ export function PrimaryFeatures() {
|
||||
))}
|
||||
</Tab.List>
|
||||
</div>
|
||||
<Tab.Panels className="lg:col-span-7">
|
||||
<Tab.Panels className="">
|
||||
{features.map((feature, index) => (
|
||||
<Tab.Panel key={`panel-${index}`}>
|
||||
<div className="relative sm:px-6 lg:hidden">
|
||||
<div className="absolute -inset-x-4 bottom-[-4.25rem] top-[-6.5rem] bg-white/10 ring-1 ring-inset ring-white/10 sm:inset-x-0 sm:rounded-t-xl" />
|
||||
<p className="relative mx-auto max-w-2xl text-base text-white sm:text-center">
|
||||
<div className="relative sm:px-6 ">
|
||||
<div className="absolute -inset-x-4 bottom-[-4.25rem] top-[-6.5rem] bg-card/60 ring-1 ring-inset ring-white/10 sm:inset-x-0 sm:rounded-t-xl" />
|
||||
<p className="relative mx-auto max-w-2xl text-base text-white sm:text-center mb-10">
|
||||
{t(feature.description)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
key={feature.title}
|
||||
initial={isMounted ? { opacity: 0.8, x: 50 } : {}}
|
||||
animate={isMounted ? { opacity: 1, x: 0 } : {}}
|
||||
initial={isMounted ? { opacity: 0.4 } : {}}
|
||||
animate={isMounted ? { opacity: 1 } : {}}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
bounce: 0.2,
|
||||
duration: 0.6,
|
||||
duration: 0.8,
|
||||
}}
|
||||
className="mt-10 h-[24rem] w-[45rem] overflow-hidden rounded-xl border shadow-xl sm:w-auto lg:mt-0 lg:h-[40rem] lg:w-[67.8125rem]"
|
||||
className="mt-10 h-[24rem] w-[45rem] overflow-hidden rounded-xl border-b shadow-xl sm:w-auto lg:mt-0 lg:h-[40rem] "
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
className="w-full"
|
||||
src={feature.image}
|
||||
srcSet={`${feature.image} 1x`}
|
||||
/>
|
||||
<div className="relative">
|
||||
<Safari
|
||||
url={"Dokploy UI"}
|
||||
className="size-full"
|
||||
src={feature.image}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Tab.Panel>
|
||||
))}
|
||||
@@ -188,7 +190,7 @@ export function PrimaryFeatures() {
|
||||
</>
|
||||
)}
|
||||
</Tab.Group>
|
||||
</Container>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
51
apps/website/components/sponsors.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
import { PlusCircleIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
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 Sponsors = () => {
|
||||
const t = useTranslations("HomePage");
|
||||
return (
|
||||
<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")}
|
||||
</h1>
|
||||
<p className="mx-auto max-w-2xl text-lg tracking-tight text-muted-foreground text-center">
|
||||
{t("hero.sponsors.description")}
|
||||
</p>
|
||||
</div>
|
||||
<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
@@ -0,0 +1,172 @@
|
||||
import { 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>
|
||||
);
|
||||
}
|
||||
61
apps/website/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDown, Minus, PlusIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn("border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180 group",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<PlusIcon className="h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=open]:hidden" />
|
||||
<Minus className="h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=closed]:hidden" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0 text-muted-foreground", className)}>
|
||||
{children}
|
||||
</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
28
apps/website/components/ui/animated-gradient-text.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function AnimatedGradientText({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative mx-auto flex max-w-fit flex-row items-center justify-center rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#8fdfff1f] backdrop-blur-sm transition-shadow duration-500 ease-out [--bg-size:300%] hover:shadow-[inset_0_-5px_10px_#8fdfff3f] bg-black/40",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
"absolute inset-0 block h-full w-full animate-gradient bg-gradient-to-r from-[#ffaa40]/50 via-[#9c40ff]/50 to-[#ffaa40]/50 bg-[length:var(--bg-size)_100%] p-[1px] ![mask-composite:subtract] [border-radius:inherit] [mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]"
|
||||
}
|
||||
/>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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;
|
||||
50
apps/website/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback };
|
||||
143
apps/website/components/ui/hero-video-dialog.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Play, XIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type AnimationStyle =
|
||||
| "from-bottom"
|
||||
| "from-center"
|
||||
| "from-top"
|
||||
| "from-left"
|
||||
| "from-right"
|
||||
| "fade"
|
||||
| "top-in-bottom-out"
|
||||
| "left-in-right-out";
|
||||
|
||||
interface HeroVideoProps {
|
||||
animationStyle?: AnimationStyle;
|
||||
videoSrc: string;
|
||||
thumbnailSrc: string;
|
||||
thumbnailAlt?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const animationVariants = {
|
||||
"from-bottom": {
|
||||
initial: { y: "100%", opacity: 0 },
|
||||
animate: { y: 0, opacity: 1 },
|
||||
exit: { y: "100%", opacity: 0 },
|
||||
},
|
||||
"from-center": {
|
||||
initial: { scale: 0.5, opacity: 0 },
|
||||
animate: { scale: 1, opacity: 1 },
|
||||
exit: { scale: 0.5, opacity: 0 },
|
||||
},
|
||||
"from-top": {
|
||||
initial: { y: "-100%", opacity: 0 },
|
||||
animate: { y: 0, opacity: 1 },
|
||||
exit: { y: "-100%", opacity: 0 },
|
||||
},
|
||||
"from-left": {
|
||||
initial: { x: "-100%", opacity: 0 },
|
||||
animate: { x: 0, opacity: 1 },
|
||||
exit: { x: "-100%", opacity: 0 },
|
||||
},
|
||||
"from-right": {
|
||||
initial: { x: "100%", opacity: 0 },
|
||||
animate: { x: 0, opacity: 1 },
|
||||
exit: { x: "100%", opacity: 0 },
|
||||
},
|
||||
fade: {
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
},
|
||||
"top-in-bottom-out": {
|
||||
initial: { y: "-100%", opacity: 0 },
|
||||
animate: { y: 0, opacity: 1 },
|
||||
exit: { y: "100%", opacity: 0 },
|
||||
},
|
||||
"left-in-right-out": {
|
||||
initial: { x: "-100%", opacity: 0 },
|
||||
animate: { x: 0, opacity: 1 },
|
||||
exit: { x: "100%", opacity: 0 },
|
||||
},
|
||||
};
|
||||
|
||||
export default function HeroVideoDialog({
|
||||
animationStyle = "from-center",
|
||||
videoSrc,
|
||||
thumbnailSrc,
|
||||
thumbnailAlt = "Video thumbnail",
|
||||
className,
|
||||
}: HeroVideoProps) {
|
||||
const [isVideoOpen, setIsVideoOpen] = useState(false);
|
||||
const selectedAnimation = animationVariants[animationStyle];
|
||||
|
||||
return (
|
||||
<div className={cn("relative", className)}>
|
||||
<div
|
||||
className="relative cursor-pointer group"
|
||||
onClick={() => setIsVideoOpen(true)}
|
||||
>
|
||||
<img
|
||||
src={thumbnailSrc}
|
||||
alt={thumbnailAlt}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="w-full transition-all duration-200 group-hover:brightness-[0.8] ease-out rounded-md shadow-lg border"
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center group-hover:scale-100 scale-[0.9] transition-all duration-200 ease-out rounded-2xl">
|
||||
<div className="bg-primary/10 flex items-center justify-center rounded-full backdrop-blur-md size-28">
|
||||
<div
|
||||
className={
|
||||
"flex items-center justify-center bg-gradient-to-b from-primary/30 to-primary shadow-md rounded-full size-20 transition-all ease-out duration-200 relative group-hover:scale-[1.2] scale-100"
|
||||
}
|
||||
>
|
||||
<Play
|
||||
className="size-8 text-white fill-white group-hover:scale-105 scale-100 transition-transform duration-200 ease-out"
|
||||
style={{
|
||||
filter:
|
||||
"drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06))",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{isVideoOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
onClick={() => setIsVideoOpen(false)}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-md"
|
||||
>
|
||||
<motion.div
|
||||
{...selectedAnimation}
|
||||
transition={{ type: "spring", damping: 30, stiffness: 300 }}
|
||||
className="relative w-full max-w-4xl aspect-video mx-4 md:mx-0"
|
||||
>
|
||||
<motion.button className="absolute -top-16 right-0 text-white text-xl bg-neutral-900/50 ring-1 backdrop-blur-md rounded-full p-2 dark:bg-neutral-100/50 dark:text-black">
|
||||
<XIcon className="size-5" />
|
||||
</motion.button>
|
||||
<div className="size-full border-2 border-white rounded-2xl overflow-hidden isolate z-[1] relative">
|
||||
{/* biome-ignore lint/a11y/useIframeTitle: <explanation> */}
|
||||
<iframe
|
||||
src={videoSrc}
|
||||
className="size-full rounded-2xl"
|
||||
allowFullScreen
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
51
apps/website/components/ui/marquee.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface MarqueeProps {
|
||||
className?: string;
|
||||
reverse?: boolean;
|
||||
pauseOnHover?: boolean;
|
||||
children?: React.ReactNode;
|
||||
vertical?: boolean;
|
||||
repeat?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function Marquee({
|
||||
className,
|
||||
reverse,
|
||||
pauseOnHover = false,
|
||||
children,
|
||||
vertical = false,
|
||||
repeat = 4,
|
||||
...props
|
||||
}: MarqueeProps) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn(
|
||||
"group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]",
|
||||
{
|
||||
"flex-row": !vertical,
|
||||
"flex-col": vertical,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{Array(repeat)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
|
||||
"animate-marquee flex-row": !vertical,
|
||||
"animate-marquee-vertical flex-col": vertical,
|
||||
"group-hover:[animation-play-state:paused]": pauseOnHover,
|
||||
"[animation-direction:reverse]": reverse,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
348
apps/website/components/ui/ripple.tsx
Normal file
@@ -0,0 +1,348 @@
|
||||
"use client";
|
||||
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;
|
||||
mainCircleOpacity?: number;
|
||||
numCircles?: number;
|
||||
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 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 premiumSponsors = [
|
||||
{
|
||||
name: "Supafort",
|
||||
image: "supafort.png",
|
||||
link: "https://supafort.com/?ref=dokploy",
|
||||
type: "premium",
|
||||
},
|
||||
];
|
||||
|
||||
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 (
|
||||
<div
|
||||
className={cn(
|
||||
// "pointer-events-none select-none absolute inset-0 [mask-image:linear-gradient(to_bottom,white,transparent)]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
{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 animate-ripple rounded-full bg-foreground/25 shadow-xl border [--i:${i}]`}
|
||||
style={{
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
opacity,
|
||||
animationDelay,
|
||||
borderStyle,
|
||||
borderWidth: "1px",
|
||||
borderColor: `hsl(var(--foreground), ${borderOpacity / 100})`,
|
||||
top: "50%",
|
||||
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">
|
||||
{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
|
||||
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-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>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{i === 1 && (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
{premiumSponsors.map((item, index) => {
|
||||
const angle = (360 / premiumSponsors.length) * index;
|
||||
const radius = mainCircleSize / 2 + 70;
|
||||
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>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{i === 3 && (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
{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}
|
||||
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-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>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Ripple.displayName = "Ripple";
|
||||
|
||||
export default Ripple;
|
||||
138
apps/website/components/ui/safari.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import type { SVGProps } from "react";
|
||||
|
||||
export interface SafariProps extends SVGProps<SVGSVGElement> {
|
||||
url?: string;
|
||||
src?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export default function Safari({
|
||||
src,
|
||||
url,
|
||||
width = 1203,
|
||||
height = 753,
|
||||
...props
|
||||
}: SafariProps) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#path0)">
|
||||
<path
|
||||
d="M0 52H1202V741C1202 747.627 1196.63 753 1190 753H12C5.37258 753 0 747.627 0 741V52Z"
|
||||
className="fill-[#404040]"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0 12C0 5.37258 5.37258 0 12 0H1190C1196.63 0 1202 5.37258 1202 12V52H0L0 12Z"
|
||||
className="fill-[#404040]"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.06738 12C1.06738 5.92487 5.99225 1 12.0674 1H1189.93C1196.01 1 1200.93 5.92487 1200.93 12V51H1.06738V12Z"
|
||||
className="fill-[#262626]"
|
||||
/>
|
||||
<circle cx="27" cy="25" r="6" className="fill-[#404040]" />
|
||||
<circle cx="47" cy="25" r="6" className="fill-[#404040]" />
|
||||
<circle cx="67" cy="25" r="6" className="fill-[#404040]" />
|
||||
<path
|
||||
d="M286 17C286 13.6863 288.686 11 292 11H946C949.314 11 952 13.6863 952 17V35C952 38.3137 949.314 41 946 41H292C288.686 41 286 38.3137 286 35V17Z"
|
||||
className="fill-[#404040]"
|
||||
/>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M566.269 32.0852H572.426C573.277 32.0852 573.696 31.6663 573.696 30.7395V25.9851C573.696 25.1472 573.353 24.7219 572.642 24.6521V23.0842C572.642 20.6721 571.036 19.5105 569.348 19.5105C567.659 19.5105 566.053 20.6721 566.053 23.0842V24.6711C565.393 24.7727 565 25.1917 565 25.9851V30.7395C565 31.6663 565.418 32.0852 566.269 32.0852ZM567.272 22.97C567.272 21.491 568.211 20.6785 569.348 20.6785C570.478 20.6785 571.423 21.491 571.423 22.97V24.6394L567.272 24.6458V22.97Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<text
|
||||
x="580"
|
||||
y="30"
|
||||
fill="#A3A3A3"
|
||||
fontSize="12"
|
||||
fontFamily="Arial, sans-serif"
|
||||
>
|
||||
{url}
|
||||
</text>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M265.5 33.8984C265.641 33.8984 265.852 33.8516 266.047 33.7422C270.547 31.2969 272.109 30.1641 272.109 27.3203V21.4219C272.109 20.4844 271.742 20.1484 270.961 19.8125C270.094 19.4453 267.18 18.4297 266.328 18.1406C266.07 18.0547 265.766 18 265.5 18C265.234 18 264.93 18.0703 264.672 18.1406C263.82 18.3828 260.906 19.4531 260.039 19.8125C259.258 20.1406 258.891 20.4844 258.891 21.4219V27.3203C258.891 30.1641 260.461 31.2812 264.945 33.7422C265.148 33.8516 265.359 33.8984 265.5 33.8984ZM265.922 19.5781C266.945 19.9766 269.172 20.7656 270.344 21.1875C270.562 21.2656 270.617 21.3828 270.617 21.6641V27.0234C270.617 29.3125 269.469 29.9375 265.945 32.0625C265.727 32.1875 265.617 32.2344 265.508 32.2344V19.4844C265.617 19.4844 265.734 19.5156 265.922 19.5781Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M936.273 24.9766C936.5 24.9766 936.68 24.9062 936.82 24.7578L940.023 21.5312C940.195 21.3594 940.273 21.1719 940.273 20.9531C940.273 20.7422 940.188 20.5391 940.023 20.3828L936.82 17.125C936.68 16.9688 936.5 16.8906 936.273 16.8906C935.852 16.8906 935.516 17.2422 935.516 17.6719C935.516 17.8828 935.594 18.0547 935.727 18.2031L937.594 20.0312C937.227 19.9766 936.852 19.9453 936.477 19.9453C932.609 19.9453 929.516 23.0391 929.516 26.9141C929.516 30.7891 932.633 33.9062 936.5 33.9062C940.375 33.9062 943.484 30.7891 943.484 26.9141C943.484 26.4453 943.156 26.1094 942.688 26.1094C942.234 26.1094 941.93 26.4453 941.93 26.9141C941.93 29.9297 939.516 32.3516 936.5 32.3516C933.492 32.3516 931.07 29.9297 931.07 26.9141C931.07 23.875 933.469 21.4688 936.477 21.4688C936.984 21.4688 937.453 21.5078 937.867 21.5781L935.734 23.6875C935.594 23.8281 935.516 24 935.516 24.2109C935.516 24.6406 935.852 24.9766 936.273 24.9766Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M1134 33.0156C1134.49 33.0156 1134.89 32.6094 1134.89 32.1484V27.2578H1139.66C1140.13 27.2578 1140.54 26.8594 1140.54 26.3672C1140.54 25.8828 1140.13 25.4766 1139.66 25.4766H1134.89V20.5859C1134.89 20.1172 1134.49 19.7188 1134 19.7188C1133.52 19.7188 1133.11 20.1172 1133.11 20.5859V25.4766H1128.34C1127.88 25.4766 1127.46 25.8828 1127.46 26.3672C1127.46 26.8594 1127.88 27.2578 1128.34 27.2578H1133.11V32.1484C1133.11 32.6094 1133.52 33.0156 1134 33.0156Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M1161.8 31.0703H1163.23V32.375C1163.23 34.0547 1164.12 34.9219 1165.81 34.9219H1174.2C1175.89 34.9219 1176.77 34.0547 1176.77 32.3828V24.0469C1176.77 22.375 1175.89 21.5 1174.2 21.5H1172.77V20.2578C1172.77 18.5859 1171.88 17.7109 1170.19 17.7109H1161.8C1160.1 17.7109 1159.23 18.5781 1159.23 20.2578V28.5234C1159.23 30.1953 1160.1 31.0703 1161.8 31.0703ZM1161.9 29.5078C1161.18 29.5078 1160.78 29.1328 1160.78 28.3828V20.3984C1160.78 19.6406 1161.18 19.2656 1161.9 19.2656H1170.09C1170.8 19.2656 1171.2 19.6406 1171.2 20.3984V21.5H1165.81C1164.12 21.5 1163.23 22.375 1163.23 24.0469V29.5078H1161.9ZM1165.91 33.3672C1165.19 33.3672 1164.8 32.9922 1164.8 32.2422V24.1875C1164.8 23.4297 1165.19 23.0625 1165.91 23.0625H1174.1C1174.81 23.0625 1175.21 23.4297 1175.21 24.1875V32.2422C1175.21 32.9922 1174.81 33.3672 1174.1 33.3672H1165.91Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M1099.51 28.4141C1099.91 28.4141 1100.24 28.0859 1100.24 27.6953V19.8359L1100.18 18.6797L1100.66 19.25L1101.75 20.4141C1101.88 20.5547 1102.06 20.625 1102.24 20.625C1102.6 20.625 1102.9 20.3672 1102.9 20C1102.9 19.8047 1102.82 19.6641 1102.69 19.5312L1100.06 17.0078C1099.88 16.8203 1099.7 16.7578 1099.51 16.7578C1099.32 16.7578 1099.14 16.8203 1098.95 17.0078L1096.33 19.5312C1096.2 19.6641 1096.12 19.8047 1096.12 20C1096.12 20.3672 1096.41 20.625 1096.77 20.625C1096.95 20.625 1097.14 20.5547 1097.27 20.4141L1098.35 19.25L1098.84 18.6719L1098.78 19.8359V27.6953C1098.78 28.0859 1099.11 28.4141 1099.51 28.4141ZM1095 34.6562H1104C1105.7 34.6562 1106.57 33.7812 1106.57 32.1094V24.4297C1106.57 22.7578 1105.7 21.8828 1104 21.8828H1101.89V23.4375H1103.9C1104.61 23.4375 1105.02 23.8125 1105.02 24.5625V31.9688C1105.02 32.7188 1104.61 33.0938 1103.9 33.0938H1095.1C1094.38 33.0938 1093.98 32.7188 1093.98 31.9688V24.5625C1093.98 23.8125 1094.38 23.4375 1095.1 23.4375H1097.13V21.8828H1095C1093.31 21.8828 1092.43 22.75 1092.43 24.4297V32.1094C1092.43 33.7812 1093.31 34.6562 1095 34.6562Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M99.5703 33.6016H112.938C114.633 33.6016 115.516 32.7266 115.516 31.0547V21.5469C115.516 19.875 114.633 19 112.938 19H99.5703C97.8828 19 97 19.8672 97 21.5469V31.0547C97 32.7266 97.8828 33.6016 99.5703 33.6016ZM99.6719 32.0469C98.9531 32.0469 98.5547 31.6719 98.5547 30.9141V21.6875C98.5547 20.9297 98.9531 20.5547 99.6719 20.5547H103.234V32.0469H99.6719ZM112.836 20.5547C113.555 20.5547 113.953 20.9297 113.953 21.6875V30.9141C113.953 31.6719 113.555 32.0469 112.836 32.0469H104.711V20.5547H112.836ZM101.703 23.4141C101.984 23.4141 102.219 23.1719 102.219 22.9062C102.219 22.6406 101.984 22.4062 101.703 22.4062H100.102C99.8203 22.4062 99.5859 22.6406 99.5859 22.9062C99.5859 23.1719 99.8203 23.4141 100.102 23.4141H101.703ZM101.703 25.5156C101.984 25.5156 102.219 25.2812 102.219 25.0078C102.219 24.7422 101.984 24.5078 101.703 24.5078H100.102C99.8203 24.5078 99.5859 24.7422 99.5859 25.0078C99.5859 25.2812 99.8203 25.5156 100.102 25.5156H101.703ZM101.703 27.6094C101.984 27.6094 102.219 27.3828 102.219 27.1094C102.219 26.8438 101.984 26.6172 101.703 26.6172H100.102C99.8203 26.6172 99.5859 26.8438 99.5859 27.1094C99.5859 27.3828 99.8203 27.6094 100.102 27.6094H101.703Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M143.914 32.5938C144.094 32.7656 144.312 32.8594 144.562 32.8594C145.086 32.8594 145.492 32.4531 145.492 31.9375C145.492 31.6797 145.391 31.4453 145.211 31.2656L139.742 25.9219L145.211 20.5938C145.391 20.4141 145.492 20.1719 145.492 19.9219C145.492 19.4062 145.086 19 144.562 19C144.312 19 144.094 19.0938 143.922 19.2656L137.844 25.2031C137.625 25.4062 137.516 25.6562 137.516 25.9297C137.516 26.2031 137.625 26.4375 137.836 26.6484L143.914 32.5938Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<g className="mix-blend-luminosity">
|
||||
<path
|
||||
d="M168.422 32.8594C168.68 32.8594 168.891 32.7656 169.07 32.5938L175.148 26.6562C175.359 26.4375 175.469 26.2109 175.469 25.9297C175.469 25.6562 175.367 25.4141 175.148 25.2109L169.07 19.2656C168.891 19.0938 168.68 19 168.422 19C167.898 19 167.492 19.4062 167.492 19.9219C167.492 20.1719 167.602 20.4141 167.773 20.5938L173.25 25.9375L167.773 31.2656C167.594 31.4531 167.492 31.6797 167.492 31.9375C167.492 32.4531 167.898 32.8594 168.422 32.8594Z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</g>
|
||||
<image
|
||||
href={src}
|
||||
width="1200"
|
||||
height="700"
|
||||
x="1"
|
||||
y="52"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
clipPath="url(#roundedBottom)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="path0">
|
||||
<rect width={width} height={height} fill="white" />
|
||||
</clipPath>
|
||||
<clipPath id="roundedBottom">
|
||||
<path
|
||||
d="M1 52H1201V741C1201 747.075 1196.08 752 1190 752H12C5.92486 752 1 747.075 1 741V52Z"
|
||||
fill="white"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
48
apps/website/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
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 };
|
||||
@@ -6,7 +6,7 @@
|
||||
"docs": "Docs",
|
||||
"pricing": "Pricing",
|
||||
"support": "Support",
|
||||
"dashboard": "Dashboard",
|
||||
"dashboard": "Sign In",
|
||||
"discord": "Discord",
|
||||
"i18nButtonPlaceholder": "Language",
|
||||
"i18nEn": "English",
|
||||
@@ -39,14 +39,16 @@
|
||||
"projectsDes": "Manage and organize all your projects in one place, keeping detailed track of progress and resource allocation.",
|
||||
"applications": "Applications & Databases",
|
||||
"applicationsDes": "Centralize control over your applications and databases for enhanced security and efficiency, simplifying access and management across your infrastructure.",
|
||||
"compose": "Compose",
|
||||
"compose": "Docker Compose",
|
||||
"composeDes": "Native Docker Compose support for manage complex applications and services with ease.",
|
||||
"multinode": "Multinode",
|
||||
"multinodeDes": "Scale applications to multiples nodes using docker swarm to manage the cluster.",
|
||||
"multiserver": "Multiserver",
|
||||
"multiserverDes": "Deploy applications to multiple servers without effort.",
|
||||
"monitoring": "Monitoring",
|
||||
"monitoringDes": "Monitor your systems' performance and health in real time, ensuring continuous and uninterrupted operation.",
|
||||
"backups": "Backups",
|
||||
"backupsDes": "Implement automatic and secure backup solutions to protect your critical data and restore it quickly when necessary."
|
||||
"backupsDes": "Implement automatic and secure backup solutions to protect your critical data and restore it quickly when necessary.",
|
||||
"traefik": "Traefik",
|
||||
"traefikDes": "Manage traefik via File Editor to configure your own domain names, certificates, and more."
|
||||
},
|
||||
"secondaryFeatures": {
|
||||
"title": "Advanced Management Tools",
|
||||
@@ -65,8 +67,8 @@
|
||||
"terminalDes": "Provides an interface to access the command line of any active container, allowing developers to execute commands, manage services, and troubleshoot directly from the dashboard"
|
||||
},
|
||||
"callToAction": {
|
||||
"title": "Unlock Your Deployment Potential",
|
||||
"des": "Streamline your deployments with our PaaS. Effortlessly manage Docker containers and traffic with Traefik. Boost your infrastructure's efficiency and security today",
|
||||
"title": "Unlock Your Deployment Potential with Dokploy Cloud",
|
||||
"des": "Say goodbye to infrastructure hassles—Dokploy Cloud handles it all. Effortlessly deploy, manage Docker containers, and secure your traffic with Traefik. Focus on building, we’ll handle the rest.",
|
||||
"button": "Get Started Now"
|
||||
},
|
||||
"faq": {
|
||||
@@ -76,22 +78,36 @@
|
||||
"a1": "Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as a free alternative self-hostable solution to platforms like Heroku, Vercel, and Netlify.",
|
||||
"q2": "Why Choose Dokploy?",
|
||||
"a2": "Dokploy offers simplicity, flexibility, and speed in application deployment and management.",
|
||||
"q3": "Is Dokploy free?",
|
||||
"a3": "Yes, Dokploy is totally free. You can use it for personal projects, small teams, or even for large-scale applications.",
|
||||
"q4": "Is it open source?",
|
||||
"a4": "Yes, Dokploy is open source and free to use.",
|
||||
"q5": "What types of languages can I deploy with Dokploy?",
|
||||
"a5": "Dokploy does not restrict programming languages. You are free to choose your preferred language and framework.",
|
||||
"q6": "How do I request a feature or report a bug?",
|
||||
"a6": "To request a feature or report a bug, please create an issue on our GitHub repository or ask in our Discord channel. We are currently focused on addressing existing bugs and plan to release new features soon.",
|
||||
"a6": "To request a feature or report a bug, please create an issue on our GitHub repository or ask in our Discord channel.",
|
||||
"q7": "Do you track the usage of Dokploy?",
|
||||
"a7": "No, we don't track any usage data.",
|
||||
"q8": "Are there any user forums or communities where I can interact with other users?",
|
||||
"a8": "Yes, we have active GitHub discussions where you can share ideas, ask for help, and connect with other users.",
|
||||
"a8": "Yes, we have active GitHub discussions and Discord where you can share ideas, ask for help, and connect with other users.",
|
||||
"q9": "What types of applications can I deploy with Dokploy?",
|
||||
"a9": "Dokploy supports a variety of applications, including those built with Docker, as well as applications from any Git repository, offering custom builds with Nixpacks, Dockerfiles, or Buildpacks like Heroku and Paketo.",
|
||||
"a9": "You can deploy any application that can be Dockerized, with no limits. Dokploy supports builds from Git repositories, Dockerfiles, Nixpacks, and Buildpacks like Heroku and Paketo.",
|
||||
"q10": "How does Dokploy handle database management?",
|
||||
"a10": "Dokploy supports multiple database systems including Postgres, MySQL, MariaDB, MongoDB, and Redis, providing tools for easy deployment and management directly from the dashboard."
|
||||
"a10": "Dokploy supports multiple database systems including Postgres, MySQL, MariaDB, MongoDB, and Redis, providing tools for easy deployment and management and backups directly from the dashboard.",
|
||||
"q11": "How does Dokploy's Open Source plan work?",
|
||||
"a11": "You can host Dokploy UI on your own infrastructure and you will be responsible for the maintenance and updates.",
|
||||
"q12": "Do I need to provide my own server for the managed plan?",
|
||||
"a12": "Yes, in the managed plan, you provide your own server eg(Hetzner, Hostinger, AWS, ETC.) VPS, and we manage the Dokploy UI infrastructure for you.",
|
||||
"q13": "What happens if I need more than one server?",
|
||||
"a13": "The first server costs $4.50/month, if you buy more than one it will be $3.50/month per server.",
|
||||
"q14": "Is there a limit on the number of deployments?",
|
||||
"a14": "No, there is no limit on the number of deployments in any of the plans.",
|
||||
"q15": "What happens if I exceed my purchased server limit?",
|
||||
"a15": "The most recently added servers will be deactivated. You won't be able to create services on inactive servers until they are reactivated.",
|
||||
"q16": "Do you offer a refunds?",
|
||||
"a16": "We do not offer refunds. However, you can cancel your subscription at any time. Feel free to try our open-source version for free before making a purchase.",
|
||||
"q17": "What kind of support do you offer?",
|
||||
"a17": "We offer community support for the open source version and priority support for paid plans (Via Discord or Email at support@dokploy.com).",
|
||||
"q18": "What's the catch on the Paid Plan?",
|
||||
"a18": "Nothing, you link your server(VPS) to your account and you can deploy unlimited applications, databases, and users and you get unlimited updates, deployments, backups and more."
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "Copyright © {year} Dokploy. All rights reserved."
|
||||
@@ -126,13 +142,10 @@
|
||||
},
|
||||
"features": {
|
||||
"f1": "Complete Flexibility: Install Dokploy UI on your own infrastructure",
|
||||
"f2": "Unlimited Deployments",
|
||||
"f3": "Self-hosted Infrastructure",
|
||||
"f4": "Community Support",
|
||||
"f5": "Access to Core Features",
|
||||
"f6": "Dokploy Integration",
|
||||
"f7": "Basic Backups",
|
||||
"f8": "Access to All Updates",
|
||||
"f2": "Self-hosted Infrastructure",
|
||||
"f3": "Community Support",
|
||||
"f4": "Access to Core Features",
|
||||
"f5": "Access to All Updates",
|
||||
"f9": "Unlimited Servers"
|
||||
},
|
||||
"go": "Installation"
|
||||
@@ -146,8 +159,12 @@
|
||||
"servers": "{serverQuantity} Servers (You bring the servers)",
|
||||
"features": {
|
||||
"f1": "Managed Hosting: No need to manage your own servers",
|
||||
"f2": "Priority Support",
|
||||
"f3": "Future-Proof Features"
|
||||
"f2": "Unlimited Deployments",
|
||||
"f3": "Unlimited Databases",
|
||||
"f4": "Unlimited Applications",
|
||||
"f5": "Unlimited Users",
|
||||
"f6": "Priority Support",
|
||||
"f7": "New Updates"
|
||||
},
|
||||
"go": "Subscribe"
|
||||
}
|
||||
|
||||
@@ -14,14 +14,20 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@radix-ui/react-accordion": "^1.2.1",
|
||||
"@radix-ui/react-avatar": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@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",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.0.24",
|
||||
"framer-motion": "^11.3.19",
|
||||
"lucide-react": "0.364.0",
|
||||
"next": "14.2.2",
|
||||
"next-intl": "^3.19.0",
|
||||
@@ -31,8 +37,7 @@
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "5.1.6",
|
||||
"@radix-ui/react-tabs": "1.1.1"
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.7.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 158 KiB |
BIN
apps/website/public/primary/primary.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
apps/website/public/primary/servers.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
apps/website/public/primary/traefik.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
apps/website/public/supafort.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
@@ -79,6 +79,22 @@ const config = {
|
||||
display: "var(--font-lexend)",
|
||||
},
|
||||
keyframes: {
|
||||
marquee: {
|
||||
from: {
|
||||
transform: "translateX(0)",
|
||||
},
|
||||
to: {
|
||||
transform: "translateX(calc(-100% - var(--gap)))",
|
||||
},
|
||||
},
|
||||
"marquee-vertical": {
|
||||
from: {
|
||||
transform: "translateY(0)",
|
||||
},
|
||||
to: {
|
||||
transform: "translateY(calc(-100% - var(--gap)))",
|
||||
},
|
||||
},
|
||||
"accordion-down": {
|
||||
from: {
|
||||
height: "0",
|
||||
@@ -103,11 +119,28 @@ const config = {
|
||||
"background-position": "calc(100% + var(--shiny-width)) 0",
|
||||
},
|
||||
},
|
||||
gradient: {
|
||||
to: {
|
||||
backgroundPosition: "var(--bg-size) 0",
|
||||
},
|
||||
},
|
||||
ripple: {
|
||||
"0%, 100%": {
|
||||
transform: "translate(-50%, -50%) scale(1)",
|
||||
},
|
||||
"50%": {
|
||||
transform: "translate(-50%, -50%) scale(0.9)",
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"shiny-text": "shiny-text 8s infinite",
|
||||
marquee: "marquee var(--duration) linear infinite",
|
||||
"marquee-vertical": "marquee-vertical var(--duration) linear infinite",
|
||||
gradient: "gradient 8s linear infinite",
|
||||
ripple: "ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
228
pnpm-lock.yaml
generated
@@ -88,6 +88,15 @@ importers:
|
||||
'@headlessui/tailwindcss':
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.1(tailwindcss@3.4.7)
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.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-avatar':
|
||||
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-scroll-area':
|
||||
specifier: ^1.2.0
|
||||
version: 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-select':
|
||||
specifier: ^2.0.0
|
||||
version: 2.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)
|
||||
@@ -100,6 +109,12 @@ 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)
|
||||
'@types/node':
|
||||
specifier: 20.4.6
|
||||
version: 20.4.6
|
||||
@@ -113,7 +128,7 @@ importers:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.1
|
||||
framer-motion:
|
||||
specifier: ^11.0.24
|
||||
specifier: ^11.3.19
|
||||
version: 11.3.19(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
lucide-react:
|
||||
specifier: 0.364.0
|
||||
@@ -586,6 +601,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-accordion@1.2.1':
|
||||
resolution: {integrity: sha512-bg/l7l5QzUjgsh8kjwDFommzAshnUsuVMV5NM56QVCm+7ZckYdd9P/ExR8xG/Oup0OajVxNLaHJ1tb8mXk+nzQ==}
|
||||
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-arrow@1.1.0':
|
||||
resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==}
|
||||
peerDependencies:
|
||||
@@ -599,6 +627,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-avatar@1.1.1':
|
||||
resolution: {integrity: sha512-eoOtThOmxeoizxpX6RiEsQZ2wj5r4+zoeqAwO0cBaFQGjJwIH3dIX0OCxNrCyrrdxG+vBweMETh3VziQG7c1kw==}
|
||||
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-collapsible@1.1.0':
|
||||
resolution: {integrity: sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==}
|
||||
peerDependencies:
|
||||
@@ -612,6 +653,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collapsible@1.1.1':
|
||||
resolution: {integrity: sha512-1///SnrfQHJEofLokyczERxQbWfCGQlQ2XsCZMucVs6it+lq9iw4vXy+uDn1edlb58cOZOWSldnfPAYcT4O/Yg==}
|
||||
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-collection@1.1.0':
|
||||
resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==}
|
||||
peerDependencies:
|
||||
@@ -731,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:
|
||||
@@ -845,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:
|
||||
@@ -936,6 +1016,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-scroll-area@1.2.0':
|
||||
resolution: {integrity: sha512-q2jMBdsJ9zB7QG6ngQNzNwlvxLQqONyL58QbEGwuyRZZb/ARQwk3uQVbCF7GvQVOtV6EU/pDxAw3zRzJZI3rpQ==}
|
||||
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-select@2.1.1':
|
||||
resolution: {integrity: sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==}
|
||||
peerDependencies:
|
||||
@@ -1006,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:
|
||||
@@ -1136,6 +1242,14 @@ packages:
|
||||
'@swc/helpers@0.5.5':
|
||||
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
|
||||
|
||||
'@tabler/icons-react@3.21.0':
|
||||
resolution: {integrity: sha512-Qq0GnZzzccbv/zuMyXAUUPlogNAqx9KsF8cr/ev3bxs+GMObqNEjXv1eZl9GFzxyQTS435siJNU8A1BaIYhX8g==}
|
||||
peerDependencies:
|
||||
react: '>= 16'
|
||||
|
||||
'@tabler/icons@3.21.0':
|
||||
resolution: {integrity: sha512-5+GkkmWCr1wgMor5cOF1/YYflTQdc15y10FUikJ3HW8hDiFjfbuoAHJi17FT1vwsr1sA78rkJMn+fDoOOjnnPA==}
|
||||
|
||||
'@tailwindcss/typography@0.5.13':
|
||||
resolution: {integrity: sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==}
|
||||
peerDependencies:
|
||||
@@ -3386,6 +3500,23 @@ snapshots:
|
||||
'@types/react': 18.3.5
|
||||
'@types/react-dom': 18.3.0
|
||||
|
||||
'@radix-ui/react-accordion@1.2.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-collapsible': 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-collection': 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)
|
||||
'@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-direction': 1.1.0(@types/react@18.3.5)(react@18.2.0)
|
||||
'@radix-ui/react-id': 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-controllable-state': 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-arrow@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)':
|
||||
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)
|
||||
@@ -3404,6 +3535,18 @@ snapshots:
|
||||
'@types/react': 18.3.5
|
||||
'@types/react-dom': 18.3.0
|
||||
|
||||
'@radix-ui/react-avatar@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/react-context': 1.1.1(@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-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-collapsible@1.1.0(@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:
|
||||
'@radix-ui/primitive': 1.1.0
|
||||
@@ -3420,6 +3563,22 @@ snapshots:
|
||||
'@types/react': 18.3.5
|
||||
'@types/react-dom': 18.3.0
|
||||
|
||||
'@radix-ui/react-collapsible@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-context': 1.1.1(@types/react@18.3.5)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.1.0(@types/react@18.3.5)(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-use-controllable-state': 1.1.0(@types/react@18.3.5)(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-collection@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)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.2.0)
|
||||
@@ -3585,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
|
||||
@@ -3749,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
|
||||
@@ -3859,6 +4041,23 @@ snapshots:
|
||||
'@types/react': 18.3.5
|
||||
'@types/react-dom': 18.3.0
|
||||
|
||||
'@radix-ui/react-scroll-area@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)':
|
||||
dependencies:
|
||||
'@radix-ui/number': 1.1.0
|
||||
'@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-direction': 1.1.0(@types/react@18.3.5)(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-use-callback-ref': 1.1.0(@types/react@18.3.5)(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-select@2.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/number': 1.1.0
|
||||
@@ -3957,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
|
||||
@@ -4108,6 +4327,13 @@ snapshots:
|
||||
'@swc/counter': 0.1.3
|
||||
tslib: 2.6.3
|
||||
|
||||
'@tabler/icons-react@3.21.0(react@18.2.0)':
|
||||
dependencies:
|
||||
'@tabler/icons': 3.21.0
|
||||
react: 18.2.0
|
||||
|
||||
'@tabler/icons@3.21.0': {}
|
||||
|
||||
'@tailwindcss/typography@0.5.13(tailwindcss@3.4.7)':
|
||||
dependencies:
|
||||
lodash.castarray: 4.4.0
|
||||
|
||||