diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..98de87d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+*.tsbuildinfo
+vite.config.js
+vite.config.d.ts
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..0522b40
--- /dev/null
+++ b/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ 2D6 Dungeon Web
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..8d1fb29
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1848 @@
+{
+ "name": "d2d6-dungeon-web",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "d2d6-dungeon-web",
+ "version": "0.1.0",
+ "dependencies": {
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "zod": "^3.24.2"
+ },
+ "devDependencies": {
+ "@types/node": "^22.13.10",
+ "@types/react": "^19.0.12",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^4.4.1",
+ "typescript": "^5.8.2",
+ "vite": "^6.2.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "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.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "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/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.15",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
+ "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "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/@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/baseline-browser-mapping": {
+ "version": "2.10.8",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz",
+ "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "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.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001779",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz",
+ "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==",
+ "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/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/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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.313",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz",
+ "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/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/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/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/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/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/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/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "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/node-releases": {
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+ "dev": 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.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "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.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "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/rollup": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "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/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/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": 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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/vite": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "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/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/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"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..771e202
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "d2d6-dungeon-web",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "npm run check && vite build",
+ "preview": "vite preview",
+ "check": "tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.node.json"
+ },
+ "dependencies": {
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "zod": "^3.24.2"
+ },
+ "devDependencies": {
+ "@types/node": "^22.13.10",
+ "@types/react": "^19.0.12",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^4.4.1",
+ "typescript": "^5.8.2",
+ "vite": "^6.2.0"
+ }
+}
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..025e45a
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,75 @@
+import { sampleContentPack } from "@/data/sampleContentPack";
+
+const planningDocs = [
+ "Planning/PROJECT_PLAN.md",
+ "Planning/GAME_SPEC.md",
+ "Planning/content-checklist.json",
+ "Planning/DATA_MODEL.md",
+ "Planning/IMPLEMENTATION_NOTES.md",
+];
+
+const nextTargets = [
+ "Encode Level 1 foundational tables into structured JSON.",
+ "Implement dice utilities for D3, D6, 2D6, and D66.",
+ "Create character creation state and validation.",
+ "Build deterministic room generation for the Level 1 loop.",
+];
+
+function App() {
+ return (
+
+
+ 2D6 Dungeon Web
+ Project scaffold is live.
+
+ The app now has a Vite + React + TypeScript foundation, shared type
+ models, and Zod schemas that mirror the planning documents.
+
+
+
+
+
+ Planning Set
+
+ {planningDocs.map((doc) => (
+ - {doc}
+ ))}
+
+
+
+
+ Immediate Build Targets
+
+ {nextTargets.map((target) => (
+ - {target}
+ ))}
+
+
+
+
+ Sample Content Pack
+
+
+
- Tables
+ - {sampleContentPack.tables.length}
+
+
+
- Weapons
+ - {sampleContentPack.weapons.length}
+
+
+
- Manoeuvres
+ - {sampleContentPack.manoeuvres.length}
+
+
+
- Creatures
+ - {sampleContentPack.creatures.length}
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/data/sampleContentPack.ts b/src/data/sampleContentPack.ts
new file mode 100644
index 0000000..251ba26
--- /dev/null
+++ b/src/data/sampleContentPack.ts
@@ -0,0 +1,164 @@
+import type { ContentPack } from "@/types/content";
+
+import { contentPackSchema } from "@/schemas/content";
+
+const samplePack = {
+ version: "0.1.0",
+ sourceBooks: [
+ "Source/2D6 Dungeon - Core Rules.pdf",
+ "Source/2D6 Dungeon - Tables Codex.pdf",
+ "Source/2D6 Dungeon - Play Sheet.pdf",
+ ],
+ tables: [
+ {
+ id: "table.weapon-manoeuvres-1",
+ code: "WMT1",
+ name: "Weapon Manoeuvres Table 1",
+ category: "generic",
+ page: 13,
+ diceKind: "d6",
+ entries: [
+ {
+ key: "1",
+ exact: 1,
+ label: "Exact Strike",
+ references: [{ type: "manoeuvre", id: "manoeuvre.exact-strike" }],
+ },
+ ],
+ notes: ["Starter sample table used to validate content wiring."],
+ mvp: true,
+ },
+ ],
+ weapons: [
+ {
+ id: "weapon.short-sword",
+ name: "Short Sword",
+ category: "melee",
+ handedness: "one-handed",
+ baseDamage: 1,
+ allowedManoeuvreIds: ["manoeuvre.exact-strike", "manoeuvre.guard-break"],
+ tags: ["starter"],
+ startingOption: true,
+ },
+ ],
+ manoeuvres: [
+ {
+ id: "manoeuvre.exact-strike",
+ name: "Exact Strike",
+ weaponCategories: ["melee"],
+ precisionModifier: 1,
+ exactStrikeBonus: true,
+ effectText: "Improves accuracy and benefits from exact strike bonuses.",
+ mvp: true,
+ },
+ {
+ id: "manoeuvre.guard-break",
+ name: "Guard Break",
+ weaponCategories: ["melee"],
+ shiftCost: 1,
+ damageModifier: 1,
+ effectText: "Trades shift for a stronger hit.",
+ mvp: true,
+ },
+ ],
+ armour: [
+ {
+ id: "armour.leather-vest",
+ name: "Leather Vest",
+ armourValue: 1,
+ startingOption: true,
+ valueGp: 6,
+ mvp: true,
+ },
+ ],
+ items: [
+ {
+ id: "item.ration",
+ name: "Ration",
+ itemType: "ration",
+ stackable: true,
+ consumable: true,
+ mvp: true,
+ },
+ {
+ id: "item.lantern",
+ name: "Lantern",
+ itemType: "light-source",
+ stackable: false,
+ consumable: false,
+ mvp: true,
+ },
+ ],
+ potions: [
+ {
+ id: "potion.healing",
+ name: "Healing Potion",
+ tableSource: "Magic Potions Table 1",
+ useTiming: "any",
+ effects: [{ type: "heal", amount: 3, target: "self" }],
+ mvp: true,
+ },
+ ],
+ scrolls: [
+ {
+ id: "scroll.lesser-heal",
+ name: "Lesser Heal",
+ tableSource: "Starting Scroll Table 1",
+ castCheck: {
+ diceKind: "d6",
+ successMin: 3,
+ },
+ onSuccess: [{ type: "heal", amount: 2, target: "self" }],
+ onFailureTableCode: "FTCCT1",
+ startingOption: true,
+ mvp: true,
+ },
+ ],
+ creatures: [
+ {
+ id: "creature.level1.giant-rat",
+ name: "Giant Rat",
+ level: 1,
+ category: "beast",
+ hp: 2,
+ attackProfile: {
+ discipline: 0,
+ precision: 0,
+ damage: 1,
+ numberAppearing: "1-2",
+ },
+ xpReward: 1,
+ sourcePage: 102,
+ traits: ["level-1", "sample"],
+ mvp: true,
+ },
+ ],
+ roomTemplates: [
+ {
+ id: "room.level1.entry",
+ level: 1,
+ roomClass: "start",
+ tableCode: "L1R",
+ tableEntryKey: "entry",
+ title: "Entry Chamber",
+ text: "A quiet starting space for the first dungeon prototype.",
+ dimensions: {
+ width: 4,
+ height: 4,
+ },
+ exits: [{ direction: "north", exitType: "door" }],
+ tags: ["starter", "entry"],
+ mvp: true,
+ },
+ ],
+ townServices: [
+ {
+ id: "service.market",
+ name: "Market",
+ serviceType: "market",
+ mvp: true,
+ },
+ ],
+} satisfies ContentPack;
+
+export const sampleContentPack = contentPackSchema.parse(samplePack);
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..c475b8a
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+
+import App from "./App";
+import "./styles.css";
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+
+
+ ,
+);
diff --git a/src/schemas/content.ts b/src/schemas/content.ts
new file mode 100644
index 0000000..1ef5474
--- /dev/null
+++ b/src/schemas/content.ts
@@ -0,0 +1,203 @@
+import { z } from "zod";
+
+import {
+ contentReferenceSchema,
+ diceKindSchema,
+ ruleEffectSchema,
+} from "./rules";
+
+export const tableEntrySchema = z.object({
+ key: z.string().min(1),
+ min: z.number().int().optional(),
+ max: z.number().int().optional(),
+ exact: z.number().int().optional(),
+ d66: z.number().int().optional(),
+ label: z.string().min(1),
+ text: z.string().optional(),
+ effects: z.array(ruleEffectSchema).optional(),
+ references: z.array(contentReferenceSchema).optional(),
+});
+
+export const tableDefinitionSchema = z.object({
+ id: z.string().min(1),
+ code: z.string().min(1),
+ name: z.string().min(1),
+ category: z.enum(["generic", "random-list", "loot", "level", "optional", "town", "room"]),
+ level: z.number().int().positive().optional(),
+ page: z.number().int().positive(),
+ diceKind: diceKindSchema,
+ usesModifiedRangesRule: z.boolean().optional(),
+ entries: z.array(tableEntrySchema),
+ notes: z.array(z.string()).optional(),
+ mvp: z.boolean(),
+});
+
+export const weaponDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ category: z.enum(["melee", "ranged"]),
+ handedness: z.enum(["one-handed", "two-handed"]),
+ baseDamage: z.number().int(),
+ allowedManoeuvreIds: z.array(z.string()),
+ tags: z.array(z.string()),
+ startingOption: z.boolean(),
+});
+
+export const manoeuvreDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ weaponCategories: z.array(z.enum(["melee", "ranged"])),
+ shiftCost: z.number().int().optional(),
+ disciplineModifier: z.number().int().optional(),
+ precisionModifier: z.number().int().optional(),
+ damageModifier: z.number().int().optional(),
+ exactStrikeBonus: z.boolean().optional(),
+ interruptRule: z.string().optional(),
+ effectText: z.string().optional(),
+ mvp: z.boolean(),
+});
+
+export const armourDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ armourValue: z.number().int(),
+ penalties: z
+ .object({
+ shift: z.number().int().optional(),
+ discipline: z.number().int().optional(),
+ precision: z.number().int().optional(),
+ })
+ .optional(),
+ deflectionRule: z.string().optional(),
+ startingOption: z.boolean(),
+ valueGp: z.number().int().optional(),
+ mvp: z.boolean(),
+});
+
+export const itemDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ itemType: z.enum([
+ "gear",
+ "treasure",
+ "quest",
+ "herb",
+ "rune",
+ "misc",
+ "ration",
+ "light-source",
+ ]),
+ stackable: z.boolean(),
+ consumable: z.boolean(),
+ valueGp: z.number().int().optional(),
+ weight: z.number().optional(),
+ rulesText: z.string().optional(),
+ effects: z.array(ruleEffectSchema).optional(),
+ mvp: z.boolean(),
+});
+
+export const potionDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ tableSource: z.string().min(1),
+ useTiming: z.enum(["combat", "exploration", "town", "any"]),
+ effects: z.array(ruleEffectSchema),
+ valueGp: z.number().int().optional(),
+ mvp: z.boolean(),
+});
+
+export const scrollDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ tableSource: z.string().min(1),
+ castCheck: z
+ .object({
+ diceKind: z.enum(["d6", "2d6"]),
+ successMin: z.number().int().optional(),
+ successMax: z.number().int().optional(),
+ })
+ .optional(),
+ onSuccess: z.array(ruleEffectSchema),
+ onFailureTableCode: z.string().optional(),
+ valueGp: z.number().int().optional(),
+ startingOption: z.boolean(),
+ mvp: z.boolean(),
+});
+
+export const creatureDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ level: z.number().int().positive(),
+ category: z.string().min(1),
+ hp: z.number().int().positive(),
+ attackProfile: z.object({
+ discipline: z.number().int(),
+ precision: z.number().int(),
+ damage: z.number().int(),
+ numberAppearing: z.string().optional(),
+ }),
+ defenceProfile: z
+ .object({
+ armour: z.number().int().optional(),
+ specialRules: z.array(z.string()).optional(),
+ })
+ .optional(),
+ xpReward: z.number().int().optional(),
+ lootTableCodes: z.array(z.string()).optional(),
+ interruptRules: z.array(z.string()).optional(),
+ traits: z.array(z.string()).optional(),
+ sourcePage: z.number().int().positive(),
+ mvp: z.boolean(),
+});
+
+export const exitTemplateSchema = z.object({
+ direction: z.enum(["north", "east", "south", "west"]).optional(),
+ exitType: z.enum(["open", "door", "locked", "secret", "shaft", "stairs"]),
+ destinationLevel: z.number().int().optional(),
+});
+
+export const roomTemplateSchema = z.object({
+ id: z.string().min(1),
+ level: z.number().int().positive(),
+ roomClass: z.enum(["normal", "small", "large", "special", "start", "stairs"]),
+ tableCode: z.string().min(1),
+ tableEntryKey: z.string().min(1),
+ title: z.string().min(1),
+ text: z.string().optional(),
+ dimensions: z
+ .object({
+ width: z.number().int().positive(),
+ height: z.number().int().positive(),
+ })
+ .optional(),
+ exits: z.array(exitTemplateSchema).optional(),
+ encounterRefs: z.array(contentReferenceSchema).optional(),
+ objectRefs: z.array(contentReferenceSchema).optional(),
+ tags: z.array(z.string()),
+ mvp: z.boolean(),
+});
+
+export const townServiceDefinitionSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ serviceType: z.enum(["market", "temple", "tavern", "healer", "smith", "quest"]),
+ tableCodes: z.array(z.string()).optional(),
+ costRules: z.array(z.string()).optional(),
+ effects: z.array(ruleEffectSchema).optional(),
+ mvp: z.boolean(),
+});
+
+export const contentPackSchema = z.object({
+ version: z.string().min(1),
+ sourceBooks: z.array(z.string()),
+ tables: z.array(tableDefinitionSchema),
+ weapons: z.array(weaponDefinitionSchema),
+ manoeuvres: z.array(manoeuvreDefinitionSchema),
+ armour: z.array(armourDefinitionSchema),
+ items: z.array(itemDefinitionSchema),
+ potions: z.array(potionDefinitionSchema),
+ scrolls: z.array(scrollDefinitionSchema),
+ creatures: z.array(creatureDefinitionSchema),
+ roomTemplates: z.array(roomTemplateSchema),
+ townServices: z.array(townServiceDefinitionSchema),
+});
diff --git a/src/schemas/rules.ts b/src/schemas/rules.ts
new file mode 100644
index 0000000..b9cabdc
--- /dev/null
+++ b/src/schemas/rules.ts
@@ -0,0 +1,71 @@
+import { z } from "zod";
+
+export const diceKindSchema = z.enum(["d3", "d6", "2d6", "d66"]);
+export const contentReferenceTypeSchema = z.enum([
+ "table",
+ "weapon",
+ "manoeuvre",
+ "armour",
+ "item",
+ "potion",
+ "scroll",
+ "creature",
+ "room",
+ "service",
+]);
+
+export const contentReferenceSchema = z.object({
+ type: contentReferenceTypeSchema,
+ id: z.string().min(1),
+});
+
+export const ruleEffectSchema = z.object({
+ type: z.enum([
+ "gain-xp",
+ "gain-gold",
+ "heal",
+ "take-damage",
+ "modify-shift",
+ "modify-discipline",
+ "modify-precision",
+ "apply-status",
+ "remove-status",
+ "add-item",
+ "remove-item",
+ "start-combat",
+ "reveal-exit",
+ "move-level",
+ "log-only",
+ ]),
+ amount: z.number().optional(),
+ statusId: z.string().optional(),
+ target: z.enum(["self", "enemy", "room", "campaign"]).optional(),
+ referenceId: z.string().optional(),
+ notes: z.string().optional(),
+});
+
+export const rollResultSchema = z.object({
+ diceKind: diceKindSchema,
+ rolls: z.array(z.number().int()),
+ primary: z.number().int().optional(),
+ secondary: z.number().int().optional(),
+ total: z.number().int().optional(),
+ modifier: z.number().int().optional(),
+ modifiedTotal: z.number().int().optional(),
+ clamped: z.boolean().optional(),
+});
+
+export const logEntrySchema = z.object({
+ id: z.string().min(1),
+ at: z.string().min(1),
+ type: z.enum(["system", "roll", "combat", "loot", "room", "town", "progression"]),
+ text: z.string().min(1),
+ relatedIds: z.array(z.string()).optional(),
+});
+
+export const actionResolutionSchema = z.object({
+ success: z.boolean(),
+ effects: z.array(ruleEffectSchema),
+ logEntries: z.array(logEntrySchema),
+ warnings: z.array(z.string()).optional(),
+});
diff --git a/src/schemas/state.ts b/src/schemas/state.ts
new file mode 100644
index 0000000..6b351cb
--- /dev/null
+++ b/src/schemas/state.ts
@@ -0,0 +1,217 @@
+import { z } from "zod";
+
+import { logEntrySchema, rollResultSchema, ruleEffectSchema } from "./rules";
+
+export const statusInstanceSchema = z.object({
+ id: z.string().min(1),
+ source: z.string().optional(),
+ duration: z.enum(["round", "combat", "room", "run", "permanent"]).optional(),
+ value: z.number().optional(),
+ notes: z.string().optional(),
+});
+
+export const inventoryEntrySchema = z.object({
+ definitionId: z.string().min(1),
+ quantity: z.number().int().nonnegative(),
+ identified: z.boolean().optional(),
+ charges: z.number().int().optional(),
+ notes: z.string().optional(),
+});
+
+export const inventoryStateSchema = z.object({
+ carried: z.array(inventoryEntrySchema),
+ equipped: z.array(inventoryEntrySchema),
+ stored: z.array(inventoryEntrySchema),
+ currency: z.object({
+ gold: z.number().int().nonnegative(),
+ }),
+ rationCount: z.number().int().nonnegative(),
+ lightSources: z.array(inventoryEntrySchema),
+});
+
+export const adventurerStateSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ level: z.number().int().positive(),
+ xp: z.number().int().nonnegative(),
+ hp: z.object({
+ current: z.number().int(),
+ max: z.number().int().positive(),
+ base: z.number().int().positive(),
+ }),
+ stats: z.object({
+ shift: z.number().int(),
+ discipline: z.number().int(),
+ precision: z.number().int(),
+ }),
+ weaponId: z.string().min(1),
+ manoeuvreIds: z.array(z.string()),
+ armourId: z.string().optional(),
+ favour: z.record(z.string(), z.number().int()),
+ statuses: z.array(statusInstanceSchema),
+ inventory: inventoryStateSchema,
+ progressionFlags: z.array(z.string()),
+});
+
+export const townStateSchema = z.object({
+ visits: z.number().int().nonnegative(),
+ knownServices: z.array(z.string()),
+ stash: z.array(inventoryEntrySchema),
+ pendingSales: z.array(inventoryEntrySchema),
+ serviceFlags: z.array(z.string()),
+});
+
+export const questStateSchema = z.object({
+ id: z.string().min(1),
+ title: z.string().min(1),
+ status: z.enum(["available", "active", "completed", "failed"]),
+ progressFlags: z.array(z.string()),
+ rewardText: z.string().optional(),
+});
+
+export const runSummarySchema = z.object({
+ runId: z.string().min(1),
+ startedAt: z.string().min(1),
+ endedAt: z.string().optional(),
+ deepestLevel: z.number().int().positive(),
+ roomsVisited: z.number().int().nonnegative(),
+ creaturesDefeated: z.array(z.string()),
+ xpGained: z.number().int().nonnegative(),
+ treasureValue: z.number().int().nonnegative(),
+ outcome: z.enum(["escaped", "defeated", "saved-in-progress"]),
+});
+
+export const campaignStateSchema = z.object({
+ id: z.string().min(1),
+ createdAt: z.string().min(1),
+ updatedAt: z.string().min(1),
+ rulesVersion: z.string().min(1),
+ contentVersion: z.string().min(1),
+ adventurer: adventurerStateSchema,
+ unlockedLevels: z.array(z.number().int().positive()),
+ completedLevels: z.array(z.number().int().positive()),
+ townState: townStateSchema,
+ questState: z.array(questStateSchema),
+ campaignFlags: z.array(z.string()),
+ runHistory: z.array(runSummarySchema),
+});
+
+export const roomExitStateSchema = z.object({
+ id: z.string().min(1),
+ direction: z.enum(["north", "east", "south", "west"]),
+ exitType: z.enum(["open", "door", "locked", "secret", "shaft", "stairs"]),
+ discovered: z.boolean(),
+ traversable: z.boolean(),
+ leadsToRoomId: z.string().optional(),
+ destinationLevel: z.number().int().optional(),
+});
+
+export const encounterStateSchema = z.object({
+ id: z.string().min(1),
+ sourceTableCode: z.string().optional(),
+ creatureIds: z.array(z.string()),
+ resolved: z.boolean(),
+ surprise: z.boolean().optional(),
+ rewardPending: z.boolean().optional(),
+});
+
+export const roomObjectStateSchema = z.object({
+ id: z.string().min(1),
+ objectType: z.enum(["container", "altar", "corpse", "hazard", "feature", "quest"]),
+ sourceTableCode: z.string().optional(),
+ interacted: z.boolean(),
+ hidden: z.boolean().optional(),
+ effects: z.array(ruleEffectSchema).optional(),
+ notes: z.string().optional(),
+});
+
+export const roomStateSchema = z.object({
+ id: z.string().min(1),
+ level: z.number().int().positive(),
+ templateId: z.string().optional(),
+ position: z.object({
+ x: z.number().int(),
+ y: z.number().int(),
+ }),
+ dimensions: z.object({
+ width: z.number().int().positive(),
+ height: z.number().int().positive(),
+ }),
+ roomClass: z.enum(["normal", "small", "large", "special", "start", "stairs"]),
+ exits: z.array(roomExitStateSchema),
+ discovery: z.object({
+ generated: z.boolean(),
+ entered: z.boolean(),
+ cleared: z.boolean(),
+ searched: z.boolean(),
+ }),
+ encounter: encounterStateSchema.optional(),
+ objects: z.array(roomObjectStateSchema),
+ notes: z.array(z.string()),
+ flags: z.array(z.string()),
+});
+
+export const dungeonLevelStateSchema = z.object({
+ level: z.number().int().positive(),
+ themeName: z.string().optional(),
+ rooms: z.record(z.string(), roomStateSchema),
+ discoveredRoomOrder: z.array(z.string()),
+ stairsUpRoomId: z.string().optional(),
+ stairsDownRoomId: z.string().optional(),
+ secretDoorUsed: z.boolean().optional(),
+ exhaustedExitSearch: z.boolean().optional(),
+});
+
+export const dungeonStateSchema = z.object({
+ levels: z.record(z.string(), dungeonLevelStateSchema),
+ revealedPercentByLevel: z.record(z.string(), z.number()),
+ globalFlags: z.array(z.string()),
+});
+
+export const combatantStateSchema = z.object({
+ id: z.string().min(1),
+ name: z.string().min(1),
+ sourceDefinitionId: z.string().optional(),
+ hpCurrent: z.number().int(),
+ hpMax: z.number().int().positive(),
+ shift: z.number().int(),
+ discipline: z.number().int(),
+ precision: z.number().int(),
+ armourValue: z.number().int().optional(),
+ statuses: z.array(statusInstanceSchema),
+ traits: z.array(z.string()),
+});
+
+export const interruptStateSchema = z.object({
+ source: z.enum(["player", "enemy"]),
+ trigger: z.string().min(1),
+ effectText: z.string().min(1),
+ resolved: z.boolean(),
+});
+
+export const combatStateSchema = z.object({
+ id: z.string().min(1),
+ round: z.number().int().positive(),
+ actingSide: z.enum(["player", "enemy"]),
+ fatigueDie: z.number().int().optional(),
+ player: combatantStateSchema,
+ enemies: z.array(combatantStateSchema),
+ selectedManoeuvreId: z.string().optional(),
+ lastRoll: rollResultSchema.optional(),
+ pendingInterrupt: interruptStateSchema.optional(),
+ combatLog: z.array(logEntrySchema),
+});
+
+export const runStateSchema = z.object({
+ id: z.string().min(1),
+ campaignId: z.string().min(1),
+ status: z.enum(["active", "paused", "completed", "failed"]),
+ startedAt: z.string().min(1),
+ currentLevel: z.number().int().positive(),
+ currentRoomId: z.string().optional(),
+ dungeon: dungeonStateSchema,
+ adventurerSnapshot: adventurerStateSchema,
+ activeCombat: combatStateSchema.optional(),
+ log: z.array(logEntrySchema),
+ pendingEffects: z.array(ruleEffectSchema),
+});
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..aa16e3f
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,130 @@
+:root {
+ font-family: "Segoe UI", "Aptos", sans-serif;
+ color: #f3f1e8;
+ background:
+ radial-gradient(circle at top, rgba(179, 121, 59, 0.35), transparent 30%),
+ linear-gradient(180deg, #17130f 0%, #0d0b09 100%);
+ line-height: 1.5;
+ font-weight: 400;
+ color-scheme: dark;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+#root {
+ min-height: 100vh;
+}
+
+.app-shell {
+ width: min(1100px, calc(100% - 2rem));
+ margin: 0 auto;
+ padding: 3rem 0 4rem;
+}
+
+.hero {
+ padding: 2rem;
+ border: 1px solid rgba(243, 241, 232, 0.16);
+ background: rgba(21, 18, 14, 0.72);
+ box-shadow: 0 30px 80px rgba(0, 0, 0, 0.35);
+ backdrop-filter: blur(12px);
+}
+
+.eyebrow {
+ margin: 0 0 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ color: #d8b27a;
+ font-size: 0.8rem;
+}
+
+.hero h1 {
+ margin: 0;
+ font-size: clamp(2.5rem, 7vw, 4.5rem);
+ line-height: 0.95;
+}
+
+.lede {
+ width: min(55ch, 100%);
+ margin: 1.25rem 0 0;
+ color: rgba(243, 241, 232, 0.82);
+ font-size: 1.05rem;
+}
+
+.panel-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 1rem;
+ margin-top: 1rem;
+}
+
+.panel {
+ padding: 1.25rem;
+ border: 1px solid rgba(243, 241, 232, 0.14);
+ background: rgba(29, 24, 19, 0.82);
+}
+
+.panel h2 {
+ margin-top: 0;
+ margin-bottom: 0.75rem;
+ font-size: 1rem;
+ color: #f6d49e;
+}
+
+.panel ul,
+.panel ol {
+ margin: 0;
+ padding-left: 1.1rem;
+}
+
+.panel li + li {
+ margin-top: 0.45rem;
+}
+
+.stats {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0.75rem;
+ margin: 0;
+}
+
+.stats div {
+ padding: 0.9rem;
+ background: rgba(243, 241, 232, 0.05);
+}
+
+.stats dt {
+ color: rgba(243, 241, 232, 0.62);
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.stats dd {
+ margin: 0.3rem 0 0;
+ font-size: 1.7rem;
+ font-weight: 700;
+}
+
+@media (max-width: 640px) {
+ .app-shell {
+ width: min(100% - 1rem, 1100px);
+ padding-top: 1rem;
+ }
+
+ .hero,
+ .panel {
+ padding: 1rem;
+ }
+}
diff --git a/src/types/content.ts b/src/types/content.ts
new file mode 100644
index 0000000..4ef1ecc
--- /dev/null
+++ b/src/types/content.ts
@@ -0,0 +1,215 @@
+import type { ContentReference, DiceKind, RuleEffect } from "./rules";
+
+export type TableCategory =
+ | "generic"
+ | "random-list"
+ | "loot"
+ | "level"
+ | "optional"
+ | "town"
+ | "room";
+
+export type TableEntry = {
+ key: string;
+ min?: number;
+ max?: number;
+ exact?: number;
+ d66?: number;
+ label: string;
+ text?: string;
+ effects?: RuleEffect[];
+ references?: ContentReference[];
+};
+
+export type TableDefinition = {
+ id: string;
+ code: string;
+ name: string;
+ category: TableCategory;
+ level?: number;
+ page: number;
+ diceKind: DiceKind;
+ usesModifiedRangesRule?: boolean;
+ entries: TableEntry[];
+ notes?: string[];
+ mvp: boolean;
+};
+
+export type WeaponCategory = "melee" | "ranged";
+
+export type WeaponDefinition = {
+ id: string;
+ name: string;
+ category: WeaponCategory;
+ handedness: "one-handed" | "two-handed";
+ baseDamage: number;
+ allowedManoeuvreIds: string[];
+ tags: string[];
+ startingOption: boolean;
+};
+
+export type ManoeuvreDefinition = {
+ id: string;
+ name: string;
+ weaponCategories: WeaponCategory[];
+ shiftCost?: number;
+ disciplineModifier?: number;
+ precisionModifier?: number;
+ damageModifier?: number;
+ exactStrikeBonus?: boolean;
+ interruptRule?: string;
+ effectText?: string;
+ mvp: boolean;
+};
+
+export type ArmourDefinition = {
+ id: string;
+ name: string;
+ armourValue: number;
+ penalties?: {
+ shift?: number;
+ discipline?: number;
+ precision?: number;
+ };
+ deflectionRule?: string;
+ startingOption: boolean;
+ valueGp?: number;
+ mvp: boolean;
+};
+
+export type ItemType =
+ | "gear"
+ | "treasure"
+ | "quest"
+ | "herb"
+ | "rune"
+ | "misc"
+ | "ration"
+ | "light-source";
+
+export type ItemDefinition = {
+ id: string;
+ name: string;
+ itemType: ItemType;
+ stackable: boolean;
+ consumable: boolean;
+ valueGp?: number;
+ weight?: number;
+ rulesText?: string;
+ effects?: RuleEffect[];
+ mvp: boolean;
+};
+
+export type PotionUseTiming = "combat" | "exploration" | "town" | "any";
+
+export type PotionDefinition = {
+ id: string;
+ name: string;
+ tableSource: string;
+ useTiming: PotionUseTiming;
+ effects: RuleEffect[];
+ valueGp?: number;
+ mvp: boolean;
+};
+
+export type ScrollDefinition = {
+ id: string;
+ name: string;
+ tableSource: string;
+ castCheck?: {
+ diceKind: Extract;
+ successMin?: number;
+ successMax?: number;
+ };
+ onSuccess: RuleEffect[];
+ onFailureTableCode?: string;
+ valueGp?: number;
+ startingOption: boolean;
+ mvp: boolean;
+};
+
+export type CreatureDefinition = {
+ id: string;
+ name: string;
+ level: number;
+ category: string;
+ hp: number;
+ attackProfile: {
+ discipline: number;
+ precision: number;
+ damage: number;
+ numberAppearing?: string;
+ };
+ defenceProfile?: {
+ armour?: number;
+ specialRules?: string[];
+ };
+ xpReward?: number;
+ lootTableCodes?: string[];
+ interruptRules?: string[];
+ traits?: string[];
+ sourcePage: number;
+ mvp: boolean;
+};
+
+export type ExitType = "open" | "door" | "locked" | "secret" | "shaft" | "stairs";
+
+export type ExitTemplate = {
+ direction?: "north" | "east" | "south" | "west";
+ exitType: ExitType;
+ destinationLevel?: number;
+};
+
+export type RoomClass = "normal" | "small" | "large" | "special" | "start" | "stairs";
+
+export type RoomTemplate = {
+ id: string;
+ level: number;
+ roomClass: RoomClass;
+ tableCode: string;
+ tableEntryKey: string;
+ title: string;
+ text?: string;
+ dimensions?: {
+ width: number;
+ height: number;
+ };
+ exits?: ExitTemplate[];
+ encounterRefs?: ContentReference[];
+ objectRefs?: ContentReference[];
+ tags: string[];
+ mvp: boolean;
+};
+
+export type TownServiceType =
+ | "market"
+ | "temple"
+ | "tavern"
+ | "healer"
+ | "smith"
+ | "quest";
+
+export type TownServiceDefinition = {
+ id: string;
+ name: string;
+ serviceType: TownServiceType;
+ tableCodes?: string[];
+ costRules?: string[];
+ effects?: RuleEffect[];
+ mvp: boolean;
+};
+
+export type ContentPack = {
+ version: string;
+ sourceBooks: string[];
+ tables: TableDefinition[];
+ weapons: WeaponDefinition[];
+ manoeuvres: ManoeuvreDefinition[];
+ armour: ArmourDefinition[];
+ items: ItemDefinition[];
+ potions: PotionDefinition[];
+ scrolls: ScrollDefinition[];
+ creatures: CreatureDefinition[];
+ roomTemplates: RoomTemplate[];
+ townServices: TownServiceDefinition[];
+};
diff --git a/src/types/rules.ts b/src/types/rules.ts
new file mode 100644
index 0000000..34d73c6
--- /dev/null
+++ b/src/types/rules.ts
@@ -0,0 +1,81 @@
+export type DiceKind = "d3" | "d6" | "2d6" | "d66";
+
+export type ContentReferenceType =
+ | "table"
+ | "weapon"
+ | "manoeuvre"
+ | "armour"
+ | "item"
+ | "potion"
+ | "scroll"
+ | "creature"
+ | "room"
+ | "service";
+
+export type ContentReference = {
+ type: ContentReferenceType;
+ id: string;
+};
+
+export type RuleEffectType =
+ | "gain-xp"
+ | "gain-gold"
+ | "heal"
+ | "take-damage"
+ | "modify-shift"
+ | "modify-discipline"
+ | "modify-precision"
+ | "apply-status"
+ | "remove-status"
+ | "add-item"
+ | "remove-item"
+ | "start-combat"
+ | "reveal-exit"
+ | "move-level"
+ | "log-only";
+
+export type RuleEffectTarget = "self" | "enemy" | "room" | "campaign";
+
+export type RuleEffect = {
+ type: RuleEffectType;
+ amount?: number;
+ statusId?: string;
+ target?: RuleEffectTarget;
+ referenceId?: string;
+ notes?: string;
+};
+
+export type RollResult = {
+ diceKind: DiceKind;
+ rolls: number[];
+ primary?: number;
+ secondary?: number;
+ total?: number;
+ modifier?: number;
+ modifiedTotal?: number;
+ clamped?: boolean;
+};
+
+export type LogEntryType =
+ | "system"
+ | "roll"
+ | "combat"
+ | "loot"
+ | "room"
+ | "town"
+ | "progression";
+
+export type LogEntry = {
+ id: string;
+ at: string;
+ type: LogEntryType;
+ text: string;
+ relatedIds?: string[];
+};
+
+export type ActionResolution = {
+ success: boolean;
+ effects: RuleEffect[];
+ logEntries: LogEntry[];
+ warnings?: string[];
+};
diff --git a/src/types/state.ts b/src/types/state.ts
new file mode 100644
index 0000000..d98200d
--- /dev/null
+++ b/src/types/state.ts
@@ -0,0 +1,218 @@
+import type { ExitType, RoomClass } from "./content";
+import type { LogEntry, RollResult, RuleEffect } from "./rules";
+
+export type StatusDuration = "round" | "combat" | "room" | "run" | "permanent";
+
+export type StatusInstance = {
+ id: string;
+ source?: string;
+ duration?: StatusDuration;
+ value?: number;
+ notes?: string;
+};
+
+export type InventoryEntry = {
+ definitionId: string;
+ quantity: number;
+ identified?: boolean;
+ charges?: number;
+ notes?: string;
+};
+
+export type InventoryState = {
+ carried: InventoryEntry[];
+ equipped: InventoryEntry[];
+ stored: InventoryEntry[];
+ currency: {
+ gold: number;
+ };
+ rationCount: number;
+ lightSources: InventoryEntry[];
+};
+
+export type AdventurerState = {
+ id: string;
+ name: string;
+ level: number;
+ xp: number;
+ hp: {
+ current: number;
+ max: number;
+ base: number;
+ };
+ stats: {
+ shift: number;
+ discipline: number;
+ precision: number;
+ };
+ weaponId: string;
+ manoeuvreIds: string[];
+ armourId?: string;
+ favour: Record;
+ statuses: StatusInstance[];
+ inventory: InventoryState;
+ progressionFlags: string[];
+};
+
+export type TownState = {
+ visits: number;
+ knownServices: string[];
+ stash: InventoryEntry[];
+ pendingSales: InventoryEntry[];
+ serviceFlags: string[];
+};
+
+export type QuestState = {
+ id: string;
+ title: string;
+ status: "available" | "active" | "completed" | "failed";
+ progressFlags: string[];
+ rewardText?: string;
+};
+
+export type RunSummary = {
+ runId: string;
+ startedAt: string;
+ endedAt?: string;
+ deepestLevel: number;
+ roomsVisited: number;
+ creaturesDefeated: string[];
+ xpGained: number;
+ treasureValue: number;
+ outcome: "escaped" | "defeated" | "saved-in-progress";
+};
+
+export type CampaignState = {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ rulesVersion: string;
+ contentVersion: string;
+ adventurer: AdventurerState;
+ unlockedLevels: number[];
+ completedLevels: number[];
+ townState: TownState;
+ questState: QuestState[];
+ campaignFlags: string[];
+ runHistory: RunSummary[];
+};
+
+export type RoomExitState = {
+ id: string;
+ direction: "north" | "east" | "south" | "west";
+ exitType: ExitType;
+ discovered: boolean;
+ traversable: boolean;
+ leadsToRoomId?: string;
+ destinationLevel?: number;
+};
+
+export type EncounterState = {
+ id: string;
+ sourceTableCode?: string;
+ creatureIds: string[];
+ resolved: boolean;
+ surprise?: boolean;
+ rewardPending?: boolean;
+};
+
+export type RoomObjectState = {
+ id: string;
+ objectType: "container" | "altar" | "corpse" | "hazard" | "feature" | "quest";
+ sourceTableCode?: string;
+ interacted: boolean;
+ hidden?: boolean;
+ effects?: RuleEffect[];
+ notes?: string;
+};
+
+export type RoomState = {
+ id: string;
+ level: number;
+ templateId?: string;
+ position: {
+ x: number;
+ y: number;
+ };
+ dimensions: {
+ width: number;
+ height: number;
+ };
+ roomClass: RoomClass;
+ exits: RoomExitState[];
+ discovery: {
+ generated: boolean;
+ entered: boolean;
+ cleared: boolean;
+ searched: boolean;
+ };
+ encounter?: EncounterState;
+ objects: RoomObjectState[];
+ notes: string[];
+ flags: string[];
+};
+
+export type DungeonLevelState = {
+ level: number;
+ themeName?: string;
+ rooms: Record;
+ discoveredRoomOrder: string[];
+ stairsUpRoomId?: string;
+ stairsDownRoomId?: string;
+ secretDoorUsed?: boolean;
+ exhaustedExitSearch?: boolean;
+};
+
+export type DungeonState = {
+ levels: Record;
+ revealedPercentByLevel: Record;
+ globalFlags: string[];
+};
+
+export type CombatantState = {
+ id: string;
+ name: string;
+ sourceDefinitionId?: string;
+ hpCurrent: number;
+ hpMax: number;
+ shift: number;
+ discipline: number;
+ precision: number;
+ armourValue?: number;
+ statuses: StatusInstance[];
+ traits: string[];
+};
+
+export type InterruptState = {
+ source: "player" | "enemy";
+ trigger: string;
+ effectText: string;
+ resolved: boolean;
+};
+
+export type CombatState = {
+ id: string;
+ round: number;
+ actingSide: "player" | "enemy";
+ fatigueDie?: number;
+ player: CombatantState;
+ enemies: CombatantState[];
+ selectedManoeuvreId?: string;
+ lastRoll?: RollResult;
+ pendingInterrupt?: InterruptState;
+ combatLog: LogEntry[];
+};
+
+export type RunState = {
+ id: string;
+ campaignId: string;
+ status: "active" | "paused" | "completed" | "failed";
+ startedAt: string;
+ currentLevel: number;
+ currentRoomId?: string;
+ dungeon: DungeonState;
+ adventurerSnapshot: AdventurerState;
+ activeCombat?: CombatState;
+ log: LogEntry[];
+ pendingEffects: RuleEffect[];
+};
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 0000000..94dc86a
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": false,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..a2d0e4c
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowSyntheticDefaultImports": true,
+ "types": ["node"]
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..ad58148
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import path from "node:path";
+
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+});