diff --git a/public/branding/dark-banner.png b/public/branding/dark-banner.png new file mode 100644 index 0000000..ccecbf6 Binary files /dev/null and b/public/branding/dark-banner.png differ diff --git a/public/branding/full-desktop-light.png b/public/branding/full-desktop-light.png new file mode 100644 index 0000000..e6f1ffb Binary files /dev/null and b/public/branding/full-desktop-light.png differ diff --git a/public/branding/full-desktop.png b/public/branding/full-desktop.png new file mode 100644 index 0000000..071c21b Binary files /dev/null and b/public/branding/full-desktop.png differ diff --git a/public/branding/light-banner.png b/public/branding/light-banner.png new file mode 100644 index 0000000..9d09f93 Binary files /dev/null and b/public/branding/light-banner.png differ diff --git a/public/favicon.ico b/public/favicon.ico index e1d43c8..9a1e9d5 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/imgs/icon-cf.png b/public/imgs/icon-cf.png new file mode 100644 index 0000000..09cfcb4 Binary files /dev/null and b/public/imgs/icon-cf.png differ diff --git a/public/imgs/icon-gd.png b/public/imgs/icon-gd.png new file mode 100644 index 0000000..45a71b9 Binary files /dev/null and b/public/imgs/icon-gd.png differ diff --git a/public/imgs/icon-gl.png b/public/imgs/icon-gl.png new file mode 100644 index 0000000..20914ae Binary files /dev/null and b/public/imgs/icon-gl.png differ diff --git a/public/imgs/icon-p.png b/public/imgs/icon-p.png new file mode 100644 index 0000000..9988606 Binary files /dev/null and b/public/imgs/icon-p.png differ diff --git a/public/imgs/icon-scf.png b/public/imgs/icon-scf.png new file mode 100644 index 0000000..b7700ac Binary files /dev/null and b/public/imgs/icon-scf.png differ diff --git a/public/imgs/icon-sgd.png b/public/imgs/icon-sgd.png new file mode 100644 index 0000000..5aa66ca Binary files /dev/null and b/public/imgs/icon-sgd.png differ diff --git a/public/imgs/icon-sgl.png b/public/imgs/icon-sgl.png new file mode 100644 index 0000000..a10f459 Binary files /dev/null and b/public/imgs/icon-sgl.png differ diff --git a/public/imgs/icon-sp.png b/public/imgs/icon-sp.png new file mode 100644 index 0000000..d43243a Binary files /dev/null and b/public/imgs/icon-sp.png differ diff --git a/public/svg/icon-cf.svg b/public/svg/icon-cf.svg new file mode 100644 index 0000000..fe3fe76 --- /dev/null +++ b/public/svg/icon-cf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/svg/icon-gd.svg b/public/svg/icon-gd.svg new file mode 100644 index 0000000..f137ee9 --- /dev/null +++ b/public/svg/icon-gd.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/svg/icon-gl.svg b/public/svg/icon-gl.svg new file mode 100644 index 0000000..b042a28 --- /dev/null +++ b/public/svg/icon-gl.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/svg/icon-p.svg b/public/svg/icon-p.svg new file mode 100644 index 0000000..7bb5692 --- /dev/null +++ b/public/svg/icon-p.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svg/icon-scf.svg b/public/svg/icon-scf.svg new file mode 100644 index 0000000..16d7d11 --- /dev/null +++ b/public/svg/icon-scf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/svg/icon-sgd.svg b/public/svg/icon-sgd.svg new file mode 100644 index 0000000..c7f2f89 --- /dev/null +++ b/public/svg/icon-sgd.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/svg/icon-sgl.svg b/public/svg/icon-sgl.svg new file mode 100644 index 0000000..dae98d0 --- /dev/null +++ b/public/svg/icon-sgl.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/svg/icon-sp.svg b/public/svg/icon-sp.svg new file mode 100644 index 0000000..a21306e --- /dev/null +++ b/public/svg/icon-sp.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/globals.css b/src/app/globals.css index 2095da8..9c0c956 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -39,6 +39,10 @@ --chart-3: 216 92% 60%; --chart-4: 210 98% 78%; --chart-5: 212 97% 87%; + + --color-one: #37ecba; + --color-two: #72afd3; + --color-three: #ff2e63; } .dark { diff --git a/src/app/help/how-to-customize/page.tsx b/src/app/help/how-to-customize/page.tsx new file mode 100644 index 0000000..d84a17d --- /dev/null +++ b/src/app/help/how-to-customize/page.tsx @@ -0,0 +1,37 @@ +import Link from "next/link"; +import { ReactNode } from "react"; + +export default function HelpPage() { + return ( +
+
+ MHSF Help Guides +
+
+ Go back to server list +
+
+

How to customize your server

+

+ Customizing a part of your server is easy, as long as you have access + to the account the server is owned under. To own a server, first go to + the server from the server list. Make sure you own an + account by linking an account or creating a new one,{" "} + + and make sure you linked your Minecraft account. + {" "} +

+

Guide

+

+ After going to the server page, click the Customization tab, and if + you own the server, click the button "Click to own this server". You + have successfully owned your Minehut server! +

+
+
+ ); +} + +function L({ H, children }: { H: string; children: ReactNode }) { + return {children}; +} diff --git a/src/app/help/how-to-link/page.tsx b/src/app/help/how-to-link/page.tsx new file mode 100644 index 0000000..cde04c3 --- /dev/null +++ b/src/app/help/how-to-link/page.tsx @@ -0,0 +1,43 @@ +import Link from "next/link"; +import { ReactNode } from "react"; + +export default function HelpPage() { + return ( +
+
+ MHSF Help Guides +
+
+ Go back to server list +
+
+

How to link your Minecraft account

+

+ To link your Minecraft account, make sure you have a MHSF account + created, and go into the settings in the top right, and press + "Security/Profile settings". Click "Link Account". +

+ +

Joining the server

+

+ After launching Minecraft, join the server{" "} + MHSFPV.minehut.gg and take note of the code being said in + chat. (You may need to go into the lobby to start up MHSFPV){" "} + Put this code the number selector, click "Submit", and you have linked + your account! +
Congratulations! +

+
+ Related Articles: +

+ {" "} + - How to customize your server +

+
+
+ ); +} + +function L({ H, children }: { H: string; children: ReactNode }) { + return {children}; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7da5052..c5a972d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,4 @@ import { GeistSans } from "geist/font/sans"; -import { Server } from "lucide-react"; import { SpeedInsights } from "@vercel/speed-insights/next"; import { Analytics } from "@vercel/analytics/react"; import "./globals.css"; @@ -21,7 +20,7 @@ import { CommandBarer } from "@/components/CommandBar"; import ThemedToaster from "@/components/misc/ThemedToaster"; import UnofficalDialog from "@/components/misc/UnofficalDialog"; import ClientFadeIn from "@/components/ClientFadeIn"; -import toast from "react-hot-toast"; +import { BrandingGenericIcon } from "@/components/Icon"; const inter = interFont({ variable: "--font-inter", subsets: ["latin"] }); export default async function RootLayout({ @@ -45,16 +44,16 @@ export default async function RootLayout({ )}
-
+
- + diff --git a/src/app/page.tsx b/src/app/page.tsx index 6b7bf2d..f15b10b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -12,7 +12,7 @@ export const metadata: Metadata = { export default function Home() { return (
-
+
diff --git a/src/components/ClientFadeIn.tsx b/src/components/ClientFadeIn.tsx index d3a69a4..a5bb2f6 100644 --- a/src/components/ClientFadeIn.tsx +++ b/src/components/ClientFadeIn.tsx @@ -4,9 +4,15 @@ import FadeIn from "react-fade-in"; export default function ClientFadeIn({ children, delay = 0, + id, }: { children: React.ReactNode; delay?: number; + id?: string; }) { - return {children}; + return ( +
+ {children} +
+ ); } diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx new file mode 100644 index 0000000..8893107 --- /dev/null +++ b/src/components/Icon.tsx @@ -0,0 +1,276 @@ +"use client"; +import { useTheme } from "next-themes"; +import type { SVGProps } from "react"; + +/** + * Returns a colorful version of the branding icon. + * + * The stored SVG file is at `/public/svg/icon-cf.svg` + * + * @param props The props for the SVG element. + * @returns A JSX element representing the colorful branding icon. + */ +export function BrandingColorfulIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + ); +} +/** + * Returns the optional Pride icon + * + * The stored SVG file is at `/public/svg/icon-p.svg` + * + * @param {SVGProps} props The props for the SVG element. + * @returns A JSX element representing the branding icon. + */ +export function BrandingPrideIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +/** + * Returns the branding icon based on the current theme. + * + * If the theme is dark, the branding icon is a dark version of the logo. + * If the theme is light, the branding icon is a light version of the logo. + * + * The stored SVG file is at `/public/svg/icon-gl.svg` or `/public/svg/icon-gd.svg` + * + * @param {SVGProps} props The props for the SVG element. + * + * @returns A JSX element representing the branding icon. + */ +export function BrandingGenericIcon(props: SVGProps) { + const { resolvedTheme } = useTheme(); + + if (resolvedTheme == "dark") { + return ( + + + + + + + + + + + + + + + ); + } else { + return ( + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/ServerList.tsx b/src/components/ServerList.tsx index 9c188df..c0fbada 100644 --- a/src/components/ServerList.tsx +++ b/src/components/ServerList.tsx @@ -1,10 +1,20 @@ "use client"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Separator } from "@/components/ui/separator"; import { Button } from "@/components/ui/button"; import { Badge } from "./ui/badge"; import ServersList from "@/lib/list"; -import { CircleUser, Network, Sun, Check, XIcon, Info } from "lucide-react"; +import { + CircleUser, + Network, + Sun, + Check, + XIcon, + Info, + ArrowDownZA, + LogIn, + ImageIcon, +} from "lucide-react"; import Stat from "./Stat"; import { Dialog, @@ -49,6 +59,50 @@ import { import ClientFadeIn from "./ClientFadeIn"; import { Skeleton } from "./ui/skeleton"; import useClipboard from "@/lib/useClipboard"; +import { SignedIn, SignedOut, useUser } from "@clerk/nextjs"; +import Link from "next/link"; +import SparklesText from "./effects/sparkles-text"; +import Particles from "./effects/particles"; +import { useTheme } from "next-themes"; +import { ChatBubbleIcon, InputIcon } from "@radix-ui/react-icons"; +import Marquee from "./effects/marquee"; +import { cn } from "@/lib/utils"; +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; +import { SignInPopover } from "./clerk/SignInPopoverButton"; +import { BentoCard, BentoGrid } from "./effects/bento-grid"; + +const features = [ + { + Icon: ChatBubbleIcon, + name: "Add a Discord widget", + description: + "Show where your players talk to each-other, including an online users count.", + href: "/help/how-to-customize", + cta: "Learn more", + background: , + className: "lg:row-start-1 lg:row-end-2 lg:col-start-2 lg:col-end-3", + }, + { + Icon: InputIcon, + name: "Descriptions", + href: "/help/how-to-customize", + cta: "Learn more", + description: + "Format your descriptions using Markdown to show what your server has to offer.", + background: , + className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-2", + }, + { + Icon: ImageIcon, + name: "Banners", + href: "/help/how-to-customize", + cta: "Learn more", + description: + "Show a banner with can contain images that show on your server page.", + background: , + className: "lg:col-start-3 lg:col-end-4 lg:row-start-1 lg:row-end-2", + }, +]; export default function ServerList() { const [loading, setLoading]: any = useState(true); @@ -62,6 +116,7 @@ export default function ServerList() { const [random, setRandom] = useState(false); const [serverList, setServerList] = useState(new ServersList([])); const [textCopied, setTextCopied] = useState(false); + const [padding, setPadding] = useState(0); const bigger = async (server: OnlineServer) => server.playerData.playerCount > 15; const smaller = async (server: OnlineServer) => @@ -73,6 +128,7 @@ export default function ServerList() { const [servers, setServers] = useState>([]); const clipboard = useClipboard(); const router = useRouter(); + const [pOS, setpOS] = useState(false); const [ipr, setIPR] = useState("4"); const [filters, setFilters] = useState< Array<(server: OnlineServer) => Promise> @@ -80,6 +136,12 @@ export default function ServerList() { const [randomData, setRandomData] = useState( undefined ); + const { resolvedTheme } = useTheme(); + const [color, setColor] = useState("#ffffff"); + + useEffect(() => { + setColor(resolvedTheme === "dark" ? "#ffffff" : "#000000"); + }, [resolvedTheme]); useEffectOnce(() => { setRandomText(getRandomText()); @@ -106,6 +168,11 @@ export default function ServerList() { }) .catch(() => setErrState(true)); }); + + const ref = useRef(null); + const [clickedPage, setClickedPage] = useState("banners"); + const [hero, setHero] = useState(false); + const { isSignedIn } = useUser(); if (inErrState) { return ( <> @@ -123,613 +190,865 @@ export default function ServerList() { if (loading) { return ( <> -
- - - -
-
- -
-
- - - - -
+ +
+ + + +
+
+ +
+
+ + + + +
+
); } return ( - <> - -
- - +
+ <> + {(!isSignedIn || hero) && ( +
+
= 3200 - ? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500" - : "" - } + style={{ + backgroundImage: + "radial-gradient(at 27% 37%,#3a8bfd 0,transparent 0),radial-gradient(at 97% 21%,#ff6 0,transparent 50%),radial-gradient(at 52% 99%,#6ff 0,transparent 50%),radial-gradient(at 10% 29%,#f6f 0,transparent 50%),radial-gradient(at 97% 96%,#f96 0,transparent 50%),radial-gradient(at 33% 50%,#69f 0,transparent 50%),radial-gradient(at 79% 53%,#f69 0,transparent 50%)", + opacity: 0.3, + filter: "blur(100px) saturate(150%)", + top: 80, + }} + /> + + <> + Meet MHSF,
the modern + server finder + +
+

+ MHSF is the next generation server list for Minehut, with + interactive filters,
{" "} + intuitive keyboard shortcuts, and everything between. +

+ + + + + + + + + + + + + + +
- Servers online{" "} +
+ + + Hero Image + Hero Image +
- } - className="relative z-0" - desc={ -
+
+
+ + For players + +
+
+

+ Find what you want now, not later +

+

+ Use interactive filters and customization modes to find the + server of your choice +
in less than 10 minutes. +

+
+ + {serverList.currentServers.slice(0, 20).map((server) => ( +
router.push(`/server/${server.name}`)} + > +
+
+
+ + {server.name} +
+ {server.author && ( +

+ by {server.author} +

+ )} +
+
+
+ ))} +
+ + {serverList.currentServers.slice(0, 20).map((server) => ( +
router.push(`/server/${server.name}`)} + > +
+
+
+ + {server.name} +
+ {server.author && ( +

+ by {server.author} +

+ )} +
+
+
+ ))} +
+
+ +
+
+
+ + For server owners + +
+
+

+ Make your server stand out +

+

+ Servers can have custom banners, Discord widgets, color schemes, + and descriptions, making your server stand out with information + that can be shown to players. +

+ + {features.map((feature, idx) => ( + + ))} + +
+ )} + + +
+ +
+
+ +
+ + = 3200 - ? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500 " + ? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500" : "" } > - {serverList.getExtraData().total_servers.toString()} + Servers online{" "}
- {serverList.getExtraData().total_servers >= 3200 && ( - - - - - - The server amount is over 3.2k, meaning that new servers - have to go into a queue before being able to be online.{" "} -
- (the server count isn't entirely accurate, so sometimes - you might not go into a queue even when the server count - is at 3.2k) -
-
- )} -
- } - icon={Network} - > - {serverList.getExtraData().total_servers >= 3200 && ( - - )} - - - {serverList.currentServers[0] != undefined - ? serverList.currentServers[0].name - : "None"}{" "} - {serverList.currentServers[0] != undefined && ( - - )} - - } - icon={Sun} - /> -
- -
- - - - - Servers - - events.emit("search-request-event")}> - Search Servers - - - +Shift+K - - - { - setRandomData(serverList.getRandomServer()); - setRandom(true); - }} - > - Pick Random Server - - - { - toast.promise( - new Promise((s, e) => { - setLoading(true); - serverList - .fetchDataAndFilter() - .then(() => { - serverList.moveListDown(); - - let stringList: Array<{ - server: string; - motd: string; - }> = []; - let obj: any = {}; - - serverList.currentServers.forEach((b) => { - stringList.push({ motd: b.motd, server: b.name }); - }); - - serverList.getMOTDs(stringList).then((c) => { - var updatedSL = motdList; - c.forEach((b: { server: string; motd: string }) => { - updatedSL[b.server] = b.motd; - }); - setMotdList(updatedSL); - setServers(serverList.currentServers); - setLoading(false); - s(false); - }); - }) - .catch(() => { - e(); - }); - }), - { - success: "Succesfully refreshed servers", - loading: "Refreshing...", - error: "Error while refreshing", - } - ); - }} - > - Refresh - - - - - Filter - - { - toast.promise( - new Promise((g, b) => { - if (v == "smaller") { - setTemplateFilter(true); - var filt = nameFilters; - filt["smaller-tf"] = true; - filt["bigger-tf"] = false; - setNameFilters(filt); - - var filt2 = filters; - filt2.push(smaller); - if (filt2.includes(bigger)) { - filt2.splice(filt2.indexOf(bigger), 1); - } - setFilters(filt2); - serverList.editFilters(filters); - serverList.fetchDataAndFilter().then(() => { - serverList.moveListDown(); - - let stringList: Array<{ - server: string; - motd: string; - }> = []; - let obj: any = {}; - - serverList.currentServers.forEach((b) => { - stringList.push({ motd: b.motd, server: b.name }); - }); - - serverList.getMOTDs(stringList).then((c) => { - var updatedSL = motdList; - c.forEach((b: { server: string; motd: string }) => { - updatedSL[b.server] = b.motd; - }); - setMotdList(updatedSL); - setServers(serverList.currentServers); - g(undefined); - }); - }); - } else if (v == "bigger") { - setTemplateFilter(true); - var filt = nameFilters; - filt["smaller-tf"] = false; - filt["bigger-tf"] = true; - setNameFilters(filt); - var filt2 = filters; - filt2.push(bigger); - - filt2.splice(filt2.indexOf(smaller), 1); - - setFilters(filt2); - serverList.editFilters(filters); - - serverList.fetchDataAndFilter().then(() => { - serverList.moveListDown(); - - let stringList: Array<{ - server: string; - motd: string; - }> = []; - let obj: any = {}; - - serverList.currentServers.forEach((b) => { - stringList.push({ motd: b.motd, server: b.name }); - }); - - serverList.getMOTDs(stringList).then((c) => { - var updatedSL = motdList; - c.forEach((b: { server: string; motd: string }) => { - updatedSL[b.server] = b.motd; - }); - setMotdList(updatedSL); - setServers(serverList.currentServers); - g(undefined); - }); - }); - } else { - var filt = nameFilters; - filt["smaller-tf"] = false; - filt["bigger-tf"] = false; - setNameFilters(filt); - setTemplateFilter(false); - - var filt2 = filters; - filt2.splice(filt2.indexOf(smaller), 1); - filt2.splice(filt2.indexOf(bigger), 1); - setFilters(filt2); - console.log(filters, filters.includes(smaller)); - serverList.editFilters(filters); - - serverList.fetchDataAndFilter().then(() => { - serverList.moveListDown(); - - let stringList: Array<{ - server: string; - motd: string; - }> = []; - let obj: any = {}; - - serverList.currentServers.forEach((b) => { - stringList.push({ motd: b.motd, server: b.name }); - }); - - serverList.getMOTDs(stringList).then((c) => { - var updatedSL = motdList; - c.forEach((b: { server: string; motd: string }) => { - updatedSL[b.server] = b.motd; - }); - setMotdList(updatedSL); - setServers(serverList.currentServers); - g(undefined); - }); - }); - } - }), - { - error: "Error while changing filters", - loading: "Changing filters...", - success: "Changed filters!", - } - ); - }} - value={(() => { - if (nameFilters["smaller-tf"]) { - return "smaller"; - } else if (nameFilters["bigger-tf"]) { - return "bigger"; - } else { - return "none"; - } - })()} - > - -
- Only allow smaller servers -
- - Only allow servers that have the player range 7-15, and - cannot
- be Always Online. -
-
-
- -
- Only allow bigger servers -
- - Only allow servers with more than 15 players. - -
-
- - No/custom requirements - -
- - - Tags - - {allTags.map((tag) => ( -
- {tag.docsName && tag.__filter == undefined && ( - { - return nameFilters["t-" + tag.docsName]; - })()} - onCheckedChange={async (b) => { - var filt = nameFilters; - filt["t-" + tag.docsName] = b; - setNameFilters(filt); - if (b) { - var filt2 = filters; - filt2.push(tag.condition); - setFilters(filt2); - } else { - var filt2 = filters; - filt2.splice(filt2.indexOf(tag.condition), 1); - setFilters(filt2); - } - serverList.editFilters(filters); - serverList.fetchDataAndFilter().then(() => { - serverList.moveListDown(); - - let stringList: Array<{ - server: string; - motd: string; - }> = []; - let obj: any = {}; - - serverList.currentServers.forEach((b) => { - stringList.push({ motd: b.motd, server: b.name }); - }); - - serverList.getMOTDs(stringList).then((c) => { - var updatedSL = motdList; - c.forEach((b: { server: string; motd: string }) => { - updatedSL[b.server] = b.motd; - }); - setMotdList(updatedSL); - setServers(serverList.currentServers); - }); - }); - }} - > - - {tag.docsName} - - - )} -
- ))} - - - - Categories - - - {allCategories.map((categorie) => ( - { - var filt = nameFilters; - filt["c-" + categorie.name] = b; - setNameFilters(filt); - if (b) { - var filt2 = filters; - filt2.push(categorie.condition); - setFilters(filt2); - } else { - var filt2 = filters; - filt2.splice(filt2.indexOf(categorie.condition), 1); - setFilters(filt2); - } - serverList.editFilters(filters); - serverList.fetchDataAndFilter().then(() => { - serverList.moveListDown(); - - let stringList: Array<{ server: string; motd: string }> = - []; - let obj: any = {}; - - serverList.currentServers.forEach((b) => { - stringList.push({ motd: b.motd, server: b.name }); - }); - - serverList.getMOTDs(stringList).then((c) => { - var updatedSL = motdList; - c.forEach((b: { server: string; motd: string }) => { - updatedSL[b.server] = b.motd; - }); - setMotdList(updatedSL); - setServers(serverList.currentServers); - }); - }); - }} - checked={(() => { - return nameFilters["c-" + categorie.name]; - })()} - > - - {categorie.name} - - - ))} -
-
- - View - - - Grid - - - - 4 items per row - - - 5 items per row - - - 6 items per row - - - - - - Sort - - - c == "favorites" && router.push("/sort/favorites") + } + className="relative z-0" + desc={ +
+
= 3200 + ? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500 " + : "" } > - - Players Online - - - Favorites - - - - - - - - - - - - {randomData == undefined && <>No data to randomize} - {randomData != undefined && ( - - - {randomData.name} - {randomData.author != undefined ? ( -
- by {randomData.author} + {serverList.getExtraData().total_servers.toString()}
- ) : ( -
- )} - -
- - - {randomData.playerData.playerCount == 0 ? ( -
- ) : ( -
+ {serverList.getExtraData().total_servers >= 3200 && ( + + + + + + The server amount is over 3.2k, meaning that new servers + have to go into a queue before being able to be online.{" "} +
+ (the server count isn't entirely accurate, so sometimes + you might not go into a queue even when the server count + is at 3.2k) +
+
)} - - - {randomData.playerData.playerCount}{" "} - {randomData.playerData.playerCount == 1 - ? "player" - : "players"}{" "} - currently online - - -
- Server IP -
-
- - {randomData.name}.minehut.gg{" "} - - - - - )} - -
- -
- { - serverList.moveListDown(); - let stringList: Array<{ server: string; motd: string }> = []; - serverList.currentServers.forEach((b) => { - stringList.push({ motd: b.motd, server: b.name }); - }); - - serverList.getMOTDs(stringList).then((c) => { - var updatedSL = motdList; - c.forEach((b: { server: string; motd: string }) => { - updatedSL[b.server] = b.motd; - }); - setMotdList(updatedSL); - setServers(serverList.currentServers); - setLoading(false); - }); - }} - loader={} - endMessage={ -

You've seen it all", - }} - /> - } - style={{ overflow: "hidden !important", paddingLeft: 6 }} - > - -

- {servers.map((b: any) => ( - <> - - - ))} +
+ } + icon={Network} + > + {serverList.getExtraData().total_servers >= 3200 && ( + + )} + + + {serverList.currentServers[0] != undefined + ? serverList.currentServers[0].name + : "None"}{" "} + {serverList.currentServers[0] != undefined && ( + + )} + + } + icon={Sun} + />
- - +
+ + + + + Servers + + events.emit("search-request-event")} + > + Search Servers + + + +Shift+K + + + { + setRandomData(serverList.getRandomServer()); + setRandom(true); + }} + > + Pick Random Server + + + { + toast.promise( + new Promise((s, e) => { + setLoading(true); + serverList + .fetchDataAndFilter() + .then(() => { + serverList.moveListDown(); + + let stringList: Array<{ + server: string; + motd: string; + }> = []; + let obj: any = {}; + + serverList.currentServers.forEach((b) => { + stringList.push({ motd: b.motd, server: b.name }); + }); + + serverList.getMOTDs(stringList).then((c) => { + var updatedSL = motdList; + c.forEach( + (b: { server: string; motd: string }) => { + updatedSL[b.server] = b.motd; + } + ); + setMotdList(updatedSL); + setServers(serverList.currentServers); + setLoading(false); + s(false); + }); + }) + .catch(() => { + e(); + }); + }), + { + success: "Succesfully refreshed servers", + loading: "Refreshing...", + error: "Error while refreshing", + } + ); + }} + > + Refresh + + + + + Filter + + { + toast.promise( + new Promise((g, b) => { + if (v == "smaller") { + setTemplateFilter(true); + var filt = nameFilters; + filt["smaller-tf"] = true; + filt["bigger-tf"] = false; + setNameFilters(filt); + + var filt2 = filters; + filt2.push(smaller); + if (filt2.includes(bigger)) { + filt2.splice(filt2.indexOf(bigger), 1); + } + setFilters(filt2); + serverList.editFilters(filters); + serverList.fetchDataAndFilter().then(() => { + serverList.moveListDown(); + + let stringList: Array<{ + server: string; + motd: string; + }> = []; + let obj: any = {}; + + serverList.currentServers.forEach((b) => { + stringList.push({ motd: b.motd, server: b.name }); + }); + + serverList.getMOTDs(stringList).then((c) => { + var updatedSL = motdList; + c.forEach( + (b: { server: string; motd: string }) => { + updatedSL[b.server] = b.motd; + } + ); + setMotdList(updatedSL); + setServers(serverList.currentServers); + g(undefined); + }); + }); + } else if (v == "bigger") { + setTemplateFilter(true); + var filt = nameFilters; + filt["smaller-tf"] = false; + filt["bigger-tf"] = true; + setNameFilters(filt); + var filt2 = filters; + filt2.push(bigger); + + filt2.splice(filt2.indexOf(smaller), 1); + + setFilters(filt2); + serverList.editFilters(filters); + + serverList.fetchDataAndFilter().then(() => { + serverList.moveListDown(); + + let stringList: Array<{ + server: string; + motd: string; + }> = []; + let obj: any = {}; + + serverList.currentServers.forEach((b) => { + stringList.push({ motd: b.motd, server: b.name }); + }); + + serverList.getMOTDs(stringList).then((c) => { + var updatedSL = motdList; + c.forEach( + (b: { server: string; motd: string }) => { + updatedSL[b.server] = b.motd; + } + ); + setMotdList(updatedSL); + setServers(serverList.currentServers); + g(undefined); + }); + }); + } else { + var filt = nameFilters; + filt["smaller-tf"] = false; + filt["bigger-tf"] = false; + setNameFilters(filt); + setTemplateFilter(false); + + var filt2 = filters; + filt2.splice(filt2.indexOf(smaller), 1); + filt2.splice(filt2.indexOf(bigger), 1); + setFilters(filt2); + console.log(filters, filters.includes(smaller)); + serverList.editFilters(filters); + + serverList.fetchDataAndFilter().then(() => { + serverList.moveListDown(); + + let stringList: Array<{ + server: string; + motd: string; + }> = []; + let obj: any = {}; + + serverList.currentServers.forEach((b) => { + stringList.push({ motd: b.motd, server: b.name }); + }); + + serverList.getMOTDs(stringList).then((c) => { + var updatedSL = motdList; + c.forEach( + (b: { server: string; motd: string }) => { + updatedSL[b.server] = b.motd; + } + ); + setMotdList(updatedSL); + setServers(serverList.currentServers); + g(undefined); + }); + }); + } + }), + { + error: "Error while changing filters", + loading: "Changing filters...", + success: "Changed filters!", + } + ); + }} + value={(() => { + if (nameFilters["smaller-tf"]) { + return "smaller"; + } else if (nameFilters["bigger-tf"]) { + return "bigger"; + } else { + return "none"; + } + })()} + > + +
+ Only allow smaller servers +
+ + Only allow servers that have the player range 7-15, and + cannot
+ be Always Online. +
+
+
+ +
+ Only allow bigger servers +
+ + Only allow servers with more than 15 players. + +
+
+ + No/custom requirements + +
+ + + + Tags + + + {allTags.map((tag) => ( +
+ {tag.docsName && tag.__filter == undefined && ( + { + return nameFilters["t-" + tag.docsName]; + })()} + onCheckedChange={async (b) => { + var filt = nameFilters; + filt["t-" + tag.docsName] = b; + setNameFilters(filt); + if (b) { + var filt2 = filters; + filt2.push(tag.condition); + setFilters(filt2); + } else { + var filt2 = filters; + filt2.splice(filt2.indexOf(tag.condition), 1); + setFilters(filt2); + } + serverList.editFilters(filters); + serverList.fetchDataAndFilter().then(() => { + serverList.moveListDown(); + + let stringList: Array<{ + server: string; + motd: string; + }> = []; + let obj: any = {}; + + serverList.currentServers.forEach((b) => { + stringList.push({ motd: b.motd, server: b.name }); + }); + + serverList.getMOTDs(stringList).then((c) => { + var updatedSL = motdList; + c.forEach( + (b: { server: string; motd: string }) => { + updatedSL[b.server] = b.motd; + } + ); + setMotdList(updatedSL); + setServers(serverList.currentServers); + }); + }); + }} + > + + {tag.docsName} + + + )} +
+ ))} + + + + Categories + + + {allCategories.map((categorie) => ( + { + var filt = nameFilters; + filt["c-" + categorie.name] = b; + setNameFilters(filt); + if (b) { + var filt2 = filters; + filt2.push(categorie.condition); + setFilters(filt2); + } else { + var filt2 = filters; + filt2.splice(filt2.indexOf(categorie.condition), 1); + setFilters(filt2); + } + serverList.editFilters(filters); + serverList.fetchDataAndFilter().then(() => { + serverList.moveListDown(); + + let stringList: Array<{ + server: string; + motd: string; + }> = []; + let obj: any = {}; + + serverList.currentServers.forEach((b) => { + stringList.push({ motd: b.motd, server: b.name }); + }); + + serverList.getMOTDs(stringList).then((c) => { + var updatedSL = motdList; + c.forEach((b: { server: string; motd: string }) => { + updatedSL[b.server] = b.motd; + }); + setMotdList(updatedSL); + setServers(serverList.currentServers); + }); + }); + }} + checked={(() => { + return nameFilters["c-" + categorie.name]; + })()} + > + + {categorie.name} + + + ))} +
+
+ + View + + + Grid + + + + 4 items per row + + + 5 items per row + + + 6 items per row + + + + + + Padding + + setPadding(Number(v))} + > + Default + + 15px + 30px + 40px + 60px + 100px + 200px + + + + Only use padding on servers + + + + + Sort + + + c == "favorites" && router.push("/sort/favorites") + } + > + + Players Online + + + Favorites + + + + + + + + Show Hero + + + + +
+
+ + + + {randomData == undefined && <>No data to randomize} + {randomData != undefined && ( + + + {randomData.name} + {randomData.author != undefined ? ( +
+ by {randomData.author} +
+ ) : ( +
+ )} + +
+ + + {randomData.playerData.playerCount == 0 ? ( +
+ ) : ( +
+ )} + + + {randomData.playerData.playerCount}{" "} + {randomData.playerData.playerCount == 1 + ? "player" + : "players"}{" "} + currently online + + +
+ Server IP +
+
+ + {randomData.name}.minehut.gg{" "} + + + + + )} + +
+ +
+ { + serverList.moveListDown(); + let stringList: Array<{ server: string; motd: string }> = []; + serverList.currentServers.forEach((b) => { + stringList.push({ motd: b.motd, server: b.name }); + }); + + serverList.getMOTDs(stringList).then((c) => { + var updatedSL = motdList; + c.forEach((b: { server: string; motd: string }) => { + updatedSL[b.server] = b.motd; + }); + setMotdList(updatedSL); + setServers(serverList.currentServers); + setLoading(false); + }); + }} + loader={} + endMessage={ +

You've seen it all", + }} + /> + } + style={{ + overflow: "hidden !important", + paddingLeft: pOS ? padding : 6, + paddingRight: pOS ? padding : 6, + }} + > + + {/** This looks stupid, but its the only way that works */} +

+ {servers.map((b: any) => ( + <> + + + ))} +
+ +
+
+
); } diff --git a/src/components/clerk/SignInPopoverButton.tsx b/src/components/clerk/SignInPopoverButton.tsx index 47ebde7..fec0616 100644 --- a/src/components/clerk/SignInPopoverButton.tsx +++ b/src/components/clerk/SignInPopoverButton.tsx @@ -21,8 +21,6 @@ export default function SignInPopoverButton({ | "ghost" | "link"; }) { - const clerk = useClerk(); - return ( @@ -31,22 +29,29 @@ export default function SignInPopoverButton({ -
- Login - - Make comments about servers and favorite servers. Secured by Clerk - -
- - -
+
); } + +export function SignInPopover() { + const clerk = useClerk(); + return ( +
+ Login + + Customize your own servers and favorite other servers. Secured by Clerk + +
+ + +
+ ); +} diff --git a/src/components/effects/bento-grid.tsx b/src/components/effects/bento-grid.tsx new file mode 100644 index 0000000..866558b --- /dev/null +++ b/src/components/effects/bento-grid.tsx @@ -0,0 +1,79 @@ +import { ReactNode } from "react"; +import { ArrowRightIcon } from "@radix-ui/react-icons"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +const BentoGrid = ({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) => { + return ( +
+ {children} +
+ ); +}; + +const BentoCard = ({ + name, + className, + background, + Icon, + description, + href, + cta, +}: { + name: string; + className: string; + background: ReactNode; + Icon: any; + description: string; + href: string; + cta: string; +}) => ( +
+
{background}
+
+ +

+ {name} +

+

{description}

+
+ + +
+
+); + +export { BentoCard, BentoGrid }; diff --git a/src/components/effects/marquee.tsx b/src/components/effects/marquee.tsx new file mode 100644 index 0000000..6f50a86 --- /dev/null +++ b/src/components/effects/marquee.tsx @@ -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 default function Marquee({ + className, + reverse, + pauseOnHover = false, + children, + vertical = false, + repeat = 4, + ...props +}: MarqueeProps) { + return ( +
+ {Array(repeat) + .fill(0) + .map((_, i) => ( +
+ {children} +
+ ))} +
+ ); +} diff --git a/src/components/effects/particles.tsx b/src/components/effects/particles.tsx new file mode 100644 index 0000000..d08df37 --- /dev/null +++ b/src/components/effects/particles.tsx @@ -0,0 +1,278 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; + +interface MousePosition { + x: number; + y: number; +} + +function MousePosition(): MousePosition { + const [mousePosition, setMousePosition] = useState({ + x: 0, + y: 0, + }); + + useEffect(() => { + const handleMouseMove = (event: MouseEvent) => { + setMousePosition({ x: event.clientX, y: event.clientY }); + }; + + window.addEventListener("mousemove", handleMouseMove); + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + }; + }, []); + + return mousePosition; +} + +interface ParticlesProps { + className?: string; + quantity?: number; + staticity?: number; + ease?: number; + size?: number; + refresh?: boolean; + color?: string; + vx?: number; + vy?: number; +} +function hexToRgb(hex: string): number[] { + hex = hex.replace("#", ""); + + if (hex.length === 3) { + hex = hex + .split("") + .map((char) => char + char) + .join(""); + } + + const hexInt = parseInt(hex, 16); + const red = (hexInt >> 16) & 255; + const green = (hexInt >> 8) & 255; + const blue = hexInt & 255; + return [red, green, blue]; +} + +const Particles: React.FC = ({ + className = "", + quantity = 100, + staticity = 50, + ease = 50, + size = 0.4, + refresh = false, + color = "#ffffff", + vx = 0, + vy = 0, +}) => { + const canvasRef = useRef(null); + const canvasContainerRef = useRef(null); + const context = useRef(null); + const circles = useRef([]); + const mousePosition = MousePosition(); + const mouse = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); + const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 }); + const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1; + + useEffect(() => { + if (canvasRef.current) { + context.current = canvasRef.current.getContext("2d"); + } + initCanvas(); + animate(); + window.addEventListener("resize", initCanvas); + + return () => { + window.removeEventListener("resize", initCanvas); + }; + }, [color]); + + useEffect(() => { + onMouseMove(); + }, [mousePosition.x, mousePosition.y]); + + useEffect(() => { + initCanvas(); + }, [refresh]); + + const initCanvas = () => { + resizeCanvas(); + drawParticles(); + }; + + const onMouseMove = () => { + if (canvasRef.current) { + const rect = canvasRef.current.getBoundingClientRect(); + const { w, h } = canvasSize.current; + const x = mousePosition.x - rect.left - w / 2; + const y = mousePosition.y - rect.top - h / 2; + const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2; + if (inside) { + mouse.current.x = x; + mouse.current.y = y; + } + } + }; + + type Circle = { + x: number; + y: number; + translateX: number; + translateY: number; + size: number; + alpha: number; + targetAlpha: number; + dx: number; + dy: number; + magnetism: number; + }; + + const resizeCanvas = () => { + if (canvasContainerRef.current && canvasRef.current && context.current) { + circles.current.length = 0; + canvasSize.current.w = canvasContainerRef.current.offsetWidth; + canvasSize.current.h = canvasContainerRef.current.offsetHeight; + canvasRef.current.width = canvasSize.current.w * dpr; + canvasRef.current.height = canvasSize.current.h * dpr; + canvasRef.current.style.width = `${canvasSize.current.w}px`; + canvasRef.current.style.height = `${canvasSize.current.h}px`; + context.current.scale(dpr, dpr); + } + }; + + const circleParams = (): Circle => { + const x = Math.floor(Math.random() * canvasSize.current.w); + const y = Math.floor(Math.random() * canvasSize.current.h); + const translateX = 0; + const translateY = 0; + const pSize = Math.floor(Math.random() * 2) + size; + const alpha = 0; + const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1)); + const dx = (Math.random() - 0.5) * 0.1; + const dy = (Math.random() - 0.5) * 0.1; + const magnetism = 0.1 + Math.random() * 4; + return { + x, + y, + translateX, + translateY, + size: pSize, + alpha, + targetAlpha, + dx, + dy, + magnetism, + }; + }; + + const rgb = hexToRgb(color); + + const drawCircle = (circle: Circle, update = false) => { + if (context.current) { + const { x, y, translateX, translateY, size, alpha } = circle; + context.current.translate(translateX, translateY); + context.current.beginPath(); + context.current.arc(x, y, size, 0, 2 * Math.PI); + context.current.fillStyle = `rgba(${rgb.join(", ")}, ${alpha})`; + context.current.fill(); + context.current.setTransform(dpr, 0, 0, dpr, 0, 0); + + if (!update) { + circles.current.push(circle); + } + } + }; + + const clearContext = () => { + if (context.current) { + context.current.clearRect( + 0, + 0, + canvasSize.current.w, + canvasSize.current.h, + ); + } + }; + + const drawParticles = () => { + clearContext(); + const particleCount = quantity; + for (let i = 0; i < particleCount; i++) { + const circle = circleParams(); + drawCircle(circle); + } + }; + + const remapValue = ( + value: number, + start1: number, + end1: number, + start2: number, + end2: number, + ): number => { + const remapped = + ((value - start1) * (end2 - start2)) / (end1 - start1) + start2; + return remapped > 0 ? remapped : 0; + }; + + const animate = () => { + clearContext(); + circles.current.forEach((circle: Circle, i: number) => { + // Handle the alpha value + const edge = [ + circle.x + circle.translateX - circle.size, // distance from left edge + canvasSize.current.w - circle.x - circle.translateX - circle.size, // distance from right edge + circle.y + circle.translateY - circle.size, // distance from top edge + canvasSize.current.h - circle.y - circle.translateY - circle.size, // distance from bottom edge + ]; + const closestEdge = edge.reduce((a, b) => Math.min(a, b)); + const remapClosestEdge = parseFloat( + remapValue(closestEdge, 0, 20, 0, 1).toFixed(2), + ); + if (remapClosestEdge > 1) { + circle.alpha += 0.02; + if (circle.alpha > circle.targetAlpha) { + circle.alpha = circle.targetAlpha; + } + } else { + circle.alpha = circle.targetAlpha * remapClosestEdge; + } + circle.x += circle.dx + vx; + circle.y += circle.dy + vy; + circle.translateX += + (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) / + ease; + circle.translateY += + (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) / + ease; + + drawCircle(circle, true); + + // circle gets out of the canvas + if ( + circle.x < -circle.size || + circle.x > canvasSize.current.w + circle.size || + circle.y < -circle.size || + circle.y > canvasSize.current.h + circle.size + ) { + // remove the circle from the array + circles.current.splice(i, 1); + // create a new circle + const newCircle = circleParams(); + drawCircle(newCircle); + // update the circle position + } + }); + window.requestAnimationFrame(animate); + }; + + return ( + + ); +}; + +export default Particles; diff --git a/src/components/effects/sparkles-text.tsx b/src/components/effects/sparkles-text.tsx new file mode 100644 index 0000000..defbbf6 --- /dev/null +++ b/src/components/effects/sparkles-text.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { CSSProperties, ReactElement, useEffect, useState } from "react"; +import { motion } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +interface Sparkle { + id: string; + x: string; + y: string; + color: string; + delay: number; + scale: number; + lifespan: number; +} + +interface SparklesTextProps { + /** + * @default
+ * @type ReactElement + * @description + * The component to be rendered as the text + * */ + as?: ReactElement; + + /** + * @default "" + * @type string + * @description + * The className of the text + */ + className?: string; + + /** + * @required + * @type string + * @description + * The text to be displayed + * */ + children: JSX.Element; + + /** + * @default 10 + * @type number + * @description + * The count of sparkles + * */ + sparklesCount?: number; + + /** + * @default "{first: '#9E7AFF', second: '#FE8BBB'}" + * @type string + * @description + * The colors of the sparkles + * */ + colors?: { + first: string; + second: string; + }; +} + +const SparklesText: React.FC = ({ + children, + colors = { first: "#9E7AFF", second: "#FE8BBB" }, + className, + sparklesCount = 10, + ...props +}) => { + const [sparkles, setSparkles] = useState([]); + + useEffect(() => { + const generateStar = (): Sparkle => { + const starX = `${Math.random() * 100}%`; + const starY = `${Math.random() * 100}%`; + const color = Math.random() > 0.5 ? colors.first : colors.second; + const delay = Math.random() * 2; + const scale = Math.random() * 1 + 0.3; + const lifespan = Math.random() * 10 + 5; + const id = `${starX}-${starY}-${Date.now()}`; + return { id, x: starX, y: starY, color, delay, scale, lifespan }; + }; + + const initializeStars = () => { + const newSparkles = Array.from({ length: sparklesCount }, generateStar); + setSparkles(newSparkles); + }; + + const updateStars = () => { + setSparkles((currentSparkles) => + currentSparkles.map((star) => { + if (star.lifespan <= 0) { + return generateStar(); + } else { + return { ...star, lifespan: star.lifespan - 0.1 }; + } + }) + ); + }; + + initializeStars(); + const interval = setInterval(updateStars, 100); + + return () => clearInterval(interval); + }, [colors.first, colors.second]); + + return ( +
+ + {sparkles.map((sparkle) => ( + + ))} + {children} + +
+ ); +}; + +const Sparkle: React.FC = ({ id, x, y, color, delay, scale }) => { + return ( + + + + ); +}; + +export default SparklesText; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index dfe20c7..9a3286a 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { @@ -39,14 +39,22 @@ export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; + noJustify?: boolean; } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { + ( + { className, variant, size, asChild = false, noJustify = false, ...props }, + ref + ) => { const Comp = asChild ? Slot : "button"; return ( diff --git a/src/version.tsx b/src/version.tsx index 81333f7..e5e1687 100644 --- a/src/version.tsx +++ b/src/version.tsx @@ -2,16 +2,15 @@ import Image from "next/image"; import Link from "next/link"; import { Separator } from "./components/ui/separator"; import { Button } from "./components/ui/button"; -import Confetti, { ConfettiButton } from "./components/effects/confetti"; +import confetti from "canvas-confetti"; -export const version = "1.0"; +export const version = "1.1.0"; const User = ({ user }: { user: string }) => ( {user} ); -import confetti from "canvas-confetti"; const handleClick = () => { const duration = 5 * 1000; const animationEnd = Date.now() + duration; @@ -78,6 +77,17 @@ export const Changelog = () => (

+
+ + Version 1.1.0 (August 24rd 2024) + +
    +
  • • Brand new hero page
  • +
  • • New padding option on server list
  • +
  • • New help guide
  • +
+
+
Version 1.0.0 (August 22nd 2024) diff --git a/tailwind.config.ts b/tailwind.config.ts index 8112d69..07babbc 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -59,6 +59,19 @@ const config = { sm: "calc(var(--radius) - 4px)", }, keyframes: { + 'image-glow': { + '0%': { + 'opacity': '0', + 'animation-timing-function': 'cubic-bezier(0.74, 0.25, 0.76, 1)', + }, + '10%': { + 'opacity': '0.7', + 'animation-timing-function': 'cubic-bezier(0.12, 0.01, 0.08, 0.99)', + }, + '100%': { + opacity: '0.4', + }, + }, "border-beam": { "100%": { "offset-distance": "100%", @@ -77,12 +90,45 @@ const config = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, + + "fade-in": { + from: { opacity: "0", transform: "translateY(-10px)" }, + to: { opacity: "1", transform: "none" }, + }, + 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)))" }, + }, + "fade-up": { + from: { opacity: "0", transform: "translateY(20px)" }, + to: { opacity: "1", transform: "none" }, + }, + + 'shimmer': { + '0%, 90%, 100%': { + 'background-position': 'calc(-100% - var(--shimmer-width)) 0', + }, + '30%, 60%': { + 'background-position': 'calc(100% + var(--shimmer-width)) 0', + }, + }, }, animation: { + marquee: "marquee var(--duration) linear infinite", + "marquee-vertical": "marquee-vertical var(--duration) linear infinite", "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", + 'image-glow': 'image-glow 4100ms 600ms ease-out forwards', + 'shimmer': 'shimmer 8s infinite', + "fade-in": "fade-in 1000ms var(--animation-delay, 0ms) ease forwards", "caret-blink": "caret-blink 1.25s ease-out infinite", "border-beam": "border-beam calc(var(--duration)*1s) infinite linear", + + "fade-up": "fade-up 1000ms var(--animation-delay, 0ms) ease forwards", }, }, },