diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5bae406 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# Pterodactyl Game Panel +GAMEPANEL_URL="https://panel.example.com" +GAMEPANEL_API="https://panel.example.com/api" +GAMEPANEL_API_KEY="ptla_your_application_api_key" + +# NextAuth - generate with: openssl rand -base64 32 +AUTH_SECRET="your_auth_secret_here" + +# Database +DATABASE_URL="postgresql://user:password@127.0.0.1:65420/nodebyte-site" + +# Crowdin (for translations) +CROWDIN_PROJECT_ID="" +CROWDIN_PERSONAL_TOKEN="" + +# GitHub (for changelog releases) +GITHUB_TOKEN="" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..64c82f5 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [NodeByteHosting, CodeMeAPixel] +custom: [https://nodebyte.host] diff --git a/.github/workflows/sync-development-to-nightly.yml b/.github/workflows/sync-development-to-nightly.yml index 4804907..3a0ab57 100644 --- a/.github/workflows/sync-development-to-nightly.yml +++ b/.github/workflows/sync-development-to-nightly.yml @@ -4,6 +4,10 @@ on: push: branches: - development + schedule: + # Run daily at midnight UTC + - cron: '0 0 * * *' + workflow_dispatch: permissions: contents: write diff --git a/.gitignore b/.gitignore index ea240d3..62e28d0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,10 @@ yarn-error.log* .pnpm-debug.log* # env files -.env* +.env +.env.local +.env.development +.env.dev # vercel .vercel @@ -26,3 +29,5 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts .vercel + +/app/generated/prisma diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a6a47c0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "translations"] + path = translations + url = https://github.com/NodeByteHosting/translations diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..62aa592 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install commitlint --edit "$1" \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..2cebb44 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,20 @@ +{ + "semi": false, + "trailingComma": "es5", + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "plugins": ["@trivago/prettier-plugin-sort-imports"], + "importOrder": [ + "^(react/(.*)$)|^(react$)", + "^(next/(.*)$)|^(next$)", + "", + "^@/components/(.*)$", + "^@/lib/(.*)$", + "^@/hooks/(.*)$", + "^@/styles/(.*)$", + "^[./]" + ], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8cbd695 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,135 @@ +# Changelog + +All notable changes to the NodeByte Hosting website will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.1.0] - 2025-12-22 + +### Added + +- **Authentication System**: NextAuth v5 integration with credentials provider +- **User Registration**: Registration form with email, username, password fields +- **User Login**: Login form with email/password authentication +- **User Menu**: Dropdown menu showing user info, panel link, admin link, and logout +- **Session Management**: Server-side and client-side session handling +- **Admin Dashboard**: Complete admin panel with Pterodactyl sync statistics +- **Admin Sync Page**: Detailed sync management with live terminal-style logging +- **Admin Users Page**: User management with search, filters, sorting, and pagination +- **Admin Servers Page**: Server listing with status indicators, resource display, and owner info +- **Admin Settings Page**: System configuration with connection testing (WIP alert for other features) +- **Admin Route Protection**: Middleware-based admin authentication checking database directly +- **Admin Panel Responsive**: Mobile-first design with slide-out drawer navigation +- **Admin User Controls**: Language selector, theme toggle, user menu, and back-to-site button in admin panel +- **Admin API Endpoints**: REST endpoints for users, servers, and settings management +- **Pterodactyl Sync Service**: Background sync of users, servers, nodes, locations, allocations, nests, eggs +- **Database Schema**: Prisma models for Users, Servers, Nodes, Locations, Allocations, Nests, Eggs, and more +- **Sync API Endpoints**: GET for stats, POST for triggering syncs with target selection +- **Layout Chrome Component**: Client-side component for conditional nav/footer visibility +- **Language Selector Improvements**: Search functionality, region grouping, ScrollArea for 30 languages +- **Currency Selector Improvements**: Country flag icons using country-flag-icons library +- **Knowledge Base System**: Complete documentation system with markdown support +- **KB Markdown Processing**: Uses remark/rehype pipeline with GFM, syntax highlighting, auto-linking headings +- **KB Categories**: Organized articles by category with metadata and icons +- **KB Article Cards**: Article previews with reading time, tags, and author info +- **KB Search**: Full-text search across all KB articles with relevance scoring +- **KB Table of Contents**: Sticky sidebar TOC with scroll spy highlighting +- **KB Sidebar Navigation**: Collapsible category navigation with article links +- **KB Breadcrumbs**: Navigation breadcrumbs for category and article pages +- **KB Previous/Next Navigation**: Article-to-article navigation within categories +- **KB Navigation Link**: Added Knowledge Base link to desktop and mobile navigation +- **Tailwind Typography**: Added `@tailwindcss/typography` plugin for prose styling +- **Getting Started Articles**: Introduction, Quick Start Guide, Game Panel Tutorial +- **Minecraft Guides**: Server Software selection guide, Installing Plugins guide +- **Rust Guides**: Getting Started with Rust, Oxide/uMod Installation guide +- **Billing Guides**: WHMCS Client Portal overview and account management +- **Syntax Highlighting**: Code block styling for documentation with hljs classes +- **Theme Toggle**: Categorized theme selector with Base, Cool Tones, Warm Tones, and Nature categories +- **Theme Swatches**: Color preview swatches for each theme option +- **Navigation Dropdowns**: Services and Resources dropdown menus with hover-to-open functionality +- **Animated Chevrons**: Chevron icons rotate based on dropdown open/closed state +- **Hero Stats**: Animated statistics cards displaying uptime, latency, and support availability +- **SLA Counter**: Animated number counter displaying 99.6% SLA guarantee +- **Features Highlights**: Feature cards now include detailed highlight lists with checkmark icons +- **About Stats Grid**: Statistics section showing SLA, latency, support hours, and server count +- **FAQ Search**: Search input for filtering FAQ questions +- **FAQ CTA Section**: "Still have questions?" section with contact and Discord buttons +- **Games Section Icons**: Dedicated icons for each game server type (Minecraft, Rust, Hytale, Coming Soon) +- **Games Feature Lists**: Each game card now displays key features with checkmark icons +- **Games Banner Images**: Minecraft and Rust cards now use banner images from public directory +- **Hytale Hosting**: Added Hytale game server hosting card to the games section +- **Footer Status Indicator**: Live status indicator linking to status page +- **Footer Link Categories**: Organized footer links with category icons +- **Contact Page Redesign**: Complete redesign with support channels, email grid, and social links +- **Contact Social Links**: Follow us section with X, GitHub, Discord, and Trustpilot links +- **Contact Discord Notice**: Important notice about Discord support limitations moved to prominent position +- **Dynamic Logo Component**: SVG logo that respects theme accent colors and light/dark mode +- **Currency System**: Multi-currency support with GBP as base currency (USD, EUR, CAD, AUD) +- **Currency Selector**: Global currency dropdown in navigation (desktop and mobile) +- **Price Component**: Auto-converting price display component using selected currency +- **Internationalization**: Added `next-intl` for multi-language support +- **Language Selector**: Global language dropdown with flag icons in navigation +- **Translation Files**: English, German, French, and Spanish translations +- **External Translations**: Support for loading translations from external GitHub repo +- **Crowdin Integration**: Configuration for community translation contributions +- **Dedicated Game Pages**: Individual pages for Minecraft, Rust, and Hytale hosting +- **Game Hero Component**: Reusable hero section for game pages with banners and features +- **Game Features Component**: Reusable features grid with icon mapping for server components +- **Game Pricing Component**: Reusable pricing cards with currency conversion +- **Game FAQ Component**: Reusable FAQ accordion for game-specific questions + +### Changed +- **Packages**: Updated next.js to version `16.0.10` to address the latest CVE +- **Project Structure**: Reorganized codebase into `packages/` directory for better modularity + - `packages/core/` - Shared hooks (`use-currency`, `use-locale`, `use-mobile`, `use-toast`) and utilities (`currency`, `translations`, `utils`) + - `packages/i18n/` - Internationalization configuration and request handling + - `packages/ui/` - All UI components, layouts, and shadcn/ui primitives +- **Navigation**: Replaced NavigationMenu with DropdownMenu components for improved reliability +- **Navigation Mobile**: Redesigned with accordion-style dropdowns (only one open at a time) +- **Navigation Settings**: Added Language, Currency, and Theme selectors to mobile menu +- **Layout Structure**: Moved Navigation and Footer to root layout for site-wide consistency +- **Layout Providers**: Added `CurrencyProvider`, `LocaleProvider`, and `NextIntlClientProvider` +- **Layout Chrome**: Nav/footer visibility now handled by client component using `usePathname()` for proper client-side navigation support +- **Translation Loading**: Changed from dynamic imports to static imports for all 30 locale files (bundler compatibility) +- **Admin Panel Layout**: Redesigned with responsive sidebar that becomes a drawer on mobile +- **Admin Dashboard**: Responsive stat cards with 2-column grid on mobile +- **Theme Toggle Layout**: Themes now displayed in organized grid layouts by category +- **Hero Section**: Complete redesign with gradient text, glassmorphism cards, and Trustpilot integration +- **Features Section**: Redesigned with badge header, gradient text, and enhanced card styling +- **About Section**: Redesigned with stats grid, value proposition cards, and gradient headers +- **FAQ Section**: Redesigned with animated accordions, search functionality, and cleaner single-column layout +- **Games Section**: Redesigned with banner images, feature lists, and consistent card heights +- **Contact Page**: Redesigned with gradient background, animated orbs, and organized support channels +- **Footer**: Complete redesign with two-section layout, categorized links, and dynamic Logo component +- **Footer Logo**: Now uses dynamic Logo component that respects theme colors +- **Card Styling**: Unified glassmorphism effect (`bg-card/30 backdrop-blur-sm border-border/50`) across all components +- **Button Styling**: Consistent rounded button styles with proper hover states +- **Section Spacing**: Standardized padding (`py-24 sm:py-32`) across all homepage sections +- **Pricing Display**: All prices now stored in GBP and auto-convert based on user's currency preference + +### Fixed + +- **Hydration Error**: Resolved React hydration mismatch in `layout.tsx` caused by `colorScheme` style attribute +- **Navigation Dropdowns**: Fixed dropdown content not loading and alignment issues +- **Theme Persistence**: Themes now properly persist across page reloads +- **Mobile Menu Bleed-through**: Fixed Discord button bleeding through mobile navigation overlay +- **Server Component Serialization**: Fixed icon props passing between Server and Client components using string-based icon mapping +- **i18n Client/Server Split**: Separated shared locale config from server-only request handling to prevent build errors +- **Translation System**: Fixed translations not switching languages due to wrong import path and dynamic import issues +- **Admin Nav/Footer Visibility**: Fixed nav/footer sometimes showing on admin pages or staying hidden after leaving admin + +### Removed + +- **HeroGraphic Sidebar**: Removed from FAQ section for cleaner layout +- **colorScheme Logic**: Removed from layout.tsx to prevent hydration errors +- **Inline Pricing Currency**: Removed currency selector from individual pricing sections (now global in nav) + +## [3.0.0] - Initial Rewrite + +Initial Next.js 15 website with App Router, shadcn/ui components, and multi-theme support. + +--- + +[3.1.0]: https://github.com/NodeByteHosting/website/compare/v3.0.0...v3.1.0 +[3.0.0]: https://github.com/NodeByteHosting/website/releases/tag/v3.0.0 diff --git a/app/about/page.tsx b/app/about/page.tsx new file mode 100644 index 0000000..524a7f9 --- /dev/null +++ b/app/about/page.tsx @@ -0,0 +1,11 @@ +import { AboutPage } from "@/packages/ui/components/Layouts/About" +import type { Metadata } from "next" + +export const metadata: Metadata = { + title: "About Us", + description: "Learn about NodeByte Hosting - Built by gamers, for gamers. Our mission is to provide fast, reliable, and affordable game server hosting.", +} + +export default function About() { + return +} diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx new file mode 100644 index 0000000..38497dd --- /dev/null +++ b/app/admin/layout.tsx @@ -0,0 +1,317 @@ +"use client" + +import { useSession } from "next-auth/react" +import { useRouter, usePathname } from "next/navigation" +import { useEffect, useState } from "react" +import { useTranslations } from "next-intl" +import Link from "next/link" +import { + LayoutDashboard, + Users, + Server, + RefreshCw, + Settings, + ChevronLeft, + ChevronRight, + Shield, + Loader2, + Menu, + Home, + LogOut, +} from "lucide-react" +import { cn } from "@/packages/core/lib/utils" +import { Button } from "@/packages/ui/components/ui/button" +import { ScrollArea } from "@/packages/ui/components/ui/scroll-area" +import { Separator } from "@/packages/ui/components/ui/separator" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/packages/ui/components/ui/tooltip" +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/packages/ui/components/ui/sheet" +import { LanguageSelector } from "@/packages/ui/components/ui/language-selector" +import { UserMenu } from "@/packages/auth/components/user-menu" +import { ThemeToggle } from "@/packages/ui/components/theme-toggle" + +interface NavItem { + title: string + href: string + icon: React.ComponentType<{ className?: string }> +} + +export default function AdminLayout({ + children, +}: { + children: React.ReactNode +}) { + const { data: session, status } = useSession() + const router = useRouter() + const pathname = usePathname() + const t = useTranslations("admin") + const tAuth = useTranslations("auth") + const [collapsed, setCollapsed] = useState(false) + const [mobileOpen, setMobileOpen] = useState(false) + + // Close mobile menu on route change + useEffect(() => { + setMobileOpen(false) + }, [pathname]) + + if (status === "loading") { + return ( +
+ +
+ ) + } + + if (!session?.user?.isAdmin) { + return null + } + + const navItems: NavItem[] = [ + { title: t("nav.dashboard"), href: "/admin", icon: LayoutDashboard }, + { title: t("nav.users"), href: "/admin/users", icon: Users }, + { title: t("nav.servers"), href: "/admin/servers", icon: Server }, + { title: t("nav.sync"), href: "/admin/sync", icon: RefreshCw }, + { title: t("nav.settings"), href: "/admin/settings", icon: Settings }, + ] + + const isActive = (href: string) => { + if (href === "/admin") return pathname === "/admin" + return pathname.startsWith(href) + } + + const NavContent = ({ mobile = false }: { mobile?: boolean }) => ( + + ) + + return ( +
+ {/* Mobile Header */} +
+
+ + + + + + + + + {t("title")} + + + + + + {/* Back to Site */} + + setMobileOpen(false)}> + + + +
+
+
+
+ {t("status.online")} +
+
+ + +
+
+
+ + + + {t("title")} +
+
+ +
+
+ + {/* Desktop Sidebar */} + + + {/* Main Content */} +
+
+ {children} +
+
+
+ ) +} diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..796f28e --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,371 @@ +"use client" + +import { useEffect, useState } from "react" +import { useTranslations } from "next-intl" +import { + Users, + Server, + HardDrive, + Database, + Layers, + Egg, + Variable, + MapPin, + Network, + RefreshCw, + AlertCircle, + CheckCircle2, + Loader2, +} from "lucide-react" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { Badge } from "@/packages/ui/components/ui/badge" +import { Skeleton } from "@/packages/ui/components/ui/skeleton" +import { Progress } from "@/packages/ui/components/ui/progress" +import { useToast } from "@/packages/ui/components/ui/use-toast" + +interface SyncStats { + success: boolean + status: { + lastSync: string | null + isSyncing: boolean + } | null + counts: { + users: number + migratedUsers: number + servers: number + nodes: number + locations: number + allocations: number + nests: number + eggs: number + eggVariables: number + serverDatabases: number + } + availableTargets: string[] +} + +interface StatCardProps { + title: string + value: number + icon: React.ComponentType<{ className?: string }> + description?: string + loading?: boolean +} + +function StatCard({ title, value, icon: Icon, description, loading }: StatCardProps) { + if (loading) { + return ( + + + + + + + + {description && } + + + ) + } + + return ( + + + {title} + + + +
{value.toLocaleString()}
+ {description && ( +

{description}

+ )} +
+
+ ) +} + +export default function AdminDashboard() { + const t = useTranslations("admin") + const { toast } = useToast() + const [stats, setStats] = useState(null) + const [loading, setLoading] = useState(true) + const [syncing, setSyncing] = useState(false) + const [syncProgress, setSyncProgress] = useState(0) + const [currentSyncTarget, setCurrentSyncTarget] = useState(null) + const [error, setError] = useState(null) + + const fetchStats = async () => { + try { + setError(null) + const response = await fetch("/api/admin/sync") + const data = await response.json() + + if (!data.success) { + setError(data.error || "Failed to fetch stats") + return + } + + setStats(data) + } catch (err) { + setError("Failed to connect to server") + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchStats() + }, []) + + const runSync = async (target: string = "all") => { + setSyncing(true) + setSyncProgress(0) + setCurrentSyncTarget(target) + + // Simulate progress stages for better UX + const progressInterval = setInterval(() => { + setSyncProgress((prev) => { + if (prev >= 90) return prev + return prev + Math.random() * 15 + }) + }, 500) + + try { + const response = await fetch("/api/admin/sync", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ target }), + }) + + const data = await response.json() + clearInterval(progressInterval) + setSyncProgress(100) + + if (!data.success) { + toast({ + title: t("sync.error"), + description: data.error, + variant: "destructive", + }) + return + } + + toast({ + title: t("sync.success"), + description: t("sync.completed", { target }), + }) + + // Refresh stats after sync + await fetchStats() + } catch (err) { + clearInterval(progressInterval) + toast({ + title: t("sync.error"), + description: "Failed to connect to server", + variant: "destructive", + }) + } finally { + setTimeout(() => { + setSyncing(false) + setSyncProgress(0) + setCurrentSyncTarget(null) + }, 1000) + } + } + + const formatLastSync = (dateString: string | null) => { + if (!dateString) return t("sync.never") + const date = new Date(dateString) + return new Intl.DateTimeFormat("en-US", { + dateStyle: "medium", + timeStyle: "short", + }).format(date) + } + + return ( +
+ {/* Header */} +
+
+

{t("dashboard.title")}

+

{t("dashboard.description")}

+
+
+ + +
+
+ + {/* Sync Progress */} + {syncing && ( + + + + + {t("sync.inProgress")} + + + {t("sync.syncingTarget", { target: currentSyncTarget })} + + + + +

+ {Math.round(syncProgress)}% {t("sync.complete")} +

+
+
+ )} + + {/* Error State */} + {error && ( + + + + + {t("error.title")} + + + +

{error}

+
+
+ )} + + {/* Sync Status */} + {stats && ( + + + + {stats.status?.isSyncing ? ( + + ) : ( + + )} + {t("sync.status")} + + + +
+ + {stats.status?.isSyncing ? t("sync.syncing") : t("sync.idle")} + + + {t("sync.lastSync")}: {formatLastSync(stats.status?.lastSync || null)} + +
+
+
+ )} + + {/* Stats Grid */} +
+ + + + +
+ +
+ + + + + +
+ + {/* Quick Sync Actions */} + + + {t("sync.quickActions")} + {t("sync.quickActionsDescription")} + + +
+ {stats?.availableTargets + .filter((target) => target !== "all") + .map((target) => ( + + ))} +
+
+
+
+ ) +} diff --git a/app/admin/servers/page.tsx b/app/admin/servers/page.tsx new file mode 100644 index 0000000..2945749 --- /dev/null +++ b/app/admin/servers/page.tsx @@ -0,0 +1,494 @@ +"use client" + +import { useEffect, useState, useCallback } from "react" +import { useTranslations } from "next-intl" +import { + Server, + Search, + RefreshCw, + HardDrive, + Cpu, + Database, + Calendar, + CheckCircle2, + XCircle, + Loader2, + ChevronLeft, + ChevronRight, + Filter, + AlertTriangle, + Play, + Square, + Pause, + Activity, +} from "lucide-react" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { Badge } from "@/packages/ui/components/ui/badge" +import { Input } from "@/packages/ui/components/ui/input" +import { Skeleton } from "@/packages/ui/components/ui/skeleton" +import { Progress } from "@/packages/ui/components/ui/progress" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/packages/ui/components/ui/table" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/packages/ui/components/ui/select" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/packages/ui/components/ui/tooltip" +import { useToast } from "@/packages/ui/components/ui/use-toast" +import { cn } from "@/packages/core/lib/utils" + +interface ServerData { + id: string + pterodactylId: number + uuid: string + name: string + description: string | null + status: string + isSuspended: boolean + memory: number + disk: number + cpu: number + owner: { + id: string + username: string + email: string + } + node: { + id: number + name: string + } + egg: { + id: number + name: string + } + createdAt: string + allocations: Array<{ + ip: string + port: number + isAssigned: boolean + }> +} + +interface ServerMeta { + total: number + page: number + perPage: number + totalPages: number +} + +type FilterStatus = "all" | "running" | "offline" | "suspended" | "installing" + +const statusConfig: Record }> = { + RUNNING: { label: "Running", variant: "default", icon: Play }, + OFFLINE: { label: "Offline", variant: "secondary", icon: Square }, + STARTING: { label: "Starting", variant: "outline", icon: Activity }, + STOPPING: { label: "Stopping", variant: "outline", icon: Pause }, + INSTALLING: { label: "Installing", variant: "outline", icon: Loader2 }, + INSTALL_FAILED: { label: "Install Failed", variant: "destructive", icon: XCircle }, + SUSPENDED: { label: "Suspended", variant: "destructive", icon: AlertTriangle }, + RESTORING_BACKUP: { label: "Restoring", variant: "outline", icon: RefreshCw }, +} + +export default function ServersPage() { + const t = useTranslations("admin") + const { toast } = useToast() + const [servers, setServers] = useState([]) + const [meta, setMeta] = useState(null) + const [loading, setLoading] = useState(true) + const [refreshing, setRefreshing] = useState(false) + const [searchQuery, setSearchQuery] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + const [currentPage, setCurrentPage] = useState(1) + const [perPage, setPerPage] = useState(25) + const [filterStatus, setFilterStatus] = useState("all") + + // Debounce search + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearch(searchQuery) + setCurrentPage(1) + }, 300) + return () => clearTimeout(timer) + }, [searchQuery]) + + const fetchServers = useCallback(async (showRefreshing = false) => { + if (showRefreshing) setRefreshing(true) + else setLoading(true) + + try { + const params = new URLSearchParams({ + page: currentPage.toString(), + perPage: perPage.toString(), + filter: filterStatus, + ...(debouncedSearch && { search: debouncedSearch }), + }) + + const response = await fetch(`/api/admin/servers?${params}`) + const data = await response.json() + + if (data.success) { + setServers(data.data) + setMeta(data.meta) + } else { + toast({ + title: t("error.title"), + description: data.error || "Failed to fetch servers", + variant: "destructive", + }) + } + } catch (error) { + toast({ + title: t("error.title"), + description: "Failed to connect to server", + variant: "destructive", + }) + } finally { + setLoading(false) + setRefreshing(false) + } + }, [currentPage, perPage, filterStatus, debouncedSearch, t, toast]) + + useEffect(() => { + fetchServers() + }, [fetchServers]) + + const formatBytes = (mb: number) => { + if (mb >= 1024) { + return `${(mb / 1024).toFixed(1)} GB` + } + return `${mb} MB` + } + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + }) + } + + const getStatusConfig = (status: string, suspended: boolean) => { + if (suspended) return statusConfig.SUSPENDED + return statusConfig[status] || { label: status, variant: "outline" as const, icon: Activity } + } + + // Calculate stats + const stats = { + total: meta?.total || 0, + running: servers.filter((s) => s.status === "RUNNING" && !s.isSuspended).length, + offline: servers.filter((s) => s.status === "OFFLINE" && !s.isSuspended).length, + suspended: servers.filter((s) => s.isSuspended).length, + } + + return ( +
+ {/* Header */} +
+
+

+ + {t("servers.title")} +

+

+ {t("servers.description")} +

+
+ +
+ + {/* Stats Cards */} +
+ + + {t("servers.stats.total")} + + + + {loading ? ( + + ) : ( +
{stats.total}
+ )} +
+
+ + + {t("servers.stats.running")} + + + + {loading ? ( + + ) : ( +
{stats.running}
+ )} +
+
+ + + {t("servers.stats.offline")} + + + + {loading ? ( + + ) : ( +
{stats.offline}
+ )} +
+
+ + + {t("servers.stats.suspended")} + + + + {loading ? ( + + ) : ( +
{stats.suspended}
+ )} +
+
+
+ + {/* Filters and Search */} + + + {t("servers.filters.title")} + + +
+
+ + setSearchQuery(e.target.value)} + className="pl-9" + /> +
+
+ + +
+
+
+
+ + {/* Servers Table */} + + +
+ + + + {t("servers.table.server")} + {t("servers.table.status")} + {t("servers.table.owner")} + {t("servers.table.resources")} + {t("servers.table.node")} + {t("servers.table.created")} + + + + {loading ? ( + Array.from({ length: 5 }).map((_, i) => ( + + + + + + + + + )) + ) : servers.length === 0 ? ( + + +
+ +

{t("servers.empty.title")}

+ {debouncedSearch && ( +

{t("servers.empty.searchHint")}

+ )} +
+
+
+ ) : ( + servers.map((server) => { + const statusCfg = getStatusConfig(server.status, server.isSuspended) + const StatusIcon = statusCfg.icon + const primaryAllocation = server.allocations?.find((a) => a.isAssigned) + + return ( + + +
+
+ +
+
+
{server.name}
+
+ {primaryAllocation ? ( + `${primaryAllocation.ip}:${primaryAllocation.port}` + ) : ( + No allocation + )} +
+
+
+
+ + + + {t(`servers.status.${server.status.toLowerCase()}`) || statusCfg.label} + + + +
+
{server.owner.username}
+
{server.owner.email}
+
+
+ +
+ +
+ + + + {formatBytes(server.memory)} + + {t("servers.resources.memory")} + +
+
+ + + + {formatBytes(server.disk)} + + {t("servers.resources.disk")} + +
+
+ + + + {server.cpu}% + + {t("servers.resources.cpu")} + +
+
+
+
+ + {server.node.name} + + +
+ + {formatDate(server.createdAt)} +
+
+
+ ) + }) + )} +
+
+
+ + {/* Pagination */} + {meta && meta.totalPages > 1 && ( +
+

+ {t("servers.pagination.showing", { + from: (currentPage - 1) * perPage + 1, + to: Math.min(currentPage * perPage, meta.total), + total: meta.total, + })} +

+
+ +
+ {currentPage} + / + {meta.totalPages} +
+ +
+
+ )} +
+
+
+ ) +} diff --git a/app/admin/settings/page.tsx b/app/admin/settings/page.tsx new file mode 100644 index 0000000..502c522 --- /dev/null +++ b/app/admin/settings/page.tsx @@ -0,0 +1,604 @@ +"use client" + +import { useEffect, useState } from "react" +import { useTranslations } from "next-intl" +import { + Settings, + Save, + RefreshCw, + Server, + Link, + Shield, + Bell, + Database, + Globe, + Key, + Mail, + ExternalLink, + CheckCircle2, + AlertCircle, + Loader2, + Eye, + EyeOff, + Copy, + Check, + Construction, +} from "lucide-react" +import { Alert, AlertDescription, AlertTitle } from "@/packages/ui/components/ui/alert" +import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { Badge } from "@/packages/ui/components/ui/badge" +import { Input } from "@/packages/ui/components/ui/input" +import { Label } from "@/packages/ui/components/ui/label" +import { Switch } from "@/packages/ui/components/ui/switch" +import { Separator } from "@/packages/ui/components/ui/separator" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/packages/ui/components/ui/tabs" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/packages/ui/components/ui/select" +import { useToast } from "@/packages/ui/components/ui/use-toast" +import { cn } from "@/packages/core/lib/utils" + +interface ConnectionStatus { + connected: boolean + latency?: number + version?: string + error?: string +} + +interface SystemSettings { + pterodactyl: { + url: string + connected: boolean + version?: string + } + database: { + connected: boolean + provider: string + } + features: { + registration: boolean + maintenance: boolean + syncEnabled: boolean + } +} + +export default function SettingsPage() { + const t = useTranslations("admin") + const { toast } = useToast() + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [testingConnection, setTestingConnection] = useState(null) + const [showApiKey, setShowApiKey] = useState(false) + const [copied, setCopied] = useState(null) + + // Connection statuses + const [pterodactylStatus, setPterodactylStatus] = useState({ connected: false }) + const [databaseStatus, setDatabaseStatus] = useState({ connected: false }) + + // Settings state + const [settings, setSettings] = useState({ + pterodactyl: { + url: "", + connected: false, + }, + database: { + connected: false, + provider: "postgresql", + }, + features: { + registration: true, + maintenance: false, + syncEnabled: true, + }, + }) + + useEffect(() => { + fetchSettings() + }, []) + + const fetchSettings = async () => { + setLoading(true) + try { + // Fetch connection status + const response = await fetch("/api/admin/settings") + const data = await response.json() + + if (data.success) { + setSettings(data.settings) + setPterodactylStatus({ + connected: data.settings.pterodactyl.connected, + version: data.settings.pterodactyl.version, + }) + setDatabaseStatus({ + connected: data.settings.database.connected, + }) + } + } catch (error) { + console.error("Failed to fetch settings:", error) + } finally { + setLoading(false) + } + } + + const testConnection = async (type: "pterodactyl" | "database") => { + setTestingConnection(type) + try { + const response = await fetch(`/api/admin/settings/test?type=${type}`) + const data = await response.json() + + if (type === "pterodactyl") { + setPterodactylStatus({ + connected: data.success, + latency: data.latency, + version: data.version, + error: data.error, + }) + } else { + setDatabaseStatus({ + connected: data.success, + latency: data.latency, + error: data.error, + }) + } + + toast({ + title: data.success ? t("settings.connection.success") : t("settings.connection.failed"), + description: data.success + ? t("settings.connection.successDesc", { type }) + : data.error, + variant: data.success ? "default" : "destructive", + }) + } catch (error) { + toast({ + title: t("settings.connection.failed"), + description: t("settings.connection.error"), + variant: "destructive", + }) + } finally { + setTestingConnection(null) + } + } + + const saveSettings = async () => { + setSaving(true) + try { + const response = await fetch("/api/admin/settings", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(settings), + }) + const data = await response.json() + + if (data.success) { + toast({ + title: t("settings.saved"), + description: t("settings.savedDesc"), + }) + } else { + throw new Error(data.error) + } + } catch (error) { + toast({ + title: t("error.title"), + description: t("settings.saveError"), + variant: "destructive", + }) + } finally { + setSaving(false) + } + } + + const copyToClipboard = async (text: string, key: string) => { + try { + await navigator.clipboard.writeText(text) + setCopied(key) + setTimeout(() => setCopied(null), 2000) + } catch { + toast({ + title: t("settings.copyFailed"), + variant: "destructive", + }) + } + } + + const ConnectionBadge = ({ status }: { status: ConnectionStatus }) => ( + + {status.connected ? ( + + ) : ( + + )} + {status.connected ? t("settings.status.connected") : t("settings.status.disconnected")} + {status.latency && ( + ({status.latency}ms) + )} + + ) + + return ( +
+ {/* Header */} +
+
+

+ + {t("settings.title")} +

+

+ {t("settings.description")} +

+
+
+ + {/* Work in Progress Alert */} + + + {t("settings.wip.title")} + + {t("settings.wip.description")} + + + + + + + + {t("settings.tabs.connections")} + + + + {t("settings.tabs.features")} + + + + {t("settings.tabs.notifications")} + + + + {t("settings.tabs.advanced")} + + + + {/* Connections Tab */} + + {/* Pterodactyl Panel */} + + +
+
+ + {t("settings.pterodactyl.title")} +
+ +
+ {t("settings.pterodactyl.description")} +
+ +
+
+ +
+ setSettings({ + ...settings, + pterodactyl: { ...settings.pterodactyl, url: e.target.value } + })} + /> + +
+
+
+ +
+
+ +
+ +
+
+
+

+ {t("settings.pterodactyl.apiKeyNote")} +

+
+
+ {pterodactylStatus.version && ( +
+ {t("settings.pterodactyl.version")}: {pterodactylStatus.version} +
+ )} +
+ + + +
+ + {/* Database */} + + +
+
+ + {t("settings.database.title")} +
+ +
+ {t("settings.database.description")} +
+ +
+
+ + +
+
+ +
+ + +
+

+ {t("settings.database.connectionStringNote")} +

+
+
+
+ + + +
+
+ + {/* Features Tab */} + + + + {t("settings.features.title")} + {t("settings.features.description")} + + +
+
+ +

+ {t("settings.features.registrationDesc")} +

+
+ setSettings({ + ...settings, + features: { ...settings.features, registration: checked } + })} + /> +
+ +
+
+ +

+ {t("settings.features.maintenanceDesc")} +

+
+ setSettings({ + ...settings, + features: { ...settings.features, maintenance: checked } + })} + /> +
+ +
+
+ +

+ {t("settings.features.autoSyncDesc")} +

+
+ setSettings({ + ...settings, + features: { ...settings.features, syncEnabled: checked } + })} + /> +
+
+
+
+ + {/* Notifications Tab */} + + + + {t("settings.notifications.title")} + {t("settings.notifications.description")} + + +
+
+ +

+ {t("settings.notifications.emailDesc")} +

+
+ +
+ +
+
+ +

+ {t("settings.notifications.discordDesc")} +

+
+ +
+ +
+ + +

+ {t("settings.notifications.webhookDesc")} +

+
+
+
+
+ + {/* Advanced Tab */} + + + + {t("settings.advanced.title")} + {t("settings.advanced.description")} + + +
+ + +

+ {t("settings.advanced.cacheTimeoutDesc")} +

+
+ +
+ + +

+ {t("settings.advanced.syncIntervalDesc")} +

+
+ +
+

+ {t("settings.advanced.dangerZone")} +

+

+ {t("settings.advanced.dangerZoneDesc")} +

+
+ + +
+
+
+
+
+
+
+ ) +} diff --git a/app/admin/sync/page.tsx b/app/admin/sync/page.tsx new file mode 100644 index 0000000..6e3d4ea --- /dev/null +++ b/app/admin/sync/page.tsx @@ -0,0 +1,399 @@ +"use client" + +import { useEffect, useState, useCallback, useRef } from "react" +import { useTranslations } from "next-intl" +import { + RefreshCw, + AlertCircle, + CheckCircle2, + Loader2, + Clock, + Play, + Users, + Server, + HardDrive, + MapPin, + Network, + Layers, + Database, + Terminal, +} from "lucide-react" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { Badge } from "@/packages/ui/components/ui/badge" +import { Progress } from "@/packages/ui/components/ui/progress" +import { ScrollArea } from "@/packages/ui/components/ui/scroll-area" +import { useToast } from "@/packages/ui/components/ui/use-toast" +import { cn } from "@/packages/core/lib/utils" + +interface SyncLog { + id: string + time: Date + message: string + type: "info" | "success" | "error" | "progress" + target?: string +} + +interface SyncTarget { + id: string + name: string + description: string + icon: React.ComponentType<{ className?: string }> + status: "idle" | "running" | "success" | "error" +} + +const initialTargets: SyncTarget[] = [ + { id: "locations", name: "Locations", description: "Data center locations", icon: MapPin, status: "idle" }, + { id: "nodes", name: "Nodes", description: "Server nodes", icon: HardDrive, status: "idle" }, + { id: "allocations", name: "Allocations", description: "IP & port allocations", icon: Network, status: "idle" }, + { id: "nests", name: "Nests & Eggs", description: "Game configurations", icon: Layers, status: "idle" }, + { id: "servers", name: "Servers", description: "Server instances", icon: Server, status: "idle" }, + { id: "databases", name: "Databases", description: "Server databases", icon: Database, status: "idle" }, + { id: "users", name: "Users", description: "User accounts", icon: Users, status: "idle" }, +] + +export default function SyncPage() { + const t = useTranslations("admin") + const { toast } = useToast() + const [targets, setTargets] = useState(initialTargets) + const [isRunning, setIsRunning] = useState(false) + const [currentTarget, setCurrentTarget] = useState(null) + const [progress, setProgress] = useState(0) + const [lastSyncTime, setLastSyncTime] = useState(null) + const [logs, setLogs] = useState([]) + const logsEndRef = useRef(null) + + // Auto-scroll logs + useEffect(() => { + logsEndRef.current?.scrollIntoView({ behavior: "smooth" }) + }, [logs]) + + const addLog = useCallback((message: string, type: SyncLog["type"] = "info", target?: string) => { + const log: SyncLog = { + id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, + time: new Date(), + message, + type, + target, + } + setLogs((prev) => [...prev, log].slice(-100)) + }, []) + + const fetchStatus = async () => { + try { + const response = await fetch("/api/admin/sync") + const data = await response.json() + if (data.success && data.status?.lastSync) { + setLastSyncTime(new Date(data.status.lastSync)) + } + } catch (error) { + console.error("Failed to fetch sync status:", error) + } + } + + useEffect(() => { + fetchStatus() + }, []) + + const updateTargetStatus = (targetId: string, status: SyncTarget["status"]) => { + setTargets((prev) => prev.map((t) => (t.id === targetId ? { ...t, status } : t))) + } + + const resetTargets = () => { + setTargets(initialTargets.map((t) => ({ ...t, status: "idle" }))) + } + + const runSingleSync = async (targetId: string) => { + if (isRunning) return + + setIsRunning(true) + setCurrentTarget(targetId) + updateTargetStatus(targetId, "running") + + const target = targets.find((t) => t.id === targetId) + addLog(`Starting sync: ${target?.name}...`, "info", targetId) + + try { + const response = await fetch("/api/admin/sync", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ target: targetId }), + }) + + const data = await response.json() + + if (data.success) { + updateTargetStatus(targetId, "success") + const result = data.result?.[targetId] + if (result) { + addLog(`✓ ${target?.name}: ${formatResult(result)}`, "success", targetId) + } else { + addLog(`✓ ${target?.name} synced successfully`, "success", targetId) + } + toast({ title: `${target?.name} synced` }) + } else { + updateTargetStatus(targetId, "error") + addLog(`✗ ${target?.name} failed: ${data.error}`, "error", targetId) + toast({ title: `${target?.name} sync failed`, variant: "destructive" }) + } + } catch (error) { + updateTargetStatus(targetId, "error") + addLog(`✗ ${target?.name} failed: Network error`, "error", targetId) + toast({ title: `${target?.name} sync failed`, variant: "destructive" }) + } finally { + setIsRunning(false) + setCurrentTarget(null) + } + } + + const runFullSync = async () => { + if (isRunning) return + + setIsRunning(true) + setProgress(0) + resetTargets() + setLogs([]) + + addLog("═══ Starting Full Sync ═══", "info") + addLog("Connecting to Pterodactyl panel...", "progress") + + const syncOrder = ["locations", "nodes", "allocations", "nests", "servers", "databases", "users"] + let completedCount = 0 + + for (const targetId of syncOrder) { + setCurrentTarget(targetId) + updateTargetStatus(targetId, "running") + + const target = targets.find((t) => t.id === targetId) + addLog(`Syncing ${target?.name}...`, "progress", targetId) + setProgress(Math.round((completedCount / syncOrder.length) * 100)) + + try { + const response = await fetch("/api/admin/sync", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ target: targetId }), + }) + + const data = await response.json() + + if (data.success) { + updateTargetStatus(targetId, "success") + const result = data.result?.[targetId] + if (result) { + addLog(` ✓ ${formatResult(result)}`, "success", targetId) + } else { + addLog(` ✓ Completed`, "success", targetId) + } + } else { + updateTargetStatus(targetId, "error") + addLog(` ✗ Failed: ${data.error}`, "error", targetId) + } + } catch (error) { + updateTargetStatus(targetId, "error") + addLog(` ✗ Network error`, "error", targetId) + } + + completedCount++ + + // Small delay between syncs for visual feedback + await new Promise((r) => setTimeout(r, 300)) + } + + setProgress(100) + setLastSyncTime(new Date()) + addLog("═══ Full Sync Complete ═══", "success") + toast({ title: "Full sync completed" }) + + setIsRunning(false) + setCurrentTarget(null) + } + + const formatResult = (result: Record): string => { + const parts: string[] = [] + if (typeof result.created === "number") parts.push(`${result.created} created`) + if (typeof result.updated === "number") parts.push(`${result.updated} updated`) + if (typeof result.synced === "number") parts.push(`${result.synced} synced`) + if (typeof result.count === "number") parts.push(`${result.count} total`) + return parts.length > 0 ? parts.join(", ") : "Synced" + } + + const formatTime = (date: Date) => { + return date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" }) + } + + const formatDate = (date: Date | null) => { + if (!date) return "Never" + return date.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" }) + } + + return ( +
+ {/* Header */} +
+
+

Sync Management

+

+ Synchronize data from your Pterodactyl panel +

+
+ +
+ + {/* Main Content Grid */} +
+ {/* Left Column - Sync Targets */} +
+ {/* Progress Bar (when running) */} + {isRunning && ( + + +
+
+ + {currentTarget && ( + Syncing {targets.find((t) => t.id === currentTarget)?.name}... + )} +
+ {progress}% +
+ +
+
+ )} + + {/* Sync Targets */} + + + Sync Targets + Click any target to sync individually + + + {targets.map((target) => ( + + ))} + + + + {/* Last Sync Info */} + + +
+
+ +
+
+

Last Full Sync

+

{formatDate(lastSyncTime)}

+
+
+ +
+
+
+ + {/* Right Column - Live Logs */} +
+ + +
+
+ + Live Logs +
+ {isRunning && ( + + + Live + + )} +
+
+ + +
+ {logs.length === 0 ? ( +

+ Run a sync to see logs here +

+ ) : ( + logs.map((log) => ( +
+ [{formatTime(log.time)}]{" "} + {log.message} +
+ )) + )} +
+
+ + + +
+
+
+ ) +} diff --git a/app/admin/users/page.tsx b/app/admin/users/page.tsx new file mode 100644 index 0000000..ef33b19 --- /dev/null +++ b/app/admin/users/page.tsx @@ -0,0 +1,517 @@ +"use client" + +import { useEffect, useState, useCallback } from "react" +import { useTranslations } from "next-intl" +import { + Users, + Search, + RefreshCw, + Shield, + ShieldCheck, + Mail, + Calendar, + CheckCircle2, + XCircle, + Loader2, + ChevronLeft, + ChevronRight, + UserCheck, + UserX, + Filter, + SortAsc, + SortDesc, +} from "lucide-react" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { Badge } from "@/packages/ui/components/ui/badge" +import { Input } from "@/packages/ui/components/ui/input" +import { Skeleton } from "@/packages/ui/components/ui/skeleton" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/packages/ui/components/ui/table" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/packages/ui/components/ui/select" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/packages/ui/components/ui/tooltip" +import { useToast } from "@/packages/ui/components/ui/use-toast" +import { cn } from "@/packages/core/lib/utils" + +interface User { + id: string + email: string + username: string + firstName: string | null + lastName: string | null + isAdmin: boolean + isMigrated: boolean + isActive: boolean + pterodactylId: number | null + createdAt: string + lastLoginAt: string | null + lastSyncedAt: string | null + _count?: { + servers: number + sessions: number + } +} + +interface UserMeta { + total: number + page: number + perPage: number + totalPages: number +} + +type SortField = "username" | "email" | "createdAt" | "lastLoginAt" +type SortOrder = "asc" | "desc" +type FilterStatus = "all" | "active" | "inactive" | "admin" | "migrated" | "not-migrated" + +export default function UsersPage() { + const t = useTranslations("admin") + const { toast } = useToast() + const [users, setUsers] = useState([]) + const [meta, setMeta] = useState(null) + const [loading, setLoading] = useState(true) + const [refreshing, setRefreshing] = useState(false) + const [searchQuery, setSearchQuery] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + const [currentPage, setCurrentPage] = useState(1) + const [perPage, setPerPage] = useState(25) + const [sortField, setSortField] = useState("createdAt") + const [sortOrder, setSortOrder] = useState("desc") + const [filterStatus, setFilterStatus] = useState("all") + + // Debounce search + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearch(searchQuery) + setCurrentPage(1) // Reset to first page on search + }, 300) + return () => clearTimeout(timer) + }, [searchQuery]) + + const fetchUsers = useCallback(async (showRefreshing = false) => { + if (showRefreshing) setRefreshing(true) + else setLoading(true) + + try { + const params = new URLSearchParams({ + page: currentPage.toString(), + perPage: perPage.toString(), + sortField, + sortOrder, + filter: filterStatus, + ...(debouncedSearch && { search: debouncedSearch }), + }) + + const response = await fetch(`/api/admin/users?${params}`) + const data = await response.json() + + if (data.success) { + setUsers(data.data) + setMeta(data.meta) + } else { + toast({ + title: t("error.title"), + description: data.error || "Failed to fetch users", + variant: "destructive", + }) + } + } catch (error) { + toast({ + title: t("error.title"), + description: "Failed to connect to server", + variant: "destructive", + }) + } finally { + setLoading(false) + setRefreshing(false) + } + }, [currentPage, perPage, sortField, sortOrder, filterStatus, debouncedSearch, t, toast]) + + useEffect(() => { + fetchUsers() + }, [fetchUsers]) + + const handleSort = (field: SortField) => { + if (sortField === field) { + setSortOrder(sortOrder === "asc" ? "desc" : "asc") + } else { + setSortField(field) + setSortOrder("desc") + } + setCurrentPage(1) + } + + const formatDate = (dateString: string | null) => { + if (!dateString) return "-" + return new Date(dateString).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + }) + } + + const formatDateTime = (dateString: string | null) => { + if (!dateString) return "-" + return new Date(dateString).toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }) + } + + const SortIcon = ({ field }: { field: SortField }) => { + if (sortField !== field) return null + return sortOrder === "asc" ? ( + + ) : ( + + ) + } + + return ( +
+ {/* Header */} +
+
+

+ + {t("users.title")} +

+

+ {t("users.description")} +

+
+ +
+ + {/* Stats Cards */} +
+ + + {t("users.stats.total")} + + + + {loading ? ( + + ) : ( +
{meta?.total || 0}
+ )} +
+
+ + + {t("users.stats.migrated")} + + + + {loading ? ( + + ) : ( +
+ {users.filter((u) => u.isMigrated).length} +
+ )} +
+
+ + + {t("users.stats.admins")} + + + + {loading ? ( + + ) : ( +
+ {users.filter((u) => u.isAdmin).length} +
+ )} +
+
+ + + {t("users.stats.active")} + + + + {loading ? ( + + ) : ( +
+ {users.filter((u) => u.isActive).length} +
+ )} +
+
+
+ + {/* Filters and Search */} + + + {t("users.filters.title")} + + +
+
+ + setSearchQuery(e.target.value)} + className="pl-9" + /> +
+
+ + +
+
+
+
+ + {/* Users Table */} + + +
+ + + + handleSort("username")} + > +
+ {t("users.table.user")} + +
+
+ handleSort("email")} + > +
+ {t("users.table.email")} + +
+
+ {t("users.table.status")} + {t("users.table.pterodactyl")} + handleSort("createdAt")} + > +
+ {t("users.table.created")} + +
+
+ handleSort("lastLoginAt")} + > +
+ {t("users.table.lastLogin")} + +
+
+
+
+ + {loading ? ( + Array.from({ length: 5 }).map((_, i) => ( + + + + + + + + + )) + ) : users.length === 0 ? ( + + +
+ +

{t("users.empty.title")}

+ {debouncedSearch && ( +

{t("users.empty.searchHint")}

+ )} +
+
+
+ ) : ( + users.map((user) => ( + + +
+
+ + {user.username.charAt(0).toUpperCase()} + +
+
+
+ {user.username} + {user.isAdmin && ( + + + + + + + {t("users.badges.admin")} + + + + )} +
+ {(user.firstName || user.lastName) && ( +

+ {[user.firstName, user.lastName].filter(Boolean).join(" ")} +

+ )} +
+
+
+ +
+ + {user.email} +
+
+ +
+ {user.isActive ? ( + + {t("users.badges.active")} + + ) : ( + + {t("users.badges.inactive")} + + )} + {user.isMigrated && ( + + {t("users.badges.migrated")} + + )} +
+
+ + {user.pterodactylId ? ( + #{user.pterodactylId} + ) : ( + - + )} + + +
+ + {formatDate(user.createdAt)} +
+
+ + + {formatDateTime(user.lastLoginAt)} + + +
+ )) + )} +
+
+
+ + {/* Pagination */} + {meta && meta.totalPages > 1 && ( +
+

+ {t("users.pagination.showing", { + from: (currentPage - 1) * perPage + 1, + to: Math.min(currentPage * perPage, meta.total), + total: meta.total, + })} +

+
+ +
+ {currentPage} + / + {meta.totalPages} +
+ +
+
+ )} +
+
+
+ ) +} diff --git a/app/api/admin/servers/route.ts b/app/api/admin/servers/route.ts new file mode 100644 index 0000000..b894cd4 --- /dev/null +++ b/app/api/admin/servers/route.ts @@ -0,0 +1,126 @@ +import { NextResponse } from "next/server" +import { requireAdmin } from "@/packages/auth" +import { prisma } from "@/packages/core/lib/prisma" + +export async function GET(request: Request) { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const { searchParams } = new URL(request.url) + const page = parseInt(searchParams.get("page") || "1", 10) + const perPage = Math.min(parseInt(searchParams.get("perPage") || "25", 10), 100) + const search = searchParams.get("search") || "" + const filter = searchParams.get("filter") || "all" + + // Build where clause + const where: any = {} + + // Search filter + if (search) { + where.OR = [ + { name: { contains: search, mode: "insensitive" } }, + { description: { contains: search, mode: "insensitive" } }, + { uuid: { contains: search, mode: "insensitive" } }, + ] + } + + // Status filter + switch (filter) { + case "running": + where.status = "RUNNING" + where.isSuspended = false + break + case "offline": + where.status = "OFFLINE" + where.isSuspended = false + break + case "suspended": + where.isSuspended = true + break + case "installing": + where.status = { in: ["INSTALLING", "INSTALL_FAILED"] } + break + } + + // Get total count + const total = await prisma.server.count({ where }) + + // Get servers with pagination + const servers = await prisma.server.findMany({ + where, + orderBy: { createdAt: "desc" }, + skip: (page - 1) * perPage, + take: perPage, + select: { + id: true, + pterodactylId: true, + uuid: true, + name: true, + description: true, + status: true, + isSuspended: true, + memory: true, + disk: true, + cpu: true, + createdAt: true, + owner: { + select: { + id: true, + username: true, + email: true, + }, + }, + node: { + select: { + id: true, + name: true, + }, + }, + egg: { + select: { + id: true, + name: true, + }, + }, + allocations: { + select: { + ip: true, + port: true, + isAssigned: true, + }, + take: 5, + }, + }, + }) + + return NextResponse.json({ + success: true, + data: servers, + meta: { + total, + page, + perPage, + totalPages: Math.ceil(total / perPage), + }, + }) + } catch (error) { + console.error("Failed to fetch servers:", error) + + return NextResponse.json( + { + success: false, + error: "Failed to fetch servers", + data: [], + meta: null, + }, + { status: 500 } + ) + } +} diff --git a/app/api/admin/settings/route.ts b/app/api/admin/settings/route.ts new file mode 100644 index 0000000..7d4ab74 --- /dev/null +++ b/app/api/admin/settings/route.ts @@ -0,0 +1,101 @@ +import { NextResponse } from "next/server" +import { requireAdmin } from "@/packages/auth" +import { prisma } from "@/packages/core/lib/prisma" + +export async function GET() { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + // Test database connection + let databaseConnected = false + try { + await prisma.$queryRaw`SELECT 1` + databaseConnected = true + } catch { + databaseConnected = false + } + + // Check Pterodactyl panel connection + let pterodactylConnected = false + let pterodactylVersion = null + const panelUrl = process.env.PTERODACTYL_API_URL + + if (panelUrl) { + try { + const response = await fetch(`${panelUrl}`, { + headers: { + Accept: "application/json", + }, + }) + pterodactylConnected = response.ok + } catch { + pterodactylConnected = false + } + } + + return NextResponse.json({ + success: true, + settings: { + pterodactyl: { + url: panelUrl || "", + connected: pterodactylConnected, + version: pterodactylVersion, + }, + database: { + connected: databaseConnected, + provider: "postgresql", + }, + features: { + registration: true, + maintenance: false, + syncEnabled: true, + }, + }, + }) + } catch (error) { + console.error("Failed to fetch settings:", error) + + return NextResponse.json( + { success: false, error: "Failed to fetch settings" }, + { status: 500 } + ) + } +} + +export async function POST(request: Request) { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + // Settings are stored in environment variables for now + // In a full implementation, you'd store these in the database + const body = await request.json() + + // For now, just acknowledge the save + // In production, you'd update database/config here + return NextResponse.json({ + success: true, + message: "Settings updated", + }) + } catch (error) { + console.error("Failed to save settings:", error) + + return NextResponse.json( + { success: false, error: "Failed to save settings" }, + { status: 500 } + ) + } +} diff --git a/app/api/admin/settings/test/route.ts b/app/api/admin/settings/test/route.ts new file mode 100644 index 0000000..0ad3377 --- /dev/null +++ b/app/api/admin/settings/test/route.ts @@ -0,0 +1,89 @@ +import { NextResponse } from "next/server" +import { requireAdmin } from "@/packages/auth" +import { prisma } from "@/packages/core/lib/prisma" + +export async function GET(request: Request) { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const { searchParams } = new URL(request.url) + const type = searchParams.get("type") + + if (type === "database") { + const start = Date.now() + try { + await prisma.$queryRaw`SELECT 1` + const latency = Date.now() - start + return NextResponse.json({ + success: true, + latency, + }) + } catch (error) { + return NextResponse.json({ + success: false, + error: "Database connection failed", + }) + } + } + + if (type === "pterodactyl") { + const panelUrl = process.env.PTERODACTYL_API_URL + const apiKey = process.env.PTERODACTYL_API_KEY + + if (!panelUrl || !apiKey) { + return NextResponse.json({ + success: false, + error: "Pterodactyl API not configured", + }) + } + + const start = Date.now() + try { + const response = await fetch(`${panelUrl}/api/application/users?per_page=1`, { + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: "application/json", + }, + }) + const latency = Date.now() - start + + if (response.ok) { + return NextResponse.json({ + success: true, + latency, + version: "1.x", // Could parse from response if available + }) + } else { + return NextResponse.json({ + success: false, + error: `Panel returned status ${response.status}`, + }) + } + } catch (error) { + return NextResponse.json({ + success: false, + error: "Failed to connect to Pterodactyl panel", + }) + } + } + + return NextResponse.json({ + success: false, + error: "Invalid test type", + }) + } catch (error) { + console.error("Connection test failed:", error) + + return NextResponse.json( + { success: false, error: "Connection test failed" }, + { status: 500 } + ) + } +} diff --git a/app/api/admin/sync/route.ts b/app/api/admin/sync/route.ts new file mode 100644 index 0000000..febc933 --- /dev/null +++ b/app/api/admin/sync/route.ts @@ -0,0 +1,286 @@ +import { NextResponse } from "next/server" +import { auth } from "@/packages/auth" +import { + syncLocations, + syncNodes, + syncAllocations, + syncNestsAndEggs, + syncServers, + syncServerDatabases, + runFullSync, + getSyncStatus, +} from "@/packages/core/lib/sync" +import { prisma } from "@/packages/core/lib/prisma" + +// Types for sync targets +type SyncTarget = "locations" | "nodes" | "allocations" | "nests" | "servers" | "databases" | "users" | "all" + +/** + * Sync ALL users from panel - creates new users if they don't exist + * New users will have isMigrated=false and no password (must register) + */ +async function syncAllPanelUsers(): Promise<{ success: boolean; created: number; updated: number; error?: string }> { + try { + const PANEL_URL = process.env.GAMEPANEL_URL + const API_KEY = process.env.GAMEPANEL_API_KEY + + if (!PANEL_URL || !API_KEY) { + return { success: false, created: 0, updated: 0, error: "Missing panel configuration" } + } + + let created = 0 + let updated = 0 + let page = 1 + let hasMore = true + + while (hasMore) { + const response = await fetch( + `${PANEL_URL}/api/application/users?page=${page}`, + { + headers: { + Authorization: `Bearer ${API_KEY}`, + Accept: "application/json", + "Content-Type": "application/json", + }, + } + ) + + if (!response.ok) { + return { success: false, created, updated, error: `Panel API error: ${response.status}` } + } + + const data = await response.json() + const users = data.data || [] + const meta = data.meta?.pagination + + for (const panelUser of users) { + const attrs = panelUser.attributes + + try { + // Check if user already exists (by pterodactylId or email) + const existingUser = await prisma.user.findFirst({ + where: { + OR: [ + { pterodactylId: attrs.id }, + { email: attrs.email }, + ], + }, + }) + + if (existingUser) { + // Update existing user (but NEVER touch password or isMigrated status) + await prisma.user.update({ + where: { id: existingUser.id }, + data: { + pterodactylId: attrs.id, + username: attrs.username, + firstName: attrs.first_name || null, + lastName: attrs.last_name || null, + isAdmin: attrs.root_admin, + lastSyncedAt: new Date(), + }, + }) + updated++ + } else { + // Create new user from panel (no password, not migrated) + await prisma.user.create({ + data: { + email: attrs.email, + password: null, // No password - must register to set one + username: attrs.username, + firstName: attrs.first_name || null, + lastName: attrs.last_name || null, + isAdmin: attrs.root_admin, + pterodactylId: attrs.id, + isMigrated: false, // Must register to complete migration + lastSyncedAt: new Date(), + }, + }) + created++ + } + } catch (error) { + console.error(`[Sync] Failed to sync panel user ${attrs.id}:`, error) + } + } + + // Check for more pages + hasMore = meta && page < meta.total_pages + page++ + } + + return { success: true, created, updated } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + return { success: false, created: 0, updated: 0, error: errorMsg } + } +} + +/** + * POST /api/admin/sync + * Manually trigger a sync operation + */ +export async function POST(request: Request) { + try { + // Check authentication + const session = await auth() + + if (!session?.user) { + return NextResponse.json( + { success: false, error: "Unauthorized" }, + { status: 401 } + ) + } + + // Check admin status from DATABASE (not JWT - it might be stale) + const dbUser = await prisma.user.findUnique({ + where: { id: session.user.id }, + select: { isAdmin: true }, + }) + + if (!dbUser?.isAdmin) { + return NextResponse.json( + { success: false, error: "Forbidden - Admin access required" }, + { status: 403 } + ) + } + + const body = await request.json() + const target: SyncTarget = body.target || "all" + + console.log(`[Admin Sync] Starting sync for: ${target} (by ${session.user.email})`) + + let result: Record + + switch (target) { + case "locations": + result = { locations: await syncLocations() } + break + + case "nodes": + result = { nodes: await syncNodes() } + break + + case "allocations": + result = { allocations: await syncAllocations() } + break + + case "nests": + result = { nests: await syncNestsAndEggs() } + break + + case "servers": + result = { servers: await syncServers() } + break + + case "databases": + result = { databases: await syncServerDatabases() } + break + + case "users": + // Sync ALL users from panel (creates new + updates existing) + result = { users: await syncAllPanelUsers() } + break + + case "all": + // Run full sync (infrastructure + servers + databases) then users + const fullSyncResult = await runFullSync() + const usersResult = await syncAllPanelUsers() + result = { + ...fullSyncResult.results, + users: usersResult, + } + break + + default: + return NextResponse.json( + { success: false, error: `Invalid sync target: ${target}` }, + { status: 400 } + ) + } + + console.log(`[Admin Sync] Completed sync for: ${target}`) + + return NextResponse.json({ + success: true, + target, + result, + }) + } catch (error) { + console.error("[Admin Sync] Error:", error) + return NextResponse.json( + { success: false, error: "Internal server error" }, + { status: 500 } + ) + } +} + +/** + * GET /api/admin/sync + * Get sync status and recent logs + */ +export async function GET() { + try { + // Check authentication + const session = await auth() + + if (!session?.user) { + return NextResponse.json( + { success: false, error: "Unauthorized" }, + { status: 401 } + ) + } + + // Check admin status from DATABASE (not JWT - it might be stale) + const dbUser = await prisma.user.findUnique({ + where: { id: session.user.id }, + select: { isAdmin: true }, + }) + + if (!dbUser?.isAdmin) { + return NextResponse.json( + { success: false, error: "Forbidden - Admin access required" }, + { status: 403 } + ) + } + + const status = await getSyncStatus() + + // Get comprehensive stats + const stats = await prisma.$transaction([ + prisma.user.count(), + prisma.user.count({ where: { isMigrated: true } }), + prisma.server.count(), + prisma.node.count(), + prisma.location.count(), + prisma.allocation.count(), + prisma.nest.count(), + prisma.egg.count(), + prisma.eggVariable.count(), + prisma.serverDatabase.count(), + ]) + + return NextResponse.json({ + success: true, + status, + counts: { + users: stats[0], + migratedUsers: stats[1], + servers: stats[2], + nodes: stats[3], + locations: stats[4], + allocations: stats[5], + nests: stats[6], + eggs: stats[7], + eggVariables: stats[8], + serverDatabases: stats[9], + }, + availableTargets: ["locations", "nodes", "allocations", "nests", "servers", "databases", "users", "all"], + }) + } catch (error) { + console.error("[Admin Sync] Error:", error) + return NextResponse.json( + { success: false, error: "Internal server error" }, + { status: 500 } + ) + } +} diff --git a/app/api/admin/users/route.ts b/app/api/admin/users/route.ts new file mode 100644 index 0000000..ac08021 --- /dev/null +++ b/app/api/admin/users/route.ts @@ -0,0 +1,115 @@ +import { NextResponse } from "next/server" +import { requireAdmin } from "@/packages/auth" +import { prisma } from "@/packages/core/lib/prisma" + +export async function GET(request: Request) { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const { searchParams } = new URL(request.url) + const page = parseInt(searchParams.get("page") || "1", 10) + const perPage = Math.min(parseInt(searchParams.get("perPage") || "25", 10), 100) + const search = searchParams.get("search") || "" + const sortField = searchParams.get("sortField") || "createdAt" + const sortOrder = searchParams.get("sortOrder") || "desc" + const filter = searchParams.get("filter") || "all" + + // Build where clause + const where: any = {} + + // Search filter + if (search) { + where.OR = [ + { email: { contains: search, mode: "insensitive" } }, + { username: { contains: search, mode: "insensitive" } }, + { firstName: { contains: search, mode: "insensitive" } }, + { lastName: { contains: search, mode: "insensitive" } }, + ] + } + + // Status filter + switch (filter) { + case "active": + where.isActive = true + break + case "inactive": + where.isActive = false + break + case "admin": + where.isAdmin = true + break + case "migrated": + where.isMigrated = true + break + case "not-migrated": + where.isMigrated = false + break + } + + // Build orderBy + const validSortFields = ["username", "email", "createdAt", "lastLoginAt"] + const orderField = validSortFields.includes(sortField) ? sortField : "createdAt" + const orderBy = { [orderField]: sortOrder === "asc" ? "asc" : "desc" } + + // Get total count + const total = await prisma.user.count({ where }) + + // Get users with pagination + const users = await prisma.user.findMany({ + where, + orderBy, + skip: (page - 1) * perPage, + take: perPage, + select: { + id: true, + email: true, + username: true, + firstName: true, + lastName: true, + isAdmin: true, + isMigrated: true, + isActive: true, + pterodactylId: true, + createdAt: true, + lastLoginAt: true, + lastSyncedAt: true, + _count: { + select: { + servers: true, + sessions: true, + }, + }, + }, + }) + + return NextResponse.json({ + success: true, + data: users, + meta: { + total, + page, + perPage, + totalPages: Math.ceil(total / perPage), + }, + }) + } catch (error) { + console.error("Failed to fetch users:", error) + + return NextResponse.json( + { + success: false, + error: "Failed to fetch users", + data: [], + meta: null, + }, + { status: 500 } + ) + } +} diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..fd2b09c --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/packages/auth" + +export const { GET, POST } = handlers diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts new file mode 100644 index 0000000..0351075 --- /dev/null +++ b/app/api/auth/register/route.ts @@ -0,0 +1,106 @@ +import { NextResponse } from "next/server" +import { registerUser, verifyPterodactylUser } from "@/packages/auth/lib/auth-service" + +export async function POST(request: Request) { + try { + const body = await request.json() + const { email, password, confirmPassword } = body + + // Validate input + if (!email || !password) { + return NextResponse.json( + { success: false, error: "missing_fields" }, + { status: 400 } + ) + } + + // Validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + if (!emailRegex.test(email)) { + return NextResponse.json( + { success: false, error: "invalid_email" }, + { status: 400 } + ) + } + + // Validate password strength + if (password.length < 8) { + return NextResponse.json( + { success: false, error: "password_too_short" }, + { status: 400 } + ) + } + + // Validate password confirmation + if (password !== confirmPassword) { + return NextResponse.json( + { success: false, error: "passwords_dont_match" }, + { status: 400 } + ) + } + + console.log(`[Register] Attempting registration for: ${email}`) + + // Register the user + const result = await registerUser(email, password) + + if (!result.success) { + console.log(`[Register] Registration failed: ${result.error}`) + + // Map errors to appropriate status codes + const statusMap: Record = { + email_exists: 409, + not_in_panel: 404, + panel_account_linked: 409, + server_error: 500, + } + + return NextResponse.json( + { success: false, error: result.error }, + { status: statusMap[result.error || "server_error"] || 500 } + ) + } + + console.log(`[Register] User registered successfully: ${email}`) + + return NextResponse.json({ + success: true, + message: "Account created successfully", + }) + } catch (error) { + console.error("[Register] Error:", error) + return NextResponse.json( + { success: false, error: "server_error" }, + { status: 500 } + ) + } +} + +// Check if email exists in panel (for pre-registration validation) +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url) + const email = searchParams.get("email") + + if (!email) { + return NextResponse.json( + { success: false, error: "missing_email" }, + { status: 400 } + ) + } + + const pteroUser = await verifyPterodactylUser(email) + + return NextResponse.json({ + success: true, + exists: !!pteroUser, + username: pteroUser?.username || null, + }) + } catch (error) { + console.error("[Register] Check error:", error) + return NextResponse.json( + { success: false, error: "server_error" }, + { status: 500 } + ) + } +} diff --git a/app/api/github/releases/route.ts b/app/api/github/releases/route.ts new file mode 100644 index 0000000..7187106 --- /dev/null +++ b/app/api/github/releases/route.ts @@ -0,0 +1,161 @@ +import { NextResponse } from 'next/server' + +const REPOSITORIES = [ + 'NodeByteHosting/Bot', + 'NodeByteHosting/wings', + 'NodeByteHosting/website', + 'NodeByteHosting/translations', + 'NodeByteHosting/module-dokploy-whmcs', + 'NodeByteHosting/module-pterodactyl-whmcs', + 'NodeByteHosting/module-pterodactyl', + 'NodeByteHosting/whmcs-sdk', + 'NodeByteHosting/nodebyte.js', + 'NodeByteHosting/Game-Panel', + 'NodeByteHosting/assets', + 'NodeByteHosting/echo' +] + +export interface GitHubRelease { + id: number + name: string + tag_name: string + body: string | null + html_url: string + published_at: string + created_at: string + draft: boolean + prerelease: boolean + author: { + login: string + avatar_url: string + html_url: string + } + assets: { + id: number + name: string + size: number + download_count: number + browser_download_url: string + }[] + repository: { + name: string + full_name: string + html_url: string + description: string | null + } +} + +export interface ReleasesResponse { + releases: GitHubRelease[] + repositories: string[] + totalCount: number + lastUpdated: string | null +} + +async function fetchRepoReleases(repo: string, token?: string): Promise { + const headers: HeadersInit = { + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + } + + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + try { + // Fetch releases + const releasesRes = await fetch( + `https://api.github.com/repos/${repo}/releases?per_page=50`, + { headers, next: { revalidate: 300 } } // Cache for 5 minutes + ) + + if (!releasesRes.ok) { + console.error(`Failed to fetch releases for ${repo}: ${releasesRes.status}`) + return [] + } + + const releases = await releasesRes.json() + + // Fetch repo info + const repoRes = await fetch( + `https://api.github.com/repos/${repo}`, + { headers, next: { revalidate: 3600 } } // Cache for 1 hour + ) + + let repoInfo = { + name: repo.split('/')[1], + full_name: repo, + html_url: `https://github.com/${repo}`, + description: null + } + + if (repoRes.ok) { + const repoData = await repoRes.json() + repoInfo = { + name: repoData.name, + full_name: repoData.full_name, + html_url: repoData.html_url, + description: repoData.description + } + } + + // Add repository info to each release + return releases.map((release: any) => ({ + ...release, + repository: repoInfo + })) + } catch (error) { + console.error(`Error fetching releases for ${repo}:`, error) + return [] + } +} + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + const repo = searchParams.get('repo') // Optional: filter by specific repo + + const token = process.env.GITHUB_TOKEN + + try { + const reposToFetch = repo + ? REPOSITORIES.filter(r => r.toLowerCase().includes(repo.toLowerCase())) + : REPOSITORIES + + // Fetch releases from all repositories in parallel + const allReleasesArrays = await Promise.all( + reposToFetch.map(r => fetchRepoReleases(r, token)) + ) + + // Flatten and sort by published date (newest first) + const allReleases = allReleasesArrays + .flat() + .sort((a, b) => { + const dateA = new Date(a.published_at || a.created_at).getTime() + const dateB = new Date(b.published_at || b.created_at).getTime() + return dateB - dateA + }) + + // Get the latest update date + const latestUpdate = allReleases.length > 0 + ? allReleases[0].published_at || allReleases[0].created_at + : null + + // Get unique repositories that have releases + const repositories = [...new Set(allReleases.map(r => r.repository.full_name))] + + const response: ReleasesResponse = { + releases: allReleases, + repositories, + totalCount: allReleases.length, + lastUpdated: latestUpdate + } + + return NextResponse.json(response) + } catch (error) { + console.error('Error fetching GitHub releases:', error) + return NextResponse.json( + { error: 'Failed to fetch releases', releases: [], repositories: [], totalCount: 0, lastUpdated: null }, + { status: 500 } + ) + } +} diff --git a/app/api/instatus/route.ts b/app/api/instatus/route.ts new file mode 100644 index 0000000..21369c1 --- /dev/null +++ b/app/api/instatus/route.ts @@ -0,0 +1,92 @@ +import { NextResponse } from "next/server" + +export const revalidate = 60 // Cache for 60 seconds + +interface InstatusPage { + name: string + url: string + status: "UP" | "HASISSUES" | "UNDERMAINTENANCE" +} + +interface InstatusIncident { + id: string + name: string + started: string + status: string + impact: string + url: string + updatedAt: string +} + +interface InstatusMaintenance { + id: string + name: string + start: string + status: string + duration: string + url: string + updatedAt: string +} + +interface InstatusResponse { + page: InstatusPage + activeIncidents: InstatusIncident[] + activeMaintenances: InstatusMaintenance[] +} + +export async function GET() { + try { + const response = await fetch("https://nodebytestat.us/summary.json", { + next: { revalidate: 60 }, + headers: { + "Accept": "application/json", + }, + }) + + if (!response.ok) { + throw new Error(`Instatus API returned ${response.status}`) + } + + const data: InstatusResponse = await response.json() + + // Safely handle potentially undefined arrays + const activeIncidents = data.activeIncidents || [] + const activeMaintenances = data.activeMaintenances || [] + + return NextResponse.json({ + status: data.page?.status || "UP", + url: data.page?.url || "https://nodebytestat.us", + hasIncidents: activeIncidents.length > 0, + hasMaintenance: activeMaintenances.length > 0, + incidents: activeIncidents.map((incident) => ({ + id: incident.id, + name: incident.name, + status: incident.status, + impact: incident.impact, + url: incident.url, + })), + maintenances: activeMaintenances.map((maintenance) => ({ + id: maintenance.id, + name: maintenance.name, + status: maintenance.status, + url: maintenance.url, + })), + }) + } catch (error) { + console.error("Failed to fetch Instatus data:", error) + + // Return a fallback response + return NextResponse.json( + { + status: "UP", + url: "https://nodebytestat.us", + hasIncidents: false, + hasMaintenance: false, + incidents: [], + maintenances: [], + error: "Failed to fetch status", + }, + { status: 200 } // Still return 200 to not break the UI + ) + } +} diff --git a/app/api/panel/counts/route.ts b/app/api/panel/counts/route.ts new file mode 100644 index 0000000..25fb31d --- /dev/null +++ b/app/api/panel/counts/route.ts @@ -0,0 +1,48 @@ +import { NextResponse } from "next/server" +import { getServerCount, getUserCount, getNodeCount } from "@/packages/core/lib/pterodactyl" +import { requireAdmin } from "@/packages/auth" + +export const revalidate = 60 // Cache for 60 seconds + +export async function GET() { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const [servers, users, nodes] = await Promise.all([ + getServerCount(), + getUserCount(), + getNodeCount(), + ]) + + return NextResponse.json({ + success: true, + data: { + servers, + users, + nodes, + }, + }) + } catch (error) { + console.error("Failed to fetch counts:", error) + + return NextResponse.json( + { + success: false, + error: "Failed to fetch counts", + data: { + servers: 0, + users: 0, + nodes: 0, + }, + }, + { status: 500 } + ) + } +} diff --git a/app/api/panel/nodes/route.ts b/app/api/panel/nodes/route.ts new file mode 100644 index 0000000..b86130d --- /dev/null +++ b/app/api/panel/nodes/route.ts @@ -0,0 +1,93 @@ +import { NextResponse } from "next/server" +import { getNodes } from "@/packages/core/lib/pterodactyl" +import { requireAdmin } from "@/packages/auth" + +export const revalidate = 60 // Cache for 60 seconds + +export async function GET(request: Request) { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const { searchParams } = new URL(request.url) + const page = parseInt(searchParams.get("page") || "1", 10) + const perPage = Math.min(parseInt(searchParams.get("per_page") || "50", 10), 100) + + const response = await getNodes(page, perPage) + + // Format node data with resource calculations + const formattedNodes = response.data.map((node) => { + const memoryTotal = node.attributes.memory + const memoryOverAllocate = node.attributes.memory_overallocate + const memoryEffective = memoryTotal + (memoryTotal * (memoryOverAllocate / 100)) + const memoryAllocated = node.attributes.allocated_resources?.memory || 0 + + const diskTotal = node.attributes.disk + const diskOverAllocate = node.attributes.disk_overallocate + const diskEffective = diskTotal + (diskTotal * (diskOverAllocate / 100)) + const diskAllocated = node.attributes.allocated_resources?.disk || 0 + + return { + id: node.attributes.id, + uuid: node.attributes.uuid, + name: node.attributes.name, + description: node.attributes.description, + public: node.attributes.public, + fqdn: node.attributes.fqdn, + scheme: node.attributes.scheme, + behind_proxy: node.attributes.behind_proxy, + maintenance_mode: node.attributes.maintenance_mode, + resources: { + memory: { + total: memoryTotal, + effective: memoryEffective, + allocated: memoryAllocated, + available: memoryEffective - memoryAllocated, + usage_percent: Math.round((memoryAllocated / memoryEffective) * 100), + overallocate_percent: memoryOverAllocate, + }, + disk: { + total: diskTotal, + effective: diskEffective, + allocated: diskAllocated, + available: diskEffective - diskAllocated, + usage_percent: Math.round((diskAllocated / diskEffective) * 100), + overallocate_percent: diskOverAllocate, + }, + }, + location_id: node.attributes.location_id, + created_at: node.attributes.created_at, + } + }) + + return NextResponse.json({ + success: true, + data: formattedNodes, + meta: { + total: response.meta.pagination.total, + count: response.meta.pagination.count, + per_page: response.meta.pagination.per_page, + current_page: response.meta.pagination.current_page, + total_pages: response.meta.pagination.total_pages, + }, + }) + } catch (error) { + console.error("Failed to fetch nodes:", error) + + return NextResponse.json( + { + success: false, + error: "Failed to fetch nodes", + data: [], + meta: null, + }, + { status: 500 } + ) + } +} diff --git a/app/api/panel/servers/route.ts b/app/api/panel/servers/route.ts new file mode 100644 index 0000000..457ee99 --- /dev/null +++ b/app/api/panel/servers/route.ts @@ -0,0 +1,66 @@ +import { NextResponse } from "next/server" +import { getServers } from "@/packages/core/lib/pterodactyl" +import { requireAdmin } from "@/packages/auth" + +export const revalidate = 60 // Cache for 60 seconds + +export async function GET(request: Request) { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const { searchParams } = new URL(request.url) + const page = parseInt(searchParams.get("page") || "1", 10) + const perPage = Math.min(parseInt(searchParams.get("per_page") || "50", 10), 100) + + const response = await getServers(page, perPage) + + // Sanitize server data - remove sensitive information + const sanitizedServers = response.data.map((server) => ({ + id: server.attributes.id, + uuid: server.attributes.uuid, + identifier: server.attributes.identifier, + name: server.attributes.name, + description: server.attributes.description, + status: server.attributes.status, + suspended: server.attributes.suspended, + limits: { + memory: server.attributes.limits.memory, + disk: server.attributes.limits.disk, + cpu: server.attributes.limits.cpu, + }, + node: server.attributes.node, + created_at: server.attributes.created_at, + })) + + return NextResponse.json({ + success: true, + data: sanitizedServers, + meta: { + total: response.meta.pagination.total, + count: response.meta.pagination.count, + per_page: response.meta.pagination.per_page, + current_page: response.meta.pagination.current_page, + total_pages: response.meta.pagination.total_pages, + }, + }) + } catch (error) { + console.error("Failed to fetch servers:", error) + + return NextResponse.json( + { + success: false, + error: "Failed to fetch servers", + data: [], + meta: null, + }, + { status: 500 } + ) + } +} diff --git a/app/api/panel/stats/route.ts b/app/api/panel/stats/route.ts new file mode 100644 index 0000000..65b148f --- /dev/null +++ b/app/api/panel/stats/route.ts @@ -0,0 +1,58 @@ +import { NextResponse } from "next/server" +import { getStats } from "@/packages/core/lib/pterodactyl" +import { requireAdmin } from "@/packages/auth" + +export const revalidate = 60 // Cache for 60 seconds + +export async function GET() { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const stats = await getStats() + + return NextResponse.json({ + success: true, + data: { + servers: stats.servers, + users: stats.users, + nodes: stats.nodes, + resources: { + memory: { + total: stats.totalMemory, + allocated: stats.allocatedMemory, + available: stats.totalMemory - stats.allocatedMemory, + usagePercent: stats.totalMemory > 0 + ? Math.round((stats.allocatedMemory / stats.totalMemory) * 100) + : 0, + }, + disk: { + total: stats.totalDisk, + allocated: stats.allocatedDisk, + available: stats.totalDisk - stats.allocatedDisk, + usagePercent: stats.totalDisk > 0 + ? Math.round((stats.allocatedDisk / stats.totalDisk) * 100) + : 0, + }, + }, + }, + }) + } catch (error) { + console.error("Failed to fetch panel stats:", error) + + return NextResponse.json( + { + success: false, + error: "Failed to fetch panel statistics", + data: null, + }, + { status: 500 } + ) + } +} diff --git a/app/api/panel/users/route.ts b/app/api/panel/users/route.ts new file mode 100644 index 0000000..94cb880 --- /dev/null +++ b/app/api/panel/users/route.ts @@ -0,0 +1,60 @@ +import { NextResponse } from "next/server" +import { getUsers } from "@/packages/core/lib/pterodactyl" +import { requireAdmin } from "@/packages/auth" + +export const revalidate = 60 // Cache for 60 seconds + +export async function GET(request: Request) { + // Require admin authentication + const authResult = await requireAdmin() + if (!authResult.authorized) { + return NextResponse.json( + { success: false, error: authResult.error }, + { status: authResult.status } + ) + } + + try { + const { searchParams } = new URL(request.url) + const page = parseInt(searchParams.get("page") || "1", 10) + const perPage = Math.min(parseInt(searchParams.get("per_page") || "50", 10), 100) + + const response = await getUsers(page, perPage) + + // Sanitize user data - only expose non-sensitive information + const sanitizedUsers = response.data.map((user) => ({ + id: user.attributes.id, + uuid: user.attributes.uuid, + username: user.attributes.username, + first_name: user.attributes.first_name, + last_name: user.attributes.last_name, + root_admin: user.attributes.root_admin, + "2fa_enabled": user.attributes["2fa"], + created_at: user.attributes.created_at, + })) + + return NextResponse.json({ + success: true, + data: sanitizedUsers, + meta: { + total: response.meta.pagination.total, + count: response.meta.pagination.count, + per_page: response.meta.pagination.per_page, + current_page: response.meta.pagination.current_page, + total_pages: response.meta.pagination.total_pages, + }, + }) + } catch (error) { + console.error("Failed to fetch users:", error) + + return NextResponse.json( + { + success: false, + error: "Failed to fetch users", + data: [], + meta: null, + }, + { status: 500 } + ) + } +} diff --git a/app/api/stats/route.ts b/app/api/stats/route.ts new file mode 100644 index 0000000..8e8b397 --- /dev/null +++ b/app/api/stats/route.ts @@ -0,0 +1,44 @@ +import { NextResponse } from "next/server" +import { getServerCount, getUserCount, getNodeCount } from "@/packages/core/lib/pterodactyl" + +export const revalidate = 300 // Cache for 5 minutes - public route + +/** + * Public stats endpoint - returns ONLY aggregate counts + * No sensitive information exposed + */ +export async function GET() { + try { + // Fetch counts in parallel + const [servers, users, nodes] = await Promise.all([ + getServerCount().catch(() => null), + getUserCount().catch(() => null), + getNodeCount().catch(() => null), + ]) + + // Return only counts - no detailed information + return NextResponse.json({ + success: true, + data: { + servers: servers ?? 0, + users: users ?? 0, + nodes: nodes ?? 0, + }, + // Note: This is public data, no sensitive info + public: true, + }) + } catch (error) { + console.error("Failed to fetch public stats:", error) + + // Return zeros on error - don't expose error details + return NextResponse.json({ + success: false, + data: { + servers: 0, + users: 0, + nodes: 0, + }, + public: true, + }) + } +} diff --git a/app/auth/error/page.tsx b/app/auth/error/page.tsx new file mode 100644 index 0000000..f36f918 --- /dev/null +++ b/app/auth/error/page.tsx @@ -0,0 +1,66 @@ +import { getTranslations } from "next-intl/server" +import Link from "next/link" +import { Button } from "@/packages/ui/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/packages/ui/components/ui/card" +import { AlertCircle, Home, ArrowLeft } from "lucide-react" + +export default async function AuthErrorPage({ + searchParams, +}: { + searchParams: Promise<{ error?: string }> +}) { + const params = await searchParams + const t = await getTranslations("auth.error") + + const errorMessages: Record = { + Configuration: t("configuration"), + AccessDenied: t("accessDenied"), + Verification: t("verification"), + Default: t("default"), + CredentialsSignin: t("credentialsSignin"), + } + + const errorMessage = params.error + ? errorMessages[params.error] || errorMessages.Default + : errorMessages.Default + + return ( +
+ + +
+ +
+ {t("title")} + {t("description")} +
+ +
+

{errorMessage}

+
+
+ + + + +
+
+ ) +} diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx new file mode 100644 index 0000000..f9ca432 --- /dev/null +++ b/app/auth/login/page.tsx @@ -0,0 +1,57 @@ +import { getTranslations } from "next-intl/server" +import { LoginForm } from "@/packages/auth/components" +import { auth } from "@/packages/auth" +import { redirect } from "next/navigation" +import { Suspense } from "react" + +export default async function LoginPage() { + // Redirect if already logged in + const session = await auth() + if (session?.user) { + redirect("/") + } + + const t = await getTranslations("auth") + + const translations = { + title: t("login.title"), + description: t("login.description"), + emailLabel: t("login.emailLabel"), + emailPlaceholder: t("login.emailPlaceholder"), + passwordLabel: t("login.passwordLabel"), + passwordPlaceholder: t("login.passwordPlaceholder"), + loginButton: t("login.loginButton"), + loggingIn: t("login.loggingIn"), + forgotPassword: t("login.forgotPassword"), + noAccount: t("login.noAccount"), + createAccount: t("login.createAccount"), + errors: { + invalid: t("login.errors.invalid"), + networkError: t("login.errors.networkError"), + generic: t("login.errors.generic"), + }, + } + + return ( +
+ {/* Background gradients */} +
+ + {/* Animated background orbs */} +
+
+
+
+
+ + {/* Grid pattern overlay */} +
+ +
+ + + +
+
+ ) +} diff --git a/app/auth/register/page.tsx b/app/auth/register/page.tsx new file mode 100644 index 0000000..8b51bba --- /dev/null +++ b/app/auth/register/page.tsx @@ -0,0 +1,44 @@ +import { RegisterForm } from "@/packages/auth/components/register-form" +import { getTranslations } from "next-intl/server" + +export const metadata = { + title: "Register | NodeByte Hosting", + description: "Create your NodeByte Hosting account to access your game servers and services.", +} + +export default async function RegisterPage() { + const t = await getTranslations("auth.register") + + const translations = { + title: t("title"), + description: t("description"), + emailLabel: t("emailLabel"), + emailPlaceholder: t("emailPlaceholder"), + passwordLabel: t("passwordLabel"), + passwordPlaceholder: t("passwordPlaceholder"), + confirmPasswordLabel: t("confirmPasswordLabel"), + confirmPasswordPlaceholder: t("confirmPasswordPlaceholder"), + registerButton: t("registerButton"), + registering: t("registering"), + alreadyHaveAccount: t("alreadyHaveAccount"), + signIn: t("signIn"), + panelAccountRequired: t("panelAccountRequired"), + panelAccountRequiredDescription: t("panelAccountRequiredDescription"), + errors: { + emailExists: t("errors.emailExists"), + notInPanel: t("errors.notInPanel"), + panelAccountLinked: t("errors.panelAccountLinked"), + passwordsDontMatch: t("errors.passwordsDontMatch"), + passwordTooShort: t("errors.passwordTooShort"), + invalidEmail: t("errors.invalidEmail"), + networkError: t("errors.networkError"), + generic: t("errors.generic"), + }, + success: { + title: t("success.title"), + description: t("success.description"), + }, + } + + return +} diff --git a/app/changelog/layout.tsx b/app/changelog/layout.tsx new file mode 100644 index 0000000..46b4708 --- /dev/null +++ b/app/changelog/layout.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from "react" + +interface ChangelogLayoutProps { + children: ReactNode +} + +export default function ChangelogLayout({ children }: ChangelogLayoutProps) { + return ( +
{children}
+ ) +} diff --git a/app/changelog/page.tsx b/app/changelog/page.tsx new file mode 100644 index 0000000..4e4d055 --- /dev/null +++ b/app/changelog/page.tsx @@ -0,0 +1,49 @@ +import { Metadata } from "next" +import { Rocket } from "lucide-react" +import { getTranslations } from "next-intl/server" +import { Badge } from "@/packages/ui/components/ui/badge" +import { ChangelogList } from "@/packages/changelog/components" + +export async function generateMetadata(): Promise { + const t = await getTranslations() + + return { + title: `${t("changelog.title")} | NodeByte Hosting`, + description: t("changelog.description"), + } +} + +export default async function ChangelogPage() { + const t = await getTranslations() + + return ( +
+ {/* Hero Section */} +
+
+
+ + {t("changelog.badge")} + + +

+ {t("changelog.title")}{" "} + {t("changelog.titleHighlight")} +

+ +

+ {t("changelog.description")} +

+
+
+
+ + {/* Changelog Content */} +
+
+ +
+
+
+ ) +} diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 9ae7641..383d921 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -1,13 +1,5 @@ -import { Contact } from "@/components/Layouts/Contact/contact" -import { Navigation } from "@/components/Static/navigation" -import { Footer } from "@/components/Static/footer" +import { Contact } from "@/packages/ui/components/Layouts/Contact/contact" export default function ContactPage() { - return ( -
- - -
-
- ) + return } \ No newline at end of file diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000..543b486 --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,13 @@ +"use client" + +import { ErrorPage } from "@/packages/ui/components/Layouts/Error" + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + return +} diff --git a/app/games/hytale/page.tsx b/app/games/hytale/page.tsx new file mode 100644 index 0000000..4caf45c --- /dev/null +++ b/app/games/hytale/page.tsx @@ -0,0 +1,132 @@ +import { GameHero } from "@/packages/ui/components/Layouts/Games/game-hero" +import { GameFeatures } from "@/packages/ui/components/Layouts/Games/game-features" +import { GamePricing } from "@/packages/ui/components/Layouts/Games/game-pricing" +import { GameFAQ } from "@/packages/ui/components/Layouts/Games/game-faq" +import type { Metadata } from "next" + +export const metadata: Metadata = { + title: "Hytale Server Hosting | NodeByte Hosting", + description: "Be ready for Hytale launch with NodeByte Hosting. Join our waitlist for early access to our Hytale server hosting platform.", +} + +const features = [ + { + title: "Day One Support", + description: "We're preparing our infrastructure to support Hytale from day one of release.", + icon: "Zap" as const, + highlights: [ + "Launch-day availability", + "Optimized configurations", + "Pre-built templates", + "Quick deployment", + ], + }, + { + title: "Mod Support", + description: "Full support for Hytale's modding capabilities. Create and host your custom experiences.", + icon: "Settings" as const, + highlights: [ + "Custom mod support", + "Easy mod management", + "Auto-updates", + "Version control", + ], + }, + { + title: "High Performance", + description: "Enterprise grade hardware ready to deliver smooth gameplay for your Hytale community.", + icon: "Cpu" as const, + highlights: [ + "Latest gen CPUs", + "NVMe SSD storage", + "High-speed networking", + "Low latency", + ], + }, + { + title: "DDoS Protection", + description: "Your server will be protected by FyfeWeb's enterprise-grade DDoS mitigation from day one.", + icon: "Shield" as const, + highlights: [ + "Always-on protection", + "FyfeWeb network filtering", + "Zero downtime", + "UK London POPs", + ], + }, + { + title: "UK Data Centers", + description: "Servers hosted in UK data centers with London POPs for excellent latency across the UK and Europe.", + icon: "Globe" as const, + highlights: [ + "London POPs", + "Low latency routing", + "FyfeWeb network", + "UK coverage", + ], + }, + { + title: "24/7 Support", + description: "Our expert support team will be ready to help you with any Hytale hosting questions.", + icon: "Server" as const, + highlights: [ + "24/7 availability", + "Game experts", + "Fast response times", + "Discord support", + ], + }, +] + +const faqs = [ + { + question: "When will Hytale hosting be available?", + answer: "We'll launch our Hytale hosting service as soon as the game releases. Join our Discord to be notified when we go live!", + }, + { + question: "Can I join a waitlist?", + answer: "Yes! Join our Discord server to be added to the waitlist. You'll get early access and special launch pricing.", + }, + { + question: "What features will be supported?", + answer: "We plan to support all Hytale server features including mods, custom worlds, and multiplayer. Specific features will be confirmed closer to launch.", + }, + { + question: "Will there be mod support?", + answer: "Yes, we'll support Hytale's modding capabilities. You'll be able to install and manage mods through our control panel.", + }, + { + question: "What regions will be available?", + answer: "We offer Hytale hosting from our UK data centers with London POPs powered by FyfeWeb, providing excellent coverage across the UK and Europe.", + }, + { + question: "How can I stay updated?", + answer: "Join our Discord server for the latest updates on our Hytale hosting plans. We'll announce pricing and features as we get closer to launch.", + }, +] + +export default function HytalePage() { + return ( + <> + + + + + + ) +} diff --git a/app/games/minecraft/page.tsx b/app/games/minecraft/page.tsx new file mode 100644 index 0000000..7a9a891 --- /dev/null +++ b/app/games/minecraft/page.tsx @@ -0,0 +1,188 @@ +import type { Metadata } from "next" +import { GameHero } from "@/packages/ui/components/Layouts/Games/game-hero" +import { GameFeatures } from "@/packages/ui/components/Layouts/Games/game-features" +import { GamePricing } from "@/packages/ui/components/Layouts/Games/game-pricing" +import { GameFAQ } from "@/packages/ui/components/Layouts/Games/game-faq" +import { getTranslations } from "next-intl/server" + +export const metadata: Metadata = { + title: "Minecraft Servers", + description: "High performance Minecraft server hosting with instant setup, DDoS protection, and 24/7 support.", +} + +export default async function MinecraftPage() { + const t = await getTranslations() + + const plans = [ + { + name: t("games.minecraft.plans.ember.name"), + description: t("games.minecraft.plans.ember.description"), + priceGBP: 4, + period: t("pricing.perMonth"), + features: [ + t("games.minecraft.planFeatures.cpu"), + t("games.minecraft.planFeatures.ram", { amount: 4 }), + t("games.minecraft.planFeatures.storage", { amount: 40 }), + t("games.minecraft.planFeatures.databases"), + t("games.minecraft.planFeatures.ddos"), + t("games.minecraft.planFeatures.panel"), + t("games.minecraft.planFeatures.jars"), + t("games.minecraft.planFeatures.uptime"), + ], + url: "https://billing.nodebyte.host/store/minecraft-server-hosting/ember" + }, + { + name: t("games.minecraft.plans.blaze.name"), + description: t("games.minecraft.plans.blaze.description"), + priceGBP: 6, + period: t("pricing.perMonth"), + features: [ + t("games.minecraft.planFeatures.cpu"), + t("games.minecraft.planFeatures.ram", { amount: 6 }), + t("games.minecraft.planFeatures.storage", { amount: 60 }), + t("games.minecraft.planFeatures.databases"), + t("games.minecraft.planFeatures.ddos"), + t("games.minecraft.planFeatures.panel"), + t("games.minecraft.planFeatures.jars"), + t("games.minecraft.planFeatures.uptime"), + ], + url: "https://billing.nodebyte.host/store/minecraft-server-hosting/blaze" + }, + { + name: t("games.minecraft.plans.inferno.name"), + description: t("games.minecraft.plans.inferno.description"), + priceGBP: 7.5, + period: t("pricing.perMonth"), + popular: true, + features: [ + t("games.minecraft.planFeatures.cpu"), + t("games.minecraft.planFeatures.ram", { amount: 8 }), + t("games.minecraft.planFeatures.storage", { amount: 80 }), + t("games.minecraft.planFeatures.databases"), + t("games.minecraft.planFeatures.ddos"), + t("games.minecraft.planFeatures.panel"), + t("games.minecraft.planFeatures.jars"), + t("games.minecraft.planFeatures.uptime"), + ], + url: "https://billing.nodebyte.host/store/minecraft-server-hosting/inferno" + }, + ] + + const features = [ + { + title: t("games.minecraft.pageFeatures.modLoaders.title"), + description: t("games.minecraft.pageFeatures.modLoaders.description"), + icon: "Settings" as const, + highlights: [ + t("games.minecraft.pageFeatures.modLoaders.highlights.0"), + t("games.minecraft.pageFeatures.modLoaders.highlights.1"), + t("games.minecraft.pageFeatures.modLoaders.highlights.2"), + t("games.minecraft.pageFeatures.modLoaders.highlights.3"), + ], + }, + { + title: t("games.minecraft.pageFeatures.hardware.title"), + description: t("games.minecraft.pageFeatures.hardware.description"), + icon: "Cpu" as const, + highlights: [ + t("games.minecraft.pageFeatures.hardware.highlights.0"), + t("games.minecraft.pageFeatures.hardware.highlights.1"), + t("games.minecraft.pageFeatures.hardware.highlights.2"), + t("games.minecraft.pageFeatures.hardware.highlights.3"), + ], + }, + { + title: t("games.minecraft.pageFeatures.ddos.title"), + description: t("games.minecraft.pageFeatures.ddos.description"), + icon: "Shield" as const, + highlights: [ + t("games.minecraft.pageFeatures.ddos.highlights.0"), + t("games.minecraft.pageFeatures.ddos.highlights.1"), + t("games.minecraft.pageFeatures.ddos.highlights.2"), + t("games.minecraft.pageFeatures.ddos.highlights.3"), + ], + }, + { + title: t("games.minecraft.pageFeatures.instant.title"), + description: t("games.minecraft.pageFeatures.instant.description"), + icon: "Zap" as const, + highlights: [ + t("games.minecraft.pageFeatures.instant.highlights.0"), + t("games.minecraft.pageFeatures.instant.highlights.1"), + t("games.minecraft.pageFeatures.instant.highlights.2"), + t("games.minecraft.pageFeatures.instant.highlights.3"), + ], + }, + { + title: t("games.minecraft.pageFeatures.ftp.title"), + description: t("games.minecraft.pageFeatures.ftp.description"), + icon: "HardDrive" as const, + highlights: [ + t("games.minecraft.pageFeatures.ftp.highlights.0"), + t("games.minecraft.pageFeatures.ftp.highlights.1"), + t("games.minecraft.pageFeatures.ftp.highlights.2"), + t("games.minecraft.pageFeatures.ftp.highlights.3"), + ], + }, + { + title: t("games.minecraft.pageFeatures.slots.title"), + description: t("games.minecraft.pageFeatures.slots.description"), + icon: "Users" as const, + highlights: [ + t("games.minecraft.pageFeatures.slots.highlights.0"), + t("games.minecraft.pageFeatures.slots.highlights.1"), + t("games.minecraft.pageFeatures.slots.highlights.2"), + t("games.minecraft.pageFeatures.slots.highlights.3"), + ], + }, + ] + + const faqs = [ + { + question: t("games.minecraft.faqs.versions.question"), + answer: t("games.minecraft.faqs.versions.answer"), + }, + { + question: t("games.minecraft.faqs.mods.question"), + answer: t("games.minecraft.faqs.mods.answer"), + }, + { + question: t("games.minecraft.faqs.upload.question"), + answer: t("games.minecraft.faqs.upload.answer"), + }, + { + question: t("games.minecraft.faqs.playerLimit.question"), + answer: t("games.minecraft.faqs.playerLimit.answer"), + }, + { + question: t("games.minecraft.faqs.upgrade.question"), + answer: t("games.minecraft.faqs.upgrade.answer"), + }, + { + question: t("games.minecraft.faqs.refunds.question"), + answer: t("games.minecraft.faqs.refunds.answer"), + }, + ] + + return ( + <> + + + + + + ) +} diff --git a/app/games/page.tsx b/app/games/page.tsx new file mode 100644 index 0000000..b89e503 --- /dev/null +++ b/app/games/page.tsx @@ -0,0 +1,196 @@ +import { Card } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { GamePrice } from "@/packages/ui/components/ui/game-price" +import { Gamepad2, ArrowRight, Check, Clock, Blocks, Sparkles } from "lucide-react" +import Image from "next/image" +import Link from "next/link" +import { getTranslations } from "next-intl/server" +import { cn } from "@/lib/utils" +import type { Metadata } from "next" + +export async function generateMetadata(): Promise { + const t = await getTranslations() + return { + title: `${t("gamesPage.title")} | NodeByte Hosting`, + description: t("gamesPage.description"), + } +} + +export default async function GamesPage() { + const t = await getTranslations() + + const games = [ + { + name: "Minecraft", + slug: "minecraft", + description: t("games.minecraft.description"), + banner: "/minecraft.png", + icon: Blocks, + tag: t("games.minecraft.tag"), + tagColor: "bg-primary text-primary-foreground", + features: [ + t("games.minecraft.features.0"), + t("games.minecraft.features.1"), + t("games.minecraft.features.2"), + t("games.minecraft.features.3"), + ], + startingPriceGBP: 2, + }, + { + name: "Rust", + slug: "rust", + description: t("games.rust.description"), + banner: "/rust.png", + icon: Gamepad2, + tag: t("games.rust.tag"), + tagColor: "bg-accent text-accent-foreground", + features: [ + t("games.rust.features.0"), + t("games.rust.features.1"), + t("games.rust.features.2"), + t("games.rust.features.3"), + ], + startingPriceGBP: 8, + }, + { + name: "Hytale", + slug: "hytale", + description: t("games.hytale.description"), + banner: "/hytale.png", + icon: Sparkles, + tag: t("games.hytale.tag"), + tagColor: "bg-muted text-muted-foreground", + features: [ + t("games.hytale.features.0"), + t("games.hytale.features.1"), + t("games.hytale.features.2"), + t("games.hytale.features.3"), + ], + comingSoon: true, + }, + ] + + return ( +
+ {/* Background */} +
+
+ + {/* Animated orbs */} +
+
+
+
+ +
+ {/* Header */} +
+
+ + {t("games.badge")} +
+

+ {t("games.title")}{" "} + + {t("games.titleHighlight")} + +

+

+ {t("games.description")} +

+
+ + {/* Games Grid */} +
+ {games.map((game) => ( + + {/* Banner */} +
+ {game.name} +
+ + {/* Tag */} +
+ {game.tag} +
+ + {/* Icon */} +
+ +
+
+ +
+ {/* Title & Price */} +
+

{game.name}

+ {game.startingPriceGBP && ( + + )} +
+ +

{game.description}

+ + {/* Features */} +
    + {game.features.map((feature, i) => ( +
  • + {game.comingSoon ? ( + + ) : ( + + )} + {feature} +
  • + ))} +
+ + {/* CTA */} + +
+ + ))} +
+ + {/* Bottom CTA */} +
+

+ {t("games.customSolution")}{" "} + + {t("games.contactUs")} + {" "} + {t("games.forCustom")} +

+
+
+
+ ) +} diff --git a/app/games/rust/page.tsx b/app/games/rust/page.tsx new file mode 100644 index 0000000..b70878f --- /dev/null +++ b/app/games/rust/page.tsx @@ -0,0 +1,191 @@ +import { GameHero } from "@/packages/ui/components/Layouts/Games/game-hero" +import { GameFeatures } from "@/packages/ui/components/Layouts/Games/game-features" +import { GamePricing } from "@/packages/ui/components/Layouts/Games/game-pricing" +import { GameFAQ } from "@/packages/ui/components/Layouts/Games/game-faq" +import { getTranslations } from "next-intl/server" +import type { Metadata } from "next" + +export const metadata: Metadata = { + title: "Rust Server Hosting | NodeByte Hosting", + description: "High-performance Rust server hosting with Oxide/uMod support, custom maps, and DDoS protection. Instant setup with 24/7 expert support.", +} + +export default async function RustPage() { + const t = await getTranslations() + + const plans = [ + { + name: t("games.rust.plans.starter.name"), + description: t("games.rust.plans.starter.description"), + priceGBP: 5.75, + period: t("pricing.perMonth"), + features: [ + t("games.rust.planFeatures.cpu"), + t("games.rust.planFeatures.ram", { amount: 8 }), + t("games.rust.planFeatures.storage", { amount: 150 }), + t("games.rust.planFeatures.ddos"), + t("games.rust.planFeatures.location"), + t("games.rust.planFeatures.databases"), + t("games.rust.planFeatures.panel"), + t("games.rust.planFeatures.oxide"), + t("games.rust.planFeatures.rustplus"), + t("games.rust.planFeatures.uptime"), + ], + }, + { + name: t("games.rust.plans.standard.name"), + description: t("games.rust.plans.standard.description"), + priceGBP: 8.95, + period: t("pricing.perMonth"), + popular: true, + features: [ + t("games.rust.planFeatures.cpu"), + t("games.rust.planFeatures.ram", { amount: 12 }), + t("games.rust.planFeatures.storage", { amount: 200 }), + t("games.rust.planFeatures.ddos"), + t("games.rust.planFeatures.location"), + t("games.rust.planFeatures.databases"), + t("games.rust.planFeatures.panel"), + t("games.rust.planFeatures.oxide"), + t("games.rust.planFeatures.rustplus"), + t("games.rust.planFeatures.uptime"), + ], + }, + { + name: t("games.rust.plans.performance.name"), + description: t("games.rust.plans.performance.description"), + priceGBP: 12.75, + period: t("pricing.perMonth"), + features: [ + t("games.rust.planFeatures.cpu"), + t("games.rust.planFeatures.ram", { amount: 16 }), + t("games.rust.planFeatures.storage", { amount: 250 }), + t("games.rust.planFeatures.ddos"), + t("games.rust.planFeatures.location"), + t("games.rust.planFeatures.databases"), + t("games.rust.planFeatures.panel"), + t("games.rust.planFeatures.oxide"), + t("games.rust.planFeatures.rustplus"), + t("games.rust.planFeatures.uptime"), + ], + }, + ] + + const features = [ + { + title: t("games.rust.pageFeatures.oxide.title"), + description: t("games.rust.pageFeatures.oxide.description"), + icon: "Settings" as const, + highlights: [ + t("games.rust.pageFeatures.oxide.highlights.0"), + t("games.rust.pageFeatures.oxide.highlights.1"), + t("games.rust.pageFeatures.oxide.highlights.2"), + t("games.rust.pageFeatures.oxide.highlights.3"), + ], + }, + { + title: t("games.rust.pageFeatures.maps.title"), + description: t("games.rust.pageFeatures.maps.description"), + icon: "Map" as const, + highlights: [ + t("games.rust.pageFeatures.maps.highlights.0"), + t("games.rust.pageFeatures.maps.highlights.1"), + t("games.rust.pageFeatures.maps.highlights.2"), + t("games.rust.pageFeatures.maps.highlights.3"), + ], + }, + { + title: t("games.rust.pageFeatures.performance.title"), + description: t("games.rust.pageFeatures.performance.description"), + icon: "Cpu" as const, + highlights: [ + t("games.rust.pageFeatures.performance.highlights.0"), + t("games.rust.pageFeatures.performance.highlights.1"), + t("games.rust.pageFeatures.performance.highlights.2"), + t("games.rust.pageFeatures.performance.highlights.3"), + ], + }, + { + title: t("games.rust.pageFeatures.ddos.title"), + description: t("games.rust.pageFeatures.ddos.description"), + icon: "Shield" as const, + highlights: [ + t("games.rust.pageFeatures.ddos.highlights.0"), + t("games.rust.pageFeatures.ddos.highlights.1"), + t("games.rust.pageFeatures.ddos.highlights.2"), + t("games.rust.pageFeatures.ddos.highlights.3"), + ], + }, + { + title: t("games.rust.pageFeatures.wipe.title"), + description: t("games.rust.pageFeatures.wipe.description"), + icon: "Zap" as const, + highlights: [ + t("games.rust.pageFeatures.wipe.highlights.0"), + t("games.rust.pageFeatures.wipe.highlights.1"), + t("games.rust.pageFeatures.wipe.highlights.2"), + t("games.rust.pageFeatures.wipe.highlights.3"), + ], + }, + { + title: t("games.rust.pageFeatures.rcon.title"), + description: t("games.rust.pageFeatures.rcon.description"), + icon: "Server" as const, + highlights: [ + t("games.rust.pageFeatures.rcon.highlights.0"), + t("games.rust.pageFeatures.rcon.highlights.1"), + t("games.rust.pageFeatures.rcon.highlights.2"), + t("games.rust.pageFeatures.rcon.highlights.3"), + ], + }, + ] + + const faqs = [ + { + question: t("games.rust.faqs.oxide.question"), + answer: t("games.rust.faqs.oxide.answer"), + }, + { + question: t("games.rust.faqs.maps.question"), + answer: t("games.rust.faqs.maps.answer"), + }, + { + question: t("games.rust.faqs.wipe.question"), + answer: t("games.rust.faqs.wipe.answer"), + }, + { + question: t("games.rust.faqs.tickRate.question"), + answer: t("games.rust.faqs.tickRate.answer"), + }, + { + question: t("games.rust.faqs.rcon.question"), + answer: t("games.rust.faqs.rcon.answer"), + }, + { + question: t("games.rust.faqs.modded.question"), + answer: t("games.rust.faqs.modded.answer"), + }, + ] + + return ( + <> + + + + + + ) +} diff --git a/app/globals.css b/app/globals.css index 410cd89..5798bf3 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,5 +1,6 @@ @import "tailwindcss"; @import "tw-animate-css"; +@plugin "@tailwindcss/typography"; :root { /* Light theme (default) - soft slate / neutral light */ @@ -582,4 +583,76 @@ .animate-glow { animation: glow 3s ease-in-out infinite; +} + +/* Syntax Highlighting for Code Blocks (rehype-highlight) */ +.hljs { + color: var(--foreground); + background: transparent; +} + +.hljs-comment, +.hljs-quote { + color: oklch(0.55 0.02 240); + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: oklch(0.65 0.2 320); +} + +.hljs-number, +.hljs-string, +.hljs-meta .hljs-meta-string, +.hljs-literal, +.hljs-doctag, +.hljs-regexp { + color: oklch(0.7 0.15 140); +} + +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: oklch(0.7 0.18 200); +} + +.hljs-attribute, +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type { + color: oklch(0.75 0.15 80); +} + +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-link { + color: oklch(0.65 0.15 30); +} + +.hljs-built_in, +.hljs-deletion { + color: oklch(0.65 0.2 25); +} + +.hljs-formula { + background: oklch(0.2 0.02 240); +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; } \ No newline at end of file diff --git a/app/kb/[category]/[article]/page.tsx b/app/kb/[category]/[article]/page.tsx new file mode 100644 index 0000000..c0bacaf --- /dev/null +++ b/app/kb/[category]/[article]/page.tsx @@ -0,0 +1,158 @@ +import { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { getTranslations } from "next-intl/server"; +import { + getCategories, + getArticle, + getArticlesByCategory, + extractHeadings, + getAllArticles, +} from "@/packages/kb/lib/kb"; +import { KBBreadcrumb } from "@/packages/kb/components/kb-breadcrumb"; +import { KBArticle } from "@/packages/kb/components/kb-article"; +import { KBTableOfContents } from "@/packages/kb/components/kb-toc"; +import { KBSidebar, SidebarCategory } from "@/packages/kb/components/kb-sidebar"; + +interface ArticlePageProps { + params: Promise<{ + category: string; + article: string; + }>; +} + +export async function generateMetadata({ + params, +}: ArticlePageProps): Promise { + const { category, article } = await params; + const articleData = await getArticle(category, article); + + if (!articleData) { + return { + title: "Article Not Found | Knowledge Base | NodeByte Hosting", + }; + } + + return { + title: `${articleData.meta.title} | Knowledge Base | NodeByte Hosting`, + description: articleData.meta.description, + keywords: articleData.meta.tags, + }; +} + +export async function generateStaticParams() { + const articles = await getAllArticles(); + return articles.map((article) => ({ + category: article.categorySlug, + article: article.slug, + })); +} + +export default async function ArticlePage({ params }: ArticlePageProps) { + const t = await getTranslations(); + const { category, article } = await params; + const articleData = await getArticle(category, article); + + if (!articleData) { + notFound(); + } + + const categories = await getCategories(); + const categoryData = categories.find((c) => c.slug === category); + const articlesInCategory = await getArticlesByCategory(category); + + // Prepare sidebar data + const sidebarCategories: SidebarCategory[] = await Promise.all( + categories.map(async (cat) => { + const catArticles = await getArticlesByCategory(cat.slug); + return { + slug: cat.slug, + title: cat.title, + icon: cat.icon, + order: cat.order, + articles: catArticles.map((a) => ({ + slug: a.slug, + title: a.title, + order: a.order, + })), + }; + }) + ); + + // Extract headings for TOC + const headings = extractHeadings(articleData.content); + + // Find adjacent articles for navigation + const sortedArticles = [...articlesInCategory].sort((a, b) => a.order - b.order); + const currentIndex = sortedArticles.findIndex((a) => a.slug === article); + const previousArticle = + currentIndex > 0 + ? { + slug: sortedArticles[currentIndex - 1].slug, + category, + title: sortedArticles[currentIndex - 1].title, + } + : null; + const nextArticle = + currentIndex < sortedArticles.length - 1 + ? { + slug: sortedArticles[currentIndex + 1].slug, + category, + title: sortedArticles[currentIndex + 1].title, + } + : null; + + return ( +
+
+ {/* Breadcrumb */} + + +
+ {/* Sidebar - Hidden on mobile */} + + + {/* Main Content */} +
+ +
+ + {/* Table of Contents - Hidden on mobile and tablet */} + {headings.length > 2 && ( + + )} +
+
+
+ ); +} diff --git a/app/kb/[category]/page.tsx b/app/kb/[category]/page.tsx new file mode 100644 index 0000000..245b8a2 --- /dev/null +++ b/app/kb/[category]/page.tsx @@ -0,0 +1,119 @@ +import { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { getTranslations } from "next-intl/server"; +import { getCategories, getArticlesByCategory } from "@/packages/kb/lib/kb"; +import { KBBreadcrumb } from "@/packages/kb/components/kb-breadcrumb"; +import { KBArticleList } from "@/packages/kb/components/kb-article-card"; +import { + Rocket, + Gamepad2, + CreditCard, + Users, + Shield, + Settings, + HelpCircle, + Server, +} from "lucide-react"; + +// Icon mapping +const iconMap: Record> = { + Rocket, + Gamepad2, + CreditCard, + Users, + Shield, + Settings, + HelpCircle, + Server, +}; + +interface CategoryPageProps { + params: Promise<{ + category: string; + }>; +} + +export async function generateMetadata({ + params, +}: CategoryPageProps): Promise { + const { category } = await params; + const categories = await getCategories(); + const categoryData = categories.find((c) => c.slug === category); + + if (!categoryData) { + return { + title: "Category Not Found | Knowledge Base | NodeByte Hosting", + }; + } + + return { + title: `${categoryData.title} | Knowledge Base | NodeByte Hosting`, + description: categoryData.description, + }; +} + +export async function generateStaticParams() { + const categories = await getCategories(); + return categories.map((category) => ({ + category: category.slug, + })); +} + +export default async function CategoryPage({ params }: CategoryPageProps) { + const t = await getTranslations(); + const { category } = await params; + const categories = await getCategories(); + const categoryData = categories.find((c) => c.slug === category); + + if (!categoryData) { + notFound(); + } + + const articles = await getArticlesByCategory(category); + const Icon = iconMap[categoryData.icon] || HelpCircle; + + return ( +
+
+ {/* Breadcrumb */} + + + {/* Category Header */} +
+
+
+ +
+
+

+ {categoryData.title} +

+

+ {categoryData.description} +

+
+
+

+ {articles.length} {t("kb.articles")} +

+
+ + {/* Articles List */} + {articles.length > 0 ? ( + + ) : ( +
+ +

{t("kb.empty.title")}

+

+ {t("kb.empty.description")} +

+
+ )} +
+
+ ); +} diff --git a/app/kb/layout.tsx b/app/kb/layout.tsx new file mode 100644 index 0000000..c105b1b --- /dev/null +++ b/app/kb/layout.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from "react"; +import { Navigation } from "@/packages/ui/components/Static/navigation"; +import { Footer } from "@/packages/ui/components/Static/footer"; + +interface KBLayoutProps { + children: ReactNode; +} + +export default function KBLayout({ children }: KBLayoutProps) { + return ( + <> +
{children}
+ + ); +} diff --git a/app/kb/page.tsx b/app/kb/page.tsx new file mode 100644 index 0000000..6a3a372 --- /dev/null +++ b/app/kb/page.tsx @@ -0,0 +1,180 @@ +import { Metadata } from "next"; +import { Book, HelpCircle, MessageSquare, Ticket } from "lucide-react"; +import Link from "next/link"; +import { getTranslations } from "next-intl/server"; +import { getCategories, getAllArticles } from "@/packages/kb/lib/kb"; +import { KBCategoryGrid } from "@/packages/kb/components/kb-category-card"; +import { KBSearch } from "@/packages/kb/components/kb-search"; +import { KBArticleList } from "@/packages/kb/components/kb-article-card"; +import { Button } from "@/packages/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/packages/ui/components/ui/card"; + +export async function generateMetadata(): Promise { + const t = await getTranslations(); + return { + title: `${t("kb.title")} | NodeByte Hosting`, + description: t("kb.description"), + }; +} + +export default async function KnowledgeBasePage() { + const t = await getTranslations(); + const categories = await getCategories(); + const allArticles = await getAllArticles(); + + // Get recent articles (last 5 by date) + const recentArticles = [...allArticles] + .sort((a, b) => { + const dateA = new Date(a.lastUpdated || 0).getTime(); + const dateB = new Date(b.lastUpdated || 0).getTime(); + return dateB - dateA; + }) + .slice(0, 5); + + // Prepare search data + const searchArticles = allArticles.map((article) => ({ + slug: article.slug, + category: article.categorySlug, + title: article.title, + description: article.description, + excerpt: article.excerpt || "", + tags: article.tags || [], + readingTime: article.readingTime, + score: 0, + })); + + return ( +
+ {/* Hero Section */} +
+
+
+
+ +
+

+ {t("kb.title")} +

+

+ {t("kb.description")} +

+ + {/* Search */} +
+ +
+
+
+
+ + {/* Categories Section */} +
+
+
+

{t("kb.browseByCategory")}

+
+ +
+
+ + {/* Recent Articles */} + {recentArticles.length > 0 && ( +
+
+
+

+ {t("kb.recentlyUpdated")} +

+
+ +
+
+ )} + + {/* Help Section */} +
+
+
+

+ {t("kb.help.title")} +

+

+ {t("kb.help.description")} +

+
+ +
+ + +
+ +
+ {t("kb.help.discord.title")} + + {t("kb.help.discord.description")} + +
+ + + +
+ + + +
+ +
+ {t("kb.help.ticket.title")} + + {t("kb.help.ticket.description")} + +
+ + + +
+ + + +
+ +
+ {t("kb.help.faq.title")} + + {t("kb.help.faq.description")} + +
+ + + +
+
+
+
+
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx index 666fcc2..ef3cf00 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,9 +3,15 @@ import type { Metadata } from "next" import { cookies } from "next/headers" import { Geist, Geist_Mono } from "next/font/google" import { Analytics } from "@vercel/analytics/next" +import { NextIntlClientProvider } from "next-intl" +import { getMessages, getLocale } from "next-intl/server" import "./globals.css" -import { Toaster } from "@/components/ui/toaster" -import { ThemeProvider } from "@/components/theme-provider" +import { Toaster } from "@/packages/ui/components/ui/toaster" +import { ThemeProvider } from "@/packages/ui/components/theme-provider" +import { CurrencyProvider } from "@/packages/core/hooks/use-currency" +import { LocaleProvider } from "@/packages/core/hooks/use-locale" +import { LayoutChrome } from "@/packages/ui/components/layout-chrome" +import { AuthProvider } from "@/packages/auth/components" const geist = Geist({ subsets: ["latin"], @@ -18,8 +24,55 @@ const geistMono = Geist_Mono({ }) export const metadata: Metadata = { - title: "NodeByte Hosting", - description: "Fast, reliable, scalable and secure hosting services for your gaming experience. Launch dedicated and managed game servers (Minecraft, Rust) with instant setup, DDoS protection and global low-latency networking." + title: { + default: "NodeByte Hosting", + template: "%s | NodeByte Hosting", + }, + description: "Fast, reliable, scalable and secure hosting services for your gaming experience. Launch dedicated and managed game servers with instant setup, DDoS protection and low latency networking.", + metadataBase: new URL("https://nodebyte.host"), + keywords: ["game server hosting", "minecraft hosting", "rust server hosting", "hytale hosting", "ark server hosting", "dedicated servers", "vps hosting", "ddos protection", "low latency gaming", "cloud servers"], + applicationName: "NodeByte Hosting", + openGraph: { + siteName: "NodeByte Hosting", + description: "Fast, reliable, scalable and secure hosting services for your gaming experience. Launch dedicated and managed game servers with instant setup, DDoS protection and low latency networking.", + images: ["/og.png"], + creators: ["@CodeMeAPixel"], + locale: "en-US", + url: "https://nodebyte.host", + }, + twitter: { + title: "NodeByte Hosting", + description: "Fast, reliable, scalable and secure hosting services for your gaming experience. Launch dedicated and managed game servers with instant setup, DDoS protection and low latency networking.", + images: ["/og.png"], + creator: "@CodeMeAPixel", + card: "summary_large_image", + site: "https://nodebyte.host", + }, + appleWebApp: { + statusBarStyle: "black-translucent", + title: "NodeByte Hosting", + }, + formatDetection: { + telephone: false, + }, + icons: { + icon: "/favicon.ico", + shortcut: "/logo.png", + apple: "/logo.png", + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-snippet": -1, + "max-video-preview": -1, + } + }, + other: { + "mobile-web-app-capable": "yes", + } } export default async function RootLayout({ @@ -33,18 +86,38 @@ export default async function RootLayout({ const knownThemes = ["light", "dark", "midnight", "rose", "forest", "desert", "ocean"] const themeClass = themeCookie && knownThemes.includes(themeCookie) ? themeCookie : undefined - const htmlClass = [geist.variable, geistMono.variable, themeClass].filter(Boolean).join(" ") + // Get locale and messages for next-intl + const locale = await getLocale() + const messages = await getMessages() - // If we know the theme server-side, set color-scheme so SSR HTML matches client - const darkThemes = ["light", "dark", "midnight", "forest", "rose", "desert", "ocean"] - const colorScheme = themeClass ? (darkThemes.includes(themeClass) ? 'dark' : 'light') : undefined + const htmlClass = [geist.variable, geistMono.variable, themeClass].filter(Boolean).join(" ") return ( - - - - {children} - + + + {/* Prevent browser translation extensions from modifying the page */} + + + + + + + + + + {children} + + + + + + diff --git a/app/not-found.tsx b/app/not-found.tsx new file mode 100644 index 0000000..7cda811 --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,5 @@ +import { NotFoundPage } from "@/packages/ui/components/Layouts/Error" + +export default function NotFound() { + return +} diff --git a/app/page.tsx b/app/page.tsx index ee786bf..d0e72ff 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,13 +1,11 @@ "use client" import { useEffect } from "react" -import { Hero } from "@/components/Layouts/Home/hero" -import { Features } from "@/components/Layouts/Home/features" -import { About } from "@/components/Layouts/Home/about" -import { Download } from "@/components/Layouts/Home/games" -import { FAQ } from "@/components/Layouts/Home/faq" -import { Footer } from "@/components/Static/footer" -import { Navigation } from "@/components/Static/navigation" +import { Hero } from "@/packages/ui/components/Layouts/Home/hero" +import { Features } from "@/packages/ui/components/Layouts/Home/features" +import { About } from "@/packages/ui/components/Layouts/Home/about" +import { Download } from "@/packages/ui/components/Layouts/Home/games" +import { FAQ } from "@/packages/ui/components/Layouts/Home/faq" export default function Home() { useEffect(() => { @@ -29,14 +27,12 @@ export default function Home() { }, []) return ( -
- + <> -
-
+ ) } \ No newline at end of file diff --git a/bun.lock b/bun.lock index e7a90ab..eaaf5a7 100644 --- a/bun.lock +++ b/bun.lock @@ -1,11 +1,12 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "defyx", "dependencies": { "@hookform/resolvers": "^3.10.0", + "@prisma/adapter-pg": "^7.2.0", + "@prisma/client": "^7.2.0", "@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-alert-dialog": "1.1.4", "@radix-ui/react-aspect-ratio": "1.1.1", @@ -37,23 +38,38 @@ "@react-three/fiber": "9.4.0", "@vercel/analytics": "latest", "autoprefixer": "^10.4.20", + "bcryptjs": "^3.0.3", "cheerio": "^1.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.4", + "country-flag-icons": "^1.6.4", "date-fns": "4.1.0", "embla-carousel-react": "8.5.1", + "gray-matter": "^4.0.3", "input-otp": "1.4.1", "lucide-react": "^0.454.0", - "next": "16.0.0", + "next": "^16.1.0", + "next-auth": "^5.0.0-beta.30", + "next-intl": "^4.6.1", "next-themes": "^0.4.6", - "react": "19.2.0", + "prisma": "^7.2.0", + "react": "^19.2.3", "react-day-picker": "9.8.0", - "react-dom": "19.2.0", + "react-dom": "^19.2.3", "react-hook-form": "^7.60.0", "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", "react-resizable-panels": "^2.1.7", + "reading-time": "^1.5.0", "recharts": "2.15.4", + "rehype-autolink-headings": "^7.1.0", + "rehype-highlight": "^7.0.2", + "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", + "remark": "^15.0.1", + "remark-gfm": "^4.0.1", + "remark-rehype": "^11.1.2", "sonner": "^1.7.4", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", @@ -61,11 +77,20 @@ "zod": "3.25.76", }, "devDependencies": { + "@commitlint/cli": "^20.2.0", + "@commitlint/config-conventional": "^20.2.0", "@tailwindcss/postcss": "^4.1.9", + "@tailwindcss/typography": "^0.5.19", + "@types/bcryptjs": "^3.0.0", "@types/node": "^22", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "commitlint": "^20.2.0", + "eslint-config-next": "^16.1.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.7", "postcss": "^8.5", + "prettier": "^3.7.4", "tailwindcss": "^4.1.9", "tw-animate-css": "1.3.3", "typescript": "^5", @@ -75,14 +100,116 @@ "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@auth/core": ["@auth/core@0.41.0", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^6.8.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], + + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], + + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@10.5.0", "", { "dependencies": { "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw=="], + + "@chevrotain/gast": ["@chevrotain/gast@10.5.0", "", { "dependencies": { "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A=="], + + "@chevrotain/types": ["@chevrotain/types@10.5.0", "", {}, "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A=="], + + "@chevrotain/utils": ["@chevrotain/utils@10.5.0", "", {}, "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ=="], + + "@commitlint/cli": ["@commitlint/cli@20.2.0", "", { "dependencies": { "@commitlint/format": "^20.2.0", "@commitlint/lint": "^20.2.0", "@commitlint/load": "^20.2.0", "@commitlint/read": "^20.2.0", "@commitlint/types": "^20.2.0", "tinyexec": "^1.0.0", "yargs": "^17.0.0" }, "bin": { "commitlint": "./cli.js" } }, "sha512-l37HkrPZ2DZy26rKiTUvdq/LZtlMcxz+PeLv9dzK9NzoFGuJdOQyYU7IEkEQj0pO++uYue89wzOpZ0hcTtoqUA=="], + + "@commitlint/config-conventional": ["@commitlint/config-conventional@20.2.0", "", { "dependencies": { "@commitlint/types": "^20.2.0", "conventional-changelog-conventionalcommits": "^7.0.2" } }, "sha512-MsRac+yNIbTB4Q/psstKK4/ciVzACHicSwz+04Sxve+4DW+PiJeTjU0JnS4m/oOnulrXYN+yBPlKaBSGemRfgQ=="], + + "@commitlint/config-validator": ["@commitlint/config-validator@20.2.0", "", { "dependencies": { "@commitlint/types": "^20.2.0", "ajv": "^8.11.0" } }, "sha512-SQCBGsL9MFk8utWNSthdxd9iOD1pIVZSHxGBwYIGfd67RTjxqzFOSAYeQVXOu3IxRC3YrTOH37ThnTLjUlyF2w=="], + + "@commitlint/ensure": ["@commitlint/ensure@20.2.0", "", { "dependencies": { "@commitlint/types": "^20.2.0", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", "lodash.startcase": "^4.4.0", "lodash.upperfirst": "^4.3.1" } }, "sha512-+8TgIGv89rOWyt3eC6lcR1H7hqChAKkpawytlq9P1i/HYugFRVqgoKJ8dhd89fMnlrQTLjA5E97/4sF09QwdoA=="], + + "@commitlint/execute-rule": ["@commitlint/execute-rule@20.0.0", "", {}, "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw=="], + + "@commitlint/format": ["@commitlint/format@20.2.0", "", { "dependencies": { "@commitlint/types": "^20.2.0", "chalk": "^5.3.0" } }, "sha512-PhNoLNhxpfIBlW/i90uZ3yG3hwSSYx7n4d9Yc+2FAorAHS0D9btYRK4ZZXX+Gm3W5tDtu911ow/eWRfcRVgNWg=="], + + "@commitlint/is-ignored": ["@commitlint/is-ignored@20.2.0", "", { "dependencies": { "@commitlint/types": "^20.2.0", "semver": "^7.6.0" } }, "sha512-Lz0OGeZCo/QHUDLx5LmZc0EocwanneYJUM8z0bfWexArk62HKMLfLIodwXuKTO5y0s6ddXaTexrYHs7v96EOmw=="], + + "@commitlint/lint": ["@commitlint/lint@20.2.0", "", { "dependencies": { "@commitlint/is-ignored": "^20.2.0", "@commitlint/parse": "^20.2.0", "@commitlint/rules": "^20.2.0", "@commitlint/types": "^20.2.0" } }, "sha512-cQEEB+jlmyQbyiji/kmh8pUJSDeUmPiWq23kFV0EtW3eM+uAaMLMuoTMajbrtWYWQpPzOMDjYltQ8jxHeHgITg=="], + + "@commitlint/load": ["@commitlint/load@20.2.0", "", { "dependencies": { "@commitlint/config-validator": "^20.2.0", "@commitlint/execute-rule": "^20.0.0", "@commitlint/resolve-extends": "^20.2.0", "@commitlint/types": "^20.2.0", "chalk": "^5.3.0", "cosmiconfig": "^9.0.0", "cosmiconfig-typescript-loader": "^6.1.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "lodash.uniq": "^4.5.0" } }, "sha512-iAK2GaBM8sPFTSwtagI67HrLKHIUxQc2BgpgNc/UMNme6LfmtHpIxQoN1TbP+X1iz58jq32HL1GbrFTCzcMi6g=="], + + "@commitlint/message": ["@commitlint/message@20.0.0", "", {}, "sha512-gLX4YmKnZqSwkmSB9OckQUrI5VyXEYiv3J5JKZRxIp8jOQsWjZgHSG/OgEfMQBK9ibdclEdAyIPYggwXoFGXjQ=="], + + "@commitlint/parse": ["@commitlint/parse@20.2.0", "", { "dependencies": { "@commitlint/types": "^20.2.0", "conventional-changelog-angular": "^7.0.0", "conventional-commits-parser": "^5.0.0" } }, "sha512-LXStagGU1ivh07X7sM+hnEr4BvzFYn1iBJ6DRg2QsIN8lBfSzyvkUcVCDwok9Ia4PWiEgei5HQjju6xfJ1YaSQ=="], + + "@commitlint/read": ["@commitlint/read@20.2.0", "", { "dependencies": { "@commitlint/top-level": "^20.0.0", "@commitlint/types": "^20.2.0", "git-raw-commits": "^4.0.0", "minimist": "^1.2.8", "tinyexec": "^1.0.0" } }, "sha512-+SjF9mxm5JCbe+8grOpXCXMMRzAnE0WWijhhtasdrpJoAFJYd5UgRTj/oCq5W3HJTwbvTOsijEJ0SUGImECD7Q=="], + + "@commitlint/resolve-extends": ["@commitlint/resolve-extends@20.2.0", "", { "dependencies": { "@commitlint/config-validator": "^20.2.0", "@commitlint/types": "^20.2.0", "global-directory": "^4.0.1", "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", "resolve-from": "^5.0.0" } }, "sha512-KVoLDi9BEuqeq+G0wRABn4azLRiCC22/YHR2aCquwx6bzCHAIN8hMt3Nuf1VFxq/c8ai6s8qBxE8+ZD4HeFTlQ=="], + + "@commitlint/rules": ["@commitlint/rules@20.2.0", "", { "dependencies": { "@commitlint/ensure": "^20.2.0", "@commitlint/message": "^20.0.0", "@commitlint/to-lines": "^20.0.0", "@commitlint/types": "^20.2.0" } }, "sha512-27rHGpeAjnYl/A+qUUiYDa7Yn1WIjof/dFJjYW4gA1Ug+LUGa1P0AexzGZ5NBxTbAlmDgaxSZkLLxtLVqtg8PQ=="], + + "@commitlint/to-lines": ["@commitlint/to-lines@20.0.0", "", {}, "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw=="], + + "@commitlint/top-level": ["@commitlint/top-level@20.0.0", "", { "dependencies": { "find-up": "^7.0.0" } }, "sha512-drXaPSP2EcopukrUXvUXmsQMu3Ey/FuJDc/5oiW4heoCfoE5BdLQyuc7veGeE3aoQaTVqZnh4D5WTWe2vefYKg=="], + + "@commitlint/types": ["@commitlint/types@20.2.0", "", { "dependencies": { "@types/conventional-commits-parser": "^5.0.0", "chalk": "^5.3.0" } }, "sha512-KTy0OqRDLR5y/zZMnizyx09z/rPlPC/zKhYgH8o/q6PuAjoQAKlRfY4zzv0M64yybQ//6//4H1n14pxaLZfUnA=="], + "@date-fns/tz": ["@date-fns/tz@1.2.0", "", {}, "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg=="], "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="], + "@electric-sql/pglite": ["@electric-sql/pglite@0.3.2", "", {}, "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w=="], + + "@electric-sql/pglite-socket": ["@electric-sql/pglite-socket@0.0.6", "", { "peerDependencies": { "@electric-sql/pglite": "0.3.2" }, "bin": { "pglite-server": "dist/scripts/server.js" } }, "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw=="], + + "@electric-sql/pglite-tools": ["@electric-sql/pglite-tools@0.2.7", "", { "peerDependencies": { "@electric-sql/pglite": "0.3.2" } }, "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg=="], + + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], + + "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], @@ -91,8 +218,28 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], + "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.6", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw=="], + + "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="], + + "@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.4", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/icu-skeleton-parser": "1.8.16", "tslib": "^2.8.0" } }, "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw=="], + + "@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.16", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "tslib": "^2.8.0" } }, "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ=="], + + "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.5.10", "", { "dependencies": { "tslib": "2" } }, "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q=="], + + "@hono/node-server": ["@hono/node-server@1.19.6", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw=="], + "@hookform/resolvers": ["@hookform/resolvers@3.10.0", "", { "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -157,23 +304,93 @@ "@monogrid/gainmap-js": ["@monogrid/gainmap-js@3.4.0", "", { "dependencies": { "promise-worker-transferable": "^1.0.4" }, "peerDependencies": { "three": ">= 0.159.0" } }, "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg=="], - "@next/env": ["@next/env@16.0.0", "", {}, "sha512-s5j2iFGp38QsG1LWRQaE2iUY3h1jc014/melHFfLdrsMJPqxqDQwWNwyQTcNoUSGZlCVZuM7t7JDMmSyRilsnA=="], + "@mrleebo/prisma-ast": ["@mrleebo/prisma-ast@0.12.1", "", { "dependencies": { "chevrotain": "^10.5.0", "lilconfig": "^2.1.0" } }, "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@next/env": ["@next/env@16.1.0", "", {}, "sha512-Dd23XQeFHmhf3KBW76leYVkejHlCdB7erakC2At2apL1N08Bm+dLYNP+nNHh0tzUXfPQcNcXiQyacw0PG4Fcpw=="], + + "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.1.0", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-sooC/k0LCF4/jLXYHpgfzJot04lZQqsttn8XJpTguP8N3GhqXN3wSkh68no2OcZzS/qeGwKDFTqhZ8WofdXmmQ=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/CntqDCnk5w2qIwMiF0a9r6+9qunZzFmU0cBX4T82LOflE72zzH6gnOjCwUXYKOBlQi8OpP/rMj8cBIr18x4TA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-onHq8dl8KjDb8taANQdzs3XmIqQWV3fYdslkGENuvVInFQzZnuBYYOG2HGHqqtvgmEU7xWzhgndXXxnhk4Z3fQ=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-hB4GZnJGKa8m4efvTGNyii6qs76vTNl+3dKHTCAUaksN6KjYy4iEO3Q5ira405NW2PKb3EcqWiRaL9DrYJfMHg=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Am6VJTp8KhLuAH13tPrAoVIXzuComlZlMwGr++o2KDjWiKPe3VwpxYhgV6I4gKls2EnsIMggL4y7GdXyDdJcFA=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-E2IHMdE+C1k+nUgndM13/BY/iJY9KGCphCftMh7SXWcaQqExq/pJU/1Hgn8n/tFwSoLoYC/yUghOv97tAsIxqg=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-fVicfaJT6QfghNyg8JErZ+EMNQ812IS0lmKfbmC01LF1nFBcKfcs4Q75Yy8IqnsCqH/hZwGhqzj3IGVfWV6vpA=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-xzgl7c7BVk4+7PDWldU+On2nlwnGgFqJ1siWp3/8S0KBBLCjonB6zwJYPtl4MUY7YZJrzzumdUpUoquu5zk8vg=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TojQnDRoX7wJWXEEwdfuJtakMDW64Q7NrxQPviUnfYJvAx5/5wcGE+1vZzQ9F17m+SdpFeeXuOr6v3jbyusYMQ=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-sdyOg4cbiCw7YUr0F/7ya42oiVBXLD21EYkSwN+PhE4csJH4MSXUsYyslliiiBwkM+KsuQH/y9wuxVz6s7Nstg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-quhNFVySW4QwXiZkZ34SbfzNBm27vLrxZ2HwTfFFO1BBP0OY1+pI0nbyewKeq1FriqU+LZrob/cm26lwsiAi8Q=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-IAXv3OBYqVaNOgyd3kxR4L3msuhmSy1bcchPHxDOjypG33i2yDWvGBwFD94OuuTjjTt/7cuIKtAmoOOml6kfbg=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-6JW0z2FZUK5iOVhUIWqE4RblAhUj1EwhZ/MwteGb//SpFTOHydnhbp3868gxalwea+mbOLWO6xgxj9wA9wNvNw=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-bmo3ncIJKUS9PWK1JD9pEVv0yuvp1KPuOsyJTHXTv8KDrEmgV/K+U0C75rl9rhIaODcS7JEb6/7eJhdwXI0XmA=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-+DK/akkAvvXn5RdYN84IOmLkSy87SCmpofJPdB8vbLmf01BzntPBSYXnMvnEEv/Vcf3HYJwt24QZ/s6sWAwOMQ=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.0", "", { "os": "win32", "cpu": "x64" }, "sha512-O1cJbT+lZp+cTjYyZGiDwsOjO3UHHzSqobkPNipdlnnuPb1swfcuY6r3p8dsKU4hAIEO4cO67ZCfVVH/M1ETXA=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Tr0j94MphimCCks+1rtYPzQFK+faJuhHWCegU9S9gDlgyOk8Y3kPmO64UcjyzZAlligeBtYZ/2bEyrKq0d2wqQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], + + "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], + + "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], + + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="], + + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="], + + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="], + + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="], + + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="], + + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="], + + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="], + + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="], + + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="], + + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="], + + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="], + + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="], + + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="], + + "@prisma/adapter-pg": ["@prisma/adapter-pg@7.2.0", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.2.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w=="], + + "@prisma/client": ["@prisma/client@7.2.0", "", { "dependencies": { "@prisma/client-runtime-utils": "7.2.0" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA=="], + + "@prisma/client-runtime-utils": ["@prisma/client-runtime-utils@7.2.0", "", {}, "sha512-dn7oB53v0tqkB0wBdMuTNFNPdEbfICEUe82Tn9FoKAhJCUkDH+fmyEp0ClciGh+9Hp2Tuu2K52kth2MTLstvmA=="], + + "@prisma/config": ["@prisma/config@7.2.0", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ=="], + + "@prisma/debug": ["@prisma/debug@7.2.0", "", {}, "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw=="], + + "@prisma/dev": ["@prisma/dev@0.17.0", "", { "dependencies": { "@electric-sql/pglite": "0.3.2", "@electric-sql/pglite-socket": "0.0.6", "@electric-sql/pglite-tools": "0.2.7", "@hono/node-server": "1.19.6", "@mrleebo/prisma-ast": "0.12.1", "@prisma/get-platform": "6.8.2", "@prisma/query-plan-executor": "6.18.0", "foreground-child": "3.3.1", "get-port-please": "3.1.2", "hono": "4.10.6", "http-status-codes": "2.3.0", "pathe": "2.0.3", "proper-lockfile": "4.1.2", "remeda": "2.21.3", "std-env": "3.9.0", "valibot": "1.2.0", "zeptomatch": "2.0.2" } }, "sha512-6sGebe5jxX+FEsQTpjHLzvOGPn6ypFQprcs3jcuIWv1Xp/5v6P/rjfdvAwTkP2iF6pDx2tCd8vGLNWcsWzImTA=="], + + "@prisma/driver-adapter-utils": ["@prisma/driver-adapter-utils@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0" } }, "sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ=="], + + "@prisma/engines": ["@prisma/engines@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0", "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", "@prisma/fetch-engine": "7.2.0", "@prisma/get-platform": "7.2.0" } }, "sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA=="], + + "@prisma/engines-version": ["@prisma/engines-version@7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", "", {}, "sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA=="], + + "@prisma/fetch-engine": ["@prisma/fetch-engine@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0", "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", "@prisma/get-platform": "7.2.0" } }, "sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w=="], + + "@prisma/get-platform": ["@prisma/get-platform@6.8.2", "", { "dependencies": { "@prisma/debug": "6.8.2" } }, "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow=="], + + "@prisma/query-plan-executor": ["@prisma/query-plan-executor@6.18.0", "", {}, "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA=="], + + "@prisma/studio-core": ["@prisma/studio-core@0.9.0", "", { "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-xA2zoR/ADu/NCSQuriBKTh6Ps4XjU0bErkEcgMfnSGh346K1VI7iWKnoq1l2DoxUqiddPHIEWwtxJ6xCHG6W7g=="], "@radix-ui/number": ["@radix-ui/number@1.1.0", "", {}, "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="], @@ -285,8 +502,40 @@ "@react-three/fiber": ["@react-three/fiber@9.4.0", "", { "dependencies": { "@babel/runtime": "^7.17.8", "@types/react-reconciler": "^0.32.0", "@types/webxr": "*", "base64-js": "^1.5.1", "buffer": "^6.0.3", "its-fine": "^2.0.0", "react-reconciler": "^0.31.0", "react-use-measure": "^2.1.7", "scheduler": "^0.25.0", "suspend-react": "^0.1.3", "use-sync-external-store": "^1.4.0", "zustand": "^5.0.3" }, "peerDependencies": { "expo": ">=43.0", "expo-asset": ">=8.4", "expo-file-system": ">=11.0", "expo-gl": ">=11.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-native": ">=0.78", "three": ">=0.156" }, "optionalPeers": ["expo", "expo-asset", "expo-file-system", "expo-gl", "react-dom", "react-native"] }, "sha512-k4iu1R6e5D54918V4sqmISUkI5OgTw3v7/sDRKEC632Wd5g2WBtUS5gyG63X0GJO/HZUj1tsjSXfyzwrUHZl1g=="], + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], + + "@schummar/icu-type-parser": ["@schummar/icu-type-parser@1.21.5", "", {}, "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@swc/core": ["@swc/core@1.15.7", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.7", "@swc/core-darwin-x64": "1.15.7", "@swc/core-linux-arm-gnueabihf": "1.15.7", "@swc/core-linux-arm64-gnu": "1.15.7", "@swc/core-linux-arm64-musl": "1.15.7", "@swc/core-linux-x64-gnu": "1.15.7", "@swc/core-linux-x64-musl": "1.15.7", "@swc/core-win32-arm64-msvc": "1.15.7", "@swc/core-win32-ia32-msvc": "1.15.7", "@swc/core-win32-x64-msvc": "1.15.7" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-kTGB8XI7P+pTKW83tnUEDVP4zduF951u3UAOn5eTi0vyW6MvL56A3+ggMdfuVFtDI0/DsbSzf5z34HVBbuScWw=="], + + "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+hNVUfezUid7LeSHqnhoC6Gh3BROABxjlDNInuZ/fie1RUxaEX4qzDwdTgozJELgHhvYxyPIg1ro8ibnKtgO4g=="], + + "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.15.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZAFuvtSYZTuXPcrhanaD5eyp27H8LlDzx2NAeVyH0FchYcuXf0h5/k3GL9ZU6Jw9eQ63R1E8KBgpXEJlgRwZUQ=="], + + "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.15.7", "", { "os": "linux", "cpu": "arm" }, "sha512-K3HTYocpqnOw8KcD8SBFxiDHjIma7G/X+bLdfWqf+qzETNBrzOub/IEkq9UaeupaJiZJkPptr/2EhEXXWryS/A=="], + + "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.15.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-HCnVIlsLnCtQ3uXcXgWrvQ6SAraskLA9QJo9ykTnqTH6TvUYqEta+TdTdGjzngD6TOE7XjlAiUs/RBtU8Z0t+Q=="], + + "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.15.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-/OOp9UZBg4v2q9+x/U21Jtld0Wb8ghzBScwhscI7YvoSh4E8RALaJ1msV8V8AKkBkZH7FUAFB7Vbv0oVzZsezA=="], + + "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.15.7", "", { "os": "linux", "cpu": "x64" }, "sha512-VBbs4gtD4XQxrHuQ2/2+TDZpPQQgrOHYRnS6SyJW+dw0Nj/OomRqH+n5Z4e/TgKRRbieufipeIGvADYC/90PYQ=="], + + "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.15.7", "", { "os": "linux", "cpu": "x64" }, "sha512-kVuy2unodso6p0rMauS2zby8/bhzoGRYxBDyD6i2tls/fEYAE74oP0VPFzxIyHaIjK1SN6u5TgvV9MpyJ5xVug=="], + + "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.15.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-uddYoo5Xmo1XKLhAnh4NBIyy5d0xk33x1sX3nIJboFySLNz878ksCFCZ3IBqrt1Za0gaoIWoOSSSk0eNhAc/sw=="], + + "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.15.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-rqq8JjNMLx3QNlh0aPTtN/4+BGLEHC94rj9mkH1stoNRf3ra6IksNHMHy+V1HUqElEgcZyx+0yeXx3eLOTcoFw=="], + + "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.15.7", "", { "os": "win32", "cpu": "x64" }, "sha512-4BK06EGdPnuplgcNhmSbOIiLdRgHYX3v1nl4HXo5uo4GZMfllXaCyBUes+0ePRfwbn9OFgVhCWPcYYjMT6hycQ=="], + + "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + "@swc/types": ["@swc/types@0.1.25", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g=="], + "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="], @@ -317,8 +566,16 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", "tailwindcss": "4.1.17" } }, "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw=="], + "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], + "@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="], + + "@types/conventional-commits-parser": ["@types/conventional-commits-parser@5.0.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g=="], + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -337,8 +594,24 @@ "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/draco3d": ["@types/draco3d@1.4.10", "", {}, "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="], "@types/offscreencanvas": ["@types/offscreencanvas@2019.7.3", "", {}, "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="], @@ -353,8 +626,70 @@ "@types/three": ["@types/three@0.181.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.22.0" } }, "sha512-MLF1ks8yRM2k71D7RprFpDb9DOX0p22DbdPqT/uAkc6AtQXjxWCVDjCy23G9t1o8HcQPk7woD2NIyiaWcWPYmA=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/webxr": ["@types/webxr@0.5.24", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.50.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/type-utils": "8.50.0", "@typescript-eslint/utils": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.50.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.50.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.50.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.50.0", "@typescript-eslint/types": "^8.50.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0" } }, "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.50.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/utils": "8.50.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.50.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.50.0", "@typescript-eslint/tsconfig-utils": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.50.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], + + "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="], + + "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g=="], + + "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ=="], + + "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.11.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw=="], + + "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw=="], + + "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw=="], + + "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ=="], + + "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w=="], + + "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.11.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA=="], + + "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ=="], + + "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew=="], + + "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.11.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg=="], + + "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w=="], + + "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA=="], + + "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.11.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ=="], + + "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw=="], + + "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ=="], + + "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "@use-gesture/core": ["@use-gesture/core@10.3.1", "", {}, "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw=="], "@use-gesture/react": ["@use-gesture/react@10.3.1", "", { "dependencies": { "@use-gesture/core": "10.3.1" }, "peerDependencies": { "react": ">= 16.8.0" } }, "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g=="], @@ -363,38 +698,164 @@ "@webgpu/types": ["@webgpu/types@0.1.66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="], + "JSONStream": ["JSONStream@1.3.5", "", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], + + "array-ify": ["array-ify@1.0.0", "", {}, "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng=="], + + "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], + + "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], + + "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="], + + "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], + + "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], + + "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], + + "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], + + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + "autoprefixer": ["autoprefixer@10.4.22", "", { "dependencies": { "browserslist": "^4.27.0", "caniuse-lite": "^1.0.30001754", "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg=="], + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + + "axe-core": ["axe-core@4.11.0", "", {}, "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.8.31", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw=="], + "bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="], + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], + + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camera-controls": ["camera-controls@3.1.2", "", { "peerDependencies": { "three": ">=0.126.1" } }, "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA=="], "caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="], + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "cheerio": ["cheerio@1.1.2", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="], "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], + "chevrotain": ["chevrotain@10.5.0", "", { "dependencies": { "@chevrotain/cst-dts-gen": "10.5.0", "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "@chevrotain/utils": "10.5.0", "lodash": "4.17.21", "regexp-to-ast": "0.5.0" } }, "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="], + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "cmdk": ["cmdk@1.0.4", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.0", "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "commitlint": ["commitlint@20.2.0", "", { "dependencies": { "@commitlint/cli": "^20.2.0", "@commitlint/types": "^20.2.0" }, "bin": { "commitlint": "cli.js" } }, "sha512-AxjKCK4PVgd3RD9MzW3nSVR6DfJPXqRi2n9Gkyq1tPrJNxq0WWqLBhHKOcwXvVCMJjameyeALV2xbYYDHlL5fg=="], + + "compare-func": ["compare-func@2.0.0", "", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], + + "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@7.0.2", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w=="], + + "conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], + + "cosmiconfig-typescript-loader": ["cosmiconfig-typescript-loader@6.2.0", "", { "dependencies": { "jiti": "^2.6.1" }, "peerDependencies": { "@types/node": "*", "cosmiconfig": ">=9", "typescript": ">=5" } }, "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ=="], + + "country-flag-icons": ["country-flag-icons@1.6.4", "", {}, "sha512-Z3Zi419FI889tlElMsVhCIS5eRkiLDWixr576J5DPiTe5RGxpbRi+enMpHdYVp5iK5WFjr8P/RgyIFAGhFsiFg=="], + "cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -403,6 +864,8 @@ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], @@ -427,18 +890,54 @@ "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], + + "dargs": ["dargs@8.1.0", "", {}, "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw=="], + + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], + + "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], + + "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + "detect-gpu": ["detect-gpu@5.0.70", "", { "dependencies": { "webgl-constants": "^1.1.1" } }, "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], @@ -449,8 +948,16 @@ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], + + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "draco3d": ["draco3d@1.5.7", "", {}, "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.262", "", {}, "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ=="], "embla-carousel": ["embla-carousel@8.5.1", "", {}, "sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A=="], @@ -459,52 +966,366 @@ "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.5.1", "", { "peerDependencies": { "embla-carousel": "8.5.1" } }, "sha512-n7VSoGIiiDIc4MfXF3ZRTO59KDp820QDuyBDGlt5/65+lumPHxX2JLz0EZ23hZ4eg4vZGUXwMkYv02fw2JVo/A=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], - "fast-equals": ["fast-equals@5.3.3", "", {}, "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw=="], + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - "fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="], + "es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="], - "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "glsl-noise": ["glsl-noise@0.0.0", "", {}, "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="], + "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - "hls.js": ["hls.js@1.6.15", "", {}, "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="], + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], + "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], + + "eslint-config-next": ["eslint-config-next@16.1.0", "", { "dependencies": { "@next/eslint-plugin-next": "16.1.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-RlPb8E2uO/Ix/w3kizxz6+6ogw99WqtNzTG0ArRZ5NEkIYcsfRb8U0j7aTG7NjRvcrsak5QtUSuxGNN2UcA58g=="], + + "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], + + "eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="], + + "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], + + "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="], + + "eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="], + + "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-equals": ["fast-equals@5.3.3", "", {}, "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw=="], + + "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], + + "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + + "get-port-please": ["get-port-please@3.1.2", "", {}, "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], + + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + + "git-raw-commits": ["git-raw-commits@4.0.0", "", { "dependencies": { "dargs": "^8.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.mjs" } }, "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ=="], + + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "global-directory": ["global-directory@4.0.1", "", { "dependencies": { "ini": "4.1.1" } }, "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q=="], + + "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], + + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + + "glsl-noise": ["glsl-noise@0.0.0", "", {}, "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "grammex": ["grammex@3.1.12", "", {}, "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ=="], + + "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], + + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hast-util-heading-rank": ["hast-util-heading-rank@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA=="], + + "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], + + "hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], + + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + + "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], + + "hls.js": ["hls.js@1.6.15", "", {}, "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="], + + "hono": ["hono@4.10.6", "", {}, "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g=="], + + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], + + "http-status-codes": ["http-status-codes@2.3.0", "", {}, "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="], + + "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + "input-otp": ["input-otp@1.4.1", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "intl-messageformat": ["intl-messageformat@10.7.18", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.4", "tslib": "^2.8.0" } }, "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g=="], + + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], + + "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], + + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], + + "is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], + + "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], + + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], + + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + + "is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="], + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], + + "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], + + "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], + + "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], + + "is-text-path": ["is-text-path@2.0.0", "", { "dependencies": { "text-extensions": "^2.0.0" } }, "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], + + "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], + + "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], + + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + "its-fine": ["its-fine@2.0.0", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng=="], "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], + + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + + "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], + + "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], @@ -531,59 +1352,329 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "lint-staged": ["lint-staged@16.2.7", "", { "dependencies": { "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow=="], + + "listr2": ["listr2@9.0.5", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.kebabcase": ["lodash.kebabcase@4.1.1", "", {}, "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lodash.mergewith": ["lodash.mergewith@4.6.2", "", {}, "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="], + + "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], + + "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "lodash.upperfirst": ["lodash.upperfirst@4.3.1", "", {}, "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg=="], + + "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lru.min": ["lru.min@1.1.3", "", {}, "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q=="], + "lucide-react": ["lucide-react@0.454.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ=="], "maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "meow": ["meow@12.1.1", "", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "meshline": ["meshline@3.3.1", "", { "peerDependencies": { "three": ">=0.137" } }, "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ=="], "meshoptimizer": ["meshoptimizer@0.22.0", "", {}, "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg=="], + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mysql2": ["mysql2@3.15.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg=="], + + "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + + "nano-spawn": ["nano-spawn@2.0.0", "", {}, "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw=="], + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "next": ["next@16.0.0", "", { "dependencies": { "@next/env": "16.0.0", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.0", "@next/swc-darwin-x64": "16.0.0", "@next/swc-linux-arm64-gnu": "16.0.0", "@next/swc-linux-arm64-musl": "16.0.0", "@next/swc-linux-x64-gnu": "16.0.0", "@next/swc-linux-x64-musl": "16.0.0", "@next/swc-win32-arm64-msvc": "16.0.0", "@next/swc-win32-x64-msvc": "16.0.0", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-nYohiNdxGu4OmBzggxy9rczmjIGI+TpR5vbKTsE1HqYwNm1B+YSiugSrFguX6omMOKnDHAmBPY4+8TNJk0Idyg=="], + "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "next": ["next@16.1.0", "", { "dependencies": { "@next/env": "16.1.0", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.0", "@next/swc-darwin-x64": "16.1.0", "@next/swc-linux-arm64-gnu": "16.1.0", "@next/swc-linux-arm64-musl": "16.1.0", "@next/swc-linux-x64-gnu": "16.1.0", "@next/swc-linux-x64-musl": "16.1.0", "@next/swc-win32-arm64-msvc": "16.1.0", "@next/swc-win32-x64-msvc": "16.1.0", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Y+KbmDbefYtHDDQKLNrmzE/YYzG2msqo2VXhzh5yrJ54tx/6TmGdkR5+kP9ma7i7LwZpZMfoY3m/AoPPPKxtVw=="], + + "next-auth": ["next-auth@5.0.0-beta.30", "", { "dependencies": { "@auth/core": "0.41.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", "nodemailer": "^7.0.7", "react": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg=="], + + "next-intl": ["next-intl@4.6.1", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "@parcel/watcher": "^2.4.1", "@swc/core": "^1.15.2", "negotiator": "^1.0.0", "next-intl-swc-plugin-extractor": "^4.6.1", "po-parser": "^2.0.0", "use-intl": "^4.6.1" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KlWgWtKLBPUsTPgxqwyjws1wCMD2QKxLlVjeeGj53DC1JWfKmBShKOrhIP0NznZrRQ0GleeoDUeHSETmyyIFeA=="], + + "next-intl-swc-plugin-extractor": ["next-intl-swc-plugin-extractor@4.6.1", "", {}, "sha512-+HHNeVERfSvuPDF7LYVn3pxst5Rf7EYdUTw7C7WIrYhcLaKiZ1b9oSRkTQddAN3mifDMCfHqO4kAQ/pcKiBl3A=="], "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], + + "oauth4webapi": ["oauth4webapi@3.8.3", "", {}, "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + + "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], + + "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], + + "object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="], + + "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + + "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], + + "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], + + "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], + + "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + + "po-parser": ["po-parser@2.0.0", "", {}, "sha512-SZvoKi3PoI/hHa2V9je9CW7Xgxl4dvO74cvaa6tWShIHT51FkPxje6pt0gTJznJrU67ix91nDaQp2hUxkOYhKA=="], + + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + + "postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="], + "preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="], + + "preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": ">=10" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="], + + "prisma": ["prisma@7.2.0", "", { "dependencies": { "@prisma/config": "7.2.0", "@prisma/dev": "0.17.0", "@prisma/engines": "7.2.0", "@prisma/studio-core": "0.9.0", "mysql2": "3.15.3", "postgres": "3.4.7" }, "peerDependencies": { "better-sqlite3": ">=9.0.0", "typescript": ">=5.4.0" }, "optionalPeers": ["better-sqlite3", "typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg=="], + "promise-worker-transferable": ["promise-worker-transferable@1.0.4", "", { "dependencies": { "is-promise": "^2.1.0", "lie": "^3.0.2" } }, "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw=="], "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], + + "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], "react-day-picker": ["react-day-picker@9.8.0", "", { "dependencies": { "@date-fns/tz": "1.2.0", "date-fns": "4.1.0", "date-fns-jalali": "4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-E0yhhg7R+pdgbl/2toTb0xBhsEAtmAx1l7qjIWYfcxOy8w4rTSVfbtBoSzVVhPwKP/5E9iL38LivzoE3AQDhCQ=="], - "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], "react-hook-form": ["react-hook-form@7.66.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA=="], @@ -591,6 +1682,8 @@ "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="], "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], @@ -607,17 +1700,81 @@ "react-use-measure": ["react-use-measure@2.1.7", "", { "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" }, "optionalPeers": ["react-dom"] }, "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "reading-time": ["reading-time@1.5.0", "", {}, "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg=="], + "recharts": ["recharts@2.15.4", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw=="], "recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], + + "regexp-to-ast": ["regexp-to-ast@0.5.0", "", {}, "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw=="], + + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + + "rehype-autolink-headings": ["rehype-autolink-headings@7.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-is-element": "^3.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw=="], + + "rehype-highlight": ["rehype-highlight@7.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-text": "^4.0.0", "lowlight": "^3.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA=="], + + "rehype-slug": ["rehype-slug@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-to-string": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A=="], + + "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], + + "remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "remeda": ["remeda@2.21.3", "", { "dependencies": { "type-fest": "^4.39.1" } }, "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], - "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], + + "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], @@ -625,16 +1782,76 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + "sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], + + "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], + "stats-gl": ["stats-gl@2.4.2", "", { "dependencies": { "@types/three": "*", "three": "^0.170.0" } }, "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ=="], "stats.js": ["stats.js@0.17.0", "", {}, "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="], + "std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="], + + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], + + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], + + "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], + + "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], + + "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], + + "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], + + "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + + "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "suspend-react": ["suspend-react@0.1.3", "", { "peerDependencies": { "react": ">=17.0" } }, "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ=="], "tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="], @@ -645,44 +1862,108 @@ "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "text-extensions": ["text-extensions@2.4.0", "", {}, "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g=="], + "three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="], "three-mesh-bvh": ["three-mesh-bvh@0.8.3", "", { "peerDependencies": { "three": ">= 0.159.0" } }, "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg=="], "three-stdlib": ["three-stdlib@2.36.1", "", { "dependencies": { "@types/draco3d": "^1.4.0", "@types/offscreencanvas": "^2019.6.4", "@types/webxr": "^0.5.2", "draco3d": "^1.4.1", "fflate": "^0.6.9", "potpack": "^1.0.1" }, "peerDependencies": { "three": ">=0.128.0" } }, "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg=="], + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + "troika-three-text": ["troika-three-text@0.52.4", "", { "dependencies": { "bidi-js": "^1.0.2", "troika-three-utils": "^0.52.4", "troika-worker-utils": "^0.52.0", "webgl-sdf-generator": "1.1.1" }, "peerDependencies": { "three": ">=0.125.0" } }, "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg=="], "troika-three-utils": ["troika-three-utils@0.52.4", "", { "peerDependencies": { "three": ">=0.125.0" } }, "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A=="], "troika-worker-utils": ["troika-worker-utils@0.52.0", "", {}, "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw=="], + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="], "tw-animate-css": ["tw-animate-css@1.3.3", "", {}, "sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], + + "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], + + "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], + + "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript-eslint": ["typescript-eslint@8.50.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.50.0", "@typescript-eslint/parser": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/utils": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A=="], + + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + "use-intl": ["use-intl@4.6.1", "", { "dependencies": { "@formatjs/fast-memoize": "^2.2.0", "@schummar/icu-type-parser": "1.21.5", "intl-messageformat": "^10.5.14" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" } }, "sha512-mUIj6QvJZ7Rk33mLDxRziz1YiBBAnIji8YW4TXXMdYHtaPEbVucrXD3iKQGAqJhbVn0VnjrEtIKYO1B18mfSJw=="], + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "utility-types": ["utility-types@3.11.0", "", {}, "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw=="], + "valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="], + "vaul": ["vaul@0.9.9", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-7afKg48srluhZwIkaU+lgGtFCUsYBSGOl8vcc8N/M3YQlZFlynHD15AE+pwrYdc826o7nrIND4lL9Y6b9WWZZQ=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], "webgl-constants": ["webgl-constants@1.1.1", "", {}, "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="], @@ -695,12 +1976,72 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], + + "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], + + "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], + + "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zeptomatch": ["zeptomatch@2.0.2", "", { "dependencies": { "grammex": "^3.1.10" } }, "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "@commitlint/config-validator/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "@commitlint/is-ignored/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "@commitlint/top-level/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], + + "@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@eslint/eslintrc/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@formatjs/ecma402-abstract/@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.2", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA=="], + + "@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + + "@prisma/engines/@prisma/get-platform": ["@prisma/get-platform@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0" } }, "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA=="], + + "@prisma/fetch-engine/@prisma/get-platform": ["@prisma/get-platform@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0" } }, "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA=="], + + "@prisma/get-platform/@prisma/debug": ["@prisma/debug@6.8.2", "", {}, "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], @@ -713,22 +2054,92 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/three/fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "cli-truncate/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "cosmiconfig/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + + "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + + "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + + "eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "is-bun-module/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "its-fine/@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="], + "listr2/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "log-update/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "mysql2/iconv-lite": ["iconv-lite@0.7.1", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw=="], + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "@commitlint/config-validator/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "@commitlint/top-level/find-up/locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], + + "@commitlint/top-level/find-up/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + + "@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@eslint/eslintrc/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -745,6 +2156,20 @@ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "cosmiconfig/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "log-update/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@commitlint/top-level/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], @@ -755,6 +2180,12 @@ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@commitlint/top-level/find-up/locate-path/p-locate/p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@commitlint/top-level/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], } } diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..2edf736 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,26 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'docs', + 'chore', + 'style', + 'refactor', + 'release', + 'ci', + 'test', + 'perf', + 'patch', + 'revert', + 'vercel', + 'update', + 'add' + ] + ] + } +}; \ No newline at end of file diff --git a/commitlintrc.json b/commitlintrc.json new file mode 100644 index 0000000..67b3aa1 --- /dev/null +++ b/commitlintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "@commitlint/config-conventional" + ] +} \ No newline at end of file diff --git a/components/Layouts/Contact/contact.tsx b/components/Layouts/Contact/contact.tsx deleted file mode 100644 index 1df1344..0000000 --- a/components/Layouts/Contact/contact.tsx +++ /dev/null @@ -1,239 +0,0 @@ -"use client" - -import type React from "react" -import { Card } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Mail, Github, MessageCircle, Bitcoin, Coins, Twitter, Instagram, Send, Copy, AlertTriangle } from "lucide-react" -import { useToast } from "@/hooks/use-toast" -import { SiDiscord, SiTrustpilot } from "react-icons/si" - -export function Contact() { - const { toast } = useToast() - - const copyToClipboard = (text: string, currency: string) => { - navigator.clipboard.writeText(text).then(() => { - toast({ - title: "Address Copied!", - description: `${currency} address copied to clipboard`, - }) - }).catch(() => { - toast({ - title: "Copy Failed", - description: "Failed to copy address to clipboard", - variant: "destructive", - }) - }) - } - - return ( -
-
-
-

- Contact NodeByte -

-

- We are here to help you with all your hosting needs. Reach out to us for support, inquiries, or feedback. -

-
- -
-
-
-
- - 24/7 Friendly Support -
-

Fast, Friendly and Professional

-

- Our support team is available 24/7 to assist you with any technical issues, account inquiries, or general questions you may have. -

-
- - -
-
- -
-
-

GitHub Discussions

-

- Report bugs, suggest features, and get community support -

-
    -
  • - - Report bugs and leave some feedback -
  • -
  • - - Reviewed within 48 hours -
  • -
- - Visit GitHub Discussions → - -
-
-
- - -
-
- -
-
-

General Support

-

- Technical support, general support , account issues, billing inquiries -

- -
-
-
-
- -
-
-
- - Stay in Touch With Us -
-

Support NodeByte

-

- Join our communities, stay updated with the latest news, and support our mission to provide secure and private hosting solutions. -

-
- - -
-
-

Join Our Community

-

- Connect with us on social media and be part of our growing community. Get the latest updates, tips, and exclusive offers. -

-
- -
-

Connect

- -
- -
-
-
- - Twitter -
-

Latest updates and security tips

-
-
-
- - GitHub -
-

Open source code and contributions

-
-
-
- - Discord -
-

Community chat and instant support

-
-
-
- - Trustpilot -
-

Customer Reviews and Feedback

-
-
-
-
-
- -
-
- Note: We do not handle billing or account related support via Discord. For privacy and security and to comply with Discord's policies please contact our support team at - {' '} - support@nodebyte.host - {' '}or use our official support channels. -
-
-
-
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/components/Layouts/Home/about.tsx b/components/Layouts/Home/about.tsx deleted file mode 100644 index a5ec1a8..0000000 --- a/components/Layouts/Home/about.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import type React from "react" -import { Card } from "@/components/ui/card" -import { Users, Heart, Code } from "lucide-react" -import HeroGraphic from "./hero-graphic" - -export function About() { - return ( -
-
-
-
-
- - Our Story -
-

- Built for Players & Devs -

-

- NodeByte started as a small group of gamers and ops engineers who wanted hosting that was fast, reliable, - and easy to manage. We build infrastructure tuned for multiplayer: low latency routing, DDoS protection, - and instant server provisioning so you can get playing fast. -

-

- We obsess over stability and developer experience. Whether you're running a modded Minecraft world or a - Rust community server, our control panel and API make deployments and maintenance painless. -

-
-
-
- -
-
-
- -
- -
- - - - - - - - - -
-
-

Built for Players

-

- Our infrastructure prioritizes low-latency routing, predictable performance, and strong uptime so your - communities stay online and responsive. -

-
-
- - -
- - - - - - - - - -
-
-

Community & Mod Support

-

- Seamless mod installs, snapshot support, and community tooling to help you run modded worlds and - persistent servers with minimal effort. -

-
-
- - -
- - - - - - - - - -
-
-

Open by Design

-

- We value transparency — from clear SLA commitments to open tooling. Our platform provides APIs and - integrations to automate server workflows and manage deployments programmatically. -

-
-
-
-
-
- ) -} - -function Shield(props: React.SVGProps) { - return ( - - - - ) -} diff --git a/components/Layouts/Home/faq.tsx b/components/Layouts/Home/faq.tsx deleted file mode 100644 index d7f35f8..0000000 --- a/components/Layouts/Home/faq.tsx +++ /dev/null @@ -1,127 +0,0 @@ -"use client" - -import { useState, useMemo } from "react" -import { ChevronDown, Search } from "lucide-react" -import HeroGraphic from "./hero-graphic" - -const faqs = [ - { - question: "How fast can I provision a server?", - answer: - "Most prebuilt server templates provision in under 60 seconds. Custom setups with large mods may take longer depending on mod install time.", - }, - { - question: "Do you offer DDoS protection?", - answer: - "Yes all plans include automated DDoS mitigation at the network edge. For large or targeted attacks we provide additional scrubbing services on higher tiers.", - }, - { - question: "Where are your POPs located?", - answer: - "We operate multiple Points-of-Presence across the UK with more regions coming online. Check our coverage map or contact sales for custom regions.", - }, - { - question: "Can I run mods and custom maps?", - answer: - "Yes our control panel and templates support common mod loaders (Forge, Fabric) and custom map uploads. Managed mod support is available on selected plans.", - }, - { - question: "What is your uptime SLA?", - answer: - "We offer a 99.6% uptime SLA on production plans. See our terms for details and eligibility criteria.", - }, - { - question: "Can I get a trial before committing?", - answer: - "Yes, we offer a trial period for new users to test our services before making a commitment. Contact our support team for more details. This lasts for 7 days.", - }, -] - -export function FAQ() { - const [openIndex, setOpenIndex] = useState(0) - const [query, setQuery] = useState("") - - const filtered = useMemo(() => { - if (!query.trim()) return faqs - const q = query.toLowerCase() - return faqs.filter((f) => f.question.toLowerCase().includes(q) || f.answer.toLowerCase().includes(q)) - }, [query]) - - return ( -
-
- -
-
-

- Frequently Asked Questions -

-

- Quick answers about NodeByte hosting, performance, and support. -

-
- -
-
-
-
- -
-
-
- -
-
- -
- - setQuery(e.target.value)} - className="flex-1 bg-transparent outline-none text-sm text-foreground" - /> -
-
- - {filtered.length === 0 && ( -
No results. Try different keywords like "DDoS", "mods", or "SLA".
- )} - - {filtered.map((faq, index) => ( -
- -
-
{faq.answer}
-
-
- ))} -
-
-
-
- ) -} diff --git a/components/Layouts/Home/features.tsx b/components/Layouts/Home/features.tsx deleted file mode 100644 index 537db70..0000000 --- a/components/Layouts/Home/features.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from "react" -import { Shield, Zap, Globe, Lock, Eye, Server } from "lucide-react" -import { Card } from "@/components/ui/card" -import { Button } from "@/components/ui/button" - -const features = [ - { - icon: Shield, - title: "DDoS Protection", - description: "Enterprise-grade mitigation and automatic scrubbing to keep servers online under attack.", - tag: "Security", - color: "from-primary to-accent", - }, - { - icon: Zap, - title: "Low Latency Network", - description: "Optimized routes, private backbone links, and regional POPs for minimal ping.", - tag: "Performance", - color: "from-accent to-primary", - }, - { - icon: Server, - title: "Instant Setup", - description: "One-click server templates, automated mod installs, and fast provisioning.", - tag: "Deploy", - color: "from-secondary to-primary", - }, - { - icon: Globe, - title: "Global Locations", - description: "Multiple regions and edge POPs to keep players close and latency low.", - tag: "Global", - color: "from-primary to-secondary", - }, - { - icon: Eye, - title: "Control Panel", - description: "A powerful UI for console access, file management, backups, and metrics.", - tag: "Manage", - color: "from-accent to-secondary", - }, - { - icon: Lock, - title: "24/7 Support", - description: "Human-first support with expert ops available any time, for installs and troubleshooting.", - tag: "Support", - color: "from-primary to-accent", - }, -] - -function Sparkline({ color = "rgba(96,165,250,0.9)" }: { color?: string }) { - // small decorative sparkline — static path for now - return ( - - - - ) -} - -export function Features() { - return ( -
-
- -
-
-

- Built for Hosting -

-

- NodeByte combines enterprise-grade infrastructure with gamer-friendly tooling — instant servers, - low-latency routing, DDoS protection, and an easy control panel so you can focus on playing. -

-
- -
- {features.map((feature, index) => ( - -
-
-
- -
-
-

{feature.title}

-
{feature.tag}
-
-
- -
- -

{feature.description}

- -
- Learn more -
Trusted by gamers • SLA
-
-
- ))} -
- - -
-
- ) -} diff --git a/components/Layouts/Home/games.tsx b/components/Layouts/Home/games.tsx deleted file mode 100644 index a729a36..0000000 --- a/components/Layouts/Home/games.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button } from "@/components/ui/button" -import { Card } from "@/components/ui/card" -import { Monitor, Server, Cloud, PartyPopper } from "lucide-react" - -export function Download() { - return ( -
-
- -
-
-

- Game Servers -

-

- Powerful, low latency game servers. Instant setup, control panel, and 24/7 - support. -

-
- -
- -
-
Dedicated
-
-
-

Minecraft

-

One click mod loaders, snapshots and plugins with dedicated reliable support.

- -
-
- - -
-
High Performance
-
-
-

Rust

-

High performance Rust hosting with custom maps and mod support.

- -
-
- - -
-
Upcoming • Stay Tuned
-
-
-

Something Exciting

-

We are working on bringing you more exciting game servers and features. Stay tuned!

- -
-
-
-
-
- ) -} diff --git a/components/Layouts/Home/hero.tsx b/components/Layouts/Home/hero.tsx deleted file mode 100644 index 34a7752..0000000 --- a/components/Layouts/Home/hero.tsx +++ /dev/null @@ -1,131 +0,0 @@ -"use client" - -import React, { useEffect, useState } from "react" -import { Button } from "@/components/ui/button" -import { ArrowRight, Shield, Zap, Globe, PartyPopper } from "lucide-react" -import Image from "next/image" -import HeroGraphic from "./hero-graphic" - -export function Hero() { - const handleDownloadClick = () => { - const element = document.getElementById("download") - if (element) { - element.scrollIntoView({ behavior: "smooth" }) - } - } - - const [uptime, setUptime] = useState(0) - const [ping, setPing] = useState(120) - - useEffect(() => { - // animate uptime to 99.99 quickly - let start: number | null = null - const duration = 900 - const from = 97.5 - const to = 99.99 - function step(ts: number) { - if (!start) start = ts - const t = Math.min(1, (ts - start) / duration) - const v = from + (to - from) * t - setUptime(Number(v.toFixed(2))) - if (t < 1) requestAnimationFrame(step) - } - requestAnimationFrame(step) - - // animate ping down to ~50ms - let pstart: number | null = null - const pduration = 1000 - const pfrom = 120 - const pto = 50 - function pstep(ts: number) { - if (!pstart) pstart = ts - const t = Math.min(1, (ts - pstart) / pduration) - const v = Math.round(pfrom + (pto - pfrom) * t) - setPing(v) - if (t < 1) requestAnimationFrame(pstep) - } - requestAnimationFrame(pstep) - }, []) - - return ( -
-
- -
-
-
-
- -
-
-
-
- - USE CODE WELCOME10 ON YOUR FIRST ORDER! -
- -

- NodeByte -

- -

- Fast, reliable, scalable and secure hosting services for your gaming experience. Launch dedicated and managed game servers (Minecraft, Rust) with instant setup, DDoS protection and global low-latency networking. Scalable plans, easy control panel, and 24/7 support. -

- -
-
-
-
- -
- -
-
-
{uptime}%
-
Uptime SLA
-
Enterprise redundancy keeping your servers online.
-
-
- -
-
-
- -
-
-
-
≈{ping}ms
-
Avg. Ping
-
Optimized routing and global POPs for low-latency gameplay.
-
-
- -
-
-
- -
-
-
-
24/7
-
Support
-
Expert help with installs, mods, and server ops anytime.
-
-
-
-
- -
-
-
- -
-
-
-
-
- ) -} \ No newline at end of file diff --git a/components/Static/footer.tsx b/components/Static/footer.tsx deleted file mode 100644 index 6aafab5..0000000 --- a/components/Static/footer.tsx +++ /dev/null @@ -1,245 +0,0 @@ -"use client" - -import React, { useEffect, useState } from "react" -import { SiDiscord, SiTrustpilot } from "react-icons/si" -import { Github, Twitter, Instagram, Send, Mail } from "lucide-react" -import { usePathname } from "next/navigation" -import Link from "next/link" - -export function Footer() { - const pathname = usePathname() - - const scrollToSection = (id: string) => { - if (pathname !== "/") { - window.location.href = `/#${id}` - return - } - - const element = document.getElementById(id) - if (element) { - element.scrollIntoView({ behavior: 'smooth' }) - } - } - - return ( -
-
-
-
-
- NodeByte Hosting - NodeByte Hosting -
-
-

- Built for Humans. Powered by Bytes. -

-
- -
-
-

Resources

-
    -
  • - - Discord Server - -
  • -
  • - - Service Status - -
  • -
  • - - Game Panel - -
  • -
  • - - Minecraft Servers - -
  • -
  • - - Rust Servers - -
  • -
-
- -
-

Docs

-
    -
  • - - Knowledge Base - -
  • -
-
- -
-

Legal

-
    -
  • - - Terms & Conditions - -
  • -
  • - - Privacy Policy - -
  • -
  • - - Refund Policy - -
  • -
  • - - Legal Hub - -
  • -
-
- - -
- -
-

- © 2025 NodeByte LTD - {" "}| All Rights Reserved | {" "} - - Registered Number: 15432941 - -

-
-
-
- ) -} - -function TrustpilotWidget() { - const [rating, setRating] = useState(null) - const [count, setCount] = useState(null) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - let mounted = true - ;(async () => { - try { - const res = await fetch('/api/trustpilot') - if (!res.ok) throw new Error(`Status ${res.status}`) - const data = await res.json() - if (!mounted) return - setRating(typeof data.rating === 'number' ? data.rating : null) - setCount(typeof data.reviewCount === 'number' ? data.reviewCount : null) - } catch (err) { - if (!mounted) return - setError(String(err)) - } finally { - if (!mounted) return - setLoading(false) - } - })() - return () => { - mounted = false - } - }, []) - - const displayRating = rating ?? 4.1 - const displayCount = count ?? 5 - - return ( -
-
- {[1, 2, 3, 4, 5].map((i) => { - const filled = Math.round(displayRating) >= i - return ( - - - - ) - })} - - - {loading ? '—' : displayRating.toFixed(1)} - - • {loading ? '...' : displayCount + ' reviews'} -
- - - Read reviews on Trustpilot - - - {error ?
Failed to load reviews
: null} -
- ) -} \ No newline at end of file diff --git a/components/Static/navigation.tsx b/components/Static/navigation.tsx deleted file mode 100644 index d2d4314..0000000 --- a/components/Static/navigation.tsx +++ /dev/null @@ -1,139 +0,0 @@ -"use client" - -import { useState, useEffect } from "react" -import { Button } from "@/components/ui/button" -import { Menu, X } from "lucide-react" -import { ThemeToggle } from "@/components/theme-toggle" -import Link from "next/link" -import { usePathname } from "next/navigation" - -export function Navigation() { - const [isScrolled, setIsScrolled] = useState(false) - const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) - const pathname = usePathname() - - useEffect(() => { - const handleScroll = () => { - setIsScrolled(window.scrollY > 50) - } - window.addEventListener("scroll", handleScroll) - return () => window.removeEventListener("scroll", handleScroll) - }, []) - - useEffect(() => { - // Close mobile menu when route changes - setIsMobileMenuOpen(false) - }, [pathname]) - - const scrollToSection = (id: string) => { - if (pathname !== "/") { - window.location.href = `/#${id}` - return - } - - const element = document.getElementById(id) - if (element) { - element.scrollIntoView({ behavior: 'smooth' }) - } - setIsMobileMenuOpen(false) - } - - return ( - - ) -} \ No newline at end of file diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx deleted file mode 100644 index 80e9391..0000000 --- a/components/theme-toggle.tsx +++ /dev/null @@ -1,146 +0,0 @@ -"use client" - -import { useEffect, useState } from "react" -import { useTheme } from "next-themes" -import { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, -} from "@/components/ui/dropdown-menu" -import { Button } from "@/components/ui/button" -import { Sun, Moon, Monitor, Cloud, Star } from "lucide-react" - -export function ThemeToggle() { - const { theme, setTheme, resolvedTheme } = useTheme() - const [mounted, setMounted] = useState(false) - - useEffect(() => setMounted(true), []) - if (!mounted) return - - // Determine selected theme string (what user explicitly chose) - const selected = theme ?? "system" - - // For display icon use resolvedTheme when user chose 'system' - const display = selected === "system" ? resolvedTheme : selected - - const Icon = display === "light" ? Sun : display === "dark" ? Moon : display === "slate" ? Cloud : display === "midnight" ? Star : display === "rose" ? Sun : display === "crimson" ? Moon : display === "forest" ? Cloud : display === "emerald" ? Cloud : display === "desert" ? Monitor : display === "amber" ? Sun : display === "ocean" ? Star : display === "teal" ? Star : display === "lavender" ? Star : display === "violet" ? Moon : Monitor - - return ( - - - - - - - Theme - { - try { - const known = ["light","dark","slate","midnight","rose","crimson","forest","emerald","desert","amber","ocean","teal","lavender","violet"] - const html = typeof document !== 'undefined' ? document.documentElement : null - if (html) known.forEach(c => html.classList.remove(c)) - // persist to cookie and localStorage for SSR and later loads - document.cookie = `theme=${encodeURIComponent(v)};path=/;max-age=${60*60*24*365}` - localStorage.setItem('theme', v) - } catch (e) {} - setTheme(v) - }}> - -
- - Light -
-
- -
- - Dark -
-
- -
- - Slate -
-
- -
- - Midnight -
-
- -
- - Rose -
-
- -
- - Crimson -
-
- -
- - Forest -
-
- -
- - Emerald -
-
- -
- - Desert -
-
- -
- - Amber -
-
- -
- - Ocean -
-
- -
- - Teal -
-
- -
- - Lavender -
-
- -
- - Violet -
-
- -
- - System -
-
-
-
-
- ) -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..43cfbda --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,31 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + rules: { + "react/no-unescaped-entities": "off", + "@next/next/no-img-element": "off", + "@next/next/no-html-link-for-pages": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ] + } + } +]; + +export default eslintConfig; \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 4cd9948..83a2cf5 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,7 @@ +import createNextIntlPlugin from 'next-intl/plugin' + +const withNextIntl = createNextIntlPlugin('./packages/i18n/request.ts') + /** @type {import('next').NextConfig} */ const nextConfig = { typescript: { @@ -8,4 +12,4 @@ const nextConfig = { }, } -export default nextConfig +export default withNextIntl(nextConfig) diff --git a/package.json b/package.json index ad714a9..3ae1047 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ }, "dependencies": { "@hookform/resolvers": "^3.10.0", + "@prisma/adapter-pg": "^7.2.0", + "@prisma/client": "^7.2.0", "@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-alert-dialog": "1.1.4", "@radix-ui/react-aspect-ratio": "1.1.1", @@ -42,23 +44,38 @@ "@react-three/fiber": "9.4.0", "@vercel/analytics": "latest", "autoprefixer": "^10.4.20", + "bcryptjs": "^3.0.3", + "cheerio": "^1.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.4", - "cheerio": "^1.1.2", + "country-flag-icons": "^1.6.4", "date-fns": "4.1.0", "embla-carousel-react": "8.5.1", + "gray-matter": "^4.0.3", "input-otp": "1.4.1", "lucide-react": "^0.454.0", - "next": "16.0.0", + "next": "^16.1.0", + "next-auth": "^5.0.0-beta.30", + "next-intl": "^4.6.1", "next-themes": "^0.4.6", - "react": "19.2.0", + "prisma": "^7.2.0", + "react": "^19.2.3", "react-day-picker": "9.8.0", - "react-dom": "19.2.0", + "react-dom": "^19.2.3", "react-hook-form": "^7.60.0", - "react-resizable-panels": "^2.1.7", "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", + "react-resizable-panels": "^2.1.7", + "reading-time": "^1.5.0", "recharts": "2.15.4", + "rehype-autolink-headings": "^7.1.0", + "rehype-highlight": "^7.0.2", + "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", + "remark": "^15.0.1", + "remark-gfm": "^4.0.1", + "remark-rehype": "^11.1.2", "sonner": "^1.7.4", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", @@ -66,13 +83,33 @@ "zod": "3.25.76" }, "devDependencies": { + "@commitlint/cli": "^20.2.0", + "@commitlint/config-conventional": "^20.2.0", "@tailwindcss/postcss": "^4.1.9", + "@tailwindcss/typography": "^0.5.19", + "@types/bcryptjs": "^3.0.0", "@types/node": "^22", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "commitlint": "^20.2.0", + "eslint-config-next": "^16.1.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.7", "postcss": "^8.5", + "prettier": "^3.7.4", "tailwindcss": "^4.1.9", "tw-animate-css": "1.3.3", "typescript": "^5" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "prettier -w ." + ], + "*.{css,scss}": [ + "prettier -w ." + ], + "*.{json,md}": [ + "prettier --w ." + ] } } \ No newline at end of file diff --git a/packages/auth/components/auth-provider.tsx b/packages/auth/components/auth-provider.tsx new file mode 100644 index 0000000..87781ec --- /dev/null +++ b/packages/auth/components/auth-provider.tsx @@ -0,0 +1,13 @@ +"use client" + +import { SessionProvider } from "next-auth/react" +import type { Session } from "next-auth" + +interface AuthProviderProps { + children: React.ReactNode + session?: Session | null +} + +export function AuthProvider({ children, session }: AuthProviderProps) { + return {children} +} diff --git a/packages/auth/components/index.ts b/packages/auth/components/index.ts new file mode 100644 index 0000000..a4682de --- /dev/null +++ b/packages/auth/components/index.ts @@ -0,0 +1,5 @@ +export { LoginForm } from "./login-form" +export { RegisterForm } from "./register-form" +export { LogoutButton } from "./logout-button" +export { UserMenu } from "./user-menu" +export { AuthProvider } from "./auth-provider" diff --git a/packages/auth/components/login-form.tsx b/packages/auth/components/login-form.tsx new file mode 100644 index 0000000..b9e1cdb --- /dev/null +++ b/packages/auth/components/login-form.tsx @@ -0,0 +1,228 @@ +"use client" + +import { useState } from "react" +import { signIn } from "next-auth/react" +import { useRouter, useSearchParams } from "next/navigation" +import { Button } from "@/packages/ui/components/ui/button" +import { Input } from "@/packages/ui/components/ui/input" +import { Label } from "@/packages/ui/components/ui/label" +import { Alert, AlertDescription } from "@/packages/ui/components/ui/alert" +import { Loader2, Mail, Lock, ExternalLink, AlertCircle, Shield, Server, Zap, Clock, Gamepad2 } from "lucide-react" +import Link from "next/link" + +interface LoginFormProps { + translations: { + title: string + description: string + emailLabel: string + emailPlaceholder: string + passwordLabel: string + passwordPlaceholder: string + loginButton: string + loggingIn: string + forgotPassword: string + noAccount: string + createAccount: string + errors: { + invalid: string + networkError: string + generic: string + } + } +} + +export function LoginForm({ translations: t }: LoginFormProps) { + const router = useRouter() + const searchParams = useSearchParams() + const callbackUrl = searchParams.get("callbackUrl") || "/" + const error = searchParams.get("error") + + const [email, setEmail] = useState("") + const [password, setPassword] = useState("") + const [isLoading, setIsLoading] = useState(false) + const [formError, setFormError] = useState(null) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setFormError(null) + setIsLoading(true) + + try { + const result = await signIn("credentials", { + email, + password, + redirect: false, + callbackUrl, + }) + + if (result?.error) { + setFormError(t.errors.invalid) + } else if (result?.ok) { + router.push(callbackUrl) + router.refresh() + } + } catch { + setFormError(t.errors.networkError) + } finally { + setIsLoading(false) + } + } + + const features = [ + { icon: Server, label: "Manage your servers" }, + { icon: Zap, label: "Instant access" }, + { icon: Clock, label: "24/7 availability" }, + { icon: Gamepad2, label: "Game panel integration" }, + ] + + return ( +
+ {/* Left side - Branding */} +
+
+
+

+ Welcome back to your + + game server dashboard + +

+

+ Sign in to manage your servers, view statistics, and access your control panel. +

+
+
+ +
+
+ {features.map((feature, i) => ( +
+
+ +
+ {feature.label} +
+ ))} +
+ +

+ © {new Date().getFullYear()} NodeByte Hosting. All rights reserved. +

+
+
+ + {/* Right side - Login Form */} +
+
+
+

{t.title}

+

{t.description}

+
+ +
+ {(error || formError) && ( + + + + {formError || t.errors.generic} + + + )} + +
+
+ +
+ + setEmail(e.target.value)} + className="pl-11 h-12 text-base" + disabled={isLoading} + required + autoComplete="email" + /> +
+
+ +
+
+ + + {t.forgotPassword} + +
+
+ + setPassword(e.target.value)} + className="pl-11 h-12 text-base" + disabled={isLoading} + required + autoComplete="current-password" + /> +
+
+
+ + +
+ +
+
+
+
+
+ or +
+
+ +
+

+ {t.noAccount}{" "} + + {t.createAccount} + +

+
+
+
+
+ ) +} diff --git a/packages/auth/components/logout-button.tsx b/packages/auth/components/logout-button.tsx new file mode 100644 index 0000000..1d32136 --- /dev/null +++ b/packages/auth/components/logout-button.tsx @@ -0,0 +1,51 @@ +"use client" + +import { signOut } from "next-auth/react" +import { Button } from "@/packages/ui/components/ui/button" +import { LogOut, Loader2 } from "lucide-react" +import { useState } from "react" + +interface LogoutButtonProps { + translations: { + logout: string + loggingOut: string + } + variant?: "default" | "outline" | "ghost" | "destructive" + size?: "default" | "sm" | "lg" | "icon" + showIcon?: boolean +} + +export function LogoutButton({ + translations: t, + variant = "outline", + size = "default", + showIcon = true, +}: LogoutButtonProps) { + const [isLoading, setIsLoading] = useState(false) + + const handleLogout = async () => { + setIsLoading(true) + await signOut({ callbackUrl: "/" }) + } + + return ( + + ) +} diff --git a/packages/auth/components/register-form.tsx b/packages/auth/components/register-form.tsx new file mode 100644 index 0000000..c06a374 --- /dev/null +++ b/packages/auth/components/register-form.tsx @@ -0,0 +1,313 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { Button } from "@/packages/ui/components/ui/button" +import { Input } from "@/packages/ui/components/ui/input" +import { Label } from "@/packages/ui/components/ui/label" +import { Alert, AlertDescription, AlertTitle } from "@/packages/ui/components/ui/alert" +import { Loader2, Mail, Lock, AlertCircle, UserPlus, CheckCircle2, Server, Zap, Clock, Gamepad2, Info } from "lucide-react" +import Link from "next/link" + +interface RegisterFormProps { + translations: { + title: string + description: string + emailLabel: string + emailPlaceholder: string + passwordLabel: string + passwordPlaceholder: string + confirmPasswordLabel: string + confirmPasswordPlaceholder: string + registerButton: string + registering: string + alreadyHaveAccount: string + signIn: string + panelAccountRequired: string + panelAccountRequiredDescription: string + errors: { + emailExists: string + notInPanel: string + panelAccountLinked: string + passwordsDontMatch: string + passwordTooShort: string + invalidEmail: string + networkError: string + generic: string + } + success: { + title: string + description: string + } + } +} + +export function RegisterForm({ translations: t }: RegisterFormProps) { + const router = useRouter() + + const [email, setEmail] = useState("") + const [password, setPassword] = useState("") + const [confirmPassword, setConfirmPassword] = useState("") + const [isLoading, setIsLoading] = useState(false) + const [formError, setFormError] = useState(null) + const [isSuccess, setIsSuccess] = useState(false) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setFormError(null) + setIsLoading(true) + + // Client-side validation + if (password.length < 8) { + setFormError(t.errors.passwordTooShort) + setIsLoading(false) + return + } + + if (password !== confirmPassword) { + setFormError(t.errors.passwordsDontMatch) + setIsLoading(false) + return + } + + try { + const response = await fetch("/api/auth/register", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email, + password, + confirmPassword, + }), + }) + + const data = await response.json() + + if (!data.success) { + const errorMap: Record = { + email_exists: t.errors.emailExists, + not_in_panel: t.errors.notInPanel, + panel_account_linked: t.errors.panelAccountLinked, + passwords_dont_match: t.errors.passwordsDontMatch, + password_too_short: t.errors.passwordTooShort, + invalid_email: t.errors.invalidEmail, + } + setFormError(errorMap[data.error] || t.errors.generic) + setIsLoading(false) + return + } + + // Success! + setIsSuccess(true) + + // Redirect to login after a short delay + setTimeout(() => { + router.push("/auth/login") + }, 2000) + } catch { + setFormError(t.errors.networkError) + } finally { + setIsLoading(false) + } + } + + const features = [ + { icon: Server, label: "Manage your servers" }, + { icon: Zap, label: "Instant access" }, + { icon: Clock, label: "24/7 availability" }, + { icon: Gamepad2, label: "Game panel integration" }, + ] + + if (isSuccess) { + return ( +
+
+
+ +
+
+

{t.success.title}

+

{t.success.description}

+
+ +
+
+ ) + } + + return ( +
+ {/* Left side - Branding */} +
+
+
+

+ Create your + + NodeByte account + +

+

+ Register to access your game servers, view statistics, and manage your hosting services. +

+
+
+ +
+
+ {features.map((feature, i) => ( +
+
+ +
+ {feature.label} +
+ ))} +
+ +

+ © {new Date().getFullYear()} NodeByte Hosting. All rights reserved. +

+
+
+ + {/* Right side - Register Form */} +
+
+
+

{t.title}

+

{t.description}

+
+ + {/* Important notice */} + + + {t.panelAccountRequired} + + {t.panelAccountRequiredDescription} + + + +
+ {formError && ( + + + {formError} + + )} + +
+
+ +
+ + setEmail(e.target.value)} + className="pl-11 h-12 text-base" + disabled={isLoading} + required + autoComplete="email" + /> +
+
+ +
+ +
+ + setPassword(e.target.value)} + className="pl-11 h-12 text-base" + disabled={isLoading} + required + minLength={8} + autoComplete="new-password" + /> +
+

+ Minimum 8 characters +

+
+ +
+ +
+ + setConfirmPassword(e.target.value)} + className="pl-11 h-12 text-base" + disabled={isLoading} + required + autoComplete="new-password" + /> +
+
+
+ + +
+ +
+
+
+
+
+ or +
+
+ +
+

+ {t.alreadyHaveAccount}{" "} + + {t.signIn} + +

+
+
+
+
+ ) +} diff --git a/packages/auth/components/user-menu.tsx b/packages/auth/components/user-menu.tsx new file mode 100644 index 0000000..2071ee0 --- /dev/null +++ b/packages/auth/components/user-menu.tsx @@ -0,0 +1,121 @@ +"use client" + +import { useSession } from "next-auth/react" +import { Avatar, AvatarFallback } from "@/packages/ui/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/packages/ui/components/ui/dropdown-menu" +import { Button } from "@/packages/ui/components/ui/button" +import { Badge } from "@/packages/ui/components/ui/badge" +import { signOut } from "next-auth/react" +import { User, LogOut, Shield, ExternalLink } from "lucide-react" +import Link from "next/link" + +interface UserMenuProps { + translations: { + myAccount: string + viewPanel: string + admin: string + logout: string + signIn: string + } +} + +export function UserMenu({ translations: t }: UserMenuProps) { + const { data: session, status } = useSession() + + if (status === "loading") { + return ( + + ) + } + + if (!session?.user) { + return ( + + ) + } + + const initials = `${session.user.firstName?.[0] || ""}${session.user.lastName?.[0] || ""}`.toUpperCase() || session.user.username?.[0]?.toUpperCase() || "U" + + return ( + + + + + + +
+
+

+ {session.user.firstName} {session.user.lastName} +

+ {session.user.isAdmin && ( + + {t.admin} + + )} +
+

+ {session.user.email} +

+
+
+ + + + + {t.viewPanel} + + + {session.user.isAdmin && ( + <> + + + + + {t.admin} + + + + )} + + signOut({ callbackUrl: "/" })} + > + + {t.logout} + +
+
+ ) +} diff --git a/packages/auth/index.ts b/packages/auth/index.ts new file mode 100644 index 0000000..972fd7a --- /dev/null +++ b/packages/auth/index.ts @@ -0,0 +1,2 @@ +export { auth, signIn, signOut, requireAdmin, requireAuth } from "./lib/auth" +export { handlers } from "./lib/auth" diff --git a/packages/auth/lib/auth-service.ts b/packages/auth/lib/auth-service.ts new file mode 100644 index 0000000..72c3195 --- /dev/null +++ b/packages/auth/lib/auth-service.ts @@ -0,0 +1,282 @@ +import { prisma } from "@/packages/core/lib/prisma" +import { syncUserFromPanel } from "@/packages/core/lib/sync" +import bcrypt from "bcryptjs" +import type { User } from "@prisma/client" + +const SALT_ROUNDS = 12 + +interface PterodactylUser { + id: number + email: string + username: string + first_name: string + last_name: string + admin: boolean +} + +interface PterodactylUserResponse { + object: "user" + attributes: PterodactylUser +} + +interface PterodactylListResponse { + object: "list" + data: PterodactylUserResponse[] +} + +/** + * Check if a user exists in the Pterodactyl panel by email + * Uses the Application API with admin key + */ +export async function verifyPterodactylUser(email: string): Promise { + const panelUrl = process.env.GAMEPANEL_URL + const apiKey = process.env.GAMEPANEL_API_KEY + + if (!panelUrl || !apiKey) { + console.error("[Auth] Missing GAMEPANEL_URL or GAMEPANEL_API_KEY") + return null + } + + try { + // Search for user by email using the Application API + const response = await fetch( + `${panelUrl}/api/application/users?filter[email]=${encodeURIComponent(email)}`, + { + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: "application/json", + "Content-Type": "application/json", + "User-Agent": "NodeByte-Website/1.0 (https://nodebyte.host)", + }, + } + ) + + if (!response.ok) { + console.error(`[Auth] Pterodactyl API error: ${response.status}`) + return null + } + + const data: PterodactylListResponse = await response.json() + + // Find exact email match (filter is case-insensitive search) + const user = data.data.find( + (u) => u.attributes.email.toLowerCase() === email.toLowerCase() + ) + + if (user) { + return user.attributes + } + + return null + } catch (error) { + console.error("[Auth] Error verifying Pterodactyl user:", error) + return null + } +} + +/** + * Register a new user + * Verifies the user exists in the panel before creating account + * Handles migrated users (synced from panel but haven't set password yet) + */ +export async function registerUser( + email: string, + password: string +): Promise<{ success: boolean; user?: User; error?: string }> { + try { + // Check if user already exists in our database + const existingUser = await prisma.user.findUnique({ + where: { email: email.toLowerCase() }, + }) + + if (existingUser) { + // User exists - check if they've already migrated (set password) + if (existingUser.isMigrated) { + return { success: false, error: "email_exists" } + } + + // User exists but hasn't set password yet (synced from panel) + // Let them "register" by setting their password + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS) + + const updatedUser = await prisma.user.update({ + where: { id: existingUser.id }, + data: { + password: hashedPassword, + isMigrated: true, // Mark as migrated - they've completed registration + emailVerified: new Date(), + }, + }) + + return { success: true, user: updatedUser } + } + + // User doesn't exist in our DB - verify they exist in Pterodactyl panel + const pteroUser = await verifyPterodactylUser(email) + + if (!pteroUser) { + return { success: false, error: "not_in_panel" } + } + + // Check if pterodactyl ID is already linked to another account + const existingPteroLink = await prisma.user.findUnique({ + where: { pterodactylId: pteroUser.id }, + }) + + if (existingPteroLink) { + // This pterodactyl account is linked to a different email + // Check if that account is migrated + if (existingPteroLink.isMigrated) { + return { success: false, error: "panel_account_linked" } + } + + // The linked account hasn't migrated - might be email change in panel + // Update the email and let them register + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS) + + const updatedUser = await prisma.user.update({ + where: { id: existingPteroLink.id }, + data: { + email: email.toLowerCase(), + password: hashedPassword, + isMigrated: true, + emailVerified: new Date(), + }, + }) + + return { success: true, user: updatedUser } + } + + // Brand new user - create account + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS) + + const user = await prisma.user.create({ + data: { + email: email.toLowerCase(), + password: hashedPassword, + username: pteroUser.username, + firstName: pteroUser.first_name || null, + lastName: pteroUser.last_name || null, + isAdmin: pteroUser.admin, + pterodactylId: pteroUser.id, + isMigrated: true, // Registering directly = already migrated + emailVerified: new Date(), // Auto-verify since they exist in panel + }, + }) + + return { success: true, user } + } catch (error) { + console.error("[Auth] Registration error:", error) + return { success: false, error: "server_error" } + } +} + +/** + * Authenticate a user with email and password + */ +export async function authenticateUser( + email: string, + password: string +): Promise<{ success: boolean; user?: User; error?: string }> { + try { + // Find user by email + const user = await prisma.user.findUnique({ + where: { email: email.toLowerCase() }, + }) + + if (!user) { + return { success: false, error: "invalid_credentials" } + } + + // Check if account is active + if (!user.isActive) { + return { success: false, error: "account_disabled" } + } + + // Check if user has set a password (migrated) + if (!user.password) { + return { success: false, error: "not_migrated" } + } + + // Verify password + const isValidPassword = await bcrypt.compare(password, user.password) + + if (!isValidPassword) { + return { success: false, error: "invalid_credentials" } + } + + // Update last login time + await prisma.user.update({ + where: { id: user.id }, + data: { lastLoginAt: new Date() }, + }) + + // Sync user data from panel in the background (don't block login) + syncUserFromPanel(user.id).catch((err) => { + console.error("[Auth] Failed to sync user on login:", err) + }) + + // Return the current user (sync happens in background) + return { success: true, user } + } catch (error) { + console.error("[Auth] Authentication error:", error) + return { success: false, error: "server_error" } + } +} + +/** + * Get user by ID + */ +export async function getUserById(id: string): Promise { + return prisma.user.findUnique({ + where: { id }, + }) +} + +/** + * Get user by email + */ +export async function getUserByEmail(email: string): Promise { + return prisma.user.findUnique({ + where: { email: email.toLowerCase() }, + }) +} + +/** + * Update user's password + */ +export async function updatePassword( + userId: string, + currentPassword: string, + newPassword: string +): Promise<{ success: boolean; error?: string }> { + try { + const user = await prisma.user.findUnique({ + where: { id: userId }, + }) + + if (!user) { + return { success: false, error: "user_not_found" } + } + + // Verify current password + const isValid = await bcrypt.compare(currentPassword, user.password) + + if (!isValid) { + return { success: false, error: "invalid_password" } + } + + // Hash and update new password + const hashedPassword = await bcrypt.hash(newPassword, SALT_ROUNDS) + + await prisma.user.update({ + where: { id: userId }, + data: { password: hashedPassword }, + }) + + return { success: true } + } catch (error) { + console.error("[Auth] Password update error:", error) + return { success: false, error: "server_error" } + } +} diff --git a/packages/auth/lib/auth.ts b/packages/auth/lib/auth.ts new file mode 100644 index 0000000..c26f306 --- /dev/null +++ b/packages/auth/lib/auth.ts @@ -0,0 +1,139 @@ +import NextAuth from "next-auth" +import Credentials from "next-auth/providers/credentials" +import { authenticateUser } from "./auth-service" + +// Extend the built-in types +declare module "next-auth" { + interface User { + id: string + email: string + username: string + firstName: string | null + lastName: string | null + isAdmin: boolean + pterodactylId: number | null + } + + interface Session { + user: User + } +} + +declare module "next-auth/jwt" { + interface JWT { + id: string + email: string + username: string + firstName: string | null + lastName: string | null + isAdmin: boolean + pterodactylId: number | null + } +} + +export const { handlers, signIn, signOut, auth } = NextAuth({ + providers: [ + Credentials({ + id: "credentials", + name: "Email & Password", + credentials: { + email: { + label: "Email", + type: "email", + placeholder: "you@example.com", + }, + password: { + label: "Password", + type: "password", + }, + }, + async authorize(credentials) { + if ( + !credentials?.email || + !credentials?.password || + typeof credentials.email !== "string" || + typeof credentials.password !== "string" + ) { + return null + } + + const result = await authenticateUser(credentials.email, credentials.password) + + if (!result.success || !result.user) { + return null + } + + return { + id: result.user.id, + email: result.user.email, + username: result.user.username, + firstName: result.user.firstName, + lastName: result.user.lastName, + isAdmin: result.user.isAdmin, + pterodactylId: result.user.pterodactylId, + } + }, + }), + ], + callbacks: { + async jwt({ token, user }) { + if (user) { + token.id = user.id + token.email = user.email! + token.username = user.username + token.firstName = user.firstName + token.lastName = user.lastName + token.isAdmin = user.isAdmin + token.pterodactylId = user.pterodactylId + } + return token + }, + async session({ session, token }) { + session.user = { + id: token.id, + email: token.email, + username: token.username, + firstName: token.firstName, + lastName: token.lastName, + isAdmin: token.isAdmin, + pterodactylId: token.pterodactylId, + } + return session + }, + }, + pages: { + signIn: "/auth/login", + error: "/auth/error", + }, + session: { + strategy: "jwt", + maxAge: 30 * 24 * 60 * 60, // 30 days + }, + trustHost: true, +}) + +// Helper function to require admin authentication +export async function requireAdmin() { + const session = await auth() + + if (!session?.user) { + return { authorized: false, error: "Not authenticated", status: 401 } + } + + if (!session.user.isAdmin) { + return { authorized: false, error: "Admin access required", status: 403 } + } + + return { authorized: true, user: session.user, status: 200 } +} + +// Helper function to require any authentication +export async function requireAuth() { + const session = await auth() + + if (!session?.user) { + return { authorized: false, error: "Not authenticated", status: 401 } + } + + return { authorized: true, user: session.user, status: 200 } +} diff --git a/packages/changelog/components/changelog-card.tsx b/packages/changelog/components/changelog-card.tsx new file mode 100644 index 0000000..305d793 --- /dev/null +++ b/packages/changelog/components/changelog-card.tsx @@ -0,0 +1,304 @@ +"use client" + +import { useState, useEffect } from "react" +import { ExternalLink, Download, ChevronDown, ChevronUp, Tag, GitBranch, User, Calendar, Package, Link2, Check } from "lucide-react" +import Link from "next/link" +import ReactMarkdown from "react-markdown" +import remarkGfm from "remark-gfm" +import { Card, CardContent, CardHeader } from "@/packages/ui/components/ui/card" +import { Badge } from "@/packages/ui/components/ui/badge" +import { Button } from "@/packages/ui/components/ui/button" +import { Avatar, AvatarFallback, AvatarImage } from "@/packages/ui/components/ui/avatar" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/packages/ui/components/ui/tooltip" +import { cn } from "@/packages/core/lib/utils" +import { type ChangelogRelease, extractSummary, formatFileSize, formatRelativeTime } from "../lib/changelog" + +interface ChangelogCardProps { + release: ChangelogRelease + translations: { + viewOnGithub: string + publishedOn: string + by: string + assets: string + downloadAsset: string + noAssets: string + preRelease: string + latest: string + draft: string + copyLink: string + linkCopied: string + } + isLatest?: boolean + isLinked?: boolean + className?: string +} + +const TYPE_COLORS: Record = { + feature: { bg: 'bg-green-500/10', text: 'text-green-600 dark:text-green-400' }, + bugfix: { bg: 'bg-red-500/10', text: 'text-red-600 dark:text-red-400' }, + improvement: { bg: 'bg-blue-500/10', text: 'text-blue-600 dark:text-blue-400' }, + breaking: { bg: 'bg-orange-500/10', text: 'text-orange-600 dark:text-orange-400' }, + security: { bg: 'bg-purple-500/10', text: 'text-purple-600 dark:text-purple-400' }, +} + +const TYPE_LABELS: Record = { + feature: 'Feature', + bugfix: 'Bug Fix', + improvement: 'Improvement', + breaking: 'Breaking', + security: 'Security', +} + +export function ChangelogCard({ release, translations, isLatest, isLinked, className }: ChangelogCardProps) { + const [showAssets, setShowAssets] = useState(false) + const [expanded, setExpanded] = useState(isLinked ?? false) + const [linkCopied, setLinkCopied] = useState(false) + + const summary = extractSummary(release.body) + const hasLongBody = (release.body?.length || 0) > 300 + const typeColor = TYPE_COLORS[release.type || 'improvement'] + const publishedDate = new Date(release.published_at || release.created_at) + + // Create a unique anchor ID for this release + const anchorId = `${release.repository.name}-${release.tag_name}` + + // Expand when linked to directly + useEffect(() => { + if (isLinked) { + setExpanded(true) + } + }, [isLinked]) + + const handleCopyLink = async () => { + const url = `${window.location.origin}${window.location.pathname}#${anchorId}` + try { + await navigator.clipboard.writeText(url) + setLinkCopied(true) + setTimeout(() => setLinkCopied(false), 2000) + } catch (err) { + console.error('Failed to copy link:', err) + } + } + + return ( + + +
+
+ {/* Repository & Version */} +
+ + + {release.repository.name} + + +
+ + {release.tag_name} +
+ + + + + + + +

{linkCopied ? translations.linkCopied : translations.copyLink}

+
+
+
+
+ + {/* Title */} +

+ + {release.name || release.tag_name} + +

+
+ + {/* Badges */} +
+ {isLatest && ( + + {translations.latest} + + )} + {release.prerelease && ( + + {translations.preRelease} + + )} + {release.draft && ( + + {translations.draft} + + )} + {release.type && ( + + {TYPE_LABELS[release.type]} + + )} +
+
+ + {/* Meta info */} +
+
+ + + {release.author.login[0].toUpperCase()} + + {release.author.login} +
+
+ + + {formatRelativeTime(release.published_at || release.created_at)} + +
+ {release.assets.length > 0 && ( +
+ + {release.assets.length} {translations.assets.toLowerCase()} +
+ )} +
+
+ + + {/* Summary/Body */} + {release.body && ( +
+
+ {expanded ? ( + + {release.body} + + ) : ( +

{summary}

+ )} +
+ {hasLongBody && ( + + )} +
+ )} + + {/* Assets */} + {release.assets.length > 0 && ( +
+ + + {showAssets && ( +
+ {release.assets.map((asset) => ( +
+
+

{asset.name}

+

+ {formatFileSize(asset.size)} • {asset.download_count} downloads +

+
+ +
+ ))} +
+ )} +
+ )} + + {/* View on GitHub */} +
+ +
+
+
+ ) +} diff --git a/packages/changelog/components/changelog-filters.tsx b/packages/changelog/components/changelog-filters.tsx new file mode 100644 index 0000000..b55ea6a --- /dev/null +++ b/packages/changelog/components/changelog-filters.tsx @@ -0,0 +1,105 @@ +"use client" + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/packages/ui/components/ui/select" +import { Button } from "@/packages/ui/components/ui/button" +import { X } from "lucide-react" +import { cn } from "@/packages/core/lib/utils" + +interface ChangelogFiltersProps { + repositories: string[] + selectedRepository: string | null + selectedType: string | null + onRepositoryChange: (repo: string | null) => void + onTypeChange: (type: string | null) => void + onClearFilters: () => void + translations: { + all: string + allTypes: string + feature: string + bugfix: string + improvement: string + breaking: string + security: string + } + className?: string +} + +const RELEASE_TYPES = [ + { value: 'feature', labelKey: 'feature' }, + { value: 'bugfix', labelKey: 'bugfix' }, + { value: 'improvement', labelKey: 'improvement' }, + { value: 'breaking', labelKey: 'breaking' }, + { value: 'security', labelKey: 'security' }, +] as const + +export function ChangelogFilters({ + repositories, + selectedRepository, + selectedType, + onRepositoryChange, + onTypeChange, + onClearFilters, + translations, + className, +}: ChangelogFiltersProps) { + const hasActiveFilters = selectedRepository !== null || selectedType !== null + + return ( +
+ {/* Repository Filter */} + + + {/* Type Filter */} + + + {/* Clear Filters Button */} + {hasActiveFilters && ( + + )} +
+ ) +} diff --git a/packages/changelog/components/changelog-list.tsx b/packages/changelog/components/changelog-list.tsx new file mode 100644 index 0000000..17c54bf --- /dev/null +++ b/packages/changelog/components/changelog-list.tsx @@ -0,0 +1,346 @@ +"use client" + +import { useState, useMemo, useEffect } from "react" +import { Rocket, Package, GitBranch, Calendar, RefreshCw, SearchX, ChevronLeft, ChevronRight } from "lucide-react" +import { useTranslations } from "next-intl" +import { Card, CardContent } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { Skeleton } from "@/packages/ui/components/ui/skeleton" +import { ChangelogCard } from "./changelog-card" +import { ChangelogSearch } from "./changelog-search" +import { ChangelogFilters } from "./changelog-filters" +import { useChangelogReleases } from "../hooks/use-changelog" +import { filterReleases, formatRelativeTime, type ChangelogFilters as Filters } from "../lib/changelog" +import { cn } from "@/packages/core/lib/utils" + +interface ChangelogListProps { + className?: string +} + +const ITEMS_PER_PAGE = 4 + +export function ChangelogList({ className }: ChangelogListProps) { + const t = useTranslations() + const { releases, repositories, isLoading, error, lastUpdated, refetch } = useChangelogReleases() + + const [filters, setFilters] = useState({ + search: "", + repository: null, + type: null, + }) + const [currentPage, setCurrentPage] = useState(1) + const [linkedReleaseId, setLinkedReleaseId] = useState(null) + + // Filter releases + const filteredReleases = useMemo(() => { + return filterReleases(releases, filters) + }, [releases, filters]) + + // Calculate pagination + const totalPages = Math.ceil(filteredReleases.length / ITEMS_PER_PAGE) + const startIndex = (currentPage - 1) * ITEMS_PER_PAGE + const paginatedReleases = filteredReleases.slice(startIndex, startIndex + ITEMS_PER_PAGE) + + // Reset to page 1 when filters change + useEffect(() => { + setCurrentPage(1) + }, [filters]) + + // Handle URL hash for direct release linking + useEffect(() => { + if (typeof window !== 'undefined' && releases.length > 0) { + const hash = window.location.hash.slice(1) // Remove # + if (hash) { + // Find the release and calculate which page it's on + const releaseIndex = filteredReleases.findIndex( + r => `${r.repository.name}-${r.tag_name}` === hash || r.tag_name === hash + ) + if (releaseIndex !== -1) { + const page = Math.floor(releaseIndex / ITEMS_PER_PAGE) + 1 + setCurrentPage(page) + setLinkedReleaseId(hash) // Track which release is linked + // Scroll to element after a small delay + setTimeout(() => { + const element = document.getElementById(hash) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }) + } + }, 100) + } + } + } + }, [releases, filteredReleases]) + + const handleClearFilters = () => { + setFilters({ search: "", repository: null, type: null }) + setCurrentPage(1) + } + + const handlePageChange = (page: number) => { + setCurrentPage(page) + // Scroll to top of list + window.scrollTo({ top: 300, behavior: 'smooth' }) + } + + // Loading state + if (isLoading) { + return ( +
+ {/* Stats skeleton */} +
+ {[1, 2, 3].map((i) => ( + + + + + + + ))} +
+ + {/* Search & filters skeleton */} +
+ +
+ + +
+
+ + {/* Release cards skeleton */} +
+ {[1, 2, 3].map((i) => ( + + +
+
+ + +
+ +
+ + +
+
+ ))} +
+
+ ) + } + + // Error state + if (error) { + return ( +
+ + +
+ +
+

{t("changelog.error.title")}

+

{t("changelog.error.description")}

+ +
+
+
+ ) + } + + return ( +
+ {/* Stats */} +
+ + +
+ +
+
+

{t("changelog.stats.totalReleases")}

+

{releases.length}

+
+
+
+ + +
+ +
+
+

{t("changelog.stats.repositories")}

+

{repositories.length}

+
+
+
+ + +
+ +
+
+

{t("changelog.stats.latestUpdate")}

+

+ {lastUpdated ? formatRelativeTime(lastUpdated) : '-'} +

+
+
+
+
+ + {/* Search & Filters */} +
+ setFilters(prev => ({ ...prev, search: value }))} + placeholder={t("changelog.searchPlaceholder")} + className="flex-1 max-w-md" + /> + setFilters(prev => ({ ...prev, repository: repo }))} + onTypeChange={(type) => setFilters(prev => ({ ...prev, type: type }))} + onClearFilters={handleClearFilters} + translations={{ + all: t("changelog.filters.all"), + allTypes: t("changelog.filters.allTypes"), + feature: t("changelog.filters.feature"), + bugfix: t("changelog.filters.bugfix"), + improvement: t("changelog.filters.improvement"), + breaking: t("changelog.filters.breaking"), + security: t("changelog.filters.security"), + }} + /> +
+ + {/* Releases List */} + {filteredReleases.length === 0 ? ( + + +
+ +
+

{t("changelog.empty.title")}

+

{t("changelog.empty.description")}

+ +
+
+ ) : ( + <> +
+ {paginatedReleases.map((release, index) => { + const releaseAnchorId = `${release.repository.name}-${release.tag_name}` + return ( + + ) + })} +
+ + {/* Pagination Controls */} + {totalPages > 1 && ( +
+ + +
+ {/* First page */} + {currentPage > 2 && ( + <> + + {currentPage > 3 && ( + ... + )} + + )} + + {/* Pages around current */} + {Array.from({ length: totalPages }, (_, i) => i + 1) + .filter(page => { + if (totalPages <= 5) return true + return Math.abs(page - currentPage) <= 1 + }) + .map(page => ( + + ))} + + {/* Last page */} + {currentPage < totalPages - 1 && ( + <> + {currentPage < totalPages - 2 && ( + ... + )} + + + )} +
+ + +
+ )} + + )} +
+ ) +} diff --git a/packages/changelog/components/changelog-search.tsx b/packages/changelog/components/changelog-search.tsx new file mode 100644 index 0000000..1958097 --- /dev/null +++ b/packages/changelog/components/changelog-search.tsx @@ -0,0 +1,41 @@ +"use client" + +import { Search, X } from "lucide-react" +import { Input } from "@/packages/ui/components/ui/input" +import { cn } from "@/packages/core/lib/utils" + +interface ChangelogSearchProps { + value: string + onChange: (value: string) => void + placeholder?: string + className?: string +} + +export function ChangelogSearch({ + value, + onChange, + placeholder = "Search releases...", + className +}: ChangelogSearchProps) { + return ( +
+ + onChange(e.target.value)} + className="pl-9 pr-9" + /> + {value && ( + + )} +
+ ) +} diff --git a/packages/changelog/components/index.ts b/packages/changelog/components/index.ts new file mode 100644 index 0000000..672f46b --- /dev/null +++ b/packages/changelog/components/index.ts @@ -0,0 +1,19 @@ +// Components +export { ChangelogCard } from './changelog-card' +export { ChangelogSearch } from './changelog-search' +export { ChangelogFilters } from './changelog-filters' +export { ChangelogList } from './changelog-list' + +// Hooks +export { useChangelogReleases } from '../hooks/use-changelog' + +// Utils +export { + detectReleaseType, + extractSummary, + formatFileSize, + formatRelativeTime, + filterReleases, + type ChangelogRelease, + type ChangelogFilters as ChangelogFiltersType, +} from '../lib/changelog' diff --git a/packages/changelog/hooks/use-changelog.ts b/packages/changelog/hooks/use-changelog.ts new file mode 100644 index 0000000..f3889b9 --- /dev/null +++ b/packages/changelog/hooks/use-changelog.ts @@ -0,0 +1,61 @@ +"use client" + +import { useState, useEffect, useCallback } from 'react' +import type { GitHubRelease, ReleasesResponse } from '@/app/api/github/releases/route' +import { detectReleaseType, type ChangelogRelease } from '../lib/changelog' + +interface UseChangelogReleasesOptions { + initialReleases?: ChangelogRelease[] +} + +export function useChangelogReleases(options: UseChangelogReleasesOptions = {}) { + const [releases, setReleases] = useState(options.initialReleases || []) + const [repositories, setRepositories] = useState([]) + const [isLoading, setIsLoading] = useState(!options.initialReleases) + const [error, setError] = useState(null) + const [lastUpdated, setLastUpdated] = useState(null) + + const fetchReleases = useCallback(async () => { + setIsLoading(true) + setError(null) + + try { + const response = await fetch('/api/github/releases') + + if (!response.ok) { + throw new Error('Failed to fetch releases') + } + + const data: ReleasesResponse = await response.json() + + // Add type detection to each release + const releasesWithType: ChangelogRelease[] = data.releases.map(release => ({ + ...release, + type: detectReleaseType(release) + })) + + setReleases(releasesWithType) + setRepositories(data.repositories) + setLastUpdated(data.lastUpdated) + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred') + } finally { + setIsLoading(false) + } + }, []) + + useEffect(() => { + if (!options.initialReleases) { + fetchReleases() + } + }, [fetchReleases, options.initialReleases]) + + return { + releases, + repositories, + isLoading, + error, + lastUpdated, + refetch: fetchReleases + } +} diff --git a/packages/changelog/lib/changelog.ts b/packages/changelog/lib/changelog.ts new file mode 100644 index 0000000..ae82163 --- /dev/null +++ b/packages/changelog/lib/changelog.ts @@ -0,0 +1,135 @@ +import type { GitHubRelease } from '@/app/api/github/releases/route' + +export interface ChangelogRelease extends GitHubRelease { + type?: 'feature' | 'bugfix' | 'improvement' | 'breaking' | 'security' +} + +export interface ChangelogFilters { + search: string + repository: string | null + type: string | null +} + +/** + * Parse release body to detect the type of release based on keywords + */ +export function detectReleaseType(release: GitHubRelease): ChangelogRelease['type'] { + const body = (release.body || '').toLowerCase() + const name = (release.name || release.tag_name).toLowerCase() + + // Check for security-related keywords + if (body.includes('security') || body.includes('vulnerability') || body.includes('cve-')) { + return 'security' + } + + // Check for breaking changes + if (body.includes('breaking change') || body.includes('breaking:') || name.includes('breaking')) { + return 'breaking' + } + + // Check for bug fixes + if (body.includes('bug fix') || body.includes('bugfix') || body.includes('fix:') || name.includes('fix')) { + return 'bugfix' + } + + // Check for new features + if (body.includes('new feature') || body.includes('feat:') || body.includes('feature:') || name.includes('feature')) { + return 'feature' + } + + // Default to improvement + return 'improvement' +} + +/** + * Parse markdown body to extract summary (first paragraph) + */ +export function extractSummary(body: string | null, maxLength = 200): string { + if (!body) return '' + + // Remove HTML comments + let text = body.replace(//g, '') + + // Remove markdown headers + text = text.replace(/^#{1,6}\s+.+$/gm, '') + + // Get first meaningful paragraph + const paragraphs = text.split(/\n\n+/).filter(p => p.trim().length > 0) + const firstPara = paragraphs[0] || '' + + // Clean up markdown formatting + let cleaned = firstPara + .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links but keep text + .replace(/[*_`]/g, '') // Remove formatting + .replace(/\n/g, ' ') // Replace newlines with spaces + .trim() + + if (cleaned.length > maxLength) { + cleaned = cleaned.substring(0, maxLength).trim() + '...' + } + + return cleaned +} + +/** + * Format file size in human readable format + */ +export function formatFileSize(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i] +} + +/** + * Format relative time + */ +export function formatRelativeTime(dateString: string): string { + const date = new Date(dateString) + const now = new Date() + const diffMs = now.getTime() - date.getTime() + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)) + + if (diffDays === 0) return 'Today' + if (diffDays === 1) return 'Yesterday' + if (diffDays < 7) return `${diffDays} days ago` + if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago` + if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago` + return `${Math.floor(diffDays / 365)} years ago` +} + +/** + * Filter releases based on search query and filters + */ +export function filterReleases( + releases: ChangelogRelease[], + filters: ChangelogFilters +): ChangelogRelease[] { + return releases.filter(release => { + // Filter by repository + if (filters.repository && release.repository.full_name !== filters.repository) { + return false + } + + // Filter by type + if (filters.type && release.type !== filters.type) { + return false + } + + // Filter by search query + if (filters.search) { + const query = filters.search.toLowerCase() + const matchName = release.name?.toLowerCase().includes(query) + const matchTag = release.tag_name.toLowerCase().includes(query) + const matchBody = release.body?.toLowerCase().includes(query) + const matchRepo = release.repository.name.toLowerCase().includes(query) + + if (!matchName && !matchTag && !matchBody && !matchRepo) { + return false + } + } + + return true + }) +} diff --git a/packages/core/constants/minecraft/faqs.ts b/packages/core/constants/minecraft/faqs.ts new file mode 100644 index 0000000..6b16a22 --- /dev/null +++ b/packages/core/constants/minecraft/faqs.ts @@ -0,0 +1,26 @@ +export const MINECRAFT_FAQS = [ + { + question: "What Minecraft versions do you support?", + answer: "We support all Minecraft versions from 1.7.10 to the latest release, including snapshots. Both Java Edition and Bedrock Edition servers are available.", + }, + { + question: "Can I install mods and plugins?", + answer: "Yes! We support all major mod loaders including Forge, Fabric, and NeoForge. For plugins, we support Paper, Spigot, Bukkit, and Purpur. You can also upload custom JARs.", + }, + { + question: "How do I upload my existing world?", + answer: "You can upload your world files via our web-based file manager or through SFTP. Simply drag and drop your world folder and it will be ready to use.", + }, + { + question: "Is there a player limit?", + answer: "No, we don't impose artificial player limits. Your server can host as many players as your allocated resources can handle.", + }, + { + question: "Can I upgrade my plan later?", + answer: "Absolutely! You can upgrade or downgrade your plan at any time from our billing panel. Changes take effect immediately with no downtime.", + }, + { + question: "Do you offer refunds?", + answer: "Yes, we offer a 48-hour money-back guarantee on all new purchases. If you're not satisfied, contact support for a full refund.", + }, +] \ No newline at end of file diff --git a/packages/core/constants/minecraft/features.ts b/packages/core/constants/minecraft/features.ts new file mode 100644 index 0000000..2a4ab1f --- /dev/null +++ b/packages/core/constants/minecraft/features.ts @@ -0,0 +1,68 @@ +export const MINECRAFT_FEATURES = [ + { + title: "One-Click Mod Loaders", + description: "Install Forge, Fabric, Paper, Spigot, and more with a single click from our control panel.", + icon: "Settings" as const, + highlights: [ + "Forge & Fabric support", + "Paper & Spigot servers", + "Bukkit compatibility", + "Custom JAR uploads", + ], + }, + { + title: "High Performance Hardware", + description: "Enterprise grade AMD Ryzen processors with NVMe storage for blazing fast performance.", + icon: "Cpu" as const, + highlights: [ + "AMD Ryzen 9 CPUs", + "NVMe SSD storage", + "DDR4 ECC memory", + "Up to 4.8GHz clock speed", + ], + }, + { + title: "DDoS Protection", + description: "Enterprise grade DDoS mitigation powered by FyfeWeb keeps your server online even during attacks.", + icon: "Shield" as const, + highlights: [ + "Layer 3/4/7 protection", + "FyfeWeb network filtering", + "Zero downtime mitigation", + "UK London POPs", + ], + }, + { + title: "Instant Setup", + description: "Your server is deployed within seconds. Start playing immediately after purchase.", + icon: "Zap" as const, + highlights: [ + "Automated provisioning", + "Pre-configured settings", + "Ready in under 60 seconds", + "No technical knowledge needed", + ], + }, + { + title: "Full FTP Access", + description: "Complete file access via FTP/SFTP. Upload worlds, plugins, and configurations with ease.", + icon: "HardDrive" as const, + highlights: [ + "SFTP file access", + "Web-based file manager", + "Drag & drop uploads", + "Automatic backups", + ], + }, + { + title: "Unlimited Slots", + description: "No artificial player limits. Host as many players as your hardware can handle.", + icon: "Users" as const, + highlights: [ + "No slot restrictions", + "Scalable resources", + "Upgrade anytime", + "Fair resource allocation", + ], + }, +] \ No newline at end of file diff --git a/packages/core/constants/minecraft/plans.ts b/packages/core/constants/minecraft/plans.ts new file mode 100644 index 0000000..fa02a31 --- /dev/null +++ b/packages/core/constants/minecraft/plans.ts @@ -0,0 +1,54 @@ +export const MINECRAFT_PLANS = [ + { + name: "Ember", + description: "Perfect for small servers and testing", + priceGBP: 4, + period: "month", + features: [ + "AMD Ryzen™ 9 5900X", + "4 GB DDR4 RAM", + "40 GB Storage", + "10 MySQL Databases", + "FyfeWeb DDoS Protection", + "BytePanel", + "Auto/Pre-Installed jars", + "99.6% Uptime SLA", + ], + url: "https://billing.nodebyte.host/store/minecraft-server-hosting/ember" + }, + { + name: "Blaze", + description: "Great for growing communities", + priceGBP: 6, + period: "month", + features: [ + "AMD Ryzen™ 9 5900X", + "6 GB DDR4 RAM", + "60 GB Storage", + "10 MySQL Databases", + "FyfeWeb DDoS Protection", + "BytePanel", + "Auto/Pre-Installed jars", + "99.6% Uptime SLA", + ], + url: "https://billing.nodebyte.host/store/minecraft-server-hosting/blaze" + }, + { + name: "Inferno", + description: "Ideal for medium-sized communities", + priceGBP: 7.5, + period: "month", + popular: true, + features: [ + "AMD Ryzen™ 9 5900X", + "8 GB DDR4 RAM", + "80 GB Storage", + "10 MySQL Databases", + "FyfeWeb DDoS Protection", + "BytePanel", + "Auto/Pre-Installed jars", + "99.6% Uptime SLA", + ], + url: "https://billing.nodebyte.host/store/minecraft-server-hosting/inferno" + }, +] \ No newline at end of file diff --git a/packages/core/constants/rust/faqs.ts b/packages/core/constants/rust/faqs.ts new file mode 100644 index 0000000..e285568 --- /dev/null +++ b/packages/core/constants/rust/faqs.ts @@ -0,0 +1,26 @@ +export const RUST_FAQS = [ + { + question: "Do you support Oxide/uMod plugins?", + answer: "Yes! We fully support Oxide and uMod. You can install Oxide with one click from our control panel and manage plugins through our plugin manager or via FTP.", + }, + { + question: "Can I use custom maps?", + answer: "Absolutely! You can use procedurally generated maps with custom seeds and sizes, or upload your own custom map files via FTP.", + }, + { + question: "How does the wipe scheduler work?", + answer: "Our wipe scheduler lets you automate server wipes on a schedule you choose. You can configure map-only wipes or full blueprint wipes, and optionally send Discord notifications.", + }, + { + question: "What's the server tick rate?", + answer: "Our Rust servers run at the default 30 tick rate. Our high-performance hardware ensures consistent performance even with many players online.", + }, + { + question: "Can I access RCON?", + answer: "Yes, you get full RCON access. You can use our web-based RCON console or connect with any standard RCON client.", + }, + { + question: "Do you support modded servers?", + answer: "Yes, we support both vanilla and modded Rust servers. Install Oxide and add any plugins you need to create your perfect modded experience.", + }, +] \ No newline at end of file diff --git a/packages/core/constants/rust/features.ts b/packages/core/constants/rust/features.ts new file mode 100644 index 0000000..0b7da32 --- /dev/null +++ b/packages/core/constants/rust/features.ts @@ -0,0 +1,68 @@ +export const RUST_FEATURES = [ + { + title: "Oxide/uMod Support", + description: "Full support for Oxide and uMod plugins. Install and manage plugins directly from our control panel.", + icon: "Settings" as const, + highlights: [ + "One-click Oxide install", + "Plugin manager", + "Auto-updates available", + "Permission management", + ], + }, + { + title: "Custom Maps", + description: "Use procedurally generated maps or upload your own custom maps. Full map customization support.", + icon: "Map" as const, + highlights: [ + "Procedural generation", + "Custom map uploads", + "Map size control", + "Seed customization", + ], + }, + { + title: "High Performance", + description: "Rust demands powerful hardware. Our servers use the latest AMD Ryzen CPUs and NVMe storage.", + icon: "Cpu" as const, + highlights: [ + "AMD Ryzen 9 CPUs", + "NVMe SSD storage", + "High single-thread performance", + "Low-latency networking", + ], + }, + { + title: "DDoS Protection", + description: "FyfeWeb's enterprise-grade DDoS mitigation through UK London POPs protects your server from attacks 24/7.", + icon: "Shield" as const, + highlights: [ + "FyfeWeb network filtering", + "UK London POPs", + "Layer 3/4/7 protection", + "Zero downtime", + ], + }, + { + title: "Wipe Scheduler", + description: "Automated wipe scheduling to keep your server fresh. Configure weekly, bi-weekly, or monthly wipes.", + icon: "Zap" as const, + highlights: [ + "Automated wipes", + "Blueprint wipe options", + "Map wipe scheduling", + "Discord notifications", + ], + }, + { + title: "Full RCON Access", + description: "Complete remote console access for server management. Execute commands from anywhere.", + icon: "Server" as const, + highlights: [ + "Web-based RCON", + "Command scheduling", + "Player management", + "Real-time logs", + ], + }, +] \ No newline at end of file diff --git a/packages/core/constants/rust/plans.ts b/packages/core/constants/rust/plans.ts new file mode 100644 index 0000000..76529e6 --- /dev/null +++ b/packages/core/constants/rust/plans.ts @@ -0,0 +1,57 @@ +export const RUST_PLANS = [ + { + name: "Starter", + description: "Recommended for 40 Players", + priceGBP: 5.75, + period: "month", + features: [ + "AMD Ryzen™ 9 5900X", + "8 GB DDR4 RAM", + "150GB SSD Storage", + "FyfeWeb DDoS Protection", + "Newcastle, United Kingdom", + "10 MySQL Databases", + "BytePanel (Game Panel)", + "Oxide/Umod Supported", + "Rust+ Supported", + "99.6% Uptime SLA", + ], + }, + { + name: "Standard", + description: "Recommended for 75 Players", + priceGBP: 8.95, + period: "month", + popular: true, + features: [ + "AMD Ryzen™ 9 5900X", + "12 GB DDR4 RAM", + "200GB SSD Storage", + "FyfeWeb DDoS Protection", + "Newcastle, United Kingdom", + "10 MySQL Databases", + "BytePanel (Game Panel)", + "Oxide/Umod Supported", + "Rust+ Supported", + "99.6% Uptime SLA", + ], + }, + { + name: "Performance", + description: "Recommended for 100 Players", + priceGBP: 12.75, + period: "month", + features: [ + "AMD Ryzen™ 9 5900X", + "16 GB DDR4 RAM", + "250GB SSD Storage", + "FyfeWeb DDoS Protection", + "Newcastle, United Kingdom", + "10 MySQL Databases", + "BytePanel (Game Panel)", + "Oxide/Umod Supported", + "Rust+ Supported", + "99.6% Uptime SLA", + ], + }, +] \ No newline at end of file diff --git a/packages/core/hooks/use-currency.tsx b/packages/core/hooks/use-currency.tsx new file mode 100644 index 0000000..05263f0 --- /dev/null +++ b/packages/core/hooks/use-currency.tsx @@ -0,0 +1,86 @@ +"use client" + +import { useState, useEffect, useCallback, createContext, useContext, type ReactNode } from "react" +import { + type CurrencyCode, + currencies, + currencyList, + convertFromGBP, + formatPrice, + convertAndFormat, + getDefaultCurrency, + CURRENCY_STORAGE_KEY, +} from "@/lib/currency" + +interface CurrencyContextValue { + currency: CurrencyCode + setCurrency: (currency: CurrencyCode) => void + convert: (amountGBP: number) => number + format: (amount: number) => string + convertAndFormat: (amountGBP: number) => string + currencies: typeof currencies + currencyList: typeof currencyList +} + +const CurrencyContext = createContext(null) + +export function CurrencyProvider({ children }: { children: ReactNode }) { + const [currency, setCurrencyState] = useState("GBP") + const [mounted, setMounted] = useState(false) + + // Initialize currency from localStorage or browser locale + useEffect(() => { + setMounted(true) + const stored = localStorage.getItem(CURRENCY_STORAGE_KEY) as CurrencyCode | null + if (stored && currencies[stored]) { + setCurrencyState(stored) + } else { + setCurrencyState(getDefaultCurrency()) + } + }, []) + + const setCurrency = useCallback((newCurrency: CurrencyCode) => { + setCurrencyState(newCurrency) + localStorage.setItem(CURRENCY_STORAGE_KEY, newCurrency) + }, []) + + const convert = useCallback( + (amountGBP: number) => convertFromGBP(amountGBP, currency), + [currency] + ) + + const format = useCallback( + (amount: number) => formatPrice(amount, currency), + [currency] + ) + + const convertAndFormatFn = useCallback( + (amountGBP: number) => convertAndFormat(amountGBP, currency), + [currency] + ) + + // Prevent hydration mismatch by returning GBP during SSR + const value: CurrencyContextValue = { + currency: mounted ? currency : "GBP", + setCurrency, + convert, + format: mounted ? format : (amount) => formatPrice(amount, "GBP"), + convertAndFormat: mounted ? convertAndFormatFn : (amount) => convertAndFormat(amount, "GBP"), + currencies, + currencyList, + } + + return ( + + {children} + + ) +} + +export function useCurrency() { + const context = useContext(CurrencyContext) + if (!context) { + throw new Error("useCurrency must be used within a CurrencyProvider") + } + return context +} diff --git a/packages/core/hooks/use-locale.tsx b/packages/core/hooks/use-locale.tsx new file mode 100644 index 0000000..76c5a54 --- /dev/null +++ b/packages/core/hooks/use-locale.tsx @@ -0,0 +1,63 @@ +"use client" + +import { useState, useEffect, useCallback, createContext, useContext, type ReactNode } from "react" +import { locales, localeNames, localeFlags, defaultLocale, LOCALE_COOKIE, type Locale } from "@/packages/i18n/config" + +interface LocaleContextValue { + locale: Locale + setLocale: (locale: Locale) => void + locales: typeof locales + localeNames: typeof localeNames + localeFlags: typeof localeFlags +} + +const LocaleContext = createContext(null) + +export function LocaleProvider({ children, initialLocale }: { children: ReactNode; initialLocale?: Locale }) { + const [locale, setLocaleState] = useState(initialLocale || defaultLocale) + + useEffect(() => { + // Read from cookie on mount + const cookieLocale = document.cookie + .split('; ') + .find(row => row.startsWith(`${LOCALE_COOKIE}=`)) + ?.split('=')[1] as Locale | undefined + + if (cookieLocale && locales.includes(cookieLocale)) { + setLocaleState(cookieLocale) + } + }, []) + + const setLocale = useCallback((newLocale: Locale) => { + // Set cookie with 1 year expiry + document.cookie = `${LOCALE_COOKIE}=${newLocale}; path=/; max-age=${60 * 60 * 24 * 365}; SameSite=Lax` + setLocaleState(newLocale) + // Hard reload to clear server-side translation cache and load new translations + window.location.reload() + }, []) + + const value: LocaleContextValue = { + locale, + setLocale, + locales, + localeNames, + localeFlags, + } + + return ( + + {children} + + ) +} + +export function useLocale() { + const context = useContext(LocaleContext) + if (!context) { + throw new Error("useLocale must be used within a LocaleProvider") + } + return context +} + +// Re-export types and constants for convenience +export { locales, localeNames, localeFlags, defaultLocale, type Locale } diff --git a/hooks/use-mobile.ts b/packages/core/hooks/use-mobile.ts similarity index 100% rename from hooks/use-mobile.ts rename to packages/core/hooks/use-mobile.ts diff --git a/hooks/use-toast.ts b/packages/core/hooks/use-toast.ts similarity index 97% rename from hooks/use-toast.ts rename to packages/core/hooks/use-toast.ts index 8932bc5..998922a 100644 --- a/hooks/use-toast.ts +++ b/packages/core/hooks/use-toast.ts @@ -3,7 +3,7 @@ // Inspired by react-hot-toast library import * as React from 'react' -import type { ToastActionElement, ToastProps } from '@/components/ui/toast' +import type { ToastActionElement, ToastProps } from '@/packages/ui/components/ui/toast' const TOAST_LIMIT = 1 const TOAST_REMOVE_DELAY = 1000000 diff --git a/packages/core/lib/currency.ts b/packages/core/lib/currency.ts new file mode 100644 index 0000000..1fc1158 --- /dev/null +++ b/packages/core/lib/currency.ts @@ -0,0 +1,80 @@ +// Currency conversion utilities +// Base currency is GBP (£) + +export type CurrencyCode = "GBP" | "USD" | "EUR" | "CAD" | "AUD" + +export interface Currency { + code: CurrencyCode + symbol: string + name: string + rate: number // Rate relative to GBP (GBP = 1) +} + +// Exchange rates relative to GBP (approximate, update as needed) +export const currencies: Record = { + GBP: { code: "GBP", symbol: "£", name: "British Pound", rate: 1 }, + USD: { code: "USD", symbol: "$", name: "US Dollar", rate: 1.27 }, + EUR: { code: "EUR", symbol: "€", name: "Euro", rate: 1.20 }, + CAD: { code: "CAD", symbol: "$", name: "Canadian Dollar", rate: 1.78 }, + AUD: { code: "AUD", symbol: "$", name: "Australian Dollar", rate: 1.97 }, +} + +export const currencyList = Object.values(currencies) + +/** + * Convert a GBP amount to another currency + */ +export function convertFromGBP(amountGBP: number, toCurrency: CurrencyCode): number { + const rate = currencies[toCurrency].rate + return Math.round(amountGBP * rate * 100) / 100 +} + +/** + * Format a price with the correct currency symbol + */ +export function formatPrice(amount: number, currencyCode: CurrencyCode): string { + const currency = currencies[currencyCode] + + // Format with 2 decimal places, but show whole numbers without decimals + const formatted = amount % 1 === 0 + ? amount.toFixed(0) + : amount.toFixed(2) + + return `${currency.symbol}${formatted}` +} + +/** + * Convert and format a GBP price to another currency + */ +export function convertAndFormat(amountGBP: number, toCurrency: CurrencyCode): string { + const converted = convertFromGBP(amountGBP, toCurrency) + return formatPrice(converted, toCurrency) +} + +/** + * Parse a price string (e.g., "£4.00") to extract the numeric value + */ +export function parsePrice(priceString: string): number { + const numericValue = priceString.replace(/[^0-9.]/g, "") + return parseFloat(numericValue) || 0 +} + +/** + * Get browser/system locale currency (best guess) + */ +export function getDefaultCurrency(): CurrencyCode { + if (typeof window === "undefined") return "GBP" + + const locale = navigator.language || "en-GB" + + // Map common locales to currencies + if (locale.includes("US")) return "USD" + if (locale.includes("CA")) return "CAD" + if (locale.includes("AU")) return "AUD" + if (locale.includes("DE") || locale.includes("FR") || locale.includes("ES") || locale.includes("IT")) return "EUR" + + return "GBP" +} + +// Storage key for persisting currency preference +export const CURRENCY_STORAGE_KEY = "nodebyte-currency" diff --git a/packages/core/lib/prisma.ts b/packages/core/lib/prisma.ts new file mode 100644 index 0000000..f095b07 --- /dev/null +++ b/packages/core/lib/prisma.ts @@ -0,0 +1,20 @@ +import { PrismaClient } from "../../../prisma/generated/prisma/client" +import { PrismaPg } from "@prisma/adapter-pg" + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +// Create the PostgreSQL adapter +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! }) + +export const prisma = + globalForPrisma.prisma ?? + new PrismaClient({ + adapter, + log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], + }) + +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma + +export default prisma diff --git a/packages/core/lib/pterodactyl.ts b/packages/core/lib/pterodactyl.ts new file mode 100644 index 0000000..6d56e87 --- /dev/null +++ b/packages/core/lib/pterodactyl.ts @@ -0,0 +1,202 @@ +// Pterodactyl API helper functions +// API Documentation: https://dashflo.net/docs/api/pterodactyl/v1/ + +interface PterodactylPaginatedResponse { + object: string + data: T[] + meta: { + pagination: { + total: number + count: number + per_page: number + current_page: number + total_pages: number + links: Record + } + } +} + +interface PterodactylServer { + object: "server" + attributes: { + id: number + external_id: string | null + uuid: string + identifier: string + name: string + description: string + status: string | null + suspended: boolean + limits: { + memory: number + swap: number + disk: number + io: number + cpu: number + threads: string | null + oom_disabled: boolean + } + feature_limits: { + databases: number + allocations: number + backups: number + } + user: number + node: number + allocation: number + nest: number + egg: number + container: { + startup_command: string + image: string + installed: number + environment: Record + } + created_at: string + updated_at: string + } +} + +interface PterodactylUser { + object: "user" + attributes: { + id: number + external_id: string | null + uuid: string + username: string + email: string + first_name: string + last_name: string + language: string + root_admin: boolean + "2fa": boolean + created_at: string + updated_at: string + } +} + +interface PterodactylNode { + object: "node" + attributes: { + id: number + uuid: string + public: boolean + name: string + description: string | null + location_id: number + fqdn: string + scheme: string + behind_proxy: boolean + maintenance_mode: boolean + memory: number + memory_overallocate: number + disk: number + disk_overallocate: number + upload_size: number + daemon_listen: number + daemon_sftp: number + daemon_base: string + created_at: string + updated_at: string + allocated_resources?: { + memory: number + disk: number + } + } +} + +async function fetchFromPterodactyl(endpoint: string): Promise { + const response = await fetch(`${process.env.GAMEPANEL_API}${endpoint}`, { + headers: { + "Authorization": `Bearer ${process.env.GAMEPANEL_API_KEY}`, + "Content-Type": "application/json", + "Accept": "application/json", + // Headers to bypass Cloudflare bot protection + "User-Agent": "NodeByte-Website/1.0 (https://nodebyte.host)", + "CF-Access-Client-Id": process.env.CF_ACCESS_CLIENT_ID || "", + "CF-Access-Client-Secret": process.env.CF_ACCESS_CLIENT_SECRET || "", + }, + next: { revalidate: 60 }, // Cache for 60 seconds + }) + + if (!response.ok) { + throw new Error(`Pterodactyl API error: ${response.status} ${response.statusText}`) + } + + return response.json() +} + +export async function getServers(page = 1, perPage = 50): Promise> { + return fetchFromPterodactyl(`/application/servers?page=${page}&per_page=${perPage}`) +} + +export async function getServerCount(): Promise { + const response = await getServers(1, 1) + return response.meta.pagination.total +} + +export async function getUsers(page = 1, perPage = 50): Promise> { + return fetchFromPterodactyl(`/application/users?page=${page}&per_page=${perPage}`) +} + +export async function getUserCount(): Promise { + const response = await getUsers(1, 1) + return response.meta.pagination.total +} + +export async function getNodes(): Promise> { + return fetchFromPterodactyl(`/application/nodes?include=allocated_resources`) +} + +export async function getNodeCount(): Promise { + const response = await getNodes() + return response.meta.pagination.total +} + +export async function getStats(): Promise<{ + servers: number + users: number + nodes: number + totalMemory: number + totalDisk: number + allocatedMemory: number + allocatedDisk: number +}> { + const [serversData, usersData, nodesData] = await Promise.all([ + getServers(1, 1), + getUsers(1, 1), + getNodes(), + ]) + + let totalMemory = 0 + let totalDisk = 0 + let allocatedMemory = 0 + let allocatedDisk = 0 + + for (const node of nodesData.data) { + const attrs = node.attributes + totalMemory += attrs.memory + totalDisk += attrs.disk + if (attrs.allocated_resources) { + allocatedMemory += attrs.allocated_resources.memory + allocatedDisk += attrs.allocated_resources.disk + } + } + + return { + servers: serversData.meta.pagination.total, + users: usersData.meta.pagination.total, + nodes: nodesData.meta.pagination.total, + totalMemory, + totalDisk, + allocatedMemory, + allocatedDisk, + } +} + +export type { + PterodactylPaginatedResponse, + PterodactylServer, + PterodactylUser, + PterodactylNode, +} diff --git a/packages/core/lib/sync.ts b/packages/core/lib/sync.ts new file mode 100644 index 0000000..6ffb42c --- /dev/null +++ b/packages/core/lib/sync.ts @@ -0,0 +1,960 @@ +/** + * Pterodactyl Sync Service + * + * This service handles synchronizing data from the Pterodactyl panel + * to our local database. This allows us to: + * 1. Cache data locally for faster access + * 2. Build custom features without hitting the panel API constantly + * 3. Prepare for potential migration away from Pterodactyl + */ + +import { prisma } from "./prisma" +import type { SyncStatus } from "../../../prisma/generated/prisma" + +const PANEL_URL = process.env.GAMEPANEL_URL +const API_KEY = process.env.GAMEPANEL_API_KEY + +interface PterodactylPagination { + total: number + count: number + per_page: number + current_page: number + total_pages: number +} + +interface PterodactylResponse { + object: "list" + data: T[] + meta: { + pagination: PterodactylPagination + } +} + +interface PterodactylSingleResponse { + object: string + attributes: T +} + +// ============================================================================ +// API HELPERS +// ============================================================================ + +async function fetchFromPanel( + endpoint: string, + options: { include?: string[] } = {} +): Promise { + if (!PANEL_URL || !API_KEY) { + console.error("[Sync] Missing GAMEPANEL_URL or GAMEPANEL_API_KEY") + return null + } + + const url = new URL(`${PANEL_URL}/api/application${endpoint}`) + if (options.include?.length) { + url.searchParams.set("include", options.include.join(",")) + } + + try { + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${API_KEY}`, + Accept: "application/json", + "Content-Type": "application/json", + "User-Agent": "NodeByte-Sync/1.0", + }, + }) + + if (!response.ok) { + console.error(`[Sync] API error: ${response.status} ${response.statusText}`) + return null + } + + return await response.json() + } catch (error) { + console.error("[Sync] Fetch error:", error) + return null + } +} + +async function fetchAllPages( + endpoint: string, + options: { include?: string[] } = {} +): Promise { + const items: T[] = [] + let page = 1 + let hasMore = true + + while (hasMore) { + const url = `${endpoint}${endpoint.includes("?") ? "&" : "?"}page=${page}` + const response = await fetchFromPanel>(url, options) + + if (!response) { + break + } + + items.push(...response.data.map((item) => item.attributes)) + + hasMore = page < response.meta.pagination.total_pages + page++ + } + + return items +} + +// ============================================================================ +// SYNC LOG HELPERS +// ============================================================================ + +async function createSyncLog(type: string) { + return prisma.syncLog.create({ + data: { + type, + status: "RUNNING", + startedAt: new Date(), + }, + }) +} + +async function updateSyncLog( + id: string, + data: { + status?: SyncStatus + itemsTotal?: number + itemsSynced?: number + itemsFailed?: number + error?: string + completedAt?: Date + } +) { + return prisma.syncLog.update({ + where: { id }, + data, + }) +} + +// ============================================================================ +// LOCATION SYNC +// ============================================================================ + +interface PteroLocation { + id: number + short: string + long: string + created_at: string + updated_at: string +} + +export async function syncLocations(): Promise<{ success: boolean; synced: number; error?: string }> { + const log = await createSyncLog("locations") + + try { + const locations = await fetchAllPages("/locations") + + await updateSyncLog(log.id, { itemsTotal: locations.length }) + + let synced = 0 + for (const location of locations) { + try { + await prisma.location.upsert({ + where: { id: location.id }, + update: { + shortCode: location.short, + description: location.long, + updatedAt: new Date(), + }, + create: { + id: location.id, + shortCode: location.short, + description: location.long, + }, + }) + synced++ + } catch (error) { + console.error(`[Sync] Failed to sync location ${location.id}:`, error) + } + } + + await updateSyncLog(log.id, { + status: "COMPLETED", + itemsSynced: synced, + itemsFailed: locations.length - synced, + completedAt: new Date(), + }) + + return { success: true, synced } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + await updateSyncLog(log.id, { + status: "FAILED", + error: errorMsg, + completedAt: new Date(), + }) + return { success: false, synced: 0, error: errorMsg } + } +} + +// ============================================================================ +// NODE SYNC +// ============================================================================ + +interface PteroNode { + id: number + uuid: string + name: string + description: string | null + location_id: number + fqdn: string + scheme: string + behind_proxy: boolean + maintenance_mode: boolean + memory: number + memory_overallocate: number + disk: number + disk_overallocate: number + upload_size: number + daemon_listen: number + daemon_sftp: number + daemon_base: string + created_at: string + updated_at: string +} + +interface PteroAllocation { + id: number + ip: string + alias: string | null + port: number + notes: string | null + assigned: boolean +} + +export async function syncNodes(): Promise<{ success: boolean; synced: number; error?: string }> { + const log = await createSyncLog("nodes") + + try { + const nodes = await fetchAllPages("/nodes") + + await updateSyncLog(log.id, { itemsTotal: nodes.length }) + + let synced = 0 + for (const node of nodes) { + try { + await prisma.node.upsert({ + where: { id: node.id }, + update: { + uuid: node.uuid, + name: node.name, + description: node.description, + fqdn: node.fqdn, + scheme: node.scheme, + behindProxy: node.behind_proxy, + memory: BigInt(node.memory), + memoryOverallocate: node.memory_overallocate, + disk: BigInt(node.disk), + diskOverallocate: node.disk_overallocate, + isMaintenanceMode: node.maintenance_mode, + daemonListenPort: node.daemon_listen, + daemonSftpPort: node.daemon_sftp, + daemonBase: node.daemon_base, + locationId: node.location_id, + updatedAt: new Date(), + }, + create: { + id: node.id, + uuid: node.uuid, + name: node.name, + description: node.description, + fqdn: node.fqdn, + scheme: node.scheme, + behindProxy: node.behind_proxy, + memory: BigInt(node.memory), + memoryOverallocate: node.memory_overallocate, + disk: BigInt(node.disk), + diskOverallocate: node.disk_overallocate, + isMaintenanceMode: node.maintenance_mode, + daemonListenPort: node.daemon_listen, + daemonSftpPort: node.daemon_sftp, + daemonBase: node.daemon_base, + locationId: node.location_id, + }, + }) + synced++ + } catch (error) { + console.error(`[Sync] Failed to sync node ${node.id}:`, error) + } + } + + await updateSyncLog(log.id, { + status: "COMPLETED", + itemsSynced: synced, + itemsFailed: nodes.length - synced, + completedAt: new Date(), + }) + + return { success: true, synced } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + await updateSyncLog(log.id, { + status: "FAILED", + error: errorMsg, + completedAt: new Date(), + }) + return { success: false, synced: 0, error: errorMsg } + } +} + +// ============================================================================ +// ALLOCATION SYNC +// ============================================================================ + +export async function syncAllocations(): Promise<{ success: boolean; synced: number; error?: string }> { + const log = await createSyncLog("allocations") + + try { + // Get all nodes first + const nodes = await prisma.node.findMany({ select: { id: true } }) + + let totalSynced = 0 + let totalAllocations = 0 + + for (const node of nodes) { + // Fetch allocations for each node + let page = 1 + let hasMore = true + + while (hasMore) { + const response = await fetchFromPanel>( + `/nodes/${node.id}/allocations?page=${page}` + ) + + if (!response) { + hasMore = false + continue + } + + totalAllocations += response.data.length + + for (const allocData of response.data) { + const alloc = allocData.attributes + try { + await prisma.allocation.upsert({ + where: { id: alloc.id }, + update: { + ip: alloc.ip, + port: alloc.port, + alias: alloc.alias, + notes: alloc.notes, + isAssigned: alloc.assigned, + nodeId: node.id, + updatedAt: new Date(), + }, + create: { + id: alloc.id, + ip: alloc.ip, + port: alloc.port, + alias: alloc.alias, + notes: alloc.notes, + isAssigned: alloc.assigned, + nodeId: node.id, + }, + }) + totalSynced++ + } catch (error) { + console.error(`[Sync] Failed to sync allocation ${alloc.id}:`, error) + } + } + + hasMore = page < response.meta.pagination.total_pages + page++ + } + } + + await updateSyncLog(log.id, { + status: "COMPLETED", + itemsTotal: totalAllocations, + itemsSynced: totalSynced, + itemsFailed: totalAllocations - totalSynced, + completedAt: new Date(), + }) + + return { success: true, synced: totalSynced } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + await updateSyncLog(log.id, { + status: "FAILED", + error: errorMsg, + completedAt: new Date(), + }) + return { success: false, synced: 0, error: errorMsg } + } +} + +// ============================================================================ +// NEST & EGG SYNC +// ============================================================================ + +interface PteroNest { + id: number + uuid: string + name: string + description: string | null + author: string + created_at: string + updated_at: string +} + +interface PteroEgg { + id: number + uuid: string + name: string + description: string | null + author: string + nest: number + docker_image: string + docker_images: Record + startup: string + script: { + privileged: boolean + install: string + entry: string + container: string + extends: string | null + } + created_at: string + updated_at: string +} + +interface PteroEggVariable { + id: number + egg_id: number + name: string + description: string + env_variable: string + default_value: string + user_viewable: boolean + user_editable: boolean + rules: string + created_at: string + updated_at: string +} + +export async function syncNestsAndEggs(): Promise<{ success: boolean; nests: number; eggs: number; variables: number; error?: string }> { + const log = await createSyncLog("nests_eggs") + + try { + // Sync nests first + const nests = await fetchAllPages("/nests") + + let nestsSynced = 0 + for (const nest of nests) { + try { + await prisma.nest.upsert({ + where: { id: nest.id }, + update: { + uuid: nest.uuid, + name: nest.name, + description: nest.description, + author: nest.author, + updatedAt: new Date(), + }, + create: { + id: nest.id, + uuid: nest.uuid, + name: nest.name, + description: nest.description, + author: nest.author, + }, + }) + nestsSynced++ + } catch (error) { + console.error(`[Sync] Failed to sync nest ${nest.id}:`, error) + } + } + + // Sync eggs for each nest + let eggsSynced = 0 + let variablesSynced = 0 + + for (const nest of nests) { + const eggsResponse = await fetchFromPanel>( + `/nests/${nest.id}/eggs`, + { include: ["variables"] } + ) + + if (!eggsResponse) continue + + for (const eggData of eggsResponse.data) { + const egg = eggData.attributes + try { + await prisma.egg.upsert({ + where: { id: egg.id }, + update: { + uuid: egg.uuid, + name: egg.name, + description: egg.description, + author: egg.author, + dockerImage: egg.docker_image, + dockerImages: egg.docker_images, + startup: egg.startup, + scriptIsPrivileged: egg.script?.privileged ?? false, + nestId: nest.id, + updatedAt: new Date(), + }, + create: { + id: egg.id, + uuid: egg.uuid, + name: egg.name, + description: egg.description, + author: egg.author, + dockerImage: egg.docker_image, + dockerImages: egg.docker_images, + startup: egg.startup, + scriptIsPrivileged: egg.script?.privileged ?? false, + nestId: nest.id, + }, + }) + eggsSynced++ + + // Sync egg variables if included + const variables = egg.relationships?.variables?.data || [] + for (const varData of variables) { + const variable = varData.attributes + try { + await prisma.eggVariable.upsert({ + where: { id: variable.id }, + update: { + name: variable.name, + description: variable.description || null, + envVariable: variable.env_variable, + defaultValue: variable.default_value || null, + userViewable: variable.user_viewable, + userEditable: variable.user_editable, + rules: variable.rules || null, + eggId: egg.id, + updatedAt: new Date(), + }, + create: { + id: variable.id, + name: variable.name, + description: variable.description || null, + envVariable: variable.env_variable, + defaultValue: variable.default_value || null, + userViewable: variable.user_viewable, + userEditable: variable.user_editable, + rules: variable.rules || null, + eggId: egg.id, + }, + }) + variablesSynced++ + } catch (error) { + console.error(`[Sync] Failed to sync egg variable ${variable.id}:`, error) + } + } + } catch (error) { + console.error(`[Sync] Failed to sync egg ${egg.id}:`, error) + } + } + } + + await updateSyncLog(log.id, { + status: "COMPLETED", + itemsSynced: nestsSynced + eggsSynced + variablesSynced, + metadata: { nests: nestsSynced, eggs: eggsSynced, variables: variablesSynced }, + completedAt: new Date(), + }) + + return { success: true, nests: nestsSynced, eggs: eggsSynced, variables: variablesSynced } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + await updateSyncLog(log.id, { + status: "FAILED", + error: errorMsg, + completedAt: new Date(), + }) + return { success: false, nests: 0, eggs: 0, variables: 0, error: errorMsg } + } +} + +// ============================================================================ +// SERVER SYNC +// ============================================================================ + +interface PteroServer { + id: number + uuid: string + identifier: string + external_id: string | null + name: string + description: string + status: string | null + suspended: boolean + limits: { + memory: number + swap: number + disk: number + io: number + cpu: number + threads: string | null + oom_disabled: boolean + } + feature_limits: { + databases: number + allocations: number + backups: number + } + user: number + node: number + egg: number + container: { + startup_command: string + image: string + installed: number + environment: Record + } + created_at: string + updated_at: string +} + +export async function syncServers(): Promise<{ success: boolean; synced: number; error?: string }> { + const log = await createSyncLog("servers") + + try { + const servers = await fetchAllPages("/servers", { + include: ["allocations"], + }) + + await updateSyncLog(log.id, { itemsTotal: servers.length }) + + let synced = 0 + for (const server of servers) { + try { + // Find the local user by pterodactylId + const user = await prisma.user.findUnique({ + where: { pterodactylId: server.user }, + }) + + if (!user) { + console.warn(`[Sync] No local user found for Pterodactyl user ${server.user}, skipping server ${server.name}`) + continue + } + + // Map Pterodactyl status to our enum + const statusMap: Record = { + installing: "INSTALLING", + install_failed: "INSTALL_FAILED", + suspended: "SUSPENDED", + restoring_backup: "RESTORING_BACKUP", + } + const status = server.suspended + ? "SUSPENDED" + : statusMap[server.status || ""] || "OFFLINE" + + await prisma.server.upsert({ + where: { pterodactylId: server.id }, + update: { + uuid: server.uuid, + uuidShort: server.identifier, + externalId: server.external_id, + name: server.name, + description: server.description, + status, + isSuspended: server.suspended, + memory: server.limits.memory, + swap: server.limits.swap, + disk: server.limits.disk, + io: server.limits.io, + cpu: server.limits.cpu, + oomDisabled: server.limits.oom_disabled, + databaseLimit: server.feature_limits.databases, + allocationLimit: server.feature_limits.allocations, + backupLimit: server.feature_limits.backups, + startup: server.container.startup_command, + image: server.container.image, + ownerId: user.id, + nodeId: server.node, + eggId: server.egg, + lastSyncedAt: new Date(), + updatedAt: new Date(), + }, + create: { + pterodactylId: server.id, + uuid: server.uuid, + uuidShort: server.identifier, + externalId: server.external_id, + name: server.name, + description: server.description, + status, + isSuspended: server.suspended, + memory: server.limits.memory, + swap: server.limits.swap, + disk: server.limits.disk, + io: server.limits.io, + cpu: server.limits.cpu, + oomDisabled: server.limits.oom_disabled, + databaseLimit: server.feature_limits.databases, + allocationLimit: server.feature_limits.allocations, + backupLimit: server.feature_limits.backups, + startup: server.container.startup_command, + image: server.container.image, + ownerId: user.id, + nodeId: server.node, + eggId: server.egg, + installedAt: server.container.installed === 1 ? new Date() : null, + }, + }) + synced++ + } catch (error) { + console.error(`[Sync] Failed to sync server ${server.id}:`, error) + } + } + + await updateSyncLog(log.id, { + status: "COMPLETED", + itemsSynced: synced, + itemsFailed: servers.length - synced, + completedAt: new Date(), + }) + + return { success: true, synced } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + await updateSyncLog(log.id, { + status: "FAILED", + error: errorMsg, + completedAt: new Date(), + }) + return { success: false, synced: 0, error: errorMsg } + } +} + +// ============================================================================ +// SERVER DATABASE SYNC +// ============================================================================ + +interface PteroServerDatabase { + id: number + server: number + host: number + database: string + username: string + remote: string + max_connections: number + created_at: string + updated_at: string + relationships?: { + host?: { + attributes: { + id: number + name: string + host: string + port: number + } + } + } +} + +export async function syncServerDatabases(): Promise<{ success: boolean; synced: number; error?: string }> { + const log = await createSyncLog("databases") + + try { + // Get all servers from our database + const servers = await prisma.server.findMany({ + select: { id: true, pterodactylId: true }, + }) + + let totalSynced = 0 + + for (const server of servers) { + // Fetch databases for each server + const response = await fetchFromPanel>( + `/servers/${server.pterodactylId}/databases`, + { include: ["host"] } + ) + + if (!response) continue + + for (const dbData of response.data) { + const db = dbData.attributes + try { + const hostInfo = db.relationships?.host?.attributes + + await prisma.serverDatabase.upsert({ + where: { id: db.id }, + update: { + name: db.database, + username: db.username, + host: hostInfo?.host || "localhost", + port: hostInfo?.port || 3306, + maxConnections: db.max_connections, + serverId: server.id, + updatedAt: new Date(), + }, + create: { + id: db.id, + name: db.database, + username: db.username, + host: hostInfo?.host || "localhost", + port: hostInfo?.port || 3306, + maxConnections: db.max_connections, + serverId: server.id, + }, + }) + totalSynced++ + } catch (error) { + console.error(`[Sync] Failed to sync database ${db.id}:`, error) + } + } + } + + await updateSyncLog(log.id, { + status: "COMPLETED", + itemsSynced: totalSynced, + completedAt: new Date(), + }) + + return { success: true, synced: totalSynced } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + await updateSyncLog(log.id, { + status: "FAILED", + error: errorMsg, + completedAt: new Date(), + }) + return { success: false, synced: 0, error: errorMsg } + } +} + +// ============================================================================ +// SERVER BACKUP SYNC (uses Client API, not Application API) +// Note: The Application API doesn't expose backups directly +// We'll skip this for now as it requires per-user client API keys +// ============================================================================ + +// ============================================================================ +// USER SYNC (sync user data from panel to our database) +// ============================================================================ + +interface PteroUser { + id: number + uuid: string + username: string + email: string + first_name: string + last_name: string + language: string + root_admin: boolean + "2fa": boolean + created_at: string + updated_at: string +} + +export async function syncUserFromPanel(userId: string): Promise<{ success: boolean; error?: string }> { + try { + const user = await prisma.user.findUnique({ + where: { id: userId }, + }) + + if (!user || !user.pterodactylId) { + return { success: false, error: "User not found or no pterodactyl ID" } + } + + const pteroUser = await fetchFromPanel>( + `/users/${user.pterodactylId}` + ) + + if (!pteroUser) { + return { success: false, error: "Could not fetch user from panel" } + } + + const { attributes } = pteroUser + + await prisma.user.update({ + where: { id: userId }, + data: { + username: attributes.username, + firstName: attributes.first_name, + lastName: attributes.last_name, + isAdmin: attributes.root_admin, + lastSyncedAt: new Date(), + }, + }) + + return { success: true } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error" + return { success: false, error: errorMsg } + } +} + +// ============================================================================ +// FULL SYNC +// ============================================================================ + +export async function runFullSync(): Promise<{ + success: boolean + results: { + locations: { success: boolean; synced: number } + nodes: { success: boolean; synced: number } + allocations: { success: boolean; synced: number } + nests: { success: boolean; nests: number; eggs: number; variables: number } + servers: { success: boolean; synced: number } + databases: { success: boolean; synced: number } + } +}> { + console.log("[Sync] Starting full sync...") + + // Sync in order (dependencies first) + const locations = await syncLocations() + console.log(`[Sync] Locations: ${locations.synced} synced`) + + const nodes = await syncNodes() + console.log(`[Sync] Nodes: ${nodes.synced} synced`) + + const allocations = await syncAllocations() + console.log(`[Sync] Allocations: ${allocations.synced} synced`) + + const nests = await syncNestsAndEggs() + console.log(`[Sync] Nests: ${nests.nests}, Eggs: ${nests.eggs}, Variables: ${nests.variables}`) + + const servers = await syncServers() + console.log(`[Sync] Servers: ${servers.synced} synced`) + + const databases = await syncServerDatabases() + console.log(`[Sync] Databases: ${databases.synced} synced`) + + const allSuccess = locations.success && nodes.success && allocations.success && nests.success && servers.success && databases.success + + console.log(`[Sync] Full sync ${allSuccess ? "completed" : "completed with errors"}`) + + return { + success: allSuccess, + results: { + locations, + nodes, + allocations, + nests, + servers, + databases, + }, + } +} + +// ============================================================================ +// GET SYNC STATUS +// ============================================================================ + +export async function getSyncStatus() { + const latestLogs = await prisma.syncLog.findMany({ + orderBy: { startedAt: "desc" }, + take: 10, + }) + + const lastFullSync = await prisma.syncLog.findFirst({ + where: { type: "servers", status: "COMPLETED" }, + orderBy: { completedAt: "desc" }, + }) + + return { + recentLogs: latestLogs, + lastFullSync: lastFullSync?.completedAt, + } +} diff --git a/packages/core/lib/translations.ts b/packages/core/lib/translations.ts new file mode 100644 index 0000000..4f8e106 --- /dev/null +++ b/packages/core/lib/translations.ts @@ -0,0 +1,90 @@ +// Translation loading utilities +// Loads translations from local files (synced from Crowdin) + +// Import all translation files statically for proper bundling +import enTemplate from '../../../translations/templates/en.json' +import afZA from '../../../translations/messages/af-ZA.json' +import arSA from '../../../translations/messages/ar-SA.json' +import caES from '../../../translations/messages/ca-ES.json' +import csCZ from '../../../translations/messages/cs-CZ.json' +import daDK from '../../../translations/messages/da-DK.json' +import deDE from '../../../translations/messages/de-DE.json' +import elGR from '../../../translations/messages/el-GR.json' +import esES from '../../../translations/messages/es-ES.json' +import fiFI from '../../../translations/messages/fi-FI.json' +import frFR from '../../../translations/messages/fr-FR.json' +import heIL from '../../../translations/messages/he-IL.json' +import huHU from '../../../translations/messages/hu-HU.json' +import itIT from '../../../translations/messages/it-IT.json' +import jaJP from '../../../translations/messages/ja-JP.json' +import koKR from '../../../translations/messages/ko-KR.json' +import nlNL from '../../../translations/messages/nl-NL.json' +import noNO from '../../../translations/messages/no-NO.json' +import plPL from '../../../translations/messages/pl-PL.json' +import ptBR from '../../../translations/messages/pt-BR.json' +import ptPT from '../../../translations/messages/pt-PT.json' +import roRO from '../../../translations/messages/ro-RO.json' +import ruRU from '../../../translations/messages/ru-RU.json' +import srSP from '../../../translations/messages/sr-SP.json' +import svSE from '../../../translations/messages/sv-SE.json' +import trTR from '../../../translations/messages/tr-TR.json' +import ukUA from '../../../translations/messages/uk-UA.json' +import viVN from '../../../translations/messages/vi-VN.json' +import zhCN from '../../../translations/messages/zh-CN.json' +import zhTW from '../../../translations/messages/zh-TW.json' + +/** + * Map locale codes to imported translation objects + */ +const translations: Record> = { + 'en': enTemplate, + 'af-ZA': afZA, + 'ar-SA': arSA, + 'ca-ES': caES, + 'cs-CZ': csCZ, + 'da-DK': daDK, + 'de-DE': deDE, + 'el-GR': elGR, + 'es-ES': esES, + 'fi-FI': fiFI, + 'fr-FR': frFR, + 'he-IL': heIL, + 'hu-HU': huHU, + 'it-IT': itIT, + 'ja-JP': jaJP, + 'ko-KR': koKR, + 'nl-NL': nlNL, + 'no-NO': noNO, + 'pl-PL': plPL, + 'pt-BR': ptBR, + 'pt-PT': ptPT, + 'ro-RO': roRO, + 'ru-RU': ruRU, + 'sr-SP': srSP, + 'sv-SE': svSE, + 'tr-TR': trTR, + 'uk-UA': ukUA, + 'vi-VN': viVN, + 'zh-CN': zhCN, + 'zh-TW': zhTW, +} + +/** + * Get translations for a locale + * English uses the source template, other locales use Crowdin-generated messages + */ +export async function fetchTranslations(locale: string): Promise> { + // Return the translations for the locale, fallback to English + return translations[locale] || translations['en'] || enTemplate +} + +/** + * Get available locales + */ +export async function fetchAvailableLocales(): Promise { + return Object.keys(translations) +} + +// Export repo info for reference +export const translationsRepoUrl = 'https://github.com/NodeByteHosting/translations' +export const crowdinProjectUrl = 'https://crowdin.com/project/nodebyte' diff --git a/lib/utils.ts b/packages/core/lib/utils.ts similarity index 100% rename from lib/utils.ts rename to packages/core/lib/utils.ts diff --git a/packages/i18n/config.ts b/packages/i18n/config.ts new file mode 100644 index 0000000..37c6800 --- /dev/null +++ b/packages/i18n/config.ts @@ -0,0 +1,106 @@ +// Shared locale configuration - can be imported by both client and server + +export const locales = [ + 'en', + 'af-ZA', + 'ar-SA', + 'ca-ES', + 'cs-CZ', + 'da-DK', + 'de-DE', + 'el-GR', + 'es-ES', + 'fi-FI', + 'fr-FR', + 'he-IL', + 'hu-HU', + 'it-IT', + 'ja-JP', + 'ko-KR', + 'nl-NL', + 'no-NO', + 'pl-PL', + 'pt-BR', + 'pt-PT', + 'ro-RO', + 'ru-RU', + 'sr-SP', + 'sv-SE', + 'tr-TR', + 'uk-UA', + 'vi-VN', + 'zh-CN', + 'zh-TW', +] as const + +export type Locale = (typeof locales)[number] + +export const localeNames: Record = { + 'en': 'English', + 'af-ZA': 'Afrikaans', + 'ar-SA': 'العربية', + 'ca-ES': 'Català', + 'cs-CZ': 'Čeština', + 'da-DK': 'Dansk', + 'de-DE': 'Deutsch', + 'el-GR': 'Ελληνικά', + 'es-ES': 'Español', + 'fi-FI': 'Suomi', + 'fr-FR': 'Français', + 'he-IL': 'עברית', + 'hu-HU': 'Magyar', + 'it-IT': 'Italiano', + 'ja-JP': '日本語', + 'ko-KR': '한국어', + 'nl-NL': 'Nederlands', + 'no-NO': 'Norsk', + 'pl-PL': 'Polski', + 'pt-BR': 'Português (Brasil)', + 'pt-PT': 'Português', + 'ro-RO': 'Română', + 'ru-RU': 'Русский', + 'sr-SP': 'Српски', + 'sv-SE': 'Svenska', + 'tr-TR': 'Türkçe', + 'uk-UA': 'Українська', + 'vi-VN': 'Tiếng Việt', + 'zh-CN': '简体中文', + 'zh-TW': '繁體中文', +} + +export const localeFlags: Record = { + 'en': '🇬🇧', + 'af-ZA': '🇿🇦', + 'ar-SA': '🇸🇦', + 'ca-ES': '🇪🇸', + 'cs-CZ': '🇨🇿', + 'da-DK': '🇩🇰', + 'de-DE': '🇩🇪', + 'el-GR': '🇬🇷', + 'es-ES': '🇪🇸', + 'fi-FI': '🇫🇮', + 'fr-FR': '🇫🇷', + 'he-IL': '🇮🇱', + 'hu-HU': '🇭🇺', + 'it-IT': '🇮🇹', + 'ja-JP': '🇯🇵', + 'ko-KR': '🇰🇷', + 'nl-NL': '🇳🇱', + 'no-NO': '🇳🇴', + 'pl-PL': '🇵🇱', + 'pt-BR': '🇧🇷', + 'pt-PT': '🇵🇹', + 'ro-RO': '🇷🇴', + 'ru-RU': '🇷🇺', + 'sr-SP': '🇷🇸', + 'sv-SE': '🇸🇪', + 'tr-TR': '🇹🇷', + 'uk-UA': '🇺🇦', + 'vi-VN': '🇻🇳', + 'zh-CN': '🇨🇳', + 'zh-TW': '🇹🇼', +} + +export const defaultLocale: Locale = 'en' + +export const LOCALE_COOKIE = 'NEXT_LOCALE' diff --git a/packages/i18n/request.ts b/packages/i18n/request.ts new file mode 100644 index 0000000..19a0668 --- /dev/null +++ b/packages/i18n/request.ts @@ -0,0 +1,19 @@ +import { getRequestConfig } from 'next-intl/server' +import { cookies } from 'next/headers' +import { locales, defaultLocale, LOCALE_COOKIE, type Locale } from './config' +import { fetchTranslations } from '@/packages/core/lib/translations' + +export default getRequestConfig(async () => { + // Read locale from cookie, fallback to default + const cookieStore = await cookies() + const localeCookie = cookieStore.get(LOCALE_COOKIE)?.value + const locale = locales.includes(localeCookie as Locale) ? localeCookie as Locale : defaultLocale + + // Fetch translations from local files (synced from Crowdin) + const messages = await fetchTranslations(locale) + + return { + locale, + messages, + } +}) diff --git a/packages/kb/components/index.ts b/packages/kb/components/index.ts new file mode 100644 index 0000000..f418032 --- /dev/null +++ b/packages/kb/components/index.ts @@ -0,0 +1,8 @@ +// Knowledge Base Components +export { KBSearch } from "./kb-search"; +export { KBCategoryCard, KBCategoryGrid, type Category } from "./kb-category-card"; +export { KBArticleCard, KBArticleList, type Article } from "./kb-article-card"; +export { KBTableOfContents, type TOCHeading } from "./kb-toc"; +export { KBBreadcrumb, type BreadcrumbItem } from "./kb-breadcrumb"; +export { KBSidebar, type SidebarCategory } from "./kb-sidebar"; +export { KBArticle } from "./kb-article"; diff --git a/packages/kb/components/kb-article-card.tsx b/packages/kb/components/kb-article-card.tsx new file mode 100644 index 0000000..46b84f8 --- /dev/null +++ b/packages/kb/components/kb-article-card.tsx @@ -0,0 +1,131 @@ +import Link from "next/link"; +import { FileText, Clock, Calendar, User, ChevronRight } from "lucide-react"; +import { cn } from "@/packages/core/lib/utils"; +import { Badge } from "@/packages/ui/components/ui/badge"; + +export interface Article { + slug: string; + category: string; + categorySlug: string; + title: string; + description: string; + tags?: string[]; + author?: string; + lastUpdated: string; + readingTime: number; + order: number; +} + +interface KBArticleCardProps { + article: Article; + showCategory?: boolean; + className?: string; + translations?: { + minRead: string; + }; +} + +export function KBArticleCard({ + article, + showCategory = false, + className, + translations, +}: KBArticleCardProps) { + return ( + +
+
+ +
+ +
+
+

+ {article.title} +

+ {showCategory && ( + + {article.category.replace(/-/g, " ")} + + )} +
+ +

+ {article.description} +

+ +
+ + + {article.readingTime} {translations?.minRead || "min read"} + + + + {new Date(article.lastUpdated).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + })} + + {article.author && ( + + + {article.author} + + )} +
+ + {article.tags && article.tags.length > 0 && ( +
+ {article.tags.slice(0, 4).map((tag) => ( + + {tag} + + ))} + {article.tags.length > 4 && ( + + +{article.tags.length - 4} more + + )} +
+ )} +
+ + +
+ + ); +} + +interface KBArticleListProps { + articles: Article[]; + showCategory?: boolean; + className?: string; +} + +export function KBArticleList({ + articles, + showCategory = false, + className, +}: KBArticleListProps) { + const sortedArticles = [...articles].sort((a, b) => a.order - b.order); + + return ( +
+ {sortedArticles.map((article) => ( + + ))} +
+ ); +} + +export default KBArticleCard; diff --git a/packages/kb/components/kb-article.tsx b/packages/kb/components/kb-article.tsx new file mode 100644 index 0000000..ce278a1 --- /dev/null +++ b/packages/kb/components/kb-article.tsx @@ -0,0 +1,187 @@ +import Link from "next/link"; +import { ChevronLeft, ChevronRight, Clock, Calendar, User, Tag, Pencil } from "lucide-react"; +import { cn } from "@/packages/core/lib/utils"; +import { Badge } from "@/packages/ui/components/ui/badge"; +import { Button } from "@/packages/ui/components/ui/button"; +import { Separator } from "@/packages/ui/components/ui/separator"; + +interface ArticleMeta { + title: string; + description: string; + tags?: string[]; + author?: string; + lastUpdated?: string; + readingTime: number; +} + +interface AdjacentArticle { + slug: string; + category: string; + title: string; +} + +interface KBArticleProps { + meta: ArticleMeta; + content: string; + previousArticle?: AdjacentArticle | null; + nextArticle?: AdjacentArticle | null; + editUrl?: string; + className?: string; + translations: { + minRead: string; + updated: string; + previous: string; + next: string; + }; +} + +export function KBArticle({ + meta, + content, + previousArticle, + nextArticle, + editUrl, + className, + translations, +}: KBArticleProps) { + return ( +
+ {/* Article Header */} +
+

+ {meta.title} +

+

{meta.description}

+ + {/* Meta info */} +
+ + + {meta.readingTime} {translations.minRead} + + {meta.lastUpdated && ( + + + {translations.updated}{" "} + {new Date(meta.lastUpdated).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + })} + + )} + {meta.author && ( + + + {meta.author} + + )} +
+ + {/* Tags */} + {meta.tags && meta.tags.length > 0 && ( +
+ + {meta.tags.map((tag) => ( + + {tag} + + ))} +
+ )} +
+ + + + {/* Article Content */} +
+ + + + {/* Article Footer */} +
+ {/* Edit link */} + {editUrl && ( + + )} + + {/* Navigation */} + {(previousArticle || nextArticle) && ( + + )} +
+
+ ); +} + +export default KBArticle; diff --git a/packages/kb/components/kb-breadcrumb.tsx b/packages/kb/components/kb-breadcrumb.tsx new file mode 100644 index 0000000..3ca847e --- /dev/null +++ b/packages/kb/components/kb-breadcrumb.tsx @@ -0,0 +1,45 @@ +import Link from "next/link"; +import { ChevronRight, Home } from "lucide-react"; +import { cn } from "@/packages/core/lib/utils"; + +export interface BreadcrumbItem { + label: string; + href?: string; +} + +interface KBBreadcrumbProps { + items: BreadcrumbItem[]; + className?: string; +} + +export function KBBreadcrumb({ items, className }: KBBreadcrumbProps) { + return ( + + ); +} + +export default KBBreadcrumb; diff --git a/packages/kb/components/kb-category-card.tsx b/packages/kb/components/kb-category-card.tsx new file mode 100644 index 0000000..84cf089 --- /dev/null +++ b/packages/kb/components/kb-category-card.tsx @@ -0,0 +1,108 @@ +import Link from "next/link"; +import { + Rocket, + Gamepad2, + CreditCard, + Users, + Shield, + Settings, + HelpCircle, + Server, + type LucideIcon, +} from "lucide-react"; +import { cn } from "@/packages/core/lib/utils"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/packages/ui/components/ui/card"; +import { Badge } from "@/packages/ui/components/ui/badge"; + +// Icon mapping for dynamic icons from _meta.json +const iconMap: Record = { + Rocket, + Gamepad2, + CreditCard, + Users, + Shield, + Settings, + HelpCircle, + Server, +}; + +export interface Category { + slug: string; + title: string; + description: string; + icon: string; + order: number; + articleCount: number; +} + +interface KBCategoryCardProps { + category: Category; + className?: string; +} + +export function KBCategoryCard({ category, className }: KBCategoryCardProps) { + const Icon = iconMap[category.icon] || HelpCircle; + + return ( + + + +
+
+ +
+ + {category.articleCount} {category.articleCount === 1 ? "article" : "articles"} + +
+ + {category.title} + + + {category.description} + +
+ + + Browse articles → + + +
+ + ); +} + +interface KBCategoryGridProps { + categories: Category[]; + className?: string; +} + +export function KBCategoryGrid({ categories, className }: KBCategoryGridProps) { + const sortedCategories = [...categories].sort((a, b) => a.order - b.order); + + return ( +
+ {sortedCategories.map((category) => ( + + ))} +
+ ); +} + +export default KBCategoryCard; diff --git a/packages/kb/components/kb-search.tsx b/packages/kb/components/kb-search.tsx new file mode 100644 index 0000000..82263c8 --- /dev/null +++ b/packages/kb/components/kb-search.tsx @@ -0,0 +1,183 @@ +"use client"; + +import { useState, useMemo } from "react"; +import { Search, X, FileText, Folder, Tag, Clock } from "lucide-react"; +import Link from "next/link"; +import { useTranslations } from "next-intl"; +import { Input } from "@/packages/ui/components/ui/input"; +import { Badge } from "@/packages/ui/components/ui/badge"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/packages/ui/components/ui/dialog"; +import { Button } from "@/packages/ui/components/ui/button"; +import { cn } from "@/packages/core/lib/utils"; + +interface SearchResult { + slug: string; + category: string; + title: string; + description: string; + excerpt: string; + tags: string[]; + readingTime: number; + score: number; +} + +interface KBSearchProps { + articles: SearchResult[]; + className?: string; + variant?: "inline" | "dialog"; +} + +export function KBSearch({ articles, className, variant = "inline" }: KBSearchProps) { + const t = useTranslations(); + const [query, setQuery] = useState(""); + const [isOpen, setIsOpen] = useState(false); + + const results = useMemo(() => { + if (!query.trim()) return []; + + const searchTerms = query.toLowerCase().split(/\s+/).filter(Boolean); + + return articles + .map((article) => { + let score = 0; + const titleLower = article.title.toLowerCase(); + const descLower = article.description.toLowerCase(); + const excerptLower = article.excerpt?.toLowerCase() || ""; + const tagsLower = article.tags?.map((t) => t.toLowerCase()) || []; + + for (const term of searchTerms) { + // Title matches (highest weight) + if (titleLower.includes(term)) score += 10; + if (titleLower.startsWith(term)) score += 5; + + // Description matches + if (descLower.includes(term)) score += 5; + + // Tag matches + if (tagsLower.some((tag) => tag.includes(term))) score += 7; + + // Content matches + if (excerptLower.includes(term)) score += 3; + } + + return { ...article, score }; + }) + .filter((article) => article.score > 0) + .sort((a, b) => b.score - a.score) + .slice(0, 8); + }, [query, articles]); + + const handleSelect = () => { + setQuery(""); + setIsOpen(false); + }; + + const SearchContent = () => ( +
+
+ + setQuery(e.target.value)} + className="pl-9 pr-9" + /> + {query && ( + + )} +
+ + {query && results.length > 0 && ( +
+ {results.map((result) => ( + +
+ +
+
+ {result.title} + + + {result.category} + +
+

+ {result.description} +

+ {result.tags && result.tags.length > 0 && ( +
+ + {result.tags.slice(0, 3).map((tag) => ( + + {tag} + + ))} +
+ )} +
+
+ + {result.readingTime} {t("kb.minRead")} +
+
+ + ))} +
+ )} + + {query && results.length === 0 && ( +
+ +

{t("kb.noResults.forQuery")} “{query}”

+

+ {t("kb.noResults.description")} +

+
+ )} +
+ ); + + if (variant === "dialog") { + return ( + + + + + + + {t("kb.search.dialogTitle")} + +
+ +
+
+
+ ); + } + + return ; +} + +export default KBSearch; diff --git a/packages/kb/components/kb-sidebar.tsx b/packages/kb/components/kb-sidebar.tsx new file mode 100644 index 0000000..479ee62 --- /dev/null +++ b/packages/kb/components/kb-sidebar.tsx @@ -0,0 +1,143 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + Rocket, + Gamepad2, + CreditCard, + Users, + Shield, + Settings, + HelpCircle, + Server, + ChevronDown, + FileText, + type LucideIcon, +} from "lucide-react"; +import { cn } from "@/packages/core/lib/utils"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/packages/ui/components/ui/collapsible"; +import { ScrollArea } from "@/packages/ui/components/ui/scroll-area"; +import { useState } from "react"; + +// Icon mapping for dynamic icons +const iconMap: Record = { + Rocket, + Gamepad2, + CreditCard, + Users, + Shield, + Settings, + HelpCircle, + Server, +}; + +export interface SidebarCategory { + slug: string; + title: string; + icon: string; + order: number; + articles: { + slug: string; + title: string; + order: number; + }[]; +} + +interface KBSidebarProps { + categories: SidebarCategory[]; + className?: string; +} + +export function KBSidebar({ categories, className }: KBSidebarProps) { + const pathname = usePathname(); + const [openCategories, setOpenCategories] = useState(() => { + // Open the category that contains the current article + const currentCategory = categories.find((cat) => + pathname.includes(`/kb/${cat.slug}`) + ); + return currentCategory ? [currentCategory.slug] : []; + }); + + const toggleCategory = (slug: string) => { + setOpenCategories((prev) => + prev.includes(slug) + ? prev.filter((s) => s !== slug) + : [...prev, slug] + ); + }; + + const sortedCategories = [...categories].sort((a, b) => a.order - b.order); + + return ( + + + + ); +} + +export default KBSidebar; diff --git a/packages/kb/components/kb-toc.tsx b/packages/kb/components/kb-toc.tsx new file mode 100644 index 0000000..875a495 --- /dev/null +++ b/packages/kb/components/kb-toc.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useTranslations } from "next-intl"; +import { cn } from "@/packages/core/lib/utils"; +import { List } from "lucide-react"; + +export interface TOCHeading { + id: string; + text: string; + level: number; +} + +interface KBTableOfContentsProps { + headings: TOCHeading[]; + className?: string; +} + +export function KBTableOfContents({ headings, className }: KBTableOfContentsProps) { + const t = useTranslations(); + const [activeId, setActiveId] = useState(""); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + }); + }, + { + rootMargin: "-80px 0px -80% 0px", + } + ); + + headings.forEach((heading) => { + const element = document.getElementById(heading.id); + if (element) { + observer.observe(element); + } + }); + + return () => { + headings.forEach((heading) => { + const element = document.getElementById(heading.id); + if (element) { + observer.unobserve(element); + } + }); + }; + }, [headings]); + + const handleClick = (e: React.MouseEvent, id: string) => { + e.preventDefault(); + const element = document.getElementById(id); + if (element) { + const offset = 100; + const elementPosition = element.getBoundingClientRect().top; + const offsetPosition = elementPosition + window.pageYOffset - offset; + + window.scrollTo({ + top: offsetPosition, + behavior: "smooth", + }); + setActiveId(id); + } + }; + + if (headings.length === 0) return null; + + return ( + + ); +} + +export default KBTableOfContents; diff --git a/packages/kb/content/billing/_meta.json b/packages/kb/content/billing/_meta.json new file mode 100644 index 0000000..c917cc4 --- /dev/null +++ b/packages/kb/content/billing/_meta.json @@ -0,0 +1,6 @@ +{ + "title": "Billing & Account", + "description": "Guides for managing your NodeByte account, billing, and services", + "icon": "CreditCard", + "order": 4 +} diff --git a/packages/kb/content/billing/client-portal.md b/packages/kb/content/billing/client-portal.md new file mode 100644 index 0000000..90f6732 --- /dev/null +++ b/packages/kb/content/billing/client-portal.md @@ -0,0 +1,248 @@ +--- +title: Client Portal Overview +description: Learn how to navigate and use the NodeByte client portal for managing your account and services. +tags: [billing, whmcs, account, portal] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 1 +--- + +# Client Portal Overview + +The NodeByte Client Portal is where you'll manage your account, services, invoices, and support tickets. + +## Accessing the Portal + +Visit [billing.nodebyte.host](https://billing.nodebyte.host) to access the client portal. + +### First Time Login + +If you've just created an account: + +1. Go to [billing.nodebyte.host](https://billing.nodebyte.host) +2. Click **Login** +3. Enter the email and password you used during registration +4. Complete any verification if prompted + +### Forgot Password? + +1. Click **Forgot Password?** on the login page +2. Enter your registered email address +3. Check your inbox for the reset link +4. Create a new secure password + +## Dashboard Overview + +After logging in, you'll see your main dashboard: + +| Section | Description | +|---------|-------------| +| **Your Active Products/Services** | All your current services | +| **Recent Invoices** | Latest billing information | +| **Recent Support Tickets** | Your support history | +| **Announcements** | Important updates from NodeByte | + +## Managing Services + +### Viewing Your Services + +1. Click **Services** → **My Services** +2. You'll see all your active, pending, and cancelled services +3. Click on any service for details + +### Service Details + +Each service page shows: + +- **Status** - Active, Suspended, Pending, etc. +- **Billing Cycle** - Monthly, Quarterly, Annually +- **Next Due Date** - When payment is due +- **Product/Service** - Your plan details +- **Quick Actions** - Manage, Upgrade, Request Cancellation + +### Accessing the Game Panel + +From your service page: + +1. Look for **Login to Game Panel** or similar button +2. This will redirect you to [panel.nodebyte.host](https://panel.nodebyte.host) +3. Use the same credentials or the ones provided in your welcome email + +## Invoices & Payments + +### Viewing Invoices + +1. Click **Billing** → **My Invoices** +2. See all invoices: Paid, Unpaid, Cancelled + +### Invoice Statuses + +| Status | Meaning | +|--------|---------| +| **Paid** | Payment received | +| **Unpaid** | Awaiting payment | +| **Overdue** | Payment past due date | +| **Cancelled** | Invoice voided | + +### Payment Methods + +We accept various payment methods: + +- 💳 Credit/Debit Cards (Visa, Mastercard, Amex) +- 🅿️ PayPal +- ₿ Cryptocurrency (Bitcoin, Ethereum, etc.) + +### Adding Funds + +You can add funds to your account balance: + +1. Go to **Billing** → **Add Funds** +2. Enter the amount +3. Choose payment method +4. Complete the transaction + +Account credit is automatically applied to future invoices. + +## Support Tickets + +### Opening a Ticket + +1. Click **Support** → **Open Ticket** +2. Choose a department: + - **General Support** - General questions + - **Technical Support** - Server issues + - **Billing** - Payment/invoice issues + - **Sales** - Pre-sales questions +3. Enter a descriptive subject +4. Provide detailed information +5. Attach files if needed +6. Click **Submit** + +### Ticket Best Practices + +For faster resolution: + +- ✅ Include your server name/IP +- ✅ Describe the issue clearly +- ✅ Include error messages or screenshots +- ✅ List steps you've already tried +- ❌ Don't open duplicate tickets +- ❌ Don't include sensitive passwords in tickets + +### Viewing Ticket History + +1. Click **Support** → **Tickets** +2. View all Open, Answered, and Closed tickets +3. Click any ticket to view the full conversation + +## Account Settings + +### Profile Information + +1. Click on your name → **Edit Account Details** +2. Update: + - Name and Company + - Email Address + - Phone Number + - Billing Address + +### Changing Password + +1. Go to **Hello, Name!** → **Change Password** +2. Enter your current password +3. Enter and confirm your new password +4. Click **Save Changes** + +### Security Settings + +We recommend enabling additional security: + +1. Go to **Security Settings** +2. Enable **Two-Factor Authentication** (2FA) +3. Download backup codes +4. Store them safely + +## Service Management + +### Upgrading Your Plan + +Need more resources? + +1. Go to **Services** → **My Services** +2. Select your service +3. Click **Upgrade/Downgrade** +4. Choose your new plan +5. Pay the prorated difference + +### Requesting Cancellation + +To cancel a service: + +1. Go to **Services** → **My Services** +2. Select the service +3. Click **Request Cancellation** +4. Choose: + - **Immediate** - Cancel now + - **End of Billing Period** - Cancel when paid period ends +5. Provide a reason (optional) +6. Confirm cancellation + +> **Note:** Cancellation requests are reviewed by our team. You'll receive confirmation via email. + +### Service Renewal + +Services renew automatically if: + +- You have a payment method on file +- You have sufficient account credit + +To ensure uninterrupted service: +- Keep payment details updated +- Pay invoices before the due date +- Consider adding account credit + +## Affiliate Program + +Earn money by referring others: + +1. Go to **Affiliates** +2. Get your unique referral link +3. Share with friends and communities +4. Earn commission on successful referrals + +## Email Notifications + +Control what emails you receive: + +1. Go to **Email History** to see past emails +2. Important emails include: + - Invoice notifications + - Service status changes + - Support ticket updates + - Promotional offers + +## Common Questions + +### Why is my service suspended? + +Common reasons: +- Overdue invoice +- Terms of Service violation +- Abuse report + +**Solution:** Check your invoices and pay any outstanding balances, or open a support ticket. + +### How do I get my Game Panel password? + +Your panel credentials were sent in your welcome email. If lost: +1. Open a support ticket +2. Request a password reset +3. We'll verify your identity and assist + +### Can I change my billing email? + +Yes! Update it in Account Details. Verification may be required. + +--- + +Need help with billing? Open a [support ticket](https://billing.nodebyte.host/submitticket.php) or email [billing@nodebyte.host](mailto:billing@nodebyte.host). diff --git a/packages/kb/content/getting-started/_meta.json b/packages/kb/content/getting-started/_meta.json new file mode 100644 index 0000000..f0d9568 --- /dev/null +++ b/packages/kb/content/getting-started/_meta.json @@ -0,0 +1,6 @@ +{ + "title": "Getting Started", + "description": "Learn the basics of NodeByte Hosting and get your server up and running quickly.", + "icon": "Rocket", + "order": 1 +} diff --git a/packages/kb/content/getting-started/game-panel.md b/packages/kb/content/getting-started/game-panel.md new file mode 100644 index 0000000..a296ee2 --- /dev/null +++ b/packages/kb/content/getting-started/game-panel.md @@ -0,0 +1,198 @@ +--- +title: Introduction to the Game Panel +description: Learn how to navigate and use the NodeByte game panel to manage your servers. +tags: [panel, pterodactyl, management, tutorial] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 3 +--- + +# Introduction to the Game Panel + +The NodeByte Game Panel is a custom fork of Pterodactyl, designed specifically for managing your Minecraft and Rust game servers. This guide will help you navigate and understand the panel's features. + +## Accessing the Panel + +Visit [panel.nodebyte.host](https://panel.nodebyte.host) and log in with your credentials. If you've forgotten your password, use the "Forgot Password" link. + +## Dashboard Overview + +When you log in, you'll see the main dashboard with: + +| Section | Description | +|---------|-------------| +| Server List | All your active servers | +| Account Settings | Profile and security options | +| API Credentials | For integrations and automation | + +## Server Console + +The console is your primary interface for managing your server: + +### Controls + +- **Start** - Boot your server +- **Stop** - Gracefully shut down +- **Restart** - Stop and start in one click +- **Kill** - Force stop (use sparingly!) + +### Console Output + +The console shows real-time server logs: + +```log +[Server] Starting Minecraft server version 1.21.4 +[Server] Loading properties... +[Server] Done! (5.234s) +``` + +You can also type commands directly into the console. + +## File Manager + +Access and manage your server files without external software: + +### Features + +- 📁 Create folders +- 📄 Create/edit files +- ⬆️ Upload files (drag & drop supported!) +- ⬇️ Download files +- 🗑️ Delete files +- ✏️ Rename files + +### Important Directories + +| Game | Config Location | +|------|-----------------| +| Minecraft | `/server.properties`, `/bukkit.yml` | +| Rust | `/server/rust/cfg/` | + +## SFTP Access + +For bulk file transfers, use SFTP: + +```bash +Host: panel.nodebyte.host +Port: 2022 +Username: your-username.server-id +Password: Your panel password +``` + +> **Note:** Some FTP clients (FileZilla, WinSCP) may need explicit SFTP configuration. + +## Database Management + +Create and manage MySQL databases: + +1. Go to **Databases** tab +2. Click **New Database** +3. Note your database credentials +4. Connect from your server/plugins + +Example connection string: +``` +jdbc:mysql://host:3306/database_name +``` + +## Backup System + +Keep your data safe with automatic and manual backups: + +### Creating Backups + +1. Navigate to **Backups** +2. Click **Create Backup** +3. Optionally name your backup +4. Wait for completion + +### Restoring Backups + +1. Find the backup you want +2. Click **Restore** +3. Confirm the restoration + +> ⚠️ **Warning:** Restoring a backup will overwrite current files! + +## Schedule Tasks + +Automate server management with scheduled tasks: + +### Common Schedules + +```javascript +// Daily restart at 4 AM +0 4 * * * power restart + +// Backup every 6 hours +0 */6 * * * backup create + +// Weekly restart on Sundays +0 3 * * 0 power restart +``` + +### Creating a Schedule + +1. Go to **Schedules** +2. Click **Create Schedule** +3. Set the cron expression +4. Add tasks to run +5. Enable the schedule + +## Subusers + +Grant access to friends or team members: + +| Permission Level | Capabilities | +|-----------------|--------------| +| **Viewer** | Console view only | +| **Operator** | Console + start/stop | +| **Admin** | Full access (except delete) | + +### Adding a Subuser + +1. Go to **Users** +2. Enter their email +3. Select permissions +4. Send invite + +## Activity Log + +Track all actions on your server: + +- Who did what +- When it happened +- What commands were run + +Great for security auditing! + +## Network Settings + +Manage allocations (IP:Port combinations): + +- View your server's connection address +- Request additional ports if needed +- See allocation usage + +## Tips & Best Practices + +1. **Regular Backups** - Schedule daily backups minimum +2. **Monitor Resources** - Check CPU/RAM usage regularly +3. **Update Regularly** - Keep server software current +4. **Secure Access** - Use strong passwords, limit subuser access +5. **Document Changes** - Note what you modify for troubleshooting + +## Keyboard Shortcuts + +| Shortcut | Action | +|----------|--------| +| `Ctrl + /` | Focus console input | +| `Up Arrow` | Previous command | +| `Down Arrow` | Next command | +| `Tab` | Command autocomplete (some servers) | + +## Need More Help? + +- Check specific game guides in our [Knowledge Base](/kb) +- Join our [Discord](https://discord.gg/wN58bTzzpW) +- Contact [Support](https://billing.nodebyte.host/submitticket.php) diff --git a/packages/kb/content/getting-started/introduction.md b/packages/kb/content/getting-started/introduction.md new file mode 100644 index 0000000..e4ccb6f --- /dev/null +++ b/packages/kb/content/getting-started/introduction.md @@ -0,0 +1,54 @@ +--- +title: Welcome to NodeByte +description: Get started with NodeByte Hosting - your home for high-performance game servers. +tags: [welcome, introduction, overview] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 1 +--- + +# Welcome to NodeByte + +Welcome to NodeByte Hosting! We're excited to have you here. This knowledge base will help you get the most out of your game server hosting experience. + +## What is NodeByte? + +NodeByte is a premium game server hosting provider focused on delivering **high-performance**, **reliable**, and **easy-to-use** hosting solutions for gamers and communities. + +### Our Key Features + +- **99.6% Uptime SLA** - Enterprise-grade infrastructure keeps your servers online +- **DDoS Protection** - Advanced mitigation protects your server from attacks +- **Instant Setup** - Your server is ready in under 60 seconds +- **24/7 Support** - Our team is always here to help + +## Getting Help + +If you can't find what you're looking for in this knowledge base, there are several ways to get help: + +### Discord Community + +Join our [Discord server](https://discord.gg/wN58bTzzpW) for real-time support and community discussions. This is the fastest way to get help! + +### Support Tickets + +For account-specific issues, you can [open a support ticket](https://billing.nodebyte.host/submitticket.php) through our WHMCS client portal. + +### Email Support + +You can also reach us via email: +- **General**: hello@nodebyte.host +- **Support**: support@nodebyte.host +- **Billing**: billing@nodebyte.host + +## What's Next? + +Ready to get started? Check out these guides: + +1. [Quick Start Guide](/kb/getting-started/quick-start) - Get your first server running +2. [Understanding the Game Panel](/kb/getting-started/game-panel) - Learn how to manage your server +3. Browse our [Minecraft](/kb/minecraft/server-software) or [Rust](/kb/rust/getting-started) guides for game-specific help + +--- + +We're constantly updating this knowledge base with new articles and guides. If you have suggestions for topics you'd like us to cover, let us know on Discord! diff --git a/packages/kb/content/getting-started/quick-start.md b/packages/kb/content/getting-started/quick-start.md new file mode 100644 index 0000000..9a943b1 --- /dev/null +++ b/packages/kb/content/getting-started/quick-start.md @@ -0,0 +1,96 @@ +--- +title: Quick Start Guide +description: Get your game server up and running in minutes with this quick start guide. +tags: [quick-start, setup, tutorial] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 2 +--- + +# Quick Start Guide + +This guide will walk you through setting up your first game server with NodeByte. In just a few minutes, you'll have a fully functional server ready for you and your friends. + +## Step 1: Create an Account + +If you haven't already, [create a NodeByte account](https://billing.nodebyte.host/register.php). You'll need: + +- A valid email address +- A secure password + +> **Tip:** Use a strong, unique password and enable two-factor authentication for added security. + +## Step 2: Choose Your Game + +Navigate to our [game hosting page](/games) and select the game you want to host: + +- **Minecraft** - Java & Bedrock servers with mod support +- **Rust** - High-performance Rust servers with Oxide/uMod +- **More games coming soon!** + +## Step 3: Select a Plan + +Choose a plan that fits your needs. All plans include: + +- ✅ DDoS Protection +- ✅ Instant Setup +- ✅ 24/7 Support +- ✅ Full FTP Access +- ✅ Automatic Backups + +## Step 4: Complete Your Order + +You'll be directed to our WHMCS client portal to complete your purchase: + +1. Review your order details +2. Apply any discount codes you have +3. Complete payment via credit card, PayPal, or cryptocurrency + +## Step 5: Access Your Server + +Once payment is complete, your server will be provisioned automatically. You'll receive: + +1. **Welcome Email** - Contains your server details and login information +2. **Game Panel Access** - Manage your server at [panel.nodebyte.host](https://panel.nodebyte.host) +3. **Server IP & Port** - Connect to your server immediately! + +## Step 6: Configure Your Server + +Log into the [Game Panel](https://panel.nodebyte.host) to: + +``` +1. Start/Stop/Restart your server +2. Access the console +3. Upload files via SFTP +4. Install mods and plugins +5. Configure server settings +6. Set up automatic backups +``` + +## Common First Steps + +### For Minecraft Servers + +1. Choose your server software (Paper, Forge, Fabric, etc.) +2. Set your server's `server.properties` +3. Upload your world files (if migrating) +4. Install plugins or mods + +### For Rust Servers + +1. Configure `server.cfg` +2. Set map size and seed +3. Install Oxide (optional) +4. Add plugins + +## Need Help? + +Stuck somewhere? Don't worry! + +- 📖 Browse our [Knowledge Base](/kb) for detailed guides +- 💬 Join our [Discord](https://discord.gg/wN58bTzzpW) for live support +- 🎫 [Open a ticket](https://billing.nodebyte.host/submitticket.php) in our WHMCS client portal for account issues + +--- + +**Congratulations!** 🎉 You're now ready to start your gaming adventure with NodeByte. Have fun! diff --git a/packages/kb/content/minecraft/_meta.json b/packages/kb/content/minecraft/_meta.json new file mode 100644 index 0000000..a455017 --- /dev/null +++ b/packages/kb/content/minecraft/_meta.json @@ -0,0 +1,6 @@ +{ + "title": "Minecraft Servers", + "description": "Guides and tutorials for setting up and managing Minecraft servers", + "icon": "Gamepad2", + "order": 2 +} diff --git a/packages/kb/content/minecraft/installing-plugins.md b/packages/kb/content/minecraft/installing-plugins.md new file mode 100644 index 0000000..bdb9f00 --- /dev/null +++ b/packages/kb/content/minecraft/installing-plugins.md @@ -0,0 +1,290 @@ +--- +title: Installing Plugins +description: Learn how to find, install, and configure plugins on your Minecraft server. +tags: [minecraft, plugins, paper, spigot, bukkit] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 2 +--- + +# Installing Plugins + +Plugins extend your Minecraft server's functionality, adding features like economy systems, permissions, minigames, and much more. This guide covers everything you need to know about plugins. + +## Requirements + +To use plugins, you need a compatible server software: + +- ✅ Paper +- ✅ Purpur +- ✅ Spigot +- ✅ Bukkit +- ❌ Vanilla (no plugin support) +- ❌ Forge (use mods instead) +- ❌ Fabric (use mods instead) + +## Finding Plugins + +### Recommended Plugin Sources + +| Source | URL | Trust Level | +|--------|-----|-------------| +| SpigotMC | spigotmc.org | ⭐⭐⭐⭐⭐ | +| Modrinth | modrinth.com | ⭐⭐⭐⭐⭐ | +| Hangar | hangar.papermc.io | ⭐⭐⭐⭐⭐ | +| Bukkit/CurseForge | dev.bukkit.org | ⭐⭐⭐⭐ | +| GitHub | github.com | ⭐⭐⭐⭐ | + +> ⚠️ **Warning:** Never download plugins from untrusted sources. Malicious plugins can destroy your server or steal data. + +### Essential Plugin Categories + +1. **Permissions** - LuckPerms, PermissionsEx +2. **Economy** - Vault, EssentialsX Economy +3. **Protection** - WorldGuard, GriefPrevention +4. **Essentials** - EssentialsX (commands, homes, warps) +5. **Chat** - LPC, ChatControl +6. **Anti-Cheat** - Vulcan, Matrix, Spartan + +## Installing a Plugin + +### Method 1: Game Panel File Manager + +1. Download the plugin `.jar` file to your computer +2. Open your server in the Game Panel +3. Navigate to **Files** > **plugins/** +4. Click **Upload** or drag and drop the file +5. Restart your server + +### Method 2: SFTP + +1. Connect to your server via SFTP +2. Navigate to `/plugins/` +3. Upload the `.jar` file +4. Restart your server + +### Verifying Installation + +After restarting, check the console for: + +```log +[Server] [PluginName] Enabling PluginName v1.0.0 +[Server] [PluginName] Plugin enabled successfully! +``` + +Or run `/plugins` in-game to see loaded plugins. + +## Configuring Plugins + +Most plugins create configuration files in their own folder: + +``` +/plugins/ +├── PluginName/ +│ ├── config.yml <- Main configuration +│ ├── messages.yml <- Customizable messages +│ └── data/ <- Plugin data +``` + +### Editing Configuration + +1. Stop your server (recommended) +2. Navigate to `/plugins/PluginName/` +3. Edit `config.yml` +4. Save changes +5. Start your server + +### Common config.yml Structure + +```yaml +# Most plugins use YAML format +settings: + enabled: true + debug: false + +features: + some-feature: true + another-feature: false + +messages: + prefix: "&a[Server]&r" + welcome: "&eWelcome to the server!" +``` + +## Essential Plugins Guide + +### LuckPerms (Permissions) + +The gold standard for permission management. + +```yaml +# Install: Drop luckperms.jar in /plugins/ +# Access web editor: /lp editor + +# Common commands: +/lp user permission set +/lp group permission set +/lp user parent set +``` + +### EssentialsX + +All-in-one utility plugin for basics. + +**Features:** +- `/home`, `/sethome` - Player homes +- `/warp`, `/setwarp` - Server warps +- `/spawn`, `/setspawn` - Spawn management +- `/tpa`, `/tpaccept` - Teleport requests +- `/msg`, `/r` - Private messaging +- `/ban`, `/kick`, `/mute` - Moderation + +### WorldGuard + WorldEdit + +Region protection and world editing. + +```bash +# Create a protected region: +//wand # Get selection tool +# Left-click first corner +# Right-click second corner +/rg create +/rg flag pvp deny +``` + +### Vault + +Economy and permissions API (required by many plugins). + +> **Note:** Vault is an API plugin. You also need an economy plugin like EssentialsX or a permissions plugin like LuckPerms. + +## Troubleshooting + +### Plugin Won't Enable + +1. **Check version compatibility** + - Plugin must support your Minecraft version + - Check the plugin page for requirements + +2. **Check for dependencies** + ```log + [Server] [PluginName] Missing dependency: Vault + ``` + Install any required dependencies first. + +3. **Check console for errors** + ```log + [Server] Error enabling PluginName + java.lang.NullPointerException... + ``` + Report errors to plugin developer or check their Discord. + +### Common Errors + +| Error | Solution | +|-------|----------| +| `NoClassDefFoundError` | Missing dependency or wrong version | +| `InvalidPluginException` | Corrupted JAR or wrong version | +| `FileNotFoundException` | Config file deleted or corrupted | +| `SQLException` | Database connection issue | + +### Plugin Conflicts + +Some plugins don't work well together: + +```log +[Server] [PluginA] Warning: Detected incompatible plugin PluginB +``` + +**Solutions:** +1. Check plugin pages for known conflicts +2. Remove conflicting plugin +3. Find an alternative plugin +4. Contact developers for guidance + +## Best Practices + +### 1. Test Before Production + +Always test plugins on a local or staging server first. + +### 2. Keep Plugins Updated + +Regular updates fix bugs and security issues. + +### 3. Backup Before Changes + +```bash +# Always backup before installing/updating plugins +``` + +### 4. Limit Plugin Count + +More plugins = more potential issues +- Start with essentials +- Add plugins as needed +- Remove unused plugins + +### 5. Read Documentation + +Most issues are answered in plugin documentation. + +## Performance Impact + +| Plugin Type | Typical Impact | +|-------------|----------------| +| Permissions | 🟢 Low | +| Economy | 🟢 Low | +| Chat | 🟢 Low | +| Essentials | 🟡 Low-Medium | +| WorldGuard | 🟡 Medium | +| Anti-Cheat | 🟠 Medium-High | +| Dynmap | 🔴 High | + +## Recommended Plugin Stack + +For a general SMP server: + +``` +Essential: +├── LuckPerms (permissions) +├── Vault (API) +├── EssentialsX (utilities) +├── EssentialsXSpawn (spawn control) +└── EssentialsXChat (chat formatting) + +Protection: +├── WorldEdit (building tool) +├── WorldGuard (region protection) +└── CoreProtect (block logging) + +Optional: +├── DiscordSRV (Discord integration) +├── dynmap (live map - resource heavy!) +└── PlaceholderAPI (placeholder support) +``` + +## Plugin Commands + +List all plugins: +``` +/plugins +/pl +``` + +Get plugin info: +``` +/version +``` + +Reload a plugin (if supported): +``` +/ reload +``` + +## Need Help? + +- Check the plugin's documentation +- Visit the plugin's Discord/support +- Ask in our [Discord](https://discord.gg/wN58bTzzpW) +- Open a [support ticket](https://billing.nodebyte.host/submitticket.php) diff --git a/packages/kb/content/minecraft/server-software.md b/packages/kb/content/minecraft/server-software.md new file mode 100644 index 0000000..34a6954 --- /dev/null +++ b/packages/kb/content/minecraft/server-software.md @@ -0,0 +1,214 @@ +--- +title: Choosing Your Minecraft Server Software +description: A comprehensive guide to selecting the right server software for your Minecraft server. +tags: [minecraft, server-software, paper, spigot, forge, fabric] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 1 +--- + +# Choosing Your Minecraft Server Software + +One of the first decisions you'll make when setting up a Minecraft server is choosing the server software. This guide covers all the popular options and helps you pick the right one for your needs. + +## Server Software Comparison + +| Software | Type | Plugin Support | Mod Support | Performance | +|----------|------|----------------|-------------|-------------| +| Vanilla | Official | ❌ | ❌ | Baseline | +| Paper | Bukkit Fork | ✅ Bukkit/Spigot | ❌ | ⭐⭐⭐⭐⭐ | +| Purpur | Paper Fork | ✅ Bukkit/Spigot | ❌ | ⭐⭐⭐⭐⭐ | +| Forge | Modded | ❌ | ✅ Forge | ⭐⭐⭐ | +| Fabric | Modded | ❌ | ✅ Fabric | ⭐⭐⭐⭐ | +| Mohist | Hybrid | ✅ Bukkit | ✅ Forge | ⭐⭐⭐ | + +## Vanilla + +The official Minecraft server software from Mojang. + +**Pros:** +- Official and always up-to-date +- Guaranteed compatibility +- Simple setup + +**Cons:** +- No plugin or mod support +- Lower performance +- Limited customization + +**Best for:** Pure vanilla experience, technical Minecraft servers + +## Paper (Recommended for Plugins) + +The most popular server software for plugin-based servers. + +**Pros:** +- Excellent performance optimization +- Bukkit/Spigot plugin compatibility +- Active development and community +- Many built-in optimizations +- Asynchronous chunk loading + +**Cons:** +- Some vanilla mechanics may be altered +- No mod support + +**Best for:** Most servers, especially those wanting plugins + +### Installing Paper + +1. In your game panel, go to **Settings** > **Server Software** +2. Select **Paper** from the dropdown +3. Choose your Minecraft version +4. Restart your server + +## Purpur + +A Paper fork with additional features and customization options. + +**Pros:** +- All Paper benefits +- Extra gameplay tweaks +- More configuration options +- Rideable mobs +- AFK system built-in + +**Cons:** +- Slightly behind Paper updates +- More complex configuration + +**Best for:** Servers wanting maximum customization + +### Notable Purpur Features + +```yaml +# Example purpur.yml customizations +purpur: + ridable: + bees: true + dolphins: true + gameplay-mechanics: + player: + idle-timeout: + kick-if-idle: false +``` + +## Forge + +The go-to choice for modded Minecraft with extensive mod libraries. + +**Pros:** +- Massive mod library +- Well-established ecosystem +- Most popular for modpacks + +**Cons:** +- Higher resource usage +- No native Bukkit plugin support +- Slower updates for new MC versions + +**Best for:** Modded servers, modpacks (FTB, ATM, etc.) + +### Installing Forge + +1. Select **Forge** as server software +2. Choose Minecraft and Forge versions +3. Upload your mods to `/mods/` folder +4. Restart server + +> ⚠️ **Important:** All players must have matching mods installed! + +## Fabric + +A lightweight, modern modding platform. + +**Pros:** +- Lightweight and fast +- Quick updates for new MC versions +- Growing mod library +- Good for performance mods + +**Cons:** +- Smaller mod library than Forge +- Mods not cross-compatible with Forge + +**Best for:** Lightweight modded servers, performance-focused setups + +### Popular Fabric Mods + +- **Lithium** - Server optimization +- **Starlight** - Lighting engine rewrite +- **FerriteCore** - Memory optimization +- **Carpet** - Technical features and fixes + +## Mohist & Hybrids + +Hybrid servers that support both mods AND plugins. + +**Pros:** +- Best of both worlds +- Flexibility for complex setups + +**Cons:** +- Potential compatibility issues +- More complex troubleshooting +- May lag behind updates + +**Best for:** Advanced users wanting both mods and plugins + +> ⚠️ **Warning:** Hybrid servers can have stability issues. Test thoroughly! + +## Quick Decision Guide + +```mermaid +graph TD + A[What do you want?] --> B{Mods or Plugins?} + B -->|Plugins| C{Extra customization?} + B -->|Mods| D{Which ecosystem?} + B -->|Both| E[Mohist/Hybrid] + C -->|Yes| F[Purpur] + C -->|No| G[Paper] + D -->|Large modpacks| H[Forge] + D -->|Performance/New features| I[Fabric] +``` + +### In Summary + +- **Just starting?** → Paper +- **Want plugins?** → Paper or Purpur +- **Playing modpacks?** → Forge +- **Want modern, light mods?** → Fabric +- **Need both?** → Mohist (advanced users) + +## Version Recommendations + +| Use Case | Software | Version | +|----------|----------|---------| +| Public SMP | Paper | Latest stable | +| Private friends | Paper/Purpur | Latest stable | +| Modpack server | Forge | Modpack specified | +| Technical server | Fabric | Latest | +| Development | Paper | Latest snapshot (optional) | + +## Performance Expectations + +With a NodeByte plan, here's what you can expect: + +| Plan | Software | Expected Players | +|------|----------|------------------| +| 4GB | Paper | 20-30 players | +| 4GB | Forge (light) | 5-10 players | +| 8GB | Paper | 40-60 players | +| 8GB | Forge (modpack) | 15-25 players | + +## Next Steps + +After choosing your server software: + +1. [Configure your server.properties](/kb/minecraft/server-properties) +2. [Install plugins](/kb/minecraft/installing-plugins) (if using Paper/Purpur) +3. [Optimize performance](/kb/minecraft/optimization) + +--- + +Need help deciding? Ask in our [Discord](https://discord.gg/wN58bTzzpW)! diff --git a/packages/kb/content/rust/_meta.json b/packages/kb/content/rust/_meta.json new file mode 100644 index 0000000..be216f1 --- /dev/null +++ b/packages/kb/content/rust/_meta.json @@ -0,0 +1,6 @@ +{ + "title": "Rust Servers", + "description": "Guides and tutorials for setting up and managing Rust game servers", + "icon": "Gamepad2", + "order": 3 +} diff --git a/packages/kb/content/rust/getting-started.md b/packages/kb/content/rust/getting-started.md new file mode 100644 index 0000000..7751f3e --- /dev/null +++ b/packages/kb/content/rust/getting-started.md @@ -0,0 +1,179 @@ +--- +title: Getting Started with Rust +description: Learn the basics of setting up and configuring your Rust server on NodeByte. +tags: [rust, getting-started, setup, tutorial] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 1 +--- + +# Getting Started with Rust + +This guide will help you set up and configure your Rust server on NodeByte. We'll cover everything from initial setup to basic configuration. + +## Accessing Your Server + +After purchasing your Rust server from NodeByte: + +1. Log into the [Game Panel](https://panel.nodebyte.host) +2. Select your Rust server from the server list +3. You'll see the main console and server controls + +## First Boot + +When you first start your server, it will: + +1. Download the latest Rust Dedicated Server files +2. Generate the default configuration +3. Create your first procedurally generated map + +> **Note:** The first startup may take several minutes as the server downloads all necessary files. + +## Server Configuration + +### Basic Settings + +Your server's main configuration is found in `server.cfg`. Key settings include: + +```cfg +# Server Identity +server.hostname "My NodeByte Rust Server" +server.description "Welcome to our server!" +server.url "https://nodebyte.host" +server.headerimage "https://your-image-url.com/banner.png" + +# Gameplay Settings +server.maxplayers 100 +server.worldsize 3500 +server.seed 12345 +server.saveinterval 300 + +# Security +server.secure true +server.encryption 1 +``` + +### Map Settings + +| Setting | Description | Recommended | +|---------|-------------|-------------| +| `server.worldsize` | Map size in meters | 3000-4000 for small, 4000-6000 for large | +| `server.seed` | Procedural generation seed | Any number, or leave blank for random | +| `server.level` | Map type | `Procedural Map` (default) | + +### Player Slots + +Your maximum players depends on your plan's RAM allocation: + +| RAM | Recommended Players | +|-----|---------------------| +| 4GB | 50-75 players | +| 8GB | 100-150 players | +| 16GB | 200+ players | + +## Connecting to Your Server + +### Finding Your Connection Info + +1. Go to your server in the Game Panel +2. Look for the **Connection Address** in the sidebar +3. Format: `IP:Port` (e.g., `192.168.1.1:28015`) + +### Client Connection + +Players can connect via: + +**Console Command:** +``` +client.connect IP:PORT +``` + +**Steam Server Browser:** +1. Open Steam → View → Servers +2. Click "Add a Server" +3. Enter your server IP:Port +4. Click "Add This Address to Favorites" + +## RCON Access + +RCON (Remote Console) allows you to run commands remotely: + +### Setting Up RCON + +In your `server.cfg`: +```cfg +rcon.ip 0.0.0.0 +rcon.port 28016 +rcon.password "your_secure_password" +rcon.web 1 +``` + +### RCON Tools + +- **RustAdmin** - Desktop application for Windows +- **Battlemetrics** - Web-based RCON and server management +- **RustIO** - Web RCON with live map + +## Essential Commands + +| Command | Description | +|---------|-------------| +| `server.save` | Force a world save | +| `server.writecfg` | Save current config | +| `kick "player"` | Kick a player | +| `ban "player"` | Ban a player | +| `unban "steamid"` | Unban a player | +| `say "message"` | Server-wide message | +| `status` | List connected players | + +## Performance Optimization + +### Recommended Settings + +```cfg +# Performance +fps.limit 60 +gc.interval 60 +gc.buffer 256 + +# Network +server.tickrate 30 +server.netlog 0 +``` + +### Monitoring + +Keep an eye on these metrics in your Game Panel: + +- **CPU Usage** - Should stay below 80% +- **RAM Usage** - Leave at least 1GB headroom +- **Tick Rate** - Should stay close to your set value + +## Wipe Schedule + +Rust servers typically wipe on a schedule: + +- **Forced Wipes** - First Thursday of each month (required by Facepunch) +- **Map Wipes** - You choose the frequency +- **Blueprint Wipes** - Optional, often monthly + +### Setting Up Automatic Wipes + +Use the Game Panel's **Schedules** feature: + +1. Create a new schedule +2. Set the cron expression (e.g., `0 12 * * 4` for Thursdays at noon) +3. Add task: `server.save` then `quit` +4. Your server will restart fresh on the scheduled time + +## Next Steps + +Now that your server is running: + +1. [Install Oxide/uMod](/kb/rust/oxide-installation) - Add plugin support +2. [Essential Plugins](/kb/rust/essential-plugins) - Must-have plugins +3. [Admin Commands](/kb/rust/admin-commands) - Full command reference + +--- + +Need help? Join our [Discord](https://discord.gg/wN58bTzzpW) or open a [support ticket](https://billing.nodebyte.host/submitticket.php). diff --git a/packages/kb/content/rust/oxide-installation.md b/packages/kb/content/rust/oxide-installation.md new file mode 100644 index 0000000..c87040c --- /dev/null +++ b/packages/kb/content/rust/oxide-installation.md @@ -0,0 +1,255 @@ +--- +title: Installing Oxide/uMod +description: Learn how to install the Oxide/uMod modding framework on your Rust server for plugin support. +tags: [rust, oxide, umod, plugins, mods] +author: NodeByte Team +lastUpdated: 2025-12-21 +order: 2 +--- + +# Installing Oxide/uMod + +Oxide (also known as uMod) is the most popular modding framework for Rust servers. It allows you to install plugins that add features, modify gameplay, and enhance server administration. + +## What is Oxide/uMod? + +Oxide/uMod is an open-source modding framework that: + +- Enables plugin installation +- Provides an extensive API for developers +- Includes permission management +- Supports thousands of community plugins + +> **Note:** Oxide and uMod refer to the same framework. uMod is the newer name, but many still call it Oxide. + +## Installing Oxide + +### Method 1: Game Panel (Recommended) + +NodeByte's Game Panel makes Oxide installation simple: + +1. Navigate to your Rust server in the [Game Panel](https://panel.nodebyte.host) +2. **Stop your server** if it's running +3. Go to **Settings** or **Startup** +4. Look for the **Oxide/uMod** option +5. Enable it and save +6. Start your server + +The server will automatically download and install the latest Oxide version. + +### Method 2: Manual Installation + +If you prefer manual installation: + +1. **Stop your server** +2. Download the latest Oxide build from [umod.org](https://umod.org/games/rust) +3. Extract the files +4. Upload via SFTP to your server's root directory +5. Overwrite existing files when prompted +6. Start your server + +## Verifying Installation + +After starting your server with Oxide, check the console for: + +```log +[Oxide] Loading Oxide Core v2.x.x... +[Oxide] Loading extensions... +[Oxide] Loaded extension Rust v2.x.x +[Oxide] Loading plugins... +``` + +You can also run this command in RCON: +``` +oxide.version +``` + +## Oxide Directory Structure + +After installation, you'll have these new folders: + +``` +/oxide/ +├── config/ # Plugin configuration files +├── data/ # Plugin data storage +├── logs/ # Oxide logs +├── plugins/ # Your installed plugins (.cs files) +└── lang/ # Language files +``` + +## Installing Plugins + +### Finding Plugins + +The best sources for Rust plugins: + +| Source | URL | Notes | +|--------|-----|-------| +| **uMod** | umod.org/plugins | Official plugin repository | +| **Codefling** | codefling.com | Premium and free plugins | +| **Lone.Design** | lone.design | Premium plugins | + +### Installing a Plugin + +1. Download the plugin `.cs` file +2. Upload it to `/oxide/plugins/` via SFTP or File Manager +3. The plugin will auto-load (no restart needed!) + +Check the console for confirmation: +```log +[Oxide] Loaded plugin PluginName v1.0.0 by Author +``` + +### Plugin Commands + +| Command | Description | +|---------|-------------| +| `oxide.reload PluginName` | Reload a specific plugin | +| `oxide.reload *` | Reload all plugins | +| `oxide.unload PluginName` | Unload a plugin | +| `oxide.load PluginName` | Load a plugin | +| `oxide.plugins` | List all loaded plugins | + +## Configuring Plugins + +Most plugins create configuration files in `/oxide/config/`: + +```json +{ + "Settings": { + "EnableFeature": true, + "MaxPlayers": 100 + }, + "Messages": { + "Welcome": "Welcome to the server!" + } +} +``` + +After editing a config: +``` +oxide.reload PluginName +``` + +## Permissions System + +Oxide includes a powerful permissions system: + +### Basic Commands + +```bash +# Grant permission to a player +oxide.grant user PlayerName permission.name + +# Grant permission to a group +oxide.grant group groupname permission.name + +# Revoke permission +oxide.revoke user PlayerName permission.name + +# Create a group +oxide.group add vip + +# Add player to group +oxide.usergroup add PlayerName vip +``` + +### Default Groups + +- `default` - All players automatically join this group +- `admin` - Create this for server administrators +- `moderator` - Create this for server moderators +- `vip` - Common group for VIP players + +### Example Setup + +```bash +# Create groups +oxide.group add admin +oxide.group add vip + +# Set up admin group +oxide.grant group admin vanish.use +oxide.grant group admin adminpanel.use + +# Set up VIP group +oxide.grant group vip kits.vip +oxide.grant group vip queue.skip +``` + +## Updating Oxide + +### Automatic Updates + +Keep Oxide updated through the Game Panel: + +1. Go to your server settings +2. Ensure auto-update is enabled +3. Oxide updates when the server restarts + +### Manual Updates + +1. Stop your server +2. Download the latest build from umod.org +3. Upload and overwrite files +4. Start your server + +> **Tip:** Oxide updates frequently. Check for updates weekly! + +## Troubleshooting + +### Plugin Won't Load + +Check the console for errors. Common issues: + +```log +[Oxide] Error while compiling: PluginName.cs +``` + +**Solutions:** +- Ensure you have the correct Oxide version +- Check plugin dependencies +- Verify the plugin supports your Rust version + +### Missing Dependencies + +Some plugins require others: + +```log +[Oxide] Missing plugin dependency: ImageLibrary +``` + +Install the required dependency plugin first. + +### Performance Issues + +If plugins cause lag: + +1. Check `/oxide/logs/` for errors +2. Use `oxide.plugins` to identify resource-heavy plugins +3. Consider alternatives or contact the plugin developer + +## Essential Plugins + +Get started with these must-have plugins: + +| Plugin | Purpose | +|--------|---------| +| **Rust:IO** | Live map and RCON | +| **GatherManager** | Adjust gather rates | +| **Kits** | Starter and VIP kits | +| **Clans** | Clan system | +| **BetterChat** | Enhanced chat formatting | +| **NTeleportation** | Teleportation system | +| **Economics** | Server economy | +| **ServerRewards** | Reward point system | + +## Next Steps + +- [Essential Plugins](/kb/rust/essential-plugins) - Detailed plugin recommendations +- [Admin Commands](/kb/rust/admin-commands) - Complete command reference +- [Server Optimization](/kb/rust/optimization) - Performance tuning + +--- + +Need help? Join our [Discord](https://discord.gg/wN58bTzzpW) or open a [support ticket](https://billing.nodebyte.host/submitticket.php). diff --git a/packages/kb/lib/kb.ts b/packages/kb/lib/kb.ts new file mode 100644 index 0000000..00a23e5 --- /dev/null +++ b/packages/kb/lib/kb.ts @@ -0,0 +1,264 @@ +// Knowledge Base markdown processing utilities +import matter from 'gray-matter' +import { remark } from 'remark' +import remarkGfm from 'remark-gfm' +import remarkRehype from 'remark-rehype' +import rehypeStringify from 'rehype-stringify' +import rehypeHighlight from 'rehype-highlight' +import rehypeSlug from 'rehype-slug' +import rehypeAutolinkHeadings from 'rehype-autolink-headings' +import readingTime from 'reading-time' +import fs from 'fs' +import path from 'path' + +// Types +export interface KBArticleMeta { + slug: string + title: string + description: string + category: string + categorySlug: string + tags?: string[] + author?: string + lastUpdated: string + readingTime: number + excerpt?: string + order: number +} + +export interface KBArticle { + meta: { + title: string + description: string + tags?: string[] + author?: string + lastUpdated?: string + } + content: string + readingTime: number +} + +export interface KBCategory { + slug: string + title: string + description: string + icon: string + articleCount: number + order: number +} + +export interface TableOfContentsItem { + id: string + text: string + level: number +} + +// Base path for KB content +const KB_CONTENT_PATH = path.join(process.cwd(), 'packages', 'kb', 'content') + +/** + * Process markdown content to HTML + */ +export async function processMarkdown(content: string): Promise { + const result = await remark() + .use(remarkGfm) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeSlug) + .use(rehypeAutolinkHeadings, { + behavior: 'wrap', + properties: { + className: ['anchor-link'], + }, + }) + .use(rehypeHighlight, { detect: true }) + .use(rehypeStringify, { allowDangerousHtml: true }) + .process(content) + + return result.toString() +} + +/** + * Extract headings from HTML content for table of contents + */ +export function extractHeadings(html: string): TableOfContentsItem[] { + const headingRegex = /]*id="([^"]*)"[^>]*>(?:]*>)?([^<]*)/g + const headings: TableOfContentsItem[] = [] + let match + + while ((match = headingRegex.exec(html)) !== null) { + const level = parseInt(match[1]) + const id = match[2] + const text = match[3].trim() + + if (id && text) { + headings.push({ id, text, level }) + } + } + + return headings +} + +/** + * Get all categories + */ +export async function getCategories(): Promise { + if (!fs.existsSync(KB_CONTENT_PATH)) { + return [] + } + + const categoryDirs = fs.readdirSync(KB_CONTENT_PATH, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + + const categories: KBCategory[] = [] + + for (const dir of categoryDirs) { + const categoryPath = path.join(KB_CONTENT_PATH, dir.name) + const metaPath = path.join(categoryPath, '_meta.json') + + let meta = { + title: dir.name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()), + description: '', + icon: 'FileText', + order: 999, + } + + if (fs.existsSync(metaPath)) { + const metaContent = fs.readFileSync(metaPath, 'utf-8') + meta = { ...meta, ...JSON.parse(metaContent) } + } + + // Count articles + const articleCount = fs.readdirSync(categoryPath) + .filter(file => file.endsWith('.md') || file.endsWith('.mdx')).length + + categories.push({ + slug: dir.name, + title: meta.title, + description: meta.description, + icon: meta.icon, + order: meta.order, + articleCount, + }) + } + + return categories.sort((a, b) => (a.order || 999) - (b.order || 999)) +} + +/** + * Get all articles in a category + */ +export async function getArticlesByCategory(categorySlug: string): Promise { + const categoryPath = path.join(KB_CONTENT_PATH, categorySlug) + + if (!fs.existsSync(categoryPath)) { + return [] + } + + const files = fs.readdirSync(categoryPath) + .filter(file => file.endsWith('.md') || file.endsWith('.mdx')) + + const articles: KBArticleMeta[] = [] + + for (const file of files) { + const filePath = path.join(categoryPath, file) + const fileContent = fs.readFileSync(filePath, 'utf-8') + const { data, content } = matter(fileContent) + const stats = readingTime(fileContent) + const slug = file.replace(/\.mdx?$/, '') + + // Get category meta for title + const metaPath = path.join(categoryPath, '_meta.json') + let categoryTitle = categorySlug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) + if (fs.existsSync(metaPath)) { + const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8')) + categoryTitle = meta.title || categoryTitle + } + + // Create excerpt from content (first 150 chars of plain text) + const excerpt = content + .replace(/^#.*$/gm, '') // Remove headings + .replace(/\n+/g, ' ') // Replace newlines with spaces + .replace(/[*_`\[\]]/g, '') // Remove markdown formatting + .trim() + .slice(0, 200) + + articles.push({ + slug, + title: data.title || slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()), + description: data.description || '', + category: categoryTitle, + categorySlug, + tags: data.tags || [], + author: data.author, + lastUpdated: data.lastUpdated || new Date().toISOString().split('T')[0], + readingTime: Math.ceil(stats.minutes), + excerpt, + order: data.order || 999, + }) + } + + return articles.sort((a, b) => (a.order || 999) - (b.order || 999)) +} + +/** + * Get a single article by category and slug + */ +export async function getArticle(categorySlug: string, articleSlug: string): Promise { + const categoryPath = path.join(KB_CONTENT_PATH, categorySlug) + + // Try .md first, then .mdx + let filePath = path.join(categoryPath, `${articleSlug}.md`) + if (!fs.existsSync(filePath)) { + filePath = path.join(categoryPath, `${articleSlug}.mdx`) + } + + if (!fs.existsSync(filePath)) { + return null + } + + const fileContent = fs.readFileSync(filePath, 'utf-8') + const { data, content } = matter(fileContent) + const stats = readingTime(fileContent) + const htmlContent = await processMarkdown(content) + + return { + meta: { + title: data.title || articleSlug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()), + description: data.description || '', + tags: data.tags || [], + author: data.author, + lastUpdated: data.lastUpdated || new Date().toISOString().split('T')[0], + }, + content: htmlContent, + readingTime: Math.ceil(stats.minutes), + } +} + +/** + * Get all articles (for search) + */ +export async function getAllArticles(): Promise { + const categories = await getCategories() + const allArticles: KBArticleMeta[] = [] + + for (const cat of categories) { + const articles = await getArticlesByCategory(cat.slug) + allArticles.push(...articles) + } + + return allArticles +} + +/** + * Search articles by query + */ +export async function searchArticles(query: string): Promise { + const articles = await getAllArticles() + const lowerQuery = query.toLowerCase() + + return articles.filter(article => + article.title.toLowerCase().includes(lowerQuery) || + article.description.toLowerCase().includes(lowerQuery) || + article.tags?.some(tag => tag.toLowerCase().includes(lowerQuery)) + ) +} diff --git a/packages/ui/components/Layouts/About/about-page.tsx b/packages/ui/components/Layouts/About/about-page.tsx new file mode 100644 index 0000000..7c9c536 --- /dev/null +++ b/packages/ui/components/Layouts/About/about-page.tsx @@ -0,0 +1,312 @@ +"use client" + +import { Card } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { + Users, + Heart, + Code, + Gamepad2, + Sparkles, + ArrowRight, + Shield, + Zap, + Globe, + Server, + Clock, + MessageCircle, + Github, + Twitter +} from "lucide-react" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" +import { Logo } from "@/packages/ui/components/logo" + +export function AboutPage() { + const t = useTranslations() + + const values = [ + { + icon: Gamepad2, + title: t("aboutPage.values.players.title"), + description: t("aboutPage.values.players.description"), + gradient: "from-blue-500 to-cyan-500", + }, + { + icon: Code, + title: t("aboutPage.values.community.title"), + description: t("aboutPage.values.community.description"), + gradient: "from-orange-500 to-rose-500", + }, + { + icon: Sparkles, + title: t("aboutPage.values.open.title"), + description: t("aboutPage.values.open.description"), + gradient: "from-violet-500 to-blue-500", + }, + ] + + const stats = [ + { value: "99.6%", label: t("aboutPage.stats.uptime"), icon: Server }, + { value: "50ms", label: t("aboutPage.stats.latency"), icon: Zap }, + { value: "24/7", label: t("aboutPage.stats.support"), icon: Clock }, + { value: "1000+", label: t("aboutPage.stats.servers"), icon: Globe }, + ] + + const timeline = [ + { + year: "2024", + title: t("aboutPage.timeline.founded.title"), + description: t("aboutPage.timeline.founded.description"), + }, + { + year: "Q1/2 - 2025", + title: t("aboutPage.timeline.growth.title"), + description: t("aboutPage.timeline.growth.description"), + }, + { + year: "Q3/4 - 2025", + title: t("aboutPage.timeline.expansion.title"), + description: t("aboutPage.timeline.expansion.description"), + }, + ] + + return ( +
+ {/* Hero Section */} +
+ {/* Background gradients */} +
+ + {/* Animated background orbs */} +
+
+
+
+
+ + {/* Grid pattern overlay */} +
+ +
+
+
+ + {t("aboutPage.badge")} +
+ +

+ {t("aboutPage.hero.title")}{" "} + + {t("aboutPage.hero.titleHighlight")} + +

+ +

+ {t("aboutPage.hero.description")} +

+ +
+ + +
+
+
+
+ + {/* Story Section */} +
+
+ +
+
+
+
+ + {t("aboutPage.story.badge")} +
+ +

+ {t("aboutPage.story.title")} +

+ +
+

{t("aboutPage.story.paragraph1")}

+

{t("aboutPage.story.paragraph2")}

+

{t("aboutPage.story.paragraph3")}

+
+
+ + {/* Stats Grid */} +
+ {stats.map((stat) => ( + + +
+ {stat.value} +
+
{stat.label}
+
+ ))} +
+
+
+
+ + {/* Values Section */} +
+
+ +
+
+
+ + {t("aboutPage.values.badge")} +
+

+ {t("aboutPage.values.title")}{" "} + + {t("aboutPage.values.titleHighlight")} + +

+

+ {t("aboutPage.values.description")} +

+
+ +
+ {values.map((value, index) => ( + +
+ +
+
+ +
+ +

{value.title}

+

+ {value.description} +

+
+ + ))} +
+
+
+ + {/* Timeline Section */} +
+
+ +
+
+
+ + {t("aboutPage.timeline.badge")} +
+

+ {t("aboutPage.timeline.title")}{" "} + + {t("aboutPage.timeline.titleHighlight")} + +

+
+ +
+
+ {/* Timeline line */} +
+ + {timeline.map((item, index) => ( +
+ {/* Timeline dot */} +
+ + +
{item.year}
+

{item.title}

+

{item.description}

+
+
+ ))} +
+
+
+
+ + {/* CTA Section */} +
+
+ +
+ +
+
+ +

+ {t("aboutPage.cta.title")} +

+

+ {t("aboutPage.cta.description")} +

+
+ + +
+
+ +
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/About/index.ts b/packages/ui/components/Layouts/About/index.ts new file mode 100644 index 0000000..2fff7a3 --- /dev/null +++ b/packages/ui/components/Layouts/About/index.ts @@ -0,0 +1 @@ +export { AboutPage } from "./about-page" diff --git a/packages/ui/components/Layouts/Contact/contact.tsx b/packages/ui/components/Layouts/Contact/contact.tsx new file mode 100644 index 0000000..ecb1c9f --- /dev/null +++ b/packages/ui/components/Layouts/Contact/contact.tsx @@ -0,0 +1,241 @@ +"use client" + +import type React from "react" +import { Card } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { + Mail, + Github, + MessageCircle, + Headphones, + ExternalLink, + AlertTriangle, + ArrowRight, + Send, + CheckCircle2 +} from "lucide-react" +import { useToast } from "@/packages/core/hooks/use-toast" +import { SiDiscord, SiTrustpilot, SiX } from "react-icons/si" +import { cn } from "@/lib/utils" +import Link from "next/link" +import { useTranslations } from "next-intl" + +export function Contact() { + const { toast } = useToast() + const t = useTranslations() + + const supportChannels = [ + { + title: t("contact.discord.title"), + description: t("contact.discord.description"), + icon: SiDiscord, + href: "https://discord.gg/wN58bTzzpW", + cta: t("contact.discord.button"), + color: "bg-[#5865F2]/10 text-[#5865F2]", + hoverColor: "hover:bg-[#5865F2] hover:text-white", + features: [ + t("contact.discord.features.0"), + t("contact.discord.features.1"), + t("contact.discord.features.2"), + ], + }, + { + title: t("contact.github.title"), + description: t("contact.github.description"), + icon: Github, + href: "https://github.com/orgs/NodeByteHosting/discussions", + cta: t("contact.github.button"), + color: "bg-muted text-foreground", + hoverColor: "hover:bg-foreground hover:text-background", + features: [ + t("contact.github.features.0"), + t("contact.github.features.1"), + t("contact.github.features.2"), + ], + }, + ] + + const emailContacts = [ + { label: t("contact.email.technical"), email: "techteam@nodebyte.host", description: t("contact.email.technicalDesc") }, + { label: t("contact.email.general"), email: "support@nodebyte.host", description: t("contact.email.generalDesc") }, + { label: t("contact.email.account"), email: "accounts@nodebyte.host", description: t("contact.email.accountDesc") }, + { label: t("contact.email.billing"), email: "billing@nodebyte.host", description: t("contact.email.billingDesc") }, + ] + + const socialLinks = [ + { name: "X (Twitter)", icon: SiX, href: "https://twitter.com/NodeByteHosting", color: "hover:text-foreground" }, + { name: "GitHub", icon: Github, href: "https://github.com/NodeByteHosting", color: "hover:text-foreground" }, + { name: "Discord", icon: SiDiscord, href: "https://discord.gg/wN58bTzzpW", color: "hover:text-[#5865F2]" }, + { name: "Trustpilot", icon: SiTrustpilot, href: "https://uk.trustpilot.com/review/nodebyte.host", color: "hover:text-[#00b67a]" }, + ] + + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text).then(() => { + toast({ + title: t("contact.copied"), + description: t("contact.copiedDesc"), + }) + }).catch(() => { + toast({ + title: t("contact.copyFailed"), + description: t("contact.copyFailedDesc"), + variant: "destructive", + }) + }) + } + + return ( +
+ {/* Background */} +
+
+ + {/* Animated orbs */} +
+
+
+
+ +
+ {/* Header */} +
+
+ + {t("contact.badge")} +
+

+ {t("contact.title")}{" "} + + {t("contact.titleHighlight")} + +

+

+ {t("contact.description")} +

+
+ + {/* Social Links & Warning - Now at top */} +
+ +
+
+

{t("contact.social.title")}

+

+ {t("contact.social.description")} +

+
+
+ {socialLinks.map((social) => ( + + + + ))} +
+
+ +
+
+ +
+

{t("contact.warning.title")}

+

+ {t("contact.warning.description")} +

+
+
+
+
+
+ + {/* Support Channels */} +
+ {supportChannels.map((channel) => ( + +
+
+ +
+ +

{channel.title}

+

{channel.description}

+ +
    + {channel.features.map((feature, i) => ( +
  • + + {feature} +
  • + ))} +
+ + +
+
+ ))} +
+ + {/* Email Contacts */} +
+
+
+ + {t("contact.email.badge")} +
+

+ {t("contact.email.title")} +

+

+ {t("contact.email.responseTime")} +

+
+ +
+ {emailContacts.map((contact) => ( + copyToClipboard(contact.email)} + > +
+
+ +
+
+

{contact.label}

+

{contact.email}

+

{contact.description}

+
+ +
+
+ ))} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/packages/ui/components/Layouts/Error/error-page.tsx b/packages/ui/components/Layouts/Error/error-page.tsx new file mode 100644 index 0000000..016b8e9 --- /dev/null +++ b/packages/ui/components/Layouts/Error/error-page.tsx @@ -0,0 +1,92 @@ +"use client" + +import { Button } from "@/packages/ui/components/ui/button" +import { Card } from "@/packages/ui/components/ui/card" +import { RefreshCw, Home, AlertTriangle, MessageCircle } from "lucide-react" +import Link from "next/link" +import { useTranslations } from "next-intl" +import { Logo } from "@/packages/ui/components/logo" + +interface ErrorPageProps { + error?: Error & { digest?: string } + reset?: () => void +} + +export function ErrorPage({ error, reset }: ErrorPageProps) { + const t = useTranslations() + + return ( +
+ {/* Background gradients */} +
+ + {/* Animated background orbs */} +
+
+
+
+
+ + {/* Grid pattern overlay */} +
+ +
+
+
+ +
+
+ +

+ {t("errorPage.title")} +

+

+ {t("errorPage.description")} +

+ + {error?.digest && ( +

+ {t("errorPage.errorCode")}: {error.digest} +

+ )} + +
+ {reset && ( + + )} + +
+ + +

+ {t("errorPage.needHelp")} +

+

+ {t("errorPage.helpDescription")} +

+
+ + +
+
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Error/index.ts b/packages/ui/components/Layouts/Error/index.ts new file mode 100644 index 0000000..dd36179 --- /dev/null +++ b/packages/ui/components/Layouts/Error/index.ts @@ -0,0 +1,2 @@ +export { NotFoundPage } from "./not-found-page" +export { ErrorPage } from "./error-page" diff --git a/packages/ui/components/Layouts/Error/not-found-page.tsx b/packages/ui/components/Layouts/Error/not-found-page.tsx new file mode 100644 index 0000000..a5aa021 --- /dev/null +++ b/packages/ui/components/Layouts/Error/not-found-page.tsx @@ -0,0 +1,83 @@ +"use client" + +import { Button } from "@/packages/ui/components/ui/button" +import { Card } from "@/packages/ui/components/ui/card" +import { Home, ArrowLeft, Search, MessageCircle } from "lucide-react" +import Link from "next/link" +import { useTranslations } from "next-intl" +import { Logo } from "@/packages/ui/components/logo" + +export function NotFoundPage() { + const t = useTranslations() + + const quickLinks = [ + { href: "/", label: t("notFound.links.home"), icon: Home }, + { href: "/games", label: t("notFound.links.games"), icon: Search }, + { href: "/kb", label: t("notFound.links.kb"), icon: Search }, + { href: "/contact", label: t("notFound.links.contact"), icon: MessageCircle }, + ] + + return ( +
+ {/* Background gradients */} +
+ + {/* Animated background orbs */} +
+
+
+
+
+ + {/* Grid pattern overlay */} +
+ +
+
+ +
+ 404 +
+
+ +

+ {t("notFound.title")} +

+

+ {t("notFound.description")} +

+ +
+ + +
+ + +

+ {t("notFound.quickLinks")} +

+
+ {quickLinks.map((link) => ( + + + {link.label} + + ))} +
+
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Games/game-faq.tsx b/packages/ui/components/Layouts/Games/game-faq.tsx new file mode 100644 index 0000000..c2f3e75 --- /dev/null +++ b/packages/ui/components/Layouts/Games/game-faq.tsx @@ -0,0 +1,103 @@ +"use client" + +import { Button } from "@/packages/ui/components/ui/button" +import { Card } from "@/packages/ui/components/ui/card" +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/packages/ui/components/ui/accordion" +import { HelpCircle, MessageCircle, Mail } from "lucide-react" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +interface FAQItem { + question: string + answer: string +} + +interface GameFAQProps { + gameName: string + faqs: FAQItem[] +} + +export function GameFAQ({ gameName, faqs }: GameFAQProps) { + const t = useTranslations() + + return ( +
+ {/* Background */} +
+ +
+ {/* Header */} +
+
+ + {t("gamePage.faq.badge")} +
+

+ {gameName}{" "} + + {t("gamePage.faq.title")} + +

+

+ {t("gamePage.faq.description", { game: gameName })} +

+
+ + {/* FAQ Accordion */} +
+ + {faqs.map((faq, index) => ( + + + {faq.question} + + + {faq.answer} + + + ))} + +
+ + {/* CTA */} + +
+

{t("gamePage.faq.stillHaveQuestions")}

+

+ {t("gamePage.faq.supportAvailable")} +

+
+ + +
+
+
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Games/game-features.tsx b/packages/ui/components/Layouts/Games/game-features.tsx new file mode 100644 index 0000000..195adf0 --- /dev/null +++ b/packages/ui/components/Layouts/Games/game-features.tsx @@ -0,0 +1,111 @@ +"use client" + +import { Card } from "@/packages/ui/components/ui/card" +import { + CheckCircle2, + Sparkles, + Settings, + Cpu, + Shield, + Zap, + HardDrive, + Users, + Server, + Map, + Globe +} from "lucide-react" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +const iconMap = { + Settings: Settings, + Cpu: Cpu, + Shield: Shield, + Zap: Zap, + HardDrive: HardDrive, + Users: Users, + Server: Server, + Map: Map, + Globe: Globe, + Sparkles: Sparkles, +} + +interface Feature { + title: string + description: string + icon: keyof typeof iconMap + highlights: string[] +} + +interface GameFeaturesProps { + gameName: string + features: Feature[] +} + +export function GameFeatures({ gameName, features }: GameFeaturesProps) { + const t = useTranslations() + + return ( +
+ {/* Background */} +
+ +
+ {/* Header */} +
+
+ + {t("gamePage.features.badge")} +
+

+ {t("gamePage.features.title")}{" "} + + {gameName} {t("gamePage.features.titleSuffix")} + +

+

+ {t("gamePage.features.description", { game: gameName })} +

+
+ + {/* Features Grid */} +
+ {features.map((feature, index) => ( + +
+ {/* Icon */} +
+ {(() => { + const IconComponent = iconMap[feature.icon] + return IconComponent ? : null + })()} +
+ + {/* Content */} +

{feature.title}

+

{feature.description}

+ + {/* Highlights */} +
    + {feature.highlights.map((highlight, i) => ( +
  • + + {highlight} +
  • + ))} +
+
+
+ ))} +
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Games/game-hero.tsx b/packages/ui/components/Layouts/Games/game-hero.tsx new file mode 100644 index 0000000..931b79e --- /dev/null +++ b/packages/ui/components/Layouts/Games/game-hero.tsx @@ -0,0 +1,151 @@ +"use client" + +import { Button } from "@/packages/ui/components/ui/button" +import { ArrowRight, ExternalLink, Star, Blocks, Gamepad2, Sparkles } from "lucide-react" +import Image from "next/image" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +const iconMap = { + Blocks: Blocks, + Gamepad2: Gamepad2, + Sparkles: Sparkles, +} + +interface GameHeroProps { + name: string + description: string + banner: string + icon: keyof typeof iconMap + tag: string + tagColor: string + billingUrl: string + features: string[] + comingSoon?: boolean +} + +export function GameHero({ + name, + description, + banner, + icon, + tag, + tagColor, + billingUrl, + features, + comingSoon, +}: GameHeroProps) { + const t = useTranslations() + const Icon = iconMap[icon] + + return ( +
+ {/* Background */} +
+ + {/* Banner overlay */} +
+ {name} +
+
+ + {/* Grid pattern */} +
+ +
+
+ {/* Left Content */} +
+ {/* Tag */} +
+ + {tag} +
+ + {/* Heading */} +

+ {name}{" "} + + {t("games.hosting")} + +

+ +

+ {description} +

+ + {/* Quick Features */} +
+ {features.slice(0, 4).map((feature) => ( +
+ + {feature} +
+ ))} +
+ + {/* CTAs */} +
+ {comingSoon ? ( + + ) : ( + <> + + + + )} +
+
+ + {/* Right - Banner Image */} +
+
+ {name} + {comingSoon && ( +
+
+

{t("gamePage.hero.comingSoon")}

+

{t("gamePage.hero.joinDiscordForUpdates")}

+
+
+ )} +
+
+
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Games/game-pricing.tsx b/packages/ui/components/Layouts/Games/game-pricing.tsx new file mode 100644 index 0000000..48f2e5c --- /dev/null +++ b/packages/ui/components/Layouts/Games/game-pricing.tsx @@ -0,0 +1,162 @@ +"use client" + +import { Button } from "@/packages/ui/components/ui/button" +import { Card } from "@/packages/ui/components/ui/card" +import { CheckCircle2, Zap, ArrowRight, ExternalLink } from "lucide-react" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { Price } from "@/packages/ui/components/ui/price" +import { useTranslations } from "next-intl" + +interface PricingPlan { + name: string + description: string + /** Price in GBP (base currency) - just the number */ + priceGBP: number + period: string + features: string[] + popular?: boolean + url?: string +} + +interface GamePricingProps { + gameName: string + billingUrl: string + plans: PricingPlan[] + comingSoon?: boolean +} + +export function GamePricing({ gameName, billingUrl, plans, comingSoon }: GamePricingProps) { + const t = useTranslations() + + return ( +
+ {/* Background */} +
+ +
+ {/* Header */} +
+
+ + {t("gamePage.pricing.badge")} +
+

+ {gameName}{" "} + + {t("gamePage.pricing.title")} + +

+

+ {t("gamePage.pricing.description")} +

+
+ + {comingSoon ? ( + +
+
+ +
+

{t("gamePage.pricing.comingSoon")}

+

+ {t("gamePage.pricing.comingSoonDesc", { game: gameName })} +

+ +
+
+ ) : ( + <> + {/* Pricing Grid */} +
+ {plans.map((plan) => ( + + {plan.popular && ( +
+ {t("gamePage.pricing.mostPopular")} +
+ )} + +
+

{plan.name}

+

{plan.description}

+ +
+ + /{plan.period} +
+ +
    + {plan.features.map((feature, i) => ( +
  • + + + {feature === "FyfeWeb DDoS Protection" ? ( + + {feature} + + ) : ( + feature + )} + +
  • + ))} +
+ + +
+
+ ))} +
+ + {/* CTA */} +
+

+ {t("gamePage.pricing.customSolution")}{" "} + + {t("gamePage.pricing.contactUs")} + +

+ +
+ + )} +
+
+ ) +} diff --git a/packages/ui/components/Layouts/Home/about.tsx b/packages/ui/components/Layouts/Home/about.tsx new file mode 100644 index 0000000..b650b22 --- /dev/null +++ b/packages/ui/components/Layouts/Home/about.tsx @@ -0,0 +1,134 @@ +"use client" + +import type React from "react" +import { Card } from "@/packages/ui/components/ui/card" +import { Users, Heart, Code, Gamepad2, Server, Sparkles, ArrowRight } from "lucide-react" +import { Button } from "@/packages/ui/components/ui/button" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +export function About() { + const t = useTranslations() + + const values = [ + { + icon: Gamepad2, + title: t("about.values.players.title"), + description: t("about.values.players.description"), + gradient: "from-blue-500 to-cyan-500", + }, + { + icon: Code, + title: t("about.values.community.title"), + description: t("about.values.community.description"), + gradient: "from-orange-500 to-rose-500", + }, + { + icon: Sparkles, + title: t("about.values.open.title"), + description: t("about.values.open.description"), + gradient: "from-violet-500 to-blue-500", + }, + ] + + const stats = [ + { value: "99.6%", label: t("about.stats.uptime") }, + { value: "50ms", label: t("about.stats.latency") }, + { value: "24/7", label: t("about.stats.support") }, + { value: "1000+", label: t("about.stats.servers") }, + ] + + return ( +
+ {/* Background */} +
+ +
+ {/* Header Section */} +
+
+
+ + {t("about.badge")} +
+ +

+ {t("about.title")}{" "} + + {t("about.titleHighlight")} + +

+ +
+

{t("about.paragraph1")}

+

{t("about.paragraph2")}

+
+ +
+ +
+
+ + {/* Stats Grid */} +
+ {stats.map((stat, index) => ( +
+
+ {stat.value} +
+
{stat.label}
+
+ ))} +
+
+ + {/* Values Grid */} +
+ {values.map((value, index) => ( + + {/* Gradient Header */} +
+ +
+ {/* Icon */} +
+ +
+ + {/* Content */} +

{value.title}

+

+ {value.description} +

+
+ + ))} +
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Home/faq.tsx b/packages/ui/components/Layouts/Home/faq.tsx new file mode 100644 index 0000000..11d87f2 --- /dev/null +++ b/packages/ui/components/Layouts/Home/faq.tsx @@ -0,0 +1,180 @@ +"use client" + +import { useState, useMemo } from "react" +import { ChevronDown, Search, HelpCircle, MessageCircle } from "lucide-react" +import { Button } from "@/packages/ui/components/ui/button" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +export function FAQ() { + const t = useTranslations() + const [openIndex, setOpenIndex] = useState(0) + const [query, setQuery] = useState("") + + const faqs = [ + { + question: t("faq.items.provision.question"), + answer: t("faq.items.provision.answer"), + }, + { + question: t("faq.items.ddos.question"), + answer: t("faq.items.ddos.answer"), + }, + { + question: t("faq.items.locations.question"), + answer: t("faq.items.locations.answer"), + }, + { + question: t("faq.items.mods.question"), + answer: t("faq.items.mods.answer"), + }, + { + question: t("faq.items.uptime.question"), + answer: t("faq.items.uptime.answer"), + }, + { + question: t("faq.items.trial.question"), + answer: t("faq.items.trial.answer"), + link: { + href: "https://billing.nodebyte.host/store/free-trial", + label: t("faq.items.trial.trialLink"), + }, + }, + ] + + const filtered = useMemo(() => { + if (!query.trim()) return faqs + const q = query.toLowerCase() + return faqs.filter((f) => f.question.toLowerCase().includes(q) || f.answer.toLowerCase().includes(q)) + }, [query, faqs]) + + return ( +
+ {/* Background */} +
+ +
+ {/* Header */} +
+
+ + {t("faq.badge")} +
+

+ {t("faq.title")}{" "} + + {t("faq.titleHighlight")} + +

+

+ {t("faq.description")} +

+
+ +
+ {/* Search */} +
+
+ + setQuery(e.target.value)} + className={cn( + "w-full pl-12 pr-4 py-3 rounded-xl border border-border/50 bg-card/30", + "text-foreground placeholder:text-muted-foreground", + "focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30", + "transition-all duration-200" + )} + /> +
+
+ + {/* FAQ Items */} +
+ {filtered.length === 0 && ( +
+ {t("faq.noResults")} +
+ )} + + {filtered.map((faq, index) => { + const isOpen = openIndex === index + return ( +
+ +
+
+ {faq.answer} + {faq.link && ( + + )} +
+
+
+ ) + })} +
+ + {/* CTA */} +
+ +

{t("faq.stillHaveQuestions")}

+

+ {t("faq.cantFind")} +

+
+ + +
+
+
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Home/features.tsx b/packages/ui/components/Layouts/Home/features.tsx new file mode 100644 index 0000000..1d2d663 --- /dev/null +++ b/packages/ui/components/Layouts/Home/features.tsx @@ -0,0 +1,161 @@ +"use client" + +import React from "react" +import { Shield, Zap, Globe, Lock, Eye, Server, ArrowRight, CheckCircle2 } from "lucide-react" +import { Card } from "@/packages/ui/components/ui/card" +import { Button } from "@/packages/ui/components/ui/button" +import { Price } from "@/packages/ui/components/ui/price" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +export function Features() { + const t = useTranslations() + + const features = [ + { + icon: Shield, + title: t("features.items.ddos.title"), + description: t("features.items.ddos.description"), + highlights: [ + t("features.items.ddos.highlights.0"), + t("features.items.ddos.highlights.1"), + t("features.items.ddos.highlights.2"), + ], + }, + { + icon: Zap, + title: t("features.items.latency.title"), + description: t("features.items.latency.description"), + highlights: [ + t("features.items.latency.highlights.0"), + t("features.items.latency.highlights.1"), + t("features.items.latency.highlights.2"), + ], + }, + { + icon: Server, + title: t("features.items.setup.title"), + description: t("features.items.setup.description"), + highlights: [ + t("features.items.setup.highlights.0"), + t("features.items.setup.highlights.1"), + t("features.items.setup.highlights.2"), + ], + }, + { + icon: Globe, + title: t("features.items.locations.title"), + description: t("features.items.locations.description"), + highlights: [ + t("features.items.locations.highlights.0"), + t("features.items.locations.highlights.1"), + t("features.items.locations.highlights.2"), + ], + }, + { + icon: Eye, + title: t("features.items.panel.title"), + description: t("features.items.panel.description"), + highlights: [ + t("features.items.panel.highlights.0"), + t("features.items.panel.highlights.1"), + t("features.items.panel.highlights.2"), + ], + }, + { + icon: Lock, + title: t("features.items.support.title"), + description: t("features.items.support.description"), + highlights: [ + t("features.items.support.highlights.0"), + t("features.items.support.highlights.1"), + t("features.items.support.highlights.2"), + ], + }, + ] + + return ( +
+ {/* Background */} +
+
+ +
+ {/* Section Header */} +
+
+ + {t("features.badge")} +
+

+ {t("features.title")}{" "} + + {t("features.titleHighlight")} + +

+

+ {t("features.description")} +

+
+ + {/* Features Grid */} +
+ {features.map((feature, index) => ( + + {/* Icon */} +
+ +
+ + {/* Content */} +

+ {feature.title} +

+

+ {feature.description} +

+ + {/* Highlights */} +
    + {feature.highlights.map((highlight, i) => ( +
  • + + {highlight} +
  • + ))} +
+ + {/* Hover arrow */} +
+ +
+
+ ))} +
+ + {/* CTA */} +
+
+ + + {t("features.startingFrom")} /{t("pricing.perMonth")} + +
+
+
+
+ ) +} diff --git a/packages/ui/components/Layouts/Home/games.tsx b/packages/ui/components/Layouts/Home/games.tsx new file mode 100644 index 0000000..d9211ce --- /dev/null +++ b/packages/ui/components/Layouts/Home/games.tsx @@ -0,0 +1,181 @@ +"use client" + +import { Button } from "@/packages/ui/components/ui/button" +import { Card } from "@/packages/ui/components/ui/card" +import { Gamepad2, Sparkles, ArrowRight, Check, Clock } from "lucide-react" +import Link from "next/link" +import Image from "next/image" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +export function Download() { + const t = useTranslations() + + const games = [ + { + name: "Minecraft", + slug: "minecraft", + description: t("games.minecraft.description"), + banner: "/minecraft.png", + tag: t("games.minecraft.tag"), + tagColor: "bg-primary text-primary-foreground", + features: [ + t("games.minecraft.features.0"), + t("games.minecraft.features.1"), + t("games.minecraft.features.2"), + t("games.minecraft.features.3"), + ], + href: "/games/minecraft", + cta: t("games.learnMore"), + }, + { + name: "Rust", + slug: "rust", + description: t("games.rust.description"), + banner: "/rust.png", + tag: t("games.rust.tag"), + tagColor: "bg-accent text-accent-foreground", + features: [ + t("games.rust.features.0"), + t("games.rust.features.1"), + t("games.rust.features.2"), + t("games.rust.features.3"), + ], + href: "/games/rust", + cta: t("games.learnMore"), + }, + { + name: "Hytale", + slug: "hytale", + description: t("games.hytale.description"), + banner: "/hytale.png", + tag: t("games.hytale.tag"), + tagColor: "bg-muted text-muted-foreground", + features: [ + t("games.hytale.features.0"), + t("games.hytale.features.1"), + t("games.hytale.features.2"), + t("games.hytale.features.3"), + ], + href: "/games/hytale", + cta: t("games.learnMore"), + gradient: "from-violet-500 to-purple-600", + comingSoon: true, + }, + ] + + return ( +
+ {/* Background */} +
+ +
+ {/* Header */} +
+
+ + {t("games.badge")} +
+

+ {t("games.title")}{" "} + + {t("games.titleHighlight")} + +

+

+ {t("games.description")} +

+
+ + {/* Games Grid */} +
+ {games.map((game, index) => ( + + {/* Banner/Header */} +
+ {game.banner ? ( + {game.name} + ) : ( +
+ {game.icon && } +
+ )} + + {/* Tag */} +
+ {game.tag} +
+
+ +
+ {/* Title */} +

{game.name}

+

+ {game.description} +

+ + {/* Features */} +
    + {game.features.map((feature, i) => ( +
  • + {game.comingSoon ? ( + + ) : ( + + )} + {feature} +
  • + ))} +
+ + {/* CTA */} + +
+
+ ))} +
+ + {/* Bottom CTA */} +
+

+ {t("games.customSolution")}{" "} + + {t("games.contactUs")} + {" "} + {t("games.forCustom")} +

+
+
+
+ ) +} diff --git a/components/Layouts/Home/hero-graphic.tsx b/packages/ui/components/Layouts/Home/hero-graphic.tsx similarity index 98% rename from components/Layouts/Home/hero-graphic.tsx rename to packages/ui/components/Layouts/Home/hero-graphic.tsx index 6456799..9d6cd82 100644 --- a/components/Layouts/Home/hero-graphic.tsx +++ b/packages/ui/components/Layouts/Home/hero-graphic.tsx @@ -90,7 +90,7 @@ export default function HeroGraphic() { {/* center label */} NodeByte - Global POP Network + UK POP Network diff --git a/packages/ui/components/Layouts/Home/hero.tsx b/packages/ui/components/Layouts/Home/hero.tsx new file mode 100644 index 0000000..8c63052 --- /dev/null +++ b/packages/ui/components/Layouts/Home/hero.tsx @@ -0,0 +1,216 @@ +"use client" + +import React, { useEffect, useState } from "react" +import { Button } from "@/packages/ui/components/ui/button" +import { ArrowRight, Shield, Zap, Globe, PartyPopper, Sparkles, Play } from "lucide-react" +import Link from "next/link" +import HeroGraphic from "./hero-graphic" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +export function Hero() { + const t = useTranslations() + const [uptime, setUptime] = useState(0) + const [ping, setPing] = useState(120) + + useEffect(() => { + // Animate uptime to 99.6 + let start: number | null = null + const duration = 1200 + const from = 95 + const to = 99.6 + function step(ts: number) { + if (!start) start = ts + const t = Math.min(1, (ts - start) / duration) + // Ease out cubic for smooth deceleration + const eased = 1 - Math.pow(1 - t, 3) + const v = from + (to - from) * eased + setUptime(Number(v.toFixed(1))) + if (t < 1) requestAnimationFrame(step) + } + requestAnimationFrame(step) + + // Animate ping down to ~50ms + let pstart: number | null = null + const pduration = 1000 + const pfrom = 120 + const pto = 50 + function pstep(ts: number) { + if (!pstart) pstart = ts + const t = Math.min(1, (ts - pstart) / pduration) + const eased = 1 - Math.pow(1 - t, 3) + const v = Math.round(pfrom + (pto - pfrom) * eased) + setPing(v) + if (t < 1) requestAnimationFrame(pstep) + } + requestAnimationFrame(pstep) + }, []) + + const stats = [ + { + icon: Shield, + value: `${uptime}%`, + label: t("hero.stats.uptime"), + description: t("hero.stats.uptimeDesc"), + color: "primary", + }, + { + icon: Zap, + value: `≈${ping}ms`, + label: t("hero.stats.ping"), + description: t("hero.stats.pingDesc"), + color: "accent", + }, + { + icon: Globe, + value: "24/7", + label: t("hero.stats.support"), + description: t("hero.stats.supportDesc"), + color: "primary", + }, + ] + + return ( +
+ {/* Background gradients */} +
+ + {/* Animated background orbs */} +
+
+
+
+
+ + {/* Grid pattern overlay */} +
+ +
+
+ {/* Left Content */} +
+ {/* Promo Badge */} +
+ + + {t("hero.promo.prefix")} WELCOME10 {t("hero.promo.suffix")} + +
+ + {/* Main Heading */} +
+

+ + {t("hero.title")} + +
+ + {t("hero.titleHighlight")} + +

+

+ {t("hero.description")} +

+
+ + {/* CTA Buttons */} +
+ + +
+ + {/* Stats Grid */} +
+ {stats.map((stat, index) => ( +
+ {/* Icon */} +
+ +
+ + {/* Value */} +
+ {stat.value} +
+ + {/* Label */} +
+ {stat.label} +
+ + {/* Description */} +

+ {stat.description} +

+
+ ))} +
+
+ + {/* Right Content - Hero Graphic */} +
+
+ {/* Glow effect behind graphic */} +
+ + {/* Main graphic container */} +
+ +
+
+
+
+ + {/* Trusted By / Social Proof */} +
+
+ {t("hero.trustedBy")} +
+
+ {[1, 2, 3, 4, 5].map((i) => ( + + + + ))} +
+ 4.8/5 + + {t("hero.trustpilotReviews")} + +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/packages/ui/components/Static/footer.tsx b/packages/ui/components/Static/footer.tsx new file mode 100644 index 0000000..8507f2b --- /dev/null +++ b/packages/ui/components/Static/footer.tsx @@ -0,0 +1,482 @@ +"use client" + +import React, { useEffect, useState } from "react" +import { SiDiscord, SiTrustpilot } from "react-icons/si" +import { Github, Twitter, Mail, ExternalLink, Server, FileText, Scale, Headphones, AlertTriangle, Wrench, CheckCircle2 } from "lucide-react" +import { usePathname } from "next/navigation" +import Link from "next/link" +import { cn } from "@/lib/utils" +import { Logo } from "@/packages/ui/components/logo" +import { useTranslations } from "next-intl" + +export function Footer() { + const t = useTranslations() + const pathname = usePathname() + + const scrollToSection = (id: string) => { + if (pathname !== "/") { + window.location.href = `/#${id}` + return + } + + const element = document.getElementById(id) + if (element) { + element.scrollIntoView({ behavior: 'smooth' }) + } + } + + return ( +
+ {/* Subtle gradient overlay */} +
+ +
+ {/* Top Section - Brand & Newsletter */} +
+ {/* Brand */} +
+ +
+ +
+
+
+ NodeByte + Hosting +
+ +

+ {t("footer.tagline")} +

+ + {/* Social Links */} +
+ {[ + { href: "https://twitter.com/NodeByteHosting", icon: Twitter, label: "Twitter" }, + { href: "https://github.com/NodeByteHosting", icon: Github, label: "GitHub" }, + { href: "https://discord.gg/wN58bTzzpW", icon: SiDiscord, label: "Discord" }, + { href: "https://uk.trustpilot.com/review/nodebyte.host", icon: SiTrustpilot, label: "Trustpilot" }, + ].map((social) => ( + + + + ))} +
+
+ + {/* Contact & Trustpilot */} +
+
+

{t("footer.getInTouch")}

+ +
+ +
+
+
{t("footer.emailUs")}
+
info@nodebyte.co.uk
+
+
+ +
+ +
+
+
{t("footer.support")}
+
{t("footer.contactTeam")}
+
+ +
+ +
+ +
+
+
+ + {/* Links Grid */} +
+ {/* Services */} +
+

+ + {t("nav.services")} +

+
    + {[ + { href: "https://billing.nodebyte.host/store/minecraft-server-hosting", label: t("footer.services.minecraftHosting") }, + { href: "https://billing.nodebyte.host/store/rust-hosting", label: t("footer.services.rustHosting") }, + { href: "https://panel.nodebyte.host/", label: t("footer.services.gamePanel") }, + { href: "https://billing.nodebyte.host/login", label: t("footer.services.clientArea") }, + ].map((link) => ( +
  • + + {link.label} + + +
  • + ))} +
+
+ + {/* Resources */} +
+

+ + {t("nav.resources")} +

+
    + {[ + { href: "https://discord.gg/wN58bTzzpW", label: t("footer.resources.discordServer") }, + { href: "https://nodebytestat.us/", label: t("footer.resources.serviceStatus") }, + { href: "/kb", label: t("footer.resources.knowledgeBase") }, + { href: "https://uk.trustpilot.com/review/nodebyte.host", label: t("footer.resources.trustPilot") }, + ].map((link) => ( +
  • + + {link.label} + {link.href.startsWith("http") && ( + + )} + +
  • + ))} +
+
+ + {/* Legal */} +
+

+ + {t("footer.legal")} +

+
    + {[ + { href: "https://nodebyte.co.uk/legal/terms", label: t("footer.legalLinks.terms") }, + { href: "https://nodebyte.co.uk/legal/privacy", label: t("footer.legalLinks.privacy") }, + { href: "https://nodebyte.co.uk/legal/payment-policy", label: t("footer.legalLinks.refund") }, + { href: "https://nodebyte.co.uk/legal", label: t("footer.legalLinks.legalHub") }, + ].map((link) => ( +
  • + + {link.label} + + +
  • + ))} +
+
+ + {/* Company */} +
+

{t("nav.company")}

+ +
+
+ + {/* Bottom Bar */} +
+
+

+ © {new Date().getFullYear()} NodeByte LTD. {t("footer.copyright")} +

+
+ + Crowdin Localization + + + + + Company No. 15432941 +
+
+
+
+
+ ) +} + +function TrustpilotWidget() { + const [rating, setRating] = useState(null) + const [count, setCount] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let mounted = true + ;(async () => { + try { + const res = await fetch('/api/trustpilot') + if (!res.ok) throw new Error(`Status ${res.status}`) + const data = await res.json() + if (!mounted) return + setRating(typeof data.rating === 'number' ? data.rating : null) + setCount(typeof data.reviewCount === 'number' ? data.reviewCount : null) + } catch (err) { + if (!mounted) return + setError(String(err)) + } finally { + if (!mounted) return + setLoading(false) + } + })() + return () => { + mounted = false + } + }, []) + + const displayRating = rating ?? 4.1 + const displayCount = count ?? 5 + + return ( +
+
+ + Trustpilot +
+ +
+
+ {[1, 2, 3, 4, 5].map((i) => { + const filled = Math.round(displayRating) >= i + return ( +
+ + + +
+ ) + })} +
+ + {loading ? '—' : displayRating.toFixed(1)} + + + ({loading ? '...' : `${displayCount} reviews`}) + +
+ + + Read our reviews + + + + {error &&
Failed to load reviews
} +
+ ) +} + +type StatusType = "UP" | "HASISSUES" | "UNDERMAINTENANCE" + +interface StatusData { + status: StatusType + url: string + hasIncidents: boolean + hasMaintenance: boolean + incidents: Array<{ + id: string + name: string + status: string + impact: string + url: string + }> + maintenances: Array<{ + id: string + name: string + status: string + url: string + }> + error?: string +} + +function StatusIndicator() { + const t = useTranslations() + const [status, setStatus] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let mounted = true + ;(async () => { + try { + const res = await fetch("/api/instatus") + if (!res.ok) throw new Error(`Status ${res.status}`) + const data = await res.json() + if (!mounted) return + setStatus(data) + } catch (err) { + if (!mounted) return + // Fallback to UP status on error + setStatus({ + status: "UP", + url: "https://nodebytestat.us", + hasIncidents: false, + hasMaintenance: false, + incidents: [], + maintenances: [], + }) + } finally { + if (!mounted) return + setLoading(false) + } + })() + return () => { + mounted = false + } + }, []) + + const getStatusConfig = (statusType: StatusType) => { + switch (statusType) { + case "UP": + return { + color: "bg-green-500", + textColor: "text-green-500", + label: t("footer.statusLabels.operational"), + icon: CheckCircle2, + } + case "HASISSUES": + return { + color: "bg-yellow-500", + textColor: "text-yellow-500", + label: t("footer.statusLabels.degraded"), + icon: AlertTriangle, + } + case "UNDERMAINTENANCE": + return { + color: "bg-blue-500", + textColor: "text-blue-500", + label: t("footer.statusLabels.maintenance"), + icon: Wrench, + } + default: + return { + color: "bg-green-500", + textColor: "text-green-500", + label: t("footer.statusLabels.operational"), + icon: CheckCircle2, + } + } + } + + if (loading) { + return ( + + + {t("common.loading")} + + ) + } + + const currentStatus = status?.status || "UP" + const config = getStatusConfig(currentStatus) + const StatusIcon = config.icon + + return ( + + + + {config.label} + + + + ) +} \ No newline at end of file diff --git a/packages/ui/components/Static/navigation.tsx b/packages/ui/components/Static/navigation.tsx new file mode 100644 index 0000000..f96e2e5 --- /dev/null +++ b/packages/ui/components/Static/navigation.tsx @@ -0,0 +1,771 @@ +"use client" + +import { useState, useEffect } from "react" +import { useSession } from "next-auth/react" +import { Button } from "@/packages/ui/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/packages/ui/components/ui/dropdown-menu" +import { Server, Gamepad2, Blocks, ExternalLink, MessageCircle, ChevronRight, ChevronDown, Book, Building2, Mail, Users, Sparkles, User, LogIn } from "lucide-react" +import { ThemeToggle } from "@/packages/ui/components/theme-toggle" +import { CurrencySelector } from "@/packages/ui/components/ui/price" +import { LanguageSelector } from "@/packages/ui/components/ui/language-selector" +import { Logo } from "@/packages/ui/components/logo" +import { UserMenu } from "@/packages/auth/components/user-menu" +import Link from "next/link" +import { usePathname } from "next/navigation" +import { cn } from "@/lib/utils" +import { useTranslations } from "next-intl" + +export function Navigation() { + const t = useTranslations() + + const company = [ + { + title: t("company.contact.title"), + href: "/contact", + description: t("company.contact.description"), + icon: Mail, + }, + { + title: t("company.about.title"), + href: "/about", + description: t("company.about.description"), + icon: Users, + }, + ] + + const services = [ + { + title: t("services.minecraft.title"), + href: "/games/minecraft", + description: t("services.minecraft.description"), + icon: Blocks, + }, + { + title: t("services.rust.title"), + href: "/games/rust", + description: t("services.rust.description"), + icon: Gamepad2, + }, + { + title: t("services.hytale.title"), + href: "/games/hytale", + description: t("services.hytale.description"), + icon: Gamepad2, + }, + { + title: t("services.all.title"), + href: "/games", + description: t("services.all.description"), + icon: Server, + }, + ] + + const resources = [ + { + title: t("resources.clientArea.title"), + href: "https://billing.nodebyte.host/login", + description: t("resources.clientArea.description"), + icon: Server, + external: true, + }, + { + title: t("resources.gamePanel.title"), + href: "https://panel.nodebyte.host/", + description: t("resources.gamePanel.description"), + icon: Gamepad2, + external: true, + }, + ] + + const extras = [ + { + title: t("extras.kb.title"), + href: "/kb", + description: t("extras.kb.description"), + icon: Book, + }, + { + title: t("extras.changelog.title"), + href: "/changelog", + description: t("extras.changelog.description"), + icon: Sparkles, + }, + ] + + const [isScrolled, setIsScrolled] = useState(false) + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) + const [companyOpen, setCompanyOpen] = useState(false) + const [servicesOpen, setServicesOpen] = useState(false) + const [resourcesOpen, setResourcesOpen] = useState(false) + const [extrasOpen, setExtrasOpen] = useState(false) + const [mobileCompanyOpen, setMobileCompanyOpen] = useState(false) + const [mobileServicesOpen, setMobileServicesOpen] = useState(false) + const [mobileResourcesOpen, setMobileResourcesOpen] = useState(false) + const [mobileExtrasOpen, setMobileExtrasOpen] = useState(false) + const pathname = usePathname() + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 20) + } + window.addEventListener("scroll", handleScroll, { passive: true }) + return () => window.removeEventListener("scroll", handleScroll) + }, []) + + useEffect(() => { + setIsMobileMenuOpen(false) + setMobileCompanyOpen(false) + setMobileServicesOpen(false) + setMobileResourcesOpen(false) + setMobileExtrasOpen(false) + }, [pathname]) + + // Prevent body scroll when mobile menu is open + useEffect(() => { + if (isMobileMenuOpen) { + document.body.style.overflow = "hidden" + } else { + document.body.style.overflow = "" + } + return () => { + document.body.style.overflow = "" + } + }, [isMobileMenuOpen]) + + // Toggle mobile dropdowns - only one open at a time + const closeMobileDropdowns = () => { + setMobileCompanyOpen(false) + setMobileServicesOpen(false) + setMobileResourcesOpen(false) + setMobileExtrasOpen(false) + } + + const toggleMobileCompany = () => { + const newState = !mobileCompanyOpen + closeMobileDropdowns() + setMobileCompanyOpen(newState) + } + + const toggleMobileServices = () => { + const newState = !mobileServicesOpen + closeMobileDropdowns() + setMobileServicesOpen(newState) + } + + const toggleMobileResources = () => { + const newState = !mobileResourcesOpen + closeMobileDropdowns() + setMobileResourcesOpen(newState) + } + + const toggleMobileExtras = () => { + const newState = !mobileExtrasOpen + closeMobileDropdowns() + setMobileExtrasOpen(newState) + } + + return ( + <> + + + {/* Mobile Menu Overlay */} +
setIsMobileMenuOpen(false)} + /> + + {/* Mobile Menu Panel */} +
+
+
+ {/* Company Dropdown */} +
+ +
+
+ {company.map((item) => ( + setIsMobileMenuOpen(false)} + > +
+ +
+
+
{item.title}
+

+ {item.description} +

+
+ + + ))} +
+
+
+ + {/* Services Dropdown */} +
+ +
+
+ {services.map((service) => ( + setIsMobileMenuOpen(false)} + > +
+ +
+
+
{service.title}
+

+ {service.description} +

+
+ + + ))} +
+
+
+ + {/* Resources Dropdown */} +
+ + +
+ + {/* Extras Dropdown */} +
+ +
+
+ {extras.map((item) => ( + setIsMobileMenuOpen(false)} + > +
+ +
+
+
{item.title}
+

+ {item.description} +

+
+ + + ))} +
+
+
+ + {/* Divider */} +
+ + {/* User Account Section */} + setIsMobileMenuOpen(false)} /> + + {/* Divider */} +
+ + {/* Settings Section */} +
+

+ {t("nav.settings")} +

+
+ {t("nav.language")} + +
+
+ {t("nav.currency")} + +
+
+ {t("nav.theme")} + +
+
+ + {/* Discord CTA */} +
+ +
+
+
+
+ + ) +} + +// Mobile user section component +function MobileUserSection({ + translations, + onClose +}: { + translations: { + myAccount: string + viewPanel: string + admin: string + logout: string + signIn: string + register: string + } + onClose: () => void +}) { + const { data: session, status } = useSession() + + if (status === "loading") { + return ( +
+
+
+ ) + } + + if (!session?.user) { + return ( +
+

+ Account +

+
+ + +
+
+ ) + } + + const initials = `${session.user.firstName?.[0] || ""}${session.user.lastName?.[0] || ""}`.toUpperCase() || + session.user.username?.[0]?.toUpperCase() || "U" + + return ( +
+

+ Account +

+
+
+
+
+ {initials} +
+ {session.user.isAdmin && ( + + + + )} +
+
+

+ {session.user.firstName} {session.user.lastName} +

+

+ {session.user.email} +

+
+
+
+ + +
+
+
+ ) +} diff --git a/packages/ui/components/layout-chrome.tsx b/packages/ui/components/layout-chrome.tsx new file mode 100644 index 0000000..3097491 --- /dev/null +++ b/packages/ui/components/layout-chrome.tsx @@ -0,0 +1,31 @@ +"use client" + +import { usePathname } from "next/navigation" +import { Navigation } from "@/packages/ui/components/Static/navigation" +import { Footer } from "@/packages/ui/components/Static/footer" + +interface LayoutChromeProps { + children: React.ReactNode +} + +/** + * Client component that handles showing/hiding the navigation and footer + * based on the current route. This needs to be a client component to properly + * react to client-side navigation changes. + */ +export function LayoutChrome({ children }: LayoutChromeProps) { + const pathname = usePathname() + + // Hide navigation and footer on admin and dashboard routes + const hideChrome = pathname.startsWith("/admin") || pathname.startsWith("/dashboard") + + return ( + <> + {!hideChrome && } +
+ {children} +
+ {!hideChrome &&