From 61054537c51ffe3147444b96290a021eeae5871e Mon Sep 17 00:00:00 2001 From: Foufure WaveBook Date: Thu, 16 Jul 2026 17:37:25 +0200 Subject: [PATCH] first commit --- .gitignore | 9 + README.md | 48 + client/index.html | 18 + client/package.json | 24 + client/src/App.tsx | 30 + client/src/api/client.ts | 54 + client/src/components/CharacterPanel.tsx | 150 + client/src/components/DungeonView.tsx | 111 + client/src/components/InventoryPanel.tsx | 87 + client/src/components/MapView.tsx | 72 + client/src/main.tsx | 16 + client/src/pages/Game.tsx | 175 + client/src/pages/Login.tsx | 64 + client/src/pages/Register.tsx | 103 + client/src/sockets/gameSocket.ts | 31 + client/src/state/AuthContext.tsx | 100 + client/src/styles.css | 773 ++++ client/src/types.ts | 94 + client/src/vite-env.d.ts | 1 + client/tsconfig.json | 21 + client/vite.config.ts | 19 + docker-compose.yml | 15 + package-lock.json | 4470 ++++++++++++++++++++++ package.json | 20 + server/.env.example | 5 + server/package.json | 37 + server/prisma/schema.prisma | 102 + server/prisma/seed.ts | 92 + server/src/index.ts | 43 + server/src/lib/config.ts | 22 + server/src/lib/prisma.ts | 13 + server/src/middleware/auth.ts | 50 + server/src/routes/auth.ts | 161 + server/src/routes/character.ts | 74 + server/src/routes/inventory.ts | 129 + server/src/services/army.ts | 35 + server/src/services/combat.ts | 348 ++ server/src/services/progression.ts | 121 + server/src/sockets/game.ts | 459 +++ server/tsconfig.json | 15 + 40 files changed, 8211 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client/index.html create mode 100644 client/package.json create mode 100644 client/src/App.tsx create mode 100644 client/src/api/client.ts create mode 100644 client/src/components/CharacterPanel.tsx create mode 100644 client/src/components/DungeonView.tsx create mode 100644 client/src/components/InventoryPanel.tsx create mode 100644 client/src/components/MapView.tsx create mode 100644 client/src/main.tsx create mode 100644 client/src/pages/Game.tsx create mode 100644 client/src/pages/Login.tsx create mode 100644 client/src/pages/Register.tsx create mode 100644 client/src/sockets/gameSocket.ts create mode 100644 client/src/state/AuthContext.tsx create mode 100644 client/src/styles.css create mode 100644 client/src/types.ts create mode 100644 client/src/vite-env.d.ts create mode 100644 client/tsconfig.json create mode 100644 client/vite.config.ts create mode 100644 docker-compose.yml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server/.env.example create mode 100644 server/package.json create mode 100644 server/prisma/schema.prisma create mode 100644 server/prisma/seed.ts create mode 100644 server/src/index.ts create mode 100644 server/src/lib/config.ts create mode 100644 server/src/lib/prisma.ts create mode 100644 server/src/middleware/auth.ts create mode 100644 server/src/routes/auth.ts create mode 100644 server/src/routes/character.ts create mode 100644 server/src/routes/inventory.ts create mode 100644 server/src/services/army.ts create mode 100644 server/src/services/combat.ts create mode 100644 server/src/services/progression.ts create mode 100644 server/src/sockets/game.ts create mode 100644 server/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08cd04b --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +build/ +.env +.env.local +*.log +.DS_Store +client/dist/ +server/dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c2fe7a --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Soulless Necromancer + +Web multiplayer necromancer game: build a zombie or shadow army, roam a shared map, and delve into click-combat crypts. + +## Stack + +- **Client:** React + Vite + TypeScript +- **Server:** Node.js + Express + Socket.io + TypeScript +- **Database:** PostgreSQL + Prisma + +## Prerequisites + +- Node.js 20+ +- Docker (for PostgreSQL) + +## Setup + +```bash +# From repo root +npm install +npm run db:setup # starts Postgres on :5433, pushes schema, seeds items +npm run dev # API :3001 + Vite :5173 +``` + +Open [http://localhost:5173](http://localhost:5173). + +Postgres runs on **port 5433** by default (see `server/.env`) to avoid clashing with a local Postgres on 5432. + +## Play loop + +1. **Register** with email, password, character name, and specialty (`ZOMBIE` or `SHADOW`). +2. **World map** — click tiles to move (max 3 Manhattan steps). Other online players appear live. +3. **Portal** — stand next to the pulsing portal tile and click it to enter your personal dungeon instance. +4. **Dungeon** — click empty tiles to **move** (Move mode) or switch to **Summon** to place units. Summons walk toward enemies when nothing is in range; combat is automatic in range. Clear the floor for XP, loot, and floor progression. +5. **Progression** — spend stat points on level-up; equip relics from the Relics panel. Outside dungeons, HP and energy regenerate slowly while you are online on the world map. + +## Useful scripts + +| Script | Description | +|--------|-------------| +| `npm run db:up` | Start Postgres container | +| `npm run db:migrate` | Apply Prisma migrations | +| `npm run db:seed` | Seed item catalogue | +| `npm run dev` | Run client + server | + +## Env + +Server env lives in `server/.env` (see defaults for local Docker Postgres). diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..84fee1d --- /dev/null +++ b/client/index.html @@ -0,0 +1,18 @@ + + + + + + Soulless Necromancer + + + + + +
+ + + diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..aa70344 --- /dev/null +++ b/client/package.json @@ -0,0 +1,24 @@ +{ + "name": "client", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.4.0", + "socket.io-client": "^4.8.1" + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.8.2", + "vite": "^6.2.2" + } +} diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..4fbdc05 --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,30 @@ +import { Navigate, Route, Routes } from "react-router-dom"; +import { useAuth } from "./state/AuthContext"; +import Login from "./pages/Login"; +import Register from "./pages/Register"; +import Game from "./pages/Game"; + +function Protected({ children }: { children: React.ReactNode }) { + const { user, loading } = useAuth(); + if (loading) return
Awakening the void...
; + if (!user) return ; + return children; +} + +export default function App() { + return ( + + } /> + } /> + + + + } + /> + } /> + + ); +} diff --git a/client/src/api/client.ts b/client/src/api/client.ts new file mode 100644 index 0000000..178f5b3 --- /dev/null +++ b/client/src/api/client.ts @@ -0,0 +1,54 @@ +import type { Character } from "../types"; + +async function request(path: string, options: RequestInit = {}): Promise { + const res = await fetch(path, { + ...options, + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers ?? {}), + }, + }); + + const data = await res.json().catch(() => ({})); + if (!res.ok) { + throw new Error(data.error ?? "Request failed"); + } + return data as T; +} + +export const api = { + me: () => + request<{ user: { id: string; email: string }; character: Character }>("/api/auth/me"), + login: (body: { email: string; password: string }) => + request<{ user: { id: string; email: string }; character: Character }>("/api/auth/login", { + method: "POST", + body: JSON.stringify(body), + }), + register: (body: { + email: string; + password: string; + characterName: string; + specialty: "ZOMBIE" | "SHADOW"; + }) => + request<{ user: { id: string; email: string }; character: Character }>("/api/auth/register", { + method: "POST", + body: JSON.stringify(body), + }), + logout: () => request<{ ok: boolean }>("/api/auth/logout", { method: "POST" }), + allocateStats: (body: { str?: number; dex?: number; int?: number }) => + request<{ character: Character }>("/api/character/stats", { + method: "POST", + body: JSON.stringify(body), + }), + equip: (inventoryItemId: string, equipped: boolean) => + request<{ character: Character }>("/api/inventory/equip", { + method: "POST", + body: JSON.stringify({ inventoryItemId, equipped }), + }), + useItem: (inventoryItemId: string) => + request<{ character: Character }>("/api/inventory/use", { + method: "POST", + body: JSON.stringify({ inventoryItemId }), + }), +}; diff --git a/client/src/components/CharacterPanel.tsx b/client/src/components/CharacterPanel.tsx new file mode 100644 index 0000000..ca30e67 --- /dev/null +++ b/client/src/components/CharacterPanel.tsx @@ -0,0 +1,150 @@ +import { useState } from "react"; +import { api } from "../api/client"; +import type { Character } from "../types"; + +type Props = { + character: Character; + onUpdate: (c: Character) => void; +}; + +const STAT_INFO = { + str: { + label: "STR", + description: + "Strength. Raises melee strike power in the crypt. Each point spent also increases max HP.", + }, + dex: { + label: "DEX", + description: + "Dexterity. Favors Shadow vessels — improves agility and finesse of your legion.", + }, + int: { + label: "INT", + description: + "Intellect. Empowers dark magic and summons. Each point spent also raises max energy.", + }, +} as const; + +export default function CharacterPanel({ character, onUpdate }: Props) { + const [pending, setPending] = useState({ str: 0, dex: 0, int: 0 }); + const [busy, setBusy] = useState(false); + const spent = pending.str + pending.dex + pending.int; + const remaining = character.unspentStatPoints - spent; + + function bump(stat: "str" | "dex" | "int", delta: number) { + setPending((p) => { + const next = Math.max(0, p[stat] + delta); + const total = Object.values({ ...p, [stat]: next }).reduce((a, b) => a + b, 0); + if (total > character.unspentStatPoints) return p; + return { ...p, [stat]: next }; + }); + } + + async function apply() { + if (spent <= 0) return; + setBusy(true); + try { + const { character: updated } = await api.allocateStats(pending); + onUpdate(updated); + setPending({ str: 0, dex: 0, int: 0 }); + } catch (e) { + alert(e instanceof Error ? e.message : "Failed"); + } finally { + setBusy(false); + } + } + + const armyTotal = character.army.reduce((s, u) => s + u.count, 0); + + return ( +
+
+

{character.name}

+

+ Lv {character.level} · {character.specialty} Legion +

+
+ +
+
+ HP +
+ +
+ + {character.hp}/{character.maxHp} + +
+
+ Energy +
+ +
+ + {character.energy}/{character.maxEnergy} + +
+
+ XP +
+ +
+ + {character.xp}/{character.xpToNext} + +
+
+ +
+ {(["str", "dex", "int"] as const).map((stat) => ( +
+ + {STAT_INFO[stat].label} + + {STAT_INFO[stat].description} + + + + {character[stat]} + {pending[stat] > 0 ? ` +${pending[stat]}` : ""} + → {character.effective[stat] + pending[stat]} + +
+ + +
+
+ ))} +
+ +

+ Unspent points: {remaining} +

+ + +
+

Army ({armyTotal})

+ {character.army.length === 0 ? ( +

Summon units in the dungeon to grow your legion.

+ ) : ( +
    + {character.army.map((u) => ( +
  • + {u.type} T{u.tier} × {u.count} +
  • + ))} +
+ )} +
+
+ ); +} diff --git a/client/src/components/DungeonView.tsx b/client/src/components/DungeonView.tsx new file mode 100644 index 0000000..b458ef3 --- /dev/null +++ b/client/src/components/DungeonView.tsx @@ -0,0 +1,111 @@ +import { useState } from "react"; +import type { Character, DungeonState } from "../types"; + +type Props = { + state: DungeonState; + character: Character; + onMove: (x: number, y: number) => void; + onSummon: (x: number, y: number) => void; + onExit: () => void; + flash?: string | null; +}; + +export default function DungeonView({ + state, + character, + onMove, + onSummon, + onExit, + flash, +}: Props) { + const [mode, setMode] = useState<"move" | "summon">("move"); + + const cells = []; + for (let y = 0; y < state.height; y++) { + for (let x = 0; x < state.width; x++) { + const enemy = state.enemies.find((e) => e.x === x && e.y === y && e.hp > 0); + const summon = state.summons.find((s) => s.x === x && s.y === y); + const isPlayer = state.playerX === x && state.playerY === y; + + cells.push( + + ); + } + } + + return ( +
+
+
+ Crypt Floor {state.floor} + — move freely; summons hunt enemies; attacks auto in range +
+
+ + + +
+
+
+ {cells} +
+ +
+ ); +} diff --git a/client/src/components/InventoryPanel.tsx b/client/src/components/InventoryPanel.tsx new file mode 100644 index 0000000..284b498 --- /dev/null +++ b/client/src/components/InventoryPanel.tsx @@ -0,0 +1,87 @@ +import { useState } from "react"; +import { api } from "../api/client"; +import type { Character } from "../types"; + +type Props = { + character: Character; + onUpdate: (c: Character) => void; +}; + +export default function InventoryPanel({ character, onUpdate }: Props) { + const [busy, setBusy] = useState(null); + + async function toggleEquip(id: string, equipped: boolean) { + setBusy(id); + try { + const { character: updated } = await api.equip(id, !equipped); + onUpdate(updated); + } catch (e) { + alert(e instanceof Error ? e.message : "Failed"); + } finally { + setBusy(null); + } + } + + async function useItem(id: string) { + setBusy(id); + try { + const { character: updated } = await api.useItem(id); + onUpdate(updated); + } catch (e) { + alert(e instanceof Error ? e.message : "Failed"); + } finally { + setBusy(null); + } + } + + return ( +
+
+

Relics

+

{character.inventory.length} carried

+
+ {character.inventory.length === 0 ? ( +

Clear dungeon floors to loot items.

+ ) : ( +
    + {character.inventory.map((inv) => ( +
  • +
    + {inv.item.name} + + {inv.item.slot} + {inv.quantity > 1 ? ` ×${inv.quantity}` : ""} + {inv.equipped ? " · equipped" : ""} + +

    {inv.item.description}

    + + +{inv.item.strBonus} STR · +{inv.item.dexBonus} DEX · +{inv.item.intBonus} INT · + + {inv.item.hpBonus} HP + +
    +
    + {inv.item.slot === "CONSUMABLE" ? ( + + ) : ( + + )} +
    +
  • + ))} +
+ )} +
+ ); +} diff --git a/client/src/components/MapView.tsx b/client/src/components/MapView.tsx new file mode 100644 index 0000000..9b09f6a --- /dev/null +++ b/client/src/components/MapView.tsx @@ -0,0 +1,72 @@ +import type { WorldPlayer } from "../types"; + +type Props = { + width: number; + height: number; + players: WorldPlayer[]; + portal: { x: number; y: number }; + selfId: string; + onTileClick: (x: number, y: number) => void; + onPortalClick: () => void; +}; + +export default function MapView({ + width, + height, + players, + portal, + selfId, + onTileClick, + onPortalClick, +}: Props) { + const cells = []; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const isPortal = x === portal.x && y === portal.y; + const here = players.filter((p) => p.x === x && p.y === y); + const terrain = + (x + y) % 7 === 0 ? "mire" : (x * 3 + y) % 11 === 0 ? "bone" : "soil"; + + cells.push( + + ); + } + } + + return ( +
+
+ {cells} +
+
+ ); +} diff --git a/client/src/main.tsx b/client/src/main.tsx new file mode 100644 index 0000000..758e31f --- /dev/null +++ b/client/src/main.tsx @@ -0,0 +1,16 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App"; +import { AuthProvider } from "./state/AuthContext"; +import "./styles.css"; + +createRoot(document.getElementById("root")!).render( + + + + + + + +); diff --git a/client/src/pages/Game.tsx b/client/src/pages/Game.tsx new file mode 100644 index 0000000..36b29c2 --- /dev/null +++ b/client/src/pages/Game.tsx @@ -0,0 +1,175 @@ +import { useEffect, useRef, useState } from "react"; +import type { Socket } from "socket.io-client"; +import { useAuth } from "../state/AuthContext"; +import { connectGameSocket } from "../sockets/gameSocket"; +import MapView from "../components/MapView"; +import DungeonView from "../components/DungeonView"; +import CharacterPanel from "../components/CharacterPanel"; +import InventoryPanel from "../components/InventoryPanel"; +import type { DungeonState, WorldPlayer } from "../types"; + +export default function Game() { + const { character, setCharacter, logout, user } = useAuth(); + const socketRef = useRef(null); + const [players, setPlayers] = useState([]); + const [portal, setPortal] = useState({ x: 20, y: 15 }); + const [mapSize, setMapSize] = useState({ width: 40, height: 30 }); + const [dungeon, setDungeon] = useState(null); + const [zone, setZone] = useState<"WORLD" | "DUNGEON">( + character?.currentZone ?? "WORLD" + ); + const [toast, setToast] = useState(null); + const [flash, setFlash] = useState(null); + const [sidebar, setSidebar] = useState<"char" | "inv">("char"); + + useEffect(() => { + if (!character) return; + + const socket = connectGameSocket({ + onPlayers: (data) => { + setPlayers(data.players); + setPortal(data.portal); + setMapSize(data.map); + }, + onCharacter: (c) => setCharacter(c), + onDungeon: (s) => { + setDungeon(s); + setZone("DUNGEON"); + }, + onZone: (z) => { + setZone(z.zone as "WORLD" | "DUNGEON"); + if (z.zone === "WORLD") setDungeon(null); + }, + onReward: (r) => { + setToast( + `Victory! +${r.xp} XP` + + (r.leveled ? ` · leveled ${r.leveled}×` : "") + + (r.loot ? ` · loot: ${r.loot}` : "") + ); + }, + onError: (msg) => setToast(msg), + }); + + socketRef.current = socket; + setZone(character.currentZone); + + return () => { + socket.disconnect(); + socketRef.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [user?.id]); + + useEffect(() => { + if (!toast) return; + const t = setTimeout(() => setToast(null), 4000); + return () => clearTimeout(t); + }, [toast]); + + // Flash enemy tiles when HP drops (auto combat feedback) + useEffect(() => { + if (!dungeon || zone !== "DUNGEON") return; + const wounded = dungeon.enemies.find((e) => e.hp < e.maxHp && e.hp > 0); + if (!wounded) return; + setFlash(`${wounded.x},${wounded.y}`); + const t = setTimeout(() => setFlash(null), 280); + return () => clearTimeout(t); + }, [dungeon?.log.length, zone]); // eslint-disable-line react-hooks/exhaustive-deps + + if (!character) { + return
Loading vessel...
; + } + + function move(x: number, y: number) { + socketRef.current?.emit("player:move", { x, y }); + } + + function enterPortal() { + socketRef.current?.emit("player:enter_dungeon"); + } + + function dungeonMove(x: number, y: number) { + socketRef.current?.emit("dungeon:move", { x, y }); + } + + function summon(x: number, y: number) { + socketRef.current?.emit("dungeon:summon", { x, y }); + } + + function exitDungeon() { + socketRef.current?.emit("player:exit_dungeon"); + } + + return ( +
+
+
+ Soulless Necromancer + + {character.name} · {zone === "WORLD" ? "Waking Map" : `Crypt ${dungeon?.floor ?? ""}`} + +
+
+ + + +
+
+ +
+
+ {zone === "DUNGEON" && dungeon ? ( + + ) : ( + <> +

+ Click any tile to move. Reach the pulsing portal (stand next to it) to enter the + crypt. +

+ + + )} +
+ + +
+ + {toast &&
{toast}
} +
+ ); +} diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx new file mode 100644 index 0000000..97365f7 --- /dev/null +++ b/client/src/pages/Login.tsx @@ -0,0 +1,64 @@ +import { useState, type FormEvent } from "react"; +import { Link, Navigate } from "react-router-dom"; +import { useAuth } from "../state/AuthContext"; + +export default function Login() { + const { login, user, loading } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [busy, setBusy] = useState(false); + + if (!loading && user) return ; + + async function onSubmit(e: FormEvent) { + e.preventDefault(); + setError(""); + setBusy(true); + try { + await login(email, password); + } catch (err) { + setError(err instanceof Error ? err.message : "Login failed"); + } finally { + setBusy(false); + } + } + + return ( +
+
+
+

Soulless Necromancer

+

Return to the wake

+

Sign in to reclaim your army.

+ {error &&

{error}

} + + + +

+ No vessel yet? Register +

+
+
+ ); +} diff --git a/client/src/pages/Register.tsx b/client/src/pages/Register.tsx new file mode 100644 index 0000000..5e3dae3 --- /dev/null +++ b/client/src/pages/Register.tsx @@ -0,0 +1,103 @@ +import { useState, type FormEvent } from "react"; +import { Link, Navigate } from "react-router-dom"; +import { useAuth } from "../state/AuthContext"; +import type { Specialty } from "../types"; + +export default function Register() { + const { register, user, loading } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [characterName, setCharacterName] = useState(""); + const [specialty, setSpecialty] = useState("ZOMBIE"); + const [error, setError] = useState(""); + const [busy, setBusy] = useState(false); + + if (!loading && user) return ; + + async function onSubmit(e: FormEvent) { + e.preventDefault(); + setError(""); + setBusy(true); + try { + await register({ email, password, characterName, specialty }); + } catch (err) { + setError(err instanceof Error ? err.message : "Register failed"); + } finally { + setBusy(false); + } + } + + return ( +
+
+
+

Soulless Necromancer

+

Forge a vessel

+

Choose your legion and step onto the waking map.

+ {error &&

{error}

} + + + +
+ Specialty + + +
+ +

+ Already bound? Login +

+
+
+ ); +} diff --git a/client/src/sockets/gameSocket.ts b/client/src/sockets/gameSocket.ts new file mode 100644 index 0000000..c5026a0 --- /dev/null +++ b/client/src/sockets/gameSocket.ts @@ -0,0 +1,31 @@ +import { io, type Socket } from "socket.io-client"; +import type { Character, DungeonState, WorldPlayer } from "../types"; + +export type GameHandlers = { + onPlayers: (data: { + players: WorldPlayer[]; + portal: { x: number; y: number }; + map: { width: number; height: number }; + }) => void; + onCharacter: (c: Character) => void; + onDungeon: (s: DungeonState) => void; + onZone: (z: { zone: string; won?: boolean }) => void; + onReward: (r: { xp: number; leveled: number; loot: string | null; nextFloor: number }) => void; + onError: (msg: string) => void; +}; + +export function connectGameSocket(handlers: GameHandlers): Socket { + const socket = io("/", { + withCredentials: true, + transports: ["websocket", "polling"], + }); + + socket.on("world:players", handlers.onPlayers); + socket.on("character:update", handlers.onCharacter); + socket.on("dungeon:state", handlers.onDungeon); + socket.on("zone:change", handlers.onZone); + socket.on("dungeon:reward", handlers.onReward); + socket.on("error:message", handlers.onError); + + return socket; +} diff --git a/client/src/state/AuthContext.tsx b/client/src/state/AuthContext.tsx new file mode 100644 index 0000000..6b14509 --- /dev/null +++ b/client/src/state/AuthContext.tsx @@ -0,0 +1,100 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, + type ReactNode, +} from "react"; +import { api } from "../api/client"; +import type { Character } from "../types"; + +type AuthState = { + user: { id: string; email: string } | null; + character: Character | null; + loading: boolean; + setCharacter: (c: Character | null) => void; + login: (email: string, password: string) => Promise; + register: (data: { + email: string; + password: string; + characterName: string; + specialty: "ZOMBIE" | "SHADOW"; + }) => Promise; + logout: () => Promise; + refresh: () => Promise; +}; + +const AuthContext = createContext(null); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState<{ id: string; email: string } | null>(null); + const [character, setCharacter] = useState(null); + const [loading, setLoading] = useState(true); + + const refresh = useCallback(async () => { + try { + const data = await api.me(); + setUser(data.user); + setCharacter(data.character); + } catch { + setUser(null); + setCharacter(null); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + void refresh(); + }, [refresh]); + + const login = useCallback(async (email: string, password: string) => { + const data = await api.login({ email, password }); + setUser(data.user); + setCharacter(data.character); + }, []); + + const register = useCallback( + async (payload: { + email: string; + password: string; + characterName: string; + specialty: "ZOMBIE" | "SHADOW"; + }) => { + const data = await api.register(payload); + setUser(data.user); + setCharacter(data.character); + }, + [] + ); + + const logout = useCallback(async () => { + await api.logout(); + setUser(null); + setCharacter(null); + }, []); + + const value = useMemo( + () => ({ + user, + character, + loading, + setCharacter, + login, + register, + logout, + refresh, + }), + [user, character, loading, login, register, logout, refresh] + ); + + return {children}; +} + +export function useAuth() { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error("useAuth outside provider"); + return ctx; +} diff --git a/client/src/styles.css b/client/src/styles.css new file mode 100644 index 0000000..4b10d53 --- /dev/null +++ b/client/src/styles.css @@ -0,0 +1,773 @@ +:root { + --bg-deep: #0b100e; + --bg-mid: #121a16; + --fog: #1a2820; + --bone: #c9b896; + --bone-dim: #8a7a5c; + --mire: #24352c; + --accent: #6b8f4e; + --accent-bright: #9fc47a; + --blood: #8b3a3a; + --energy: #4a7a6a; + --danger: #c45a4a; + --text: #e6e0d4; + --muted: #9a9284; + --panel: rgba(14, 20, 17, 0.92); + --line: rgba(201, 184, 150, 0.18); + --tile: 18px; + --dtile: 42px; + --font-display: "Cinzel Decorative", serif; + --font-body: "Source Sans 3", sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + margin: 0; + min-height: 100%; + background: var(--bg-deep); + color: var(--text); + font-family: var(--font-body); +} + +button, +input { + font: inherit; +} + +a { + color: var(--accent-bright); +} + +.boot { + min-height: 100vh; + display: grid; + place-items: center; + font-family: var(--font-display); + letter-spacing: 0.04em; + color: var(--bone); +} + +/* Auth */ +.auth-page { + min-height: 100vh; + display: grid; + place-items: center; + position: relative; + padding: 2rem; + background: + radial-gradient(ellipse 80% 60% at 50% 20%, rgba(107, 143, 78, 0.12), transparent 55%), + linear-gradient(180deg, #0e1612 0%, #0b100e 55%, #080c0a 100%); + overflow: hidden; +} + +.auth-mist { + position: absolute; + inset: -20%; + background: + radial-gradient(circle at 20% 40%, rgba(74, 122, 106, 0.15), transparent 40%), + radial-gradient(circle at 80% 60%, rgba(139, 58, 58, 0.1), transparent 35%); + animation: drift 18s ease-in-out infinite alternate; + pointer-events: none; +} + +@keyframes drift { + from { + transform: translate(-2%, -1%) scale(1); + } + to { + transform: translate(2%, 2%) scale(1.05); + } +} + +.auth-panel { + position: relative; + width: min(420px, 100%); + padding: 2rem 1.75rem; + background: var(--panel); + border: 1px solid var(--line); + display: flex; + flex-direction: column; + gap: 0.85rem; + animation: rise 0.7s ease-out; +} + +@keyframes rise { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: none; + } +} + +.brand { + margin: 0; + font-family: var(--font-display); + font-size: 1.35rem; + color: var(--bone); + letter-spacing: 0.03em; +} + +.auth-panel h1 { + margin: 0; + font-size: 1.15rem; + font-weight: 600; + color: var(--text); +} + +.lede, +.muted { + margin: 0; + color: var(--muted); + font-size: 0.95rem; +} + +.auth-panel label { + display: flex; + flex-direction: column; + gap: 0.35rem; + font-size: 0.85rem; + color: var(--muted); +} + +.auth-panel input[type="email"], +.auth-panel input[type="password"], +.auth-panel input[type="text"], +.auth-panel input:not([type]) { + background: #0a0f0c; + border: 1px solid var(--line); + color: var(--text); + padding: 0.65rem 0.75rem; + outline: none; +} + +.auth-panel input:focus { + border-color: var(--accent); +} + +.auth-panel button[type="submit"], +.panel > button, +.inv-actions button, +.stat-btns button, +.top-actions button, +.dungeon-bar button, +.ghost { + cursor: pointer; + border: 1px solid var(--line); + background: linear-gradient(180deg, #1c2a22, #141c18); + color: var(--text); + padding: 0.6rem 0.9rem; +} + +.auth-panel button[type="submit"]:hover, +.panel > button:hover, +.inv-actions button:hover, +.top-actions button:hover { + border-color: var(--accent); + color: var(--accent-bright); +} + +.auth-panel button:disabled, +.panel > button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.error { + margin: 0; + color: var(--danger); + font-size: 0.9rem; +} + +.auth-switch { + margin: 0.25rem 0 0; + font-size: 0.9rem; + color: var(--muted); +} + +.specialty { + border: 1px solid var(--line); + padding: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + margin: 0; +} + +.specialty legend { + padding: 0 0.35rem; + color: var(--bone-dim); +} + +.specialty > label { + flex-direction: row; + align-items: flex-start; + gap: 0.65rem; + padding: 0.55rem; + border: 1px solid transparent; + cursor: pointer; +} + +.specialty > label.selected { + border-color: var(--accent); + background: rgba(107, 143, 78, 0.08); +} + +.specialty input { + margin-top: 0.25rem; +} + +.specialty strong { + display: block; + color: var(--text); +} + +/* Game shell */ +.game-root { + min-height: 100vh; + display: flex; + flex-direction: column; + background: + radial-gradient(ellipse 100% 70% at 50% -10%, rgba(36, 53, 44, 0.55), transparent 50%), + var(--bg-deep); +} + +.topbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--line); + background: rgba(8, 12, 10, 0.85); + backdrop-filter: blur(6px); + z-index: 2; +} + +.brand-inline { + display: flex; + flex-direction: column; + gap: 0.1rem; +} + +.brand-inline span { + font-family: var(--font-display); + font-size: 1.05rem; + color: var(--bone); +} + +.brand-inline small { + color: var(--muted); +} + +.top-actions { + display: flex; + gap: 0.4rem; + flex-wrap: wrap; +} + +.top-actions button.active { + border-color: var(--accent); + color: var(--accent-bright); +} + +.game-layout { + flex: 1; + display: grid; + grid-template-columns: 1fr min(320px, 34vw); + min-height: 0; +} + +.stage { + position: relative; + overflow: auto; + padding: 0.75rem; + background: + linear-gradient(180deg, rgba(11, 16, 14, 0.2), rgba(11, 16, 14, 0.6)), + repeating-linear-gradient( + 0deg, + transparent, + transparent 3px, + rgba(255, 255, 255, 0.01) 3px, + rgba(255, 255, 255, 0.01) 4px + ); +} + +.map-hint { + margin: 0 0 0.5rem; + color: var(--muted); + font-size: 0.9rem; +} + +.map-shell { + width: max-content; + max-width: 100%; + border: 1px solid var(--line); + box-shadow: inset 0 0 80px rgba(0, 0, 0, 0.45); + animation: fade-in 0.5s ease; +} + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.map-grid { + display: grid; + background: #0a0f0c; +} + +.tile { + width: var(--tile); + height: var(--tile); + padding: 0; + border: 0; + position: relative; + cursor: pointer; + background: #162018; +} + +.tile.soil { + background: #15201a; +} + +.tile.mire { + background: #1a2a22; +} + +.tile.bone { + background: #1e2620; +} + +.tile:hover { + outline: 1px solid rgba(159, 196, 122, 0.35); + z-index: 1; +} + +.tile.portal { + background: radial-gradient(circle at center, #2a3f30 0%, #0f1612 70%); + animation: pulse-portal 2.4s ease-in-out infinite; +} + +@keyframes pulse-portal { + 0%, + 100% { + box-shadow: inset 0 0 6px rgba(107, 143, 78, 0.35); + } + 50% { + box-shadow: inset 0 0 14px rgba(159, 196, 122, 0.65); + } +} + +.portal-glyph { + position: absolute; + inset: 2px; + border: 1px solid rgba(159, 196, 122, 0.5); + border-radius: 50%; +} + +.avatar { + position: absolute; + inset: 1px; + display: grid; + place-items: center; + font-size: 9px; + font-weight: 700; + border-radius: 2px; + background: #3a2e24; + color: var(--bone); + z-index: 2; +} + +.avatar.zombie { + background: #2f4a2a; +} + +.avatar.shadow { + background: #1e2c36; +} + +.avatar.self { + outline: 1px solid var(--accent-bright); + animation: bob 1.8s ease-in-out infinite; +} + +@keyframes bob { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-1px); + } +} + +.side { + border-left: 1px solid var(--line); + background: var(--panel); + overflow: auto; + padding: 1rem; +} + +.panel header h2 { + margin: 0; + font-family: var(--font-display); + font-size: 1.1rem; + color: var(--bone); +} + +.panel header p { + margin: 0.2rem 0 1rem; + color: var(--muted); + font-size: 0.85rem; +} + +.bars { + display: flex; + flex-direction: column; + gap: 0.45rem; + margin-bottom: 1rem; +} + +.bar { + display: grid; + grid-template-columns: 54px 1fr 70px; + align-items: center; + gap: 0.4rem; + font-size: 0.78rem; + color: var(--muted); +} + +.bar div { + height: 8px; + background: #0a0f0c; + border: 1px solid var(--line); + overflow: hidden; +} + +.bar i { + display: block; + height: 100%; +} + +.bar i.hp { + background: var(--blood); +} + +.bar i.en { + background: var(--energy); +} + +.bar i.xp { + background: var(--accent); +} + +.bar em { + font-style: normal; + text-align: right; +} + +.stats { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.stat-row { + display: grid; + grid-template-columns: 40px 1fr auto; + align-items: center; + gap: 0.4rem; + font-size: 0.9rem; +} + +.stat-label { + position: relative; + cursor: help; + border-bottom: 1px dotted var(--bone-dim); + color: var(--bone); + width: fit-content; +} + +.stat-tooltip { + position: absolute; + left: 0; + bottom: calc(100% + 8px); + z-index: 10; + width: max-content; + max-width: 220px; + padding: 0.55rem 0.65rem; + background: #0f1612; + border: 1px solid var(--accent); + color: var(--text); + font-size: 0.78rem; + line-height: 1.35; + font-weight: 400; + letter-spacing: 0; + text-transform: none; + opacity: 0; + visibility: hidden; + transform: translateY(4px); + transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s; + pointer-events: none; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45); +} + +.stat-label:hover .stat-tooltip, +.stat-label:focus-visible .stat-tooltip { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.stat-row small { + color: var(--muted); + font-weight: 400; +} + +.stat-btns { + display: flex; + gap: 0.25rem; +} + +.stat-btns button { + width: 28px; + padding: 0.2rem; +} + +.points { + margin: 0.75rem 0; + color: var(--muted); +} + +.army-summary { + margin-top: 1.25rem; + padding-top: 1rem; + border-top: 1px solid var(--line); +} + +.army-summary h3 { + margin: 0 0 0.5rem; + font-size: 0.95rem; + color: var(--bone-dim); +} + +.army-summary ul { + margin: 0; + padding-left: 1.1rem; + color: var(--text); +} + +.inv-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.inv-list li { + display: flex; + justify-content: space-between; + gap: 0.75rem; + padding: 0.75rem; + border: 1px solid var(--line); + background: rgba(10, 15, 12, 0.7); +} + +.inv-list strong { + display: block; + color: var(--bone); +} + +.inv-list span { + color: var(--muted); + font-size: 0.8rem; +} + +.inv-list p { + margin: 0.35rem 0; + font-size: 0.85rem; + color: var(--text); +} + +.inv-list small { + color: var(--muted); +} + +.rarity-uncommon strong { + color: var(--accent-bright); +} + +.rarity-rare strong { + color: #d4a574; +} + +/* Dungeon */ +.dungeon-shell { + display: flex; + flex-direction: column; + gap: 0.75rem; + animation: fade-in 0.4s ease; +} + +.dungeon-bar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + color: var(--muted); + font-size: 0.9rem; + flex-wrap: wrap; +} + +.dungeon-modes { + display: flex; + gap: 0.4rem; + flex-wrap: wrap; +} + +.dungeon-modes button.active { + border-color: var(--accent); + color: var(--accent-bright); +} + +.dungeon-bar strong { + color: var(--bone); +} + +.dungeon-grid { + display: grid; + width: max-content; + border: 1px solid var(--line); + background: #0c1210; +} + +.dtile { + width: var(--dtile); + height: var(--dtile); + border: 1px solid rgba(201, 184, 150, 0.08); + background: #152019; + position: relative; + padding: 0; + cursor: crosshair; +} + +.dtile:hover { + background: #1a2820; +} + +.dtile.enemy:hover { + background: #2a1a1a; +} + +.dtile.hit { + animation: hitflash 0.28s ease; +} + +@keyframes hitflash { + from { + background: #6b3a3a; + } + to { + background: #152019; + } +} + +.d-avatar, +.d-enemy, +.d-summon { + position: absolute; + inset: 4px; + display: grid; + place-items: center; + font-weight: 700; + font-size: 0.85rem; +} + +.d-avatar.self { + background: #2f4a2a; + color: var(--bone); + outline: 1px solid var(--accent-bright); +} + +.d-enemy { + background: #4a2424; + color: #f0d0d0; +} + +.d-enemy i { + position: absolute; + left: 0; + bottom: 0; + height: 3px; + background: var(--danger); +} + +.d-summon.zombie { + background: #355233; + color: #cfe0c4; +} + +.d-summon.shadow { + background: #243640; + color: #c5d6e0; +} + +.combat-log { + max-width: 560px; + padding: 0.75rem; + border: 1px solid var(--line); + background: rgba(10, 15, 12, 0.8); + max-height: 160px; + overflow: auto; +} + +.combat-log p { + margin: 0.2rem 0; + font-size: 0.85rem; + color: var(--muted); +} + +.toast { + position: fixed; + bottom: 1.25rem; + left: 50%; + transform: translateX(-50%); + background: #1a2820; + border: 1px solid var(--accent); + color: var(--text); + padding: 0.75rem 1.2rem; + z-index: 20; + animation: rise 0.35s ease; +} + +@media (max-width: 900px) { + .game-layout { + grid-template-columns: 1fr; + grid-template-rows: minmax(50vh, 1fr) auto; + } + + .side { + border-left: 0; + border-top: 1px solid var(--line); + max-height: 42vh; + } + + :root { + --tile: 14px; + --dtile: 32px; + } +} diff --git a/client/src/types.ts b/client/src/types.ts new file mode 100644 index 0000000..d5854b6 --- /dev/null +++ b/client/src/types.ts @@ -0,0 +1,94 @@ +export type Specialty = "ZOMBIE" | "SHADOW"; +export type Zone = "WORLD" | "DUNGEON"; + +export type ItemData = { + id: string; + key: string; + name: string; + description: string; + slot: string; + strBonus: number; + dexBonus: number; + intBonus: number; + hpBonus: number; + rarity: string; +}; + +export type InventoryEntry = { + id: string; + equipped: boolean; + quantity: number; + item: ItemData; +}; + +export type ArmyUnit = { + id: string; + type: Specialty; + tier: number; + count: number; +}; + +export type Character = { + id: string; + name: string; + specialty: Specialty; + level: number; + xp: number; + xpToNext: number; + unspentStatPoints: number; + hp: number; + maxHp: number; + energy: number; + maxEnergy: number; + str: number; + dex: number; + int: number; + effective: { str: number; dex: number; int: number; maxHp: number; maxEnergy: number }; + mapX: number; + mapY: number; + currentZone: Zone; + dungeonFloor: number; + inventory: InventoryEntry[]; + army: ArmyUnit[]; +}; + +export type WorldPlayer = { + characterId: string; + name: string; + specialty: string; + x: number; + y: number; +}; + +export type Enemy = { + id: string; + name: string; + x: number; + y: number; + hp: number; + maxHp: number; + atk: number; +}; + +export type DungeonState = { + characterId: string; + floor: number; + width: number; + height: number; + playerX: number; + playerY: number; + energy: number; + enemies: Enemy[]; + summons: Array<{ + id: string; + type: string; + x: number; + y: number; + hp: number; + maxHp: number; + atk: number; + }>; + log: string[]; + over: boolean; + won: boolean; +}; diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/client/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..7350c19 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/client/vite.config.ts b/client/vite.config.ts new file mode 100644 index 0000000..f41a0f4 --- /dev/null +++ b/client/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + proxy: { + "/api": { + target: "http://localhost:3001", + changeOrigin: true, + }, + "/socket.io": { + target: "http://localhost:3001", + ws: true, + }, + }, + }, +}); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e9bb148 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: soulless + POSTGRES_PASSWORD: soulless + POSTGRES_DB: soulless_necromancer + ports: + - "5433:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2ae4555 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4470 @@ +{ + "name": "soulless-necromancer", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "soulless-necromancer", + "workspaces": [ + "client", + "server" + ], + "devDependencies": { + "concurrently": "^9.1.2" + } + }, + "client": { + "version": "1.0.0", + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.4.0", + "socket.io-client": "^4.8.1" + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.8.2", + "vite": "^6.2.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@prisma/client": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.21.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz", + "integrity": "sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "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" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/client": { + "resolved": "client", + "link": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/effect": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", + "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "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" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.8.tgz", + "integrity": "sha512-Q9K4Diu6l5u6xJQogeFSs/zKtyMSgFKFtRQV+tHP4kL7KPm2grpBU0dFIwFaXwNxN0MtfKWc43VpCugAa+LPsw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.2.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prisma": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/server": { + "resolved": "server", + "link": true + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.21.0" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "server": { + "version": "1.0.0", + "dependencies": { + "@prisma/client": "^6.5.0", + "bcryptjs": "^3.0.2", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", + "socket.io": "^4.8.1", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cookie-parser": "^1.4.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jsonwebtoken": "^9.0.9", + "@types/node": "^22.13.10", + "prisma": "^6.5.0", + "tsx": "^4.19.3", + "typescript": "^5.8.2" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b61ee8a --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "soulless-necromancer", + "private": true, + "workspaces": [ + "client", + "server" + ], + "scripts": { + "dev": "concurrently -n server,client -c green,cyan \"npm run dev -w server\" \"npm run dev -w client\"", + "build": "npm run build -w server && npm run build -w client", + "db:up": "docker start soulless-postgres 2>/dev/null || docker run -d --name soulless-postgres -e POSTGRES_USER=soulless -e POSTGRES_PASSWORD=soulless -e POSTGRES_DB=soulless_necromancer -p 5433:5432 -v soulless_pgdata:/var/lib/postgresql/data postgres:16-alpine", + "db:down": "docker stop soulless-postgres", + "db:migrate": "npm run db:push -w server", + "db:seed": "npm run db:seed -w server", + "db:setup": "npm run db:up && sleep 4 && npm run db:migrate && npm run db:seed" + }, + "devDependencies": { + "concurrently": "^9.1.2" + } +} diff --git a/server/.env.example b/server/.env.example new file mode 100644 index 0000000..3e9d9c0 --- /dev/null +++ b/server/.env.example @@ -0,0 +1,5 @@ +DATABASE_URL="postgresql://soulless:soulless@localhost:5433/soulless_necromancer?schema=public" +JWT_SECRET="soulless-necromancer-dev-secret-change-in-prod" +PORT=3001 +CLIENT_ORIGIN="http://localhost:5173" +COOKIE_NAME="sn_token" diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..cedda7c --- /dev/null +++ b/server/package.json @@ -0,0 +1,37 @@ +{ + "name": "server", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "db:migrate": "prisma db push && prisma generate", + "db:push": "prisma db push && prisma generate", + "db:seed": "tsx prisma/seed.ts", + "db:studio": "prisma studio" + }, + "dependencies": { + "@prisma/client": "^6.5.0", + "bcryptjs": "^3.0.2", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", + "socket.io": "^4.8.1", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cookie-parser": "^1.4.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jsonwebtoken": "^9.0.9", + "@types/node": "^22.13.10", + "prisma": "^6.5.0", + "tsx": "^4.19.3", + "typescript": "^5.8.2" + } +} diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma new file mode 100644 index 0000000..a602095 --- /dev/null +++ b/server/prisma/schema.prisma @@ -0,0 +1,102 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +enum Specialty { + ZOMBIE + SHADOW +} + +enum Zone { + WORLD + DUNGEON +} + +enum ItemSlot { + WEAPON + ARMOR + ACCESSORY + CONSUMABLE +} + +enum ArmyUnitType { + ZOMBIE + SHADOW +} + +model User { + id String @id @default(cuid()) + email String @unique + passwordHash String + createdAt DateTime @default(now()) + character Character? +} + +model Character { + id String @id @default(cuid()) + userId String @unique + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + name String + specialty Specialty + level Int @default(1) + xp Int @default(0) + unspentStatPoints Int @default(0) + hp Int @default(100) + maxHp Int @default(100) + energy Int @default(50) + maxEnergy Int @default(50) + str Int @default(5) + dex Int @default(5) + int Int @default(5) + mapX Int @default(5) + mapY Int @default(5) + currentZone Zone @default(WORLD) + dungeonFloor Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + inventory InventoryItem[] + army ArmyUnit[] +} + +model Item { + id String @id @default(cuid()) + key String @unique + name String + description String + slot ItemSlot + strBonus Int @default(0) + dexBonus Int @default(0) + intBonus Int @default(0) + hpBonus Int @default(0) + rarity String @default("common") + inventory InventoryItem[] +} + +model InventoryItem { + id String @id @default(cuid()) + characterId String + character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) + itemId String + item Item @relation(fields: [itemId], references: [id]) + equipped Boolean @default(false) + quantity Int @default(1) + + @@index([characterId]) +} + +model ArmyUnit { + id String @id @default(cuid()) + characterId String + character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) + type ArmyUnitType + tier Int @default(1) + count Int @default(1) + + @@unique([characterId, type, tier]) + @@index([characterId]) +} diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts new file mode 100644 index 0000000..8c1f20f --- /dev/null +++ b/server/prisma/seed.ts @@ -0,0 +1,92 @@ +import { PrismaClient, ItemSlot } from "@prisma/client"; + +const prisma = new PrismaClient(); + +const items = [ + { + key: "bone_dagger", + name: "Bone Dagger", + description: "A crude blade carved from a fallen warrior.", + slot: ItemSlot.WEAPON, + strBonus: 2, + dexBonus: 1, + intBonus: 0, + hpBonus: 0, + rarity: "common", + }, + { + key: "shadow_veil", + name: "Shadow Veil", + description: "Cloth woven from twilight itself.", + slot: ItemSlot.ARMOR, + strBonus: 0, + dexBonus: 2, + intBonus: 1, + hpBonus: 10, + rarity: "common", + }, + { + key: "grimoire_fragment", + name: "Grimoire Fragment", + description: "Pages torn from a forbidden tome.", + slot: ItemSlot.ACCESSORY, + strBonus: 0, + dexBonus: 0, + intBonus: 3, + hpBonus: 0, + rarity: "uncommon", + }, + { + key: "necrotic_blade", + name: "Necrotic Blade", + description: "Steel that drinks life force.", + slot: ItemSlot.WEAPON, + strBonus: 4, + dexBonus: 0, + intBonus: 2, + hpBonus: 0, + rarity: "rare", + }, + { + key: "ossuary_plate", + name: "Ossuary Plate", + description: "Armor fashioned from countless bones.", + slot: ItemSlot.ARMOR, + strBonus: 2, + dexBonus: 0, + intBonus: 0, + hpBonus: 25, + rarity: "rare", + }, + { + key: "soul_phial", + name: "Soul Phial", + description: "A vial of restless essence. Restores energy.", + slot: ItemSlot.CONSUMABLE, + strBonus: 0, + dexBonus: 0, + intBonus: 0, + hpBonus: 0, + rarity: "common", + }, +]; + +async function main() { + for (const item of items) { + await prisma.item.upsert({ + where: { key: item.key }, + update: item, + create: item, + }); + } + console.log(`Seeded ${items.length} items.`); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..1f0ad56 --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,43 @@ +import express from "express"; +import cors from "cors"; +import cookieParser from "cookie-parser"; +import http from "http"; +import { Server } from "socket.io"; +import { config } from "./lib/config.js"; +import { authRouter } from "./routes/auth.js"; +import { characterRouter } from "./routes/character.js"; +import { inventoryRouter } from "./routes/inventory.js"; +import { registerSockets } from "./sockets/game.js"; + +const app = express(); +const server = http.createServer(app); + +const io = new Server(server, { + cors: { + origin: config.clientOrigin, + credentials: true, + }, +}); + +app.use( + cors({ + origin: config.clientOrigin, + credentials: true, + }) +); +app.use(express.json()); +app.use(cookieParser()); + +app.get("/api/health", (_req, res) => { + res.json({ ok: true, name: "Soulless Necromancer" }); +}); + +app.use("/api/auth", authRouter); +app.use("/api/character", characterRouter); +app.use("/api/inventory", inventoryRouter); + +registerSockets(io); + +server.listen(config.port, () => { + console.log(`Soulless Necromancer server on http://localhost:${config.port}`); +}); diff --git a/server/src/lib/config.ts b/server/src/lib/config.ts new file mode 100644 index 0000000..1a4e991 --- /dev/null +++ b/server/src/lib/config.ts @@ -0,0 +1,22 @@ +import dotenv from "dotenv"; + +dotenv.config(); + +export const config = { + port: Number(process.env.PORT ?? 3001), + jwtSecret: process.env.JWT_SECRET ?? "dev-secret", + clientOrigin: process.env.CLIENT_ORIGIN ?? "http://localhost:5173", + cookieName: process.env.COOKIE_NAME ?? "sn_token", + mapWidth: 40, + mapHeight: 30, + portalX: 20, + portalY: 15, + spawnX: 5, + spawnY: 5, + dungeonWidth: 12, + dungeonHeight: 10, + /** World-map regen tick interval (ms) */ + regenIntervalMs: 4000, + regenHp: 4, + regenEnergy: 2, +}; diff --git a/server/src/lib/prisma.ts b/server/src/lib/prisma.ts new file mode 100644 index 0000000..40c2472 --- /dev/null +++ b/server/src/lib/prisma.ts @@ -0,0 +1,13 @@ +import { PrismaClient } from "@prisma/client"; + +const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }; + +export const prisma = + globalForPrisma.prisma ?? + new PrismaClient({ + log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"], + }); + +if (process.env.NODE_ENV !== "production") { + globalForPrisma.prisma = prisma; +} diff --git a/server/src/middleware/auth.ts b/server/src/middleware/auth.ts new file mode 100644 index 0000000..fc8ca35 --- /dev/null +++ b/server/src/middleware/auth.ts @@ -0,0 +1,50 @@ +import type { Request, Response, NextFunction } from "express"; +import jwt from "jsonwebtoken"; +import { config } from "../lib/config.js"; + +export type AuthPayload = { + userId: string; + characterId: string; +}; + +declare global { + namespace Express { + interface Request { + auth?: AuthPayload; + } + } +} + +export function signToken(payload: AuthPayload): string { + return jwt.sign(payload, config.jwtSecret, { expiresIn: "7d" }); +} + +export function verifyToken(token: string): AuthPayload | null { + try { + return jwt.verify(token, config.jwtSecret) as AuthPayload; + } catch { + return null; + } +} + +export function requireAuth(req: Request, res: Response, next: NextFunction) { + const token = + req.cookies?.[config.cookieName] ?? + (req.headers.authorization?.startsWith("Bearer ") + ? req.headers.authorization.slice(7) + : undefined); + + if (!token) { + res.status(401).json({ error: "Unauthorized" }); + return; + } + + const payload = verifyToken(token); + if (!payload) { + res.status(401).json({ error: "Invalid token" }); + return; + } + + req.auth = payload; + next(); +} diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts new file mode 100644 index 0000000..be225be --- /dev/null +++ b/server/src/routes/auth.ts @@ -0,0 +1,161 @@ +import { Router } from "express"; +import bcrypt from "bcryptjs"; +import { z } from "zod"; +import { prisma } from "../lib/prisma.js"; +import { config } from "../lib/config.js"; +import { requireAuth, signToken } from "../middleware/auth.js"; +import { baseStatsForSpecialty, serializeCharacter } from "../services/progression.js"; + +export const authRouter = Router(); + +const registerSchema = z.object({ + email: z.string().email(), + password: z.string().min(6), + characterName: z.string().min(2).max(20), + specialty: z.enum(["ZOMBIE", "SHADOW"]), +}); + +const loginSchema = z.object({ + email: z.string().email(), + password: z.string().min(1), +}); + +function setAuthCookie(res: import("express").Response, token: string) { + res.cookie(config.cookieName, token, { + httpOnly: true, + sameSite: "lax", + secure: process.env.NODE_ENV === "production", + maxAge: 7 * 24 * 60 * 60 * 1000, + }); +} + +authRouter.post("/register", async (req, res) => { + const parsed = registerSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid input", details: parsed.error.flatten() }); + return; + } + + const { email, password, characterName, specialty } = parsed.data; + const existing = await prisma.user.findUnique({ where: { email } }); + if (existing) { + res.status(409).json({ error: "Email already registered" }); + return; + } + + const nameTaken = await prisma.character.findFirst({ where: { name: characterName } }); + if (nameTaken) { + res.status(409).json({ error: "Character name already taken" }); + return; + } + + const passwordHash = await bcrypt.hash(password, 10); + const stats = baseStatsForSpecialty(specialty); + + const starter = await prisma.item.findUnique({ where: { key: "soul_phial" } }); + + const user = await prisma.user.create({ + data: { + email, + passwordHash, + character: { + create: { + name: characterName, + specialty, + str: stats.str, + dex: stats.dex, + int: stats.int, + maxHp: stats.maxHp, + hp: stats.maxHp, + maxEnergy: stats.maxEnergy, + energy: stats.maxEnergy, + mapX: config.spawnX, + mapY: config.spawnY, + inventory: starter + ? { + create: { + itemId: starter.id, + quantity: 2, + }, + } + : undefined, + }, + }, + }, + include: { + character: { + include: { inventory: { include: { item: true } }, army: true }, + }, + }, + }); + + const character = user.character!; + const token = signToken({ userId: user.id, characterId: character.id }); + setAuthCookie(res, token); + + res.status(201).json({ + user: { id: user.id, email: user.email }, + character: serializeCharacter(character), + }); +}); + +authRouter.post("/login", async (req, res) => { + const parsed = loginSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid input" }); + return; + } + + const user = await prisma.user.findUnique({ + where: { email: parsed.data.email }, + include: { + character: { + include: { inventory: { include: { item: true } }, army: true }, + }, + }, + }); + + if (!user || !(await bcrypt.compare(parsed.data.password, user.passwordHash))) { + res.status(401).json({ error: "Invalid email or password" }); + return; + } + + if (!user.character) { + res.status(500).json({ error: "Character missing" }); + return; + } + + const token = signToken({ userId: user.id, characterId: user.character.id }); + setAuthCookie(res, token); + + res.json({ + user: { id: user.id, email: user.email }, + character: serializeCharacter(user.character), + }); +}); + +authRouter.post("/logout", (_req, res) => { + res.clearCookie(config.cookieName); + res.json({ ok: true }); +}); + +authRouter.get("/me", requireAuth, async (req, res) => { + const user = await prisma.user.findUnique({ + where: { id: req.auth!.userId }, + include: { + character: { + include: { inventory: { include: { item: true } }, army: true }, + }, + }, + }); + + if (!user?.character) { + res.status(404).json({ error: "Not found" }); + return; + } + + res.json({ + user: { id: user.id, email: user.email }, + character: serializeCharacter(user.character), + }); +}); diff --git a/server/src/routes/character.ts b/server/src/routes/character.ts new file mode 100644 index 0000000..5196e15 --- /dev/null +++ b/server/src/routes/character.ts @@ -0,0 +1,74 @@ +import { Router } from "express"; +import { z } from "zod"; +import { prisma } from "../lib/prisma.js"; +import { requireAuth } from "../middleware/auth.js"; +import { serializeCharacter } from "../services/progression.js"; + +export const characterRouter = Router(); + +characterRouter.use(requireAuth); + +async function loadCharacter(characterId: string) { + return prisma.character.findUnique({ + where: { id: characterId }, + include: { inventory: { include: { item: true } }, army: true }, + }); +} + +characterRouter.get("/", async (req, res) => { + const character = await loadCharacter(req.auth!.characterId); + if (!character) { + res.status(404).json({ error: "Character not found" }); + return; + } + res.json({ character: serializeCharacter(character) }); +}); + +const statsSchema = z.object({ + str: z.number().int().min(0).max(50).optional(), + dex: z.number().int().min(0).max(50).optional(), + int: z.number().int().min(0).max(50).optional(), +}); + +characterRouter.post("/stats", async (req, res) => { + const parsed = statsSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid input" }); + return; + } + + const character = await loadCharacter(req.auth!.characterId); + if (!character) { + res.status(404).json({ error: "Character not found" }); + return; + } + + const addStr = parsed.data.str ?? 0; + const addDex = parsed.data.dex ?? 0; + const addInt = parsed.data.int ?? 0; + const total = addStr + addDex + addInt; + + if (total <= 0) { + res.status(400).json({ error: "No points allocated" }); + return; + } + if (total > character.unspentStatPoints) { + res.status(400).json({ error: "Not enough stat points" }); + return; + } + + const updated = await prisma.character.update({ + where: { id: character.id }, + data: { + str: character.str + addStr, + dex: character.dex + addDex, + int: character.int + addInt, + unspentStatPoints: character.unspentStatPoints - total, + maxHp: character.maxHp + addStr * 2, + maxEnergy: character.maxEnergy + addInt, + }, + include: { inventory: { include: { item: true } }, army: true }, + }); + + res.json({ character: serializeCharacter(updated) }); +}); diff --git a/server/src/routes/inventory.ts b/server/src/routes/inventory.ts new file mode 100644 index 0000000..660b04c --- /dev/null +++ b/server/src/routes/inventory.ts @@ -0,0 +1,129 @@ +import { Router } from "express"; +import { z } from "zod"; +import { prisma } from "../lib/prisma.js"; +import { requireAuth } from "../middleware/auth.js"; +import { serializeCharacter } from "../services/progression.js"; + +export const inventoryRouter = Router(); + +inventoryRouter.use(requireAuth); + +async function loadCharacter(characterId: string) { + return prisma.character.findUnique({ + where: { id: characterId }, + include: { inventory: { include: { item: true } }, army: true }, + }); +} + +inventoryRouter.get("/", async (req, res) => { + const character = await loadCharacter(req.auth!.characterId); + if (!character) { + res.status(404).json({ error: "Not found" }); + return; + } + res.json({ character: serializeCharacter(character) }); +}); + +const equipSchema = z.object({ + inventoryItemId: z.string(), + equipped: z.boolean(), +}); + +inventoryRouter.post("/equip", async (req, res) => { + const parsed = equipSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid input" }); + return; + } + + const inv = await prisma.inventoryItem.findFirst({ + where: { + id: parsed.data.inventoryItemId, + characterId: req.auth!.characterId, + }, + include: { item: true }, + }); + + if (!inv) { + res.status(404).json({ error: "Item not found" }); + return; + } + + if (inv.item.slot === "CONSUMABLE") { + res.status(400).json({ error: "Cannot equip consumable" }); + return; + } + + if (parsed.data.equipped) { + // Unequip same slot + const sameSlot = await prisma.inventoryItem.findMany({ + where: { + characterId: req.auth!.characterId, + equipped: true, + item: { slot: inv.item.slot }, + }, + }); + for (const other of sameSlot) { + await prisma.inventoryItem.update({ + where: { id: other.id }, + data: { equipped: false }, + }); + } + } + + await prisma.inventoryItem.update({ + where: { id: inv.id }, + data: { equipped: parsed.data.equipped }, + }); + + const character = await loadCharacter(req.auth!.characterId); + res.json({ character: serializeCharacter(character!) }); +}); + +inventoryRouter.post("/use", async (req, res) => { + const schema = z.object({ inventoryItemId: z.string() }); + const parsed = schema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid input" }); + return; + } + + const inv = await prisma.inventoryItem.findFirst({ + where: { + id: parsed.data.inventoryItemId, + characterId: req.auth!.characterId, + }, + include: { item: true }, + }); + + if (!inv || inv.item.slot !== "CONSUMABLE") { + res.status(400).json({ error: "Not a consumable" }); + return; + } + + const character = await prisma.character.findUnique({ where: { id: req.auth!.characterId } }); + if (!character) { + res.status(404).json({ error: "Not found" }); + return; + } + + await prisma.character.update({ + where: { id: character.id }, + data: { + energy: Math.min(character.maxEnergy, character.energy + 25), + hp: Math.min(character.maxHp, character.hp + 15), + }, + }); + + if (inv.quantity <= 1) { + await prisma.inventoryItem.delete({ where: { id: inv.id } }); + } else { + await prisma.inventoryItem.update({ + where: { id: inv.id }, + data: { quantity: inv.quantity - 1 }, + }); + } + + const updated = await loadCharacter(req.auth!.characterId); + res.json({ character: serializeCharacter(updated!) }); +}); diff --git a/server/src/services/army.ts b/server/src/services/army.ts new file mode 100644 index 0000000..9798664 --- /dev/null +++ b/server/src/services/army.ts @@ -0,0 +1,35 @@ +import type { ArmyUnitType, Specialty } from "@prisma/client"; +import { prisma } from "../lib/prisma.js"; + +export function armyTypeForSpecialty(specialty: Specialty): ArmyUnitType { + return specialty === "ZOMBIE" ? "ZOMBIE" : "SHADOW"; +} + +export function summonEnergyCost(tier: number): number { + return 8 + tier * 4; +} + +export function unitPower(type: ArmyUnitType, tier: number, count: number, casterInt: number) { + const base = type === "ZOMBIE" ? 6 : 5; + return (base + tier * 3 + Math.floor(casterInt / 2)) * count; +} + +export async function addArmyUnit( + characterId: string, + type: ArmyUnitType, + tier = 1, + count = 1 +) { + return prisma.armyUnit.upsert({ + where: { + characterId_type_tier: { characterId, type, tier }, + }, + update: { count: { increment: count } }, + create: { characterId, type, tier, count }, + }); +} + +export async function getArmyPower(characterId: string, casterInt: number) { + const units = await prisma.armyUnit.findMany({ where: { characterId } }); + return units.reduce((sum, u) => sum + unitPower(u.type, u.tier, u.count, casterInt), 0); +} diff --git a/server/src/services/combat.ts b/server/src/services/combat.ts new file mode 100644 index 0000000..93c7c4f --- /dev/null +++ b/server/src/services/combat.ts @@ -0,0 +1,348 @@ +import { config } from "../lib/config.js"; +import { effectiveStats, type CharacterWithRelations } from "./progression.js"; +import { getArmyPower, unitPower } from "./army.js"; +import type { ArmyUnitType } from "@prisma/client"; + +export const PLAYER_ATTACK_RANGE = 2; +export const UNIT_ATTACK_RANGE = 2; +export const ENEMY_ATTACK_RANGE = 2; + +export type Enemy = { + id: string; + name: string; + x: number; + y: number; + hp: number; + maxHp: number; + atk: number; +}; + +export type Summon = { + id: string; + type: ArmyUnitType; + x: number; + y: number; + hp: number; + maxHp: number; + atk: number; +}; + +export type DungeonState = { + characterId: string; + floor: number; + width: number; + height: number; + playerX: number; + playerY: number; + energy: number; + enemies: Enemy[]; + summons: Summon[]; + log: string[]; + over: boolean; + won: boolean; +}; + +function enemyName(floor: number, i: number): string { + const names = ["Bone Wretch", "Hollow Scout", "Grave Shade", "Carrion Knight", "Void Hound"]; + return `${names[i % names.length]} Lv${floor}`; +} + +function manhattan(ax: number, ay: number, bx: number, by: number) { + return Math.abs(ax - bx) + Math.abs(ay - by); +} + +function nearestEnemy(fromX: number, fromY: number, enemies: Enemy[], range: number): Enemy | null { + const inRange = enemies + .filter((e) => e.hp > 0 && manhattan(fromX, fromY, e.x, e.y) <= range) + .sort((a, b) => manhattan(fromX, fromY, a.x, a.y) - manhattan(fromX, fromY, b.x, b.y)); + return inRange[0] ?? null; +} + +function nearestEnemyAny(fromX: number, fromY: number, enemies: Enemy[]): Enemy | null { + const living = enemies + .filter((e) => e.hp > 0) + .sort((a, b) => manhattan(fromX, fromY, a.x, a.y) - manhattan(fromX, fromY, b.x, b.y)); + return living[0] ?? null; +} + +function isWalkable(state: DungeonState, x: number, y: number, ignoreSummonId?: string): boolean { + if (x < 0 || y < 0 || x >= state.width || y >= state.height) return false; + if (x === state.playerX && y === state.playerY) return false; + if (state.enemies.some((e) => e.hp > 0 && e.x === x && e.y === y)) return false; + if (state.summons.some((s) => s.id !== ignoreSummonId && s.hp > 0 && s.x === x && s.y === y)) { + return false; + } + return true; +} + +/** One orthogonal step closer to target, if possible. */ +function stepToward( + state: DungeonState, + fromX: number, + fromY: number, + toX: number, + toY: number, + ignoreSummonId?: string +): { x: number; y: number } | null { + const candidates: Array<{ x: number; y: number }> = []; + if (toX !== fromX) candidates.push({ x: fromX + Math.sign(toX - fromX), y: fromY }); + if (toY !== fromY) candidates.push({ x: fromX, y: fromY + Math.sign(toY - fromY) }); + // Prefer the step that reduces Manhattan distance the most + candidates.sort( + (a, b) => manhattan(a.x, a.y, toX, toY) - manhattan(b.x, b.y, toX, toY) + ); + for (const c of candidates) { + if (isWalkable(state, c.x, c.y, ignoreSummonId)) return c; + } + // Sidestep if direct path blocked + const sides = [ + { x: fromX + 1, y: fromY }, + { x: fromX - 1, y: fromY }, + { x: fromX, y: fromY + 1 }, + { x: fromX, y: fromY - 1 }, + ].filter((c) => isWalkable(state, c.x, c.y, ignoreSummonId)); + sides.sort((a, b) => manhattan(a.x, a.y, toX, toY) - manhattan(b.x, b.y, toX, toY)); + return sides[0] ?? null; +} + +export function moveSummonTowardEnemy(state: DungeonState, summon: Summon): boolean { + const foe = nearestEnemyAny(summon.x, summon.y, state.enemies); + if (!foe) return false; + const step = stepToward(state, summon.x, summon.y, foe.x, foe.y, summon.id); + if (!step) return false; + summon.x = step.x; + summon.y = step.y; + return true; +} + +export function createDungeon(character: CharacterWithRelations): DungeonState { + const floor = Math.max(1, character.dungeonFloor || 1); + const enemyCount = 3 + Math.min(4, floor); + const enemies: Enemy[] = []; + const startY = Math.floor(config.dungeonHeight / 2); + + for (let i = 0; i < enemyCount; i++) { + let x = 0; + let y = 0; + let attempts = 0; + do { + x = 2 + Math.floor(Math.random() * (config.dungeonWidth - 3)); + y = 1 + Math.floor(Math.random() * (config.dungeonHeight - 2)); + attempts++; + } while ( + attempts < 40 && + (enemies.some((e) => e.x === x && e.y === y) || (x === 1 && y === startY)) + ); + + const hp = 25 + floor * 12 + i * 5; + enemies.push({ + id: `enemy-${i}-${Date.now()}`, + name: enemyName(floor, i), + x, + y, + hp, + maxHp: hp, + atk: 4 + floor * 2, + }); + } + + return { + characterId: character.id, + floor, + width: config.dungeonWidth, + height: config.dungeonHeight, + playerX: 1, + playerY: startY, + energy: character.energy, + enemies, + summons: [], + log: [`Floor ${floor}: the crypt awakens. Combat starts when in range.`], + over: false, + won: false, + }; +} + +export function moveInDungeon( + state: DungeonState, + x: number, + y: number +): { ok: boolean; error?: string; state: DungeonState } { + if (state.over) return { ok: false, error: "Combat over", state }; + x = Math.floor(x); + y = Math.floor(y); + if (x < 0 || y < 0 || x >= state.width || y >= state.height) { + return { ok: false, error: "Out of bounds", state }; + } + if (x === state.playerX && y === state.playerY) { + return { ok: false, error: "Already there", state }; + } + if (state.enemies.some((e) => e.hp > 0 && e.x === x && e.y === y)) { + return { ok: false, error: "Tile blocked by enemy", state }; + } + if (state.summons.some((s) => s.x === x && s.y === y)) { + return { ok: false, error: "Tile occupied by ally", state }; + } + + state.playerX = x; + state.playerY = y; + state.log.push(`You move to (${x}, ${y}).`); + return { ok: true, state }; +} + +export function playerAttackDamage(character: CharacterWithRelations, state: DungeonState): number { + const stats = effectiveStats(character); + const armyBonus = character.army.reduce( + (s, u) => s + unitPower(u.type, u.tier, u.count, stats.int), + 0 + ); + return Math.max( + 1, + Math.floor(stats.str * 1.2 + stats.int * 0.8 + armyBonus * 0.15) + ); +} + +/** One auto-combat round: summons move or fight, player hits if in range, enemies retaliate. */ +export function resolveAutoCombatRound( + state: DungeonState, + character: CharacterWithRelations +): { state: DungeonState; damageToPlayer: number; acted: boolean } { + if (state.over) return { state, damageToPlayer: 0, acted: false }; + + let acted = false; + const playerDmg = playerAttackDamage(character, state); + + const playerTarget = nearestEnemy(state.playerX, state.playerY, state.enemies, PLAYER_ATTACK_RANGE); + if (playerTarget) { + playerTarget.hp = Math.max(0, playerTarget.hp - playerDmg); + state.log.push(`You strike ${playerTarget.name} for ${playerDmg}.`); + acted = true; + if (playerTarget.hp <= 0) { + state.log.push(`${playerTarget.name} falls.`); + state.enemies = state.enemies.filter((e) => e.id !== playerTarget.id); + } + } + + for (const summon of [...state.summons]) { + if (summon.hp <= 0) continue; + const target = nearestEnemy(summon.x, summon.y, state.enemies, UNIT_ATTACK_RANGE); + if (target) { + target.hp = Math.max(0, target.hp - summon.atk); + state.log.push( + `Your ${summon.type.toLowerCase()} hits ${target.name} for ${summon.atk}.` + ); + acted = true; + if (target.hp <= 0) { + state.log.push(`${target.name} falls.`); + state.enemies = state.enemies.filter((e) => e.id !== target.id); + } + } else if (moveSummonTowardEnemy(state, summon)) { + acted = true; + } + } + + let damageToPlayer = 0; + for (const enemy of [...state.enemies]) { + if (enemy.hp <= 0) continue; + + const nearPlayer = manhattan(enemy.x, enemy.y, state.playerX, state.playerY) <= ENEMY_ATTACK_RANGE; + const nearSummons = state.summons.filter( + (s) => s.hp > 0 && manhattan(enemy.x, enemy.y, s.x, s.y) <= ENEMY_ATTACK_RANGE + ); + + if (!nearPlayer && nearSummons.length === 0) continue; + + acted = true; + if (nearSummons.length > 0 && (!nearPlayer || Math.random() < 0.55)) { + const target = nearSummons[Math.floor(Math.random() * nearSummons.length)]; + target.hp -= enemy.atk; + state.log.push(`${enemy.name} hits your ${target.type.toLowerCase()} for ${enemy.atk}.`); + if (target.hp <= 0) { + state.summons = state.summons.filter((s) => s.id !== target.id); + state.log.push(`Your ${target.type.toLowerCase()} is destroyed.`); + } + } else if (nearPlayer) { + damageToPlayer += enemy.atk; + } + } + + if (damageToPlayer > 0) { + state.log.push(`You take ${damageToPlayer} damage.`); + } + + if (state.enemies.filter((e) => e.hp > 0).length === 0) { + state.over = true; + state.won = true; + state.log.push("Floor cleared!"); + } + + if (state.log.length > 40) { + state.log = state.log.slice(-40); + } + + return { state, damageToPlayer, acted }; +} + +/** Combat tick needed if fight in range OR summons can still hunt enemies. */ +export function anyoneInCombatRange(state: DungeonState): boolean { + if (state.over) return false; + if (nearestEnemy(state.playerX, state.playerY, state.enemies, PLAYER_ATTACK_RANGE)) return true; + for (const s of state.summons) { + if (s.hp <= 0) continue; + if (nearestEnemy(s.x, s.y, state.enemies, UNIT_ATTACK_RANGE)) return true; + } + for (const e of state.enemies) { + if (e.hp <= 0) continue; + if (manhattan(e.x, e.y, state.playerX, state.playerY) <= ENEMY_ATTACK_RANGE) return true; + if (state.summons.some((s) => s.hp > 0 && manhattan(e.x, e.y, s.x, s.y) <= ENEMY_ATTACK_RANGE)) { + return true; + } + } + return false; +} + +export function dungeonNeedsTick(state: DungeonState): boolean { + if (state.over) return false; + if (state.enemies.every((e) => e.hp <= 0)) return false; + if (anyoneInCombatRange(state)) return true; + return state.summons.some((s) => s.hp > 0); +} + +export function placeSummon( + state: DungeonState, + type: ArmyUnitType, + x: number, + y: number, + casterInt: number +): { ok: boolean; error?: string; state: DungeonState } { + if (state.over) return { ok: false, error: "Combat over", state }; + if (x < 0 || y < 0 || x >= state.width || y >= state.height) { + return { ok: false, error: "Out of bounds", state }; + } + if (state.enemies.some((e) => e.x === x && e.y === y && e.hp > 0)) { + return { ok: false, error: "Tile occupied", state }; + } + if (state.summons.some((s) => s.x === x && s.y === y)) { + return { ok: false, error: "Tile occupied", state }; + } + if (x === state.playerX && y === state.playerY) { + return { ok: false, error: "Cannot summon on self", state }; + } + + const hp = 18 + casterInt * 2; + const atk = 4 + Math.floor(casterInt / 2); + state.summons.push({ + id: `summon-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, + type, + x, + y, + hp, + maxHp: hp, + atk, + }); + state.log.push(`You summon a ${type.toLowerCase()} at (${x},${y}).`); + return { ok: true, state }; +} + +export async function estimateArmyPower(characterId: string, casterInt: number) { + return getArmyPower(characterId, casterInt); +} diff --git a/server/src/services/progression.ts b/server/src/services/progression.ts new file mode 100644 index 0000000..b6a2f61 --- /dev/null +++ b/server/src/services/progression.ts @@ -0,0 +1,121 @@ +import type { Character, InventoryItem, Item, Specialty, ArmyUnit } from "@prisma/client"; +import { prisma } from "../lib/prisma.js"; + +export type CharacterWithRelations = Character & { + inventory: (InventoryItem & { item: Item })[]; + army: ArmyUnit[]; +}; + +export function baseStatsForSpecialty(specialty: Specialty) { + if (specialty === "ZOMBIE") { + return { str: 8, dex: 4, int: 5, maxHp: 120, maxEnergy: 40 }; + } + return { str: 4, dex: 7, int: 8, maxHp: 90, maxEnergy: 60 }; +} + +export function xpToNextLevel(level: number): number { + return 50 + level * 40; +} + +export function equipmentBonuses(character: CharacterWithRelations) { + return character.inventory + .filter((i) => i.equipped) + .reduce( + (acc, inv) => ({ + str: acc.str + inv.item.strBonus, + dex: acc.dex + inv.item.dexBonus, + int: acc.int + inv.item.intBonus, + hp: acc.hp + inv.item.hpBonus, + }), + { str: 0, dex: 0, int: 0, hp: 0 } + ); +} + +export function effectiveStats(character: CharacterWithRelations) { + const bonus = equipmentBonuses(character); + return { + str: character.str + bonus.str, + dex: character.dex + bonus.dex, + int: character.int + bonus.int, + maxHp: character.maxHp + bonus.hp, + maxEnergy: character.maxEnergy, + }; +} + +export function serializeCharacter(character: CharacterWithRelations) { + const stats = effectiveStats(character); + return { + id: character.id, + name: character.name, + specialty: character.specialty, + level: character.level, + xp: character.xp, + xpToNext: xpToNextLevel(character.level), + unspentStatPoints: character.unspentStatPoints, + hp: character.hp, + maxHp: stats.maxHp, + energy: character.energy, + maxEnergy: stats.maxEnergy, + str: character.str, + dex: character.dex, + int: character.int, + effective: stats, + mapX: character.mapX, + mapY: character.mapY, + currentZone: character.currentZone, + dungeonFloor: character.dungeonFloor, + inventory: character.inventory.map((inv) => ({ + id: inv.id, + equipped: inv.equipped, + quantity: inv.quantity, + item: { + id: inv.item.id, + key: inv.item.key, + name: inv.item.name, + description: inv.item.description, + slot: inv.item.slot, + strBonus: inv.item.strBonus, + dexBonus: inv.item.dexBonus, + intBonus: inv.item.intBonus, + hpBonus: inv.item.hpBonus, + rarity: inv.item.rarity, + }, + })), + army: character.army.map((u) => ({ + id: u.id, + type: u.type, + tier: u.tier, + count: u.count, + })), + }; +} + +export async function applyXp( + characterId: string, + current: Character, + amount: number +) { + let { level, xp, unspentStatPoints } = current; + xp += amount; + let leveled = 0; + while (xp >= xpToNextLevel(level)) { + xp -= xpToNextLevel(level); + level += 1; + unspentStatPoints += 3; + leveled += 1; + } + + const updated = await prisma.character.update({ + where: { id: characterId }, + data: { + level, + xp, + unspentStatPoints, + maxHp: current.maxHp + leveled * 10, + hp: Math.min(current.hp + leveled * 10, current.maxHp + leveled * 10), + maxEnergy: current.maxEnergy + leveled * 2, + }, + }); + + return { updated, leveled, xpGained: amount }; +} diff --git a/server/src/sockets/game.ts b/server/src/sockets/game.ts new file mode 100644 index 0000000..ad484c1 --- /dev/null +++ b/server/src/sockets/game.ts @@ -0,0 +1,459 @@ +import type { Server, Socket } from "socket.io"; +import { prisma } from "../lib/prisma.js"; +import { config } from "../lib/config.js"; +import { verifyToken, type AuthPayload } from "../middleware/auth.js"; +import { + createDungeon, + dungeonNeedsTick, + moveInDungeon, + placeSummon, + resolveAutoCombatRound, + type DungeonState, +} from "../services/combat.js"; +import { + applyXp, + effectiveStats, + serializeCharacter, + type CharacterWithRelations, +} from "../services/progression.js"; +import { addArmyUnit, armyTypeForSpecialty, summonEnergyCost } from "../services/army.js"; + +type AuthedSocket = Socket & { data: { auth: AuthPayload } }; + +const worldPresence = new Map< + string, + { characterId: string; name: string; specialty: string; x: number; y: number; socketId: string } +>(); + +const dungeons = new Map(); +const combatTimers = new Map>(); +const dungeonSockets = new Map(); // characterId -> socketId + +const COMBAT_TICK_MS = 900; + +async function loadFullCharacter(characterId: string) { + return prisma.character.findUnique({ + where: { id: characterId }, + include: { inventory: { include: { item: true } }, army: true }, + }); +} + +function broadcastWorld(io: Server) { + const players = [...worldPresence.values()].map((p) => ({ + characterId: p.characterId, + name: p.name, + specialty: p.specialty, + x: p.x, + y: p.y, + })); + io.to("world").emit("world:players", { + players, + portal: { x: config.portalX, y: config.portalY }, + map: { width: config.mapWidth, height: config.mapHeight }, + }); +} + +function authHandshake(socket: Socket): AuthPayload | null { + const cookieHeader = socket.handshake.headers.cookie ?? ""; + const match = cookieHeader + .split(";") + .map((c) => c.trim()) + .find((c) => c.startsWith(`${config.cookieName}=`)); + const fromCookie = match ? decodeURIComponent(match.split("=").slice(1).join("=")) : undefined; + const fromAuth = socket.handshake.auth?.token as string | undefined; + const token = fromAuth || fromCookie; + if (!token) return null; + return verifyToken(token); +} + +function stopCombatLoop(characterId: string) { + const t = combatTimers.get(characterId); + if (t) { + clearInterval(t); + combatTimers.delete(characterId); + } +} + +function startCombatLoop(io: Server, characterId: string) { + if (combatTimers.has(characterId)) return; + const timer = setInterval(() => { + void runCombatTick(io, characterId); + }, COMBAT_TICK_MS); + combatTimers.set(characterId, timer); +} + +async function runCombatTick(io: Server, characterId: string) { + const state = dungeons.get(characterId); + if (!state || state.over) { + stopCombatLoop(characterId); + return; + } + + if (!dungeonNeedsTick(state)) return; + + const char = await loadFullCharacter(characterId); + if (!char || char.currentZone !== "DUNGEON") { + stopCombatLoop(characterId); + return; + } + + const socketId = dungeonSockets.get(characterId); + const socket = socketId ? io.sockets.sockets.get(socketId) : undefined; + + const { state: next, damageToPlayer, acted } = resolveAutoCombatRound(state, char); + if (!acted && damageToPlayer === 0) return; + + let updatedChar: CharacterWithRelations = char; + + if (damageToPlayer > 0) { + const newHp = Math.max(0, char.hp - damageToPlayer); + updatedChar = await prisma.character.update({ + where: { id: char.id }, + data: { hp: newHp }, + include: { inventory: { include: { item: true } }, army: true }, + }); + + if (newHp <= 0) { + next.over = true; + next.won = false; + next.log.push("You have fallen. Returning to the world..."); + dungeons.set(characterId, next); + stopCombatLoop(characterId); + if (socket) { + socket.emit("dungeon:state", next); + socket.emit("character:update", serializeCharacter(updatedChar)); + await exitDungeon(io, socket, characterId, false, true); + } + return; + } + } + + dungeons.set(characterId, next); + if (socket) { + socket.emit("dungeon:state", next); + if (damageToPlayer > 0) { + socket.emit("character:update", serializeCharacter(updatedChar)); + } + } + + if (next.won) { + stopCombatLoop(characterId); + if (socket) { + await rewardAndExit(io, socket, updatedChar, next); + } + } +} + +async function runWorldRegen(io: Server) { + for (const presence of worldPresence.values()) { + const char = await loadFullCharacter(presence.characterId); + if (!char || char.currentZone !== "WORLD") continue; + + const stats = effectiveStats(char); + if (char.hp >= stats.maxHp && char.energy >= char.maxEnergy) continue; + + const newHp = Math.min(stats.maxHp, char.hp + config.regenHp); + const newEnergy = Math.min(char.maxEnergy, char.energy + config.regenEnergy); + if (newHp === char.hp && newEnergy === char.energy) continue; + + const updated = await prisma.character.update({ + where: { id: char.id }, + data: { hp: newHp, energy: newEnergy }, + include: { inventory: { include: { item: true } }, army: true }, + }); + + const socket = io.sockets.sockets.get(presence.socketId); + socket?.emit("character:update", serializeCharacter(updated)); + } +} + +export function registerSockets(io: Server) { + setInterval(() => { + void runWorldRegen(io); + }, config.regenIntervalMs); + + io.use((socket, next) => { + const auth = authHandshake(socket); + if (!auth) { + next(new Error("Unauthorized")); + return; + } + (socket as AuthedSocket).data.auth = auth; + next(); + }); + + io.on("connection", async (socket: Socket) => { + const auth = (socket as AuthedSocket).data.auth; + const character = await loadFullCharacter(auth.characterId); + if (!character) { + socket.disconnect(true); + return; + } + + socket.join("world"); + if (character.currentZone === "WORLD") { + worldPresence.set(character.id, { + characterId: character.id, + name: character.name, + specialty: character.specialty, + x: character.mapX, + y: character.mapY, + socketId: socket.id, + }); + broadcastWorld(io); + } else { + let state = dungeons.get(character.id); + if (!state || state.over) { + state = createDungeon(character); + dungeons.set(character.id, state); + } + dungeonSockets.set(character.id, socket.id); + socket.join(`dungeon:${character.id}`); + socket.emit("dungeon:state", state); + startCombatLoop(io, character.id); + } + + socket.emit("character:update", serializeCharacter(character)); + + socket.on("player:move", async (payload: { x: number; y: number }) => { + const char = await loadFullCharacter(auth.characterId); + if (!char || char.currentZone !== "WORLD") return; + + const x = Math.floor(payload.x); + const y = Math.floor(payload.y); + if (x < 0 || y < 0 || x >= config.mapWidth || y >= config.mapHeight) { + socket.emit("error:message", "Out of bounds"); + return; + } + if (x === char.mapX && y === char.mapY) return; + + const updated = await prisma.character.update({ + where: { id: char.id }, + data: { mapX: x, mapY: y }, + include: { inventory: { include: { item: true } }, army: true }, + }); + + worldPresence.set(updated.id, { + characterId: updated.id, + name: updated.name, + specialty: updated.specialty, + x: updated.mapX, + y: updated.mapY, + socketId: socket.id, + }); + broadcastWorld(io); + socket.emit("character:update", serializeCharacter(updated)); + }); + + socket.on("player:enter_dungeon", async () => { + const char = await loadFullCharacter(auth.characterId); + if (!char || char.currentZone !== "WORLD") return; + + const dist = + Math.abs(char.mapX - config.portalX) + Math.abs(char.mapY - config.portalY); + if (dist > 1) { + socket.emit("error:message", "Move closer to the portal"); + return; + } + + const floor = Math.max(1, char.dungeonFloor || 1); + const updated = await prisma.character.update({ + where: { id: char.id }, + data: { currentZone: "DUNGEON", dungeonFloor: floor }, + include: { inventory: { include: { item: true } }, army: true }, + }); + + worldPresence.delete(char.id); + broadcastWorld(io); + + const state = createDungeon(updated); + dungeons.set(char.id, state); + dungeonSockets.set(char.id, socket.id); + socket.leave("world"); + socket.join(`dungeon:${char.id}`); + socket.emit("zone:change", { zone: "DUNGEON" }); + socket.emit("dungeon:state", state); + socket.emit("character:update", serializeCharacter(updated)); + startCombatLoop(io, char.id); + }); + + socket.on("player:exit_dungeon", async () => { + stopCombatLoop(auth.characterId); + await exitDungeon(io, socket, auth.characterId, false); + }); + + socket.on("dungeon:move", async (payload: { x: number; y: number }) => { + const char = await loadFullCharacter(auth.characterId); + const state = dungeons.get(auth.characterId); + if (!char || !state || char.currentZone !== "DUNGEON") return; + + const result = moveInDungeon(state, payload.x, payload.y); + if (!result.ok) { + socket.emit("error:message", result.error ?? "Cannot move"); + return; + } + + dungeons.set(auth.characterId, result.state); + dungeonSockets.set(auth.characterId, socket.id); + socket.emit("dungeon:state", result.state); + startCombatLoop(io, auth.characterId); + if (dungeonNeedsTick(result.state)) { + await runCombatTick(io, auth.characterId); + } + }); + + socket.on("dungeon:summon", async (payload: { x: number; y: number }) => { + await handleSummon(io, socket, auth.characterId, payload.x, payload.y); + }); + + socket.on("disconnect", () => { + const presence = [...worldPresence.entries()].find(([, p]) => p.socketId === socket.id); + if (presence) { + worldPresence.delete(presence[0]); + broadcastWorld(io); + } + if (dungeonSockets.get(auth.characterId) === socket.id) { + dungeonSockets.delete(auth.characterId); + // Keep dungeon state; pause combat until reconnect + stopCombatLoop(auth.characterId); + } + }); + }); +} + +async function handleSummon( + io: Server, + socket: Socket, + characterId: string, + x: number, + y: number +) { + const char = await loadFullCharacter(characterId); + const state = dungeons.get(characterId); + if (!char || !state || char.currentZone !== "DUNGEON") return; + + const cost = summonEnergyCost(1); + if (char.energy < cost) { + socket.emit("error:message", "Not enough energy"); + return; + } + + const stats = effectiveStats(char); + const type = armyTypeForSpecialty(char.specialty); + const placed = placeSummon(state, type, Math.floor(x), Math.floor(y), stats.int); + if (!placed.ok) { + socket.emit("error:message", placed.error ?? "Summon failed"); + return; + } + + const updated = await prisma.character.update({ + where: { id: char.id }, + data: { energy: char.energy - cost }, + include: { inventory: { include: { item: true } }, army: true }, + }); + + await addArmyUnit(char.id, type, 1, 1); + + const refreshed = await loadFullCharacter(characterId); + state.energy = updated.energy; + dungeons.set(characterId, state); + dungeonSockets.set(characterId, socket.id); + socket.emit("dungeon:state", state); + socket.emit("character:update", serializeCharacter(refreshed!)); + startCombatLoop(io, characterId); + if (dungeonNeedsTick(state)) { + await runCombatTick(io, characterId); + } +} + +async function rewardAndExit( + io: Server, + socket: Socket, + character: CharacterWithRelations, + state: DungeonState +) { + const xpGain = 30 + state.floor * 20; + const { leveled } = await applyXp(character.id, character, xpGain); + + const lootKeys = [ + "bone_dagger", + "shadow_veil", + "grimoire_fragment", + "soul_phial", + "necrotic_blade", + "ossuary_plate", + ]; + const key = lootKeys[Math.floor(Math.random() * lootKeys.length)]; + const item = await prisma.item.findUnique({ where: { key } }); + if (item) { + await prisma.inventoryItem.create({ + data: { characterId: character.id, itemId: item.id, quantity: 1 }, + }); + } + + await prisma.character.update({ + where: { id: character.id }, + data: { + dungeonFloor: state.floor + 1, + energy: Math.min(character.maxEnergy, character.energy + 10), + }, + }); + + socket.emit("dungeon:reward", { + xp: xpGain, + leveled, + loot: item ? item.name : null, + nextFloor: state.floor + 1, + }); + + await exitDungeon(io, socket, character.id, true); +} + +async function exitDungeon( + io: Server, + socket: Socket, + characterId: string, + won: boolean, + defeated = false +) { + stopCombatLoop(characterId); + dungeons.delete(characterId); + dungeonSockets.delete(characterId); + + const data: { + currentZone: "WORLD"; + mapX?: number; + mapY?: number; + hp?: number; + } = { currentZone: "WORLD" }; + + if (defeated) { + data.mapX = config.spawnX; + data.mapY = config.spawnY; + const char = await prisma.character.findUnique({ where: { id: characterId } }); + if (char) { + data.hp = Math.max(1, Math.floor(char.maxHp * 0.4)); + } + } + + const updated = await prisma.character.update({ + where: { id: characterId }, + data, + include: { inventory: { include: { item: true } }, army: true }, + }); + + worldPresence.set(updated.id, { + characterId: updated.id, + name: updated.name, + specialty: updated.specialty, + x: updated.mapX, + y: updated.mapY, + socketId: socket.id, + }); + + socket.leave(`dungeon:${characterId}`); + socket.join("world"); + broadcastWorld(io); + socket.emit("zone:change", { zone: "WORLD", won }); + socket.emit("character:update", serializeCharacter(updated)); +} diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..50d5d11 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true + }, + "include": ["src/**/*"] +}