feat: i didn't keep track 💀

This commit is contained in:
dvelo 2025-02-22 10:38:19 -06:00
parent 75fa71cdf1
commit 8b938f5c2c
19 changed files with 440 additions and 105 deletions

@ -1,18 +0,0 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"style": {
"useTemplate": "off",
"useImportType": "warn"
},
"suspicious": {
"noExplicitAny": "off",
"noDoubleEquals": "warn"
},
"complexity": {
"noForEach": "off"
}
}
}
}

@ -47,6 +47,7 @@
"input-otp": "^1.2.4",
"json-beautify": "^1.1.1",
"lucide-react": "^0.454.0",
"mini-svg-data-uri": "^1.4.4",
"minimessage-2-html": "1.6.0",
"minimessage-js": "^1.1.3",
"mongodb": "^6.8.0",

@ -0,0 +1,17 @@
import HomePageComponent from "@/components/feat/home-page/home-page";
import type { Metadata } from "next";
export const metadata: Metadata = {
applicationName: "MHSF",
title: "The modern server list. · MHSF",
description:
"The open-source, modern and friendly server list wrapper for Minehut.",
};
export default function HomePage() {
return (
<div className="overflow-x-hidden">
<HomePageComponent />
</div>
);
}

@ -41,6 +41,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
import { ThemeProvider } from "@/components/util/theme-provider";
import { FontBoundary } from "@/components/util/font-boundary";
import { ClerkProvider } from "@/components/util/clerk-provider";
import { Toaster } from "sonner";
export default function RootLayout({
children,
@ -65,23 +66,26 @@ export default function RootLayout({
</Placeholder>
</main>
</noscript>
<IsScript>
<FontBoundary>
<TooltipProvider>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<ClerkProvider>
<IsScript>
<FontBoundary>
<TooltipProvider>
<Toaster richColors position="top-center" />
<ClerkProvider>
<NavBar />
<div className="pt-16">{children}</div>
</ClerkProvider>
</ThemeProvider>
</TooltipProvider>
</FontBoundary>
</IsScript>
</ClerkProvider>
</ThemeProvider>
</html>
);
}

@ -1,12 +1,14 @@
@import "tailwindcss";
@plugin 'tailwindcss-animate';
@config '../../tailwind-hero.config.ts';
@custom-variant dark (&:is(.dark *));
@theme {
--animate-spin: spin 1s linear infinite;
--animate-scale-in: scaleIn 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
--animate-fade-in: fade-in 1000ms var(--animation-delay, 0ms) ease forwards;
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
@ -142,6 +144,7 @@
@layer base {
:root {
--background: 0 0% 100%;
--border: 214.3 31.8% 91.4%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
@ -174,6 +177,13 @@
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
*,
::before,
::after {
/* Workaround for Tailwind being stupid */
border-color: hsl(214.3 31.8% 91.4%);
}
}
.dark {
--border: 216 34% 17%;
@ -287,6 +297,16 @@
clip-path: inset(0 0 0 0);
}
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: none;
}
}
.dark::view-transition-new(root),
.light::view-transition-new(root) {

@ -31,8 +31,6 @@
"use client";
import "./globals.css";
import { useSearchParams } from "next/navigation";
import { ThemeProvider } from "@/components/util/theme-provider";
import { ClerkProvider } from "@/components/util/clerk-provider";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
@ -49,14 +47,7 @@ export default function RootLayout({
<html lang="en">
<body className={inter.className}>
<noscript>{children}</noscript>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<ClerkProvider>{children}</ClerkProvider>
</ThemeProvider>
{children}
</body>
</html>
);

@ -0,0 +1,109 @@
"use client";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { useClerk, useUser } from "@clerk/nextjs";
import { ArrowDown, GalleryVertical } from "lucide-react";
import { useTheme } from "next-themes";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { Gradient } from "stripe-gradient";
export default function HomePageComponent() {
const clerk = useClerk();
const router = useRouter();
const { isSignedIn } = useUser();
const { resolvedTheme } = useTheme();
const [gradientId, setGradientId] = useState("gradient-banner");
useEffect(() => {
setGradientId("gradient-banner");
const gradient = new Gradient();
gradient.initGradient("#" + gradientId);
}, [gradientId]);
return (
<div className="pt-20">
<canvas
id={gradientId}
// Slightly outside of container to give a REALLY nice glow effect
className="w-screen h-[610px] absolute blur-sm border-b z-1 opacity-0 animate-fade-in [--animation-delay:800ms]"
style={
{
"--gradient-color-1":
resolvedTheme === "dark" ? "#043D5D" : "#1F9EA3",
"--gradient-color-2":
resolvedTheme === "dark" ? "#032E46" : "#F8BD97",
"--gradient-color-3":
resolvedTheme === "dark" ? "#23B684" : "#9E5428",
"--gradient-color-4":
resolvedTheme === "dark" ? "#0F595E" : "#EEEEEE",
webKitMaskImage:
"linear-gradient(to top, black, black, transparent)",
maskImage: "linear-gradient(to top, black, black, transparent)",
} as React.CSSProperties
}
height="64"
width={window.screen.width}
/>
<div className="rounded-lg p-[72px] dark:bg-grid-white/[0.2] bg-grid-black/[0.2] w-full mx-auto relative z-9 min-h-[600px]">
<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-[rgb(10,10,10)] bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] " />
<h1 className="bg-clip-text animate-fade-in -translate-y-4 bg-gradient-to-br from-black from-30% to-black/40 pb-6 text-5xl font-semibold tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-5xl md:text-6xl lg:text-7xl dark:from-white dark:to-white/40">
Meet MHSF, <br />
the modern server list
</h1>
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-neutral-400 opacity-0 [--animation-delay:400ms] md:text-xl ">
MHSF is the next generation Minehut server list wrapper, with <br />
interactive filters, customizable web-pages, a modern interface and{" "}
<br />
everything in-between.
</p>
<span className="flex items-center gap-2 -translate-y-4">
<Button
onClick={() => router.push("/servers")}
className="animate-fade-in opacity-0 [--animation-delay:600ms] flex items-center gap-2"
>
<GalleryVertical size={16} />
Find a server
</Button>
<Button
variant="secondary"
onClick={() => clerk.openSignUp()}
disabled={isSignedIn}
>
Sign up
</Button>
</span>
</div>
<div className="mx-auto justify-center py-15 py-auto w-full text-center text-sm border border-dashed group h-[150px]">
<span>See more</span>
<span className="flex justify-center group-hover:translate-y-6 transition-all">
<ArrowDown size={16} />
</span>
</div>
<br />
<br />
<div className="flex items-center justify-center w-full">
<span className="animate-fade-in -translate-y-4 bg-green-400/60 px-4 py-2 rounded">
For players
</span>
</div>
<span className="mt-15 flex justify-between px-6 items-center">
<span>
<h1 className="animate-fade-in -translate-y-4 text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 text-2xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-2xl md:text-3xl lg:text-4xl dark:from-white dark:to-white/40">
Find what you want now. <br />
Not later.
</h1>
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-md tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
MHSF is built for finding servers, and only that, along with <br />
allowing for maximum customizability with <br />
both your experience and the webpages you interact with. <br />
</p>
</span>
<Skeleton className="h-[300px] w-[500px] rounded-xl" />
</span>
</div>
);
}

@ -32,6 +32,22 @@
import { useTheme } from "next-themes";
import type { SVGProps } from "react";
export const brandingIconClipboard = `<svg width="266" height="265" viewBox="0 0 266 265" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.524048" width="264.939" height="264.939" rx="66" fill="url(#paint0_linear_1_19)"/>
<path d="M104.513 123.27H94.8717C92.3148 123.27 89.8626 122.254 88.0546 120.446C86.2466 118.638 85.2309 116.186 85.2309 113.629V94.3476C85.2309 91.7907 86.2466 89.3385 88.0546 87.5305C89.8626 85.7225 92.3148 84.7068 94.8717 84.7068H171.998C174.555 84.7068 177.007 85.7225 178.815 87.5305C180.623 89.3385 181.639 91.7907 181.639 94.3476V113.629C181.639 116.186 180.623 118.638 178.815 120.446C177.007 122.254 174.555 123.27 171.998 123.27H162.357M104.513 142.552H94.8717C92.3148 142.552 89.8626 143.567 88.0546 145.376C86.2466 147.184 85.2309 149.636 85.2309 152.193V171.474C85.2309 174.031 86.2466 176.483 88.0546 178.291C89.8626 180.099 92.3148 181.115 94.8717 181.115H171.998C174.555 181.115 177.007 180.099 178.815 178.291C180.623 176.483 181.639 174.031 181.639 171.474V152.193C181.639 149.636 180.623 147.184 178.815 145.376C177.007 143.567 174.555 142.552 171.998 142.552H162.357M104.513 103.988H104.561M104.513 161.833H104.561M138.255 103.988L118.974 132.911H147.896L128.615 161.833" stroke="white" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="132.993" cy="132.469" r="91.3779" stroke="url(#paint1_linear_1_19)" stroke-width="8"/>
<defs>
<linearGradient id="paint0_linear_1_19" x1="107.824" y1="54.754" x2="230.579" y2="225.198" gradientUnits="userSpaceOnUse">
<stop stop-color="#007BFF"/>
<stop offset="1" stop-color="#BF00FF" stop-opacity="0.5"/>
</linearGradient>
<linearGradient id="paint1_linear_1_19" x1="132.993" y1="37.0914" x2="132.993" y2="227.847" gradientUnits="userSpaceOnUse">
<stop stop-color="#EFEC32"/>
<stop offset="1" stop-color="#98FF60"/>
</linearGradient>
</defs>
</svg>`;
/**
* Returns a colorful version of the branding icon.
*

@ -1,5 +1,8 @@
"use client";
import { BrandingGenericIcon } from "@/components/feat/icons/branding-icons";
import {
BrandingGenericIcon,
brandingIconClipboard,
} from "@/components/feat/icons/branding-icons";
import { Button } from "@/components/ui/button";
import {
ContextMenu,
@ -11,6 +14,7 @@ import {
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import Github from "@/components/ui/github";
@ -18,18 +22,31 @@ import { Link } from "@/components/util/link";
import { version } from "@/config/version";
import { useScroll } from "@/lib/hooks/use-scroll";
import { cn } from "@/lib/utils";
import { Menu, ServerCrash } from "lucide-react";
import { Home, Image, Menu, ServerCrash } from "lucide-react";
import { MenuDropdown } from "./menu-dropdown";
import useClipboard from "@/lib/useClipboard";
import { toast } from "sonner";
import { SignedIn, SignedOut, useUser } from "@clerk/nextjs";
import NextImage from "next/image";
import { usePathname } from "next/navigation";
const animatedTopbarPages = ["/home"];
export function NavBar() {
const showBorder = useScroll(40);
const clipboard = useClipboard();
const pathname = usePathname();
const { user } = useUser();
return (
<div
className={cn(
"w-screen h-[3rem] grid-cols-3 fixed backdrop-blur-xl z-10 flex",
"items-center justify-self-start me-auto pl-4 flex-1 transition-all justify-between",
showBorder ? "border-b" : ""
showBorder ? "border-b" : "",
pathname !== null && animatedTopbarPages.includes(pathname)
? "[--animation-delay:1000ms] opacity-0 animate-fade-in"
: ""
)}
>
<span>
@ -46,13 +63,34 @@ export function NavBar() {
</Link>
</ContextMenuTrigger>
<ContextMenuContent className="overflow-hidden min-w-[300px]">
<DropdownMenuSeparator>Platform</DropdownMenuSeparator>
<Link href="Special:Root">
<ContextMenuItem>
<span className="pl-2 flex gap-2 items-center">
<ServerCrash size={16} /> Go home
<ServerCrash size={16} /> Go to Dynamic Home Page
</span>
</ContextMenuItem>
</Link>
<Link href="/home">
<ContextMenuItem>
<span className="pl-2 flex gap-2 items-center">
<Home size={16} /> Go to Home Page
</span>
</ContextMenuItem>
</Link>
<ContextMenuSeparator />
<ContextMenuItem
onClick={() => {
clipboard.writeText(brandingIconClipboard);
toast.success("Copied icon to clipboard!");
}}
>
<span className="pl-2 flex gap-2 items-center">
<Image size={16} /> Copy Logo as SVG
</span>
</ContextMenuItem>
<ContextMenuSeparator />
<Link href="Special:GitHub">
<ContextMenuItem>
@ -74,18 +112,62 @@ export function NavBar() {
<span className="mr-3 flex items-center">
<DropdownMenu>
<DropdownMenuTrigger>
<SignedOut>
<Button
className="rounded-full flex items-center"
className={cn(
"rounded-full flex items-center",
pathname !== null && animatedTopbarPages.includes(pathname)
? "[--animation-delay:2000ms] opacity-0 animate-fade-in"
: ""
)}
size="square-lg"
variant="secondary"
>
<Menu size={16} />
</Button>
</SignedOut>
<SignedIn>
<Button
size="square-lg"
variant="tertiary"
className={cn(
"rounded-full flex items-center",
pathname !== null && animatedTopbarPages.includes(pathname)
? "[--animation-delay:2000ms] opacity-0 animate-fade-in"
: ""
)}
>
<NextImage
alt="Clerk Image"
src={
user?.imageUrl === undefined
? "https://img.clerk.com/preview.png?size=144&seed=seed&initials=AD&isSquare=true&bgType=marble&bgColor=6c47ff&fgType=silhouette&fgColor=FFFFFF&type=user&w=48&q=75"
: user?.imageUrl
}
width={26}
height={26}
className="rounded-full"
/>
</Button>
</SignedIn>
</DropdownMenuTrigger>
<DropdownMenuContent className="max-w-[280px] w-[280px] mt-2 mr-2">
<MenuDropdown />
</DropdownMenuContent>
</DropdownMenu>
<SignedIn>
<div
className="absolute right-0 -z-10 h-full
overflow-hidden w-full ml-auto"
style={{ borderRadius: "inherit" }}
>
<img
src={user?.imageUrl ?? ""}
className="blur-2xl -z-10 object-cover w-48 h-48 opacity-20 dark:opacity-50 ml-auto"
alt=""
/>
</div>
</SignedIn>
</span>
</div>
);

@ -6,17 +6,40 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { toast } from "sonner";
export default function ServerCard({ server }: { server: OnlineServer }) {
export default function ServerCard({
server,
motd,
}: {
server: OnlineServer;
motd: string | undefined;
}) {
return (
<Material key={server.name}>
<Material
key={server.name}
className="min-h-[250px] max-h-[250px] cursor-pointer outline-0 group hover:drop-shadow-card-hover focus:drop-shadow-card-hover transition-all"
onClick={() => toast.success("pluh")}
tabIndex={0}
onKeyDown={(e) => {
// Only send user when they hit "Enter"
if (e.key === "Enter") toast.success("keyboard");
}}
>
<span className="text-sm hidden group-focus-visible:block text-muted-foreground mb-2">
Hit{" "}
<kbd className="ml-0.5 hidden rounded px-2 py-0.5 text-xs font-light transition-all duration-75 md:inline-block dark:bg-gray-700 dark:text-gray-400 bg-gray-300 text-gray-600">
Enter
</kbd>{" "}
to go to {server.name}
</span>
<span className="flex gap-2 items-center">
<IconDisplay server={server} />
<strong className="text-lg">{server.name}</strong>
</span>
<Tooltip>
<TooltipTrigger>
<span className="text-muted-foreground">
<span className="text-muted-foreground cursor-pointer">
by {server.author || "Nobody"}
</span>
</TooltipTrigger>
@ -35,6 +58,12 @@ export default function ServerCard({ server }: { server: OnlineServer }) {
)}
</TooltipContent>
</Tooltip>
{motd && (
<span
className="block break-all overflow-hidden mt-3"
dangerouslySetInnerHTML={{ __html: motd }}
/>
)}
</Material>
);
}

@ -6,11 +6,33 @@ import { Separator } from "@/components/ui/separator";
import { Statistics } from "./statistics";
import InfiniteScroll from "react-infinite-scroll-component";
import { useInfiniteScrolling } from "@/lib/hooks/use-infinite-scrolling";
import { useEffect, useState } from "react";
import MiniMessage from "minimessage-js";
export function ServerList() {
const { servers, loading, serverCount, playerCount } = useServers();
const { itemsLength, fetchMoreData, hasMoreData, data } =
useInfiniteScrolling(servers);
const [motdList, setMotdList] = useState<{ name: string; motd: string }[]>(
[]
);
useEffect(() => {
const result: Array<{ name: string; motd: string }> = [];
const mm = MiniMessage.miniMessage();
servers.forEach((c) => {
try {
result.push({
name: c.name,
motd: mm.toHTML(mm.deserialize(c.motd)),
});
} catch (e) {
console.log(e);
}
});
setMotdList(result);
}, [servers]);
if (loading)
return (
@ -45,7 +67,11 @@ export function ServerList() {
>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mt-3">
{data.map((c) => (
<ServerCard server={c} key={c.name} />
<ServerCard
server={c}
key={c.name}
motd={motdList.find((x) => x.name === c.name)?.motd}
/>
))}
</div>
</InfiniteScroll>

@ -49,7 +49,7 @@ export function Statistics({
}, []);
return (
<div className="grid grid-cols-3 gap-2">
<div className="md:grid grid-cols-3 gap-2">
<Material className="gap-3">
<strong className="justify-between flex items-center">
<span className="flex items-center gap-1">
@ -96,7 +96,7 @@ export function Statistics({
{averagesLoading && <FormSpinner />}
</span>
</Material>
<Material className="gap-3">
<Material className="gap-3 max-md:mt-2">
<strong className="justify-between flex items-center">
<span className="flex items-center gap-1">
Total Servers
@ -150,7 +150,7 @@ export function Statistics({
{averagesLoading && <FormSpinner />}
</span>
</Material>
<Material className="gap-3">
<Material className="gap-3 max-md:mt-2">
<strong className="justify-between flex items-center">
Top Server <ChartArea size={16} className="text-muted-foreground" />
</strong>{" "}

@ -1,4 +1,4 @@
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
function Skeleton({
className,
@ -6,10 +6,10 @@ function Skeleton({
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
className={cn("animate-pulse rounded-md bg-shadcn-primary/10", className)}
{...props}
/>
)
);
}
export { Skeleton }
export { Skeleton };

@ -1,15 +1,16 @@
"use client";
import { useSettingsStore } from "@/lib/hooks/use-settings-store";
import { Inter, Roboto } from "next/font/google";
import { useEffect, useState, type ReactNode } from "react";
import { GeistSans } from "geist/font/sans";
import { usePathname } from "next/navigation";
const inter = Inter({ subsets: ["latin"] });
const roboto = Roboto({
subsets: ["latin"],
weight: ["100", "300", "400", "500", "700", "900"],
});
const overflowXHiddenPages = ["/home"];
export function FontBoundary({
children,
@ -18,6 +19,7 @@ export function FontBoundary({
}) {
const settingsStore = useSettingsStore();
const [fontFamily, setFontFamily] = useState("inter");
const pathname = usePathname();
useEffect(() => {
setFontFamily((settingsStore.get("font-family") ?? "inter") as string);
@ -39,7 +41,7 @@ export function FontBoundary({
default:
return "system-ui-font--font-boundary";
}
})()}`}
})()} ${pathname !== null && overflowXHiddenPages.includes(pathname) ? "overflow-x-hidden" : ""}`}
>
{children}
</body>

@ -1,28 +0,0 @@
import { useEffect, useState } from "react";
import type { OnlineServer } from "../types/mh-server";
import MiniMessage from "minimessage-js";
export function useMOTD(servers: OnlineServer[]) {
const [motdList, setMotdList] = useState<{ name: string; motd: string }[]>(
[]
);
useEffect(() => {
setMotdList(
servers.map((server) => {
return {
name: server.name,
motd: MiniMessage.miniMessage().toHTML(
MiniMessage.miniMessage().deserialize(server.motd)
),
};
})
);
}, [servers]);
return {
motdList,
getMotdForServer: (server: OnlineServer) =>
motdList.find((c) => c.name === server.name)?.motd,
};
}

@ -29,14 +29,21 @@
*/
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
import { NextRequest } from "next/server";
import { NextRequest, NextResponse } from "next/server";
const isProtectedRoute = createRouteMatcher(["/account(.*)"]);
const isEmbed = createRouteMatcher(["/emebed(.*)"]);
// Thanks for the router matcher API Clerk <3
const isRootRoute = createRouteMatcher(["/"]);
export default process.env.NEXT_PUBLIC_IS_AUTH === "true"
? clerkMiddleware((auth, req) => {
if (isProtectedRoute(req)) auth.protect();
? clerkMiddleware(async (auth, req) => {
if (isRootRoute(req)) {
switch ((await auth()).userId === null) {
case false:
return NextResponse.redirect(new URL("/servers", req.url));
case true:
return NextResponse.redirect(new URL("/home", req.url));
}
}
})
: (request: NextRequest) => {};

@ -0,0 +1,54 @@
import type { Config } from "tailwindcss";
import svgToDataUri from "mini-svg-data-uri";
import flattenColorPalette from "tailwindcss/lib/util/flattenColorPalette";
export default {
theme: {
extend: {
dropShadow: {
"card-hover": ["0 8px 12px #222A350d", "0 32px 80px #2f30370f"],
},
},
},
plugins: [
addVariablesForColors,
({ matchUtilities, theme }: any) => {
matchUtilities(
{
"bg-grid": (value: any) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="72" height="72" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`,
)}")`,
}),
"bg-grid-small": (value: any) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="8" height="8" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`,
)}")`,
}),
"bg-dot": (value: any) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" fill="none"><circle fill="${value}" id="pattern-circle" cx="10" cy="10" r="1.6257413380501518"></circle></svg>`,
)}")`,
}),
},
{
values: flattenColorPalette(theme("backgroundColor")),
type: "color",
},
);
},
],
} satisfies Config;
function addVariablesForColors({ addBase, theme }: any) {
const allColors = flattenColorPalette(theme("colors"));
const newVars = Object.fromEntries(
Object.entries(allColors).map(([key, val]) => [`--${key}`, val]),
);
addBase({
":root": newVars,
});
}

18
biome.json Normal file

@ -0,0 +1,18 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"style": {
"useTemplate": "off",
"useImportType": "warn"
},
"suspicious": {
"noExplicitAny": "off",
"noDoubleEquals": "warn"
},
"complexity": {
"noForEach": "off"
}
}
}
}

@ -7572,6 +7572,11 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mini-svg-data-uri@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939"
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"