diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..ab1ad2eca42cf82d1f8a3c3f5c09b9c585e65d26
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,43 @@
+# Git
+.git
+.gitignore
+.gitattributes
+
+# Documentation
+README.md
+*.md
+
+# Node modules (will be installed during build)
+node_modules
+**/node_modules
+
+# Build outputs (will be generated during build)
+dist
+**/dist
+
+# Test files
+tests
+**/tests
+*.test.ts
+*.test.js
+
+# Development files
+.vscode
+.idea
+*.log
+*.lock
+
+# Environment files
+.env
+.env.local
+.env.*.local
+
+# Temporary files
+*.tmp
+*.temp
+tmp/
+temp/
+
+# OS files
+.DS_Store
+Thumbs.db
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..6f434b0bd9c0947776803d830991d90f01d8998f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,42 @@
+FROM node:20-slim
+
+# Set noninteractive installation
+ENV DEBIAN_FRONTEND=noninteractive
+
+# Install build dependencies
+RUN apt-get update && apt-get install -y \
+ curl \
+ git \
+ && rm -rf /var/lib/apt/lists/*
+
+# Create app directory
+WORKDIR /app
+
+# Copy entire trigo-web project
+COPY trigo-web/ ./
+
+# Install all dependencies (root, app, backend)
+RUN npm install && \
+ cd app && npm install && \
+ cd ../backend && npm install && \
+ cd ..
+
+# Build jison parsers (required for the game)
+RUN npm run build:parsers
+
+# Build frontend (generates dist folder)
+RUN cd app && npm run build
+
+# Build backend TypeScript
+RUN cd backend && npm run build
+
+# Set environment variables for Hugging Face Spaces
+ENV PORT=7860
+ENV HOST=0.0.0.0
+ENV NODE_ENV=production
+
+# Expose port 7860 (required by Hugging Face Spaces)
+EXPOSE 7860
+
+# Start backend server (which will also serve frontend static files)
+CMD ["npm", "run", "start:prod"]
diff --git a/deploy.sh b/deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..07e652172f5d5c53ab43ee0cdb332f5cc0f2f7de
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+# Trigo Hugging Face Space Deployment Script
+# This script copies the trigo-web project to the HF Space repo and prepares it for deployment
+
+set -e
+
+echo "๐ Trigo HF Space Deployment Script"
+echo "===================================="
+echo ""
+
+# Define paths
+PROJECT_ROOT="/home/camus/work/trigo"
+TRIGO_WEB="$PROJECT_ROOT/trigo-web"
+HF_SPACE="$PROJECT_ROOT/third_party/trigo-hfspace"
+
+# Check if directories exist
+if [ ! -d "$TRIGO_WEB" ]; then
+ echo "โ Error: trigo-web directory not found at $TRIGO_WEB"
+ exit 1
+fi
+
+if [ ! -d "$HF_SPACE" ]; then
+ echo "โ Error: HF Space directory not found at $HF_SPACE"
+ exit 1
+fi
+
+echo "๐ Project directory: $TRIGO_WEB"
+echo "๐ HF Space directory: $HF_SPACE"
+echo ""
+
+# Clean up old trigo-web in HF space if it exists
+if [ -d "$HF_SPACE/trigo-web" ]; then
+ echo "๐งน Cleaning up old trigo-web directory..."
+ rm -rf "$HF_SPACE/trigo-web"
+fi
+
+# Copy trigo-web to HF space
+echo "๐ฆ Copying trigo-web to HF Space repository..."
+cp -r "$TRIGO_WEB" "$HF_SPACE/"
+
+# Remove node_modules and dist folders (will be built in Docker)
+echo "๐งน Removing node_modules and dist folders (will be rebuilt in Docker)..."
+find "$HF_SPACE/trigo-web" -type d -name "node_modules" -exec rm -rf {} + 2>/dev/null || true
+find "$HF_SPACE/trigo-web" -type d -name "dist" -exec rm -rf {} + 2>/dev/null || true
+
+# Remove test files
+echo "๐งน Removing test files..."
+rm -rf "$HF_SPACE/trigo-web/tests" 2>/dev/null || true
+
+echo ""
+echo "โ
Files prepared successfully!"
+echo ""
+echo "๐ Next steps:"
+echo " 1. cd $HF_SPACE"
+echo " 2. git add ."
+echo " 3. git commit -m 'Deploy Trigo game'"
+echo " 4. git push"
+echo ""
+echo "๐ After pushing, HF will automatically build and deploy your app."
+echo ""
diff --git a/trigo-web/.prettierignore b/trigo-web/.prettierignore
new file mode 100644
index 0000000000000000000000000000000000000000..b90674a8c0cdd3b5b87626333412f33dbec7d612
--- /dev/null
+++ b/trigo-web/.prettierignore
@@ -0,0 +1,9 @@
+node_modules
+dist
+build
+coverage
+.vscode
+.idea
+*.log
+*.min.js
+*.min.css
\ No newline at end of file
diff --git a/trigo-web/.prettierrc b/trigo-web/.prettierrc
new file mode 100644
index 0000000000000000000000000000000000000000..cec11afd0936e4530cc67e068f9ad39a492aaf07
--- /dev/null
+++ b/trigo-web/.prettierrc
@@ -0,0 +1,17 @@
+{
+ "semi": true,
+ "trailingComma": "none",
+ "singleQuote": false,
+ "printWidth": 100,
+ "tabWidth": 4,
+ "useTabs": true,
+ "bracketSpacing": true,
+ "arrowParens": "always",
+ "endOfLine": "lf",
+ "vueIndentScriptAndStyle": true,
+ "quoteProps": "as-needed",
+ "bracketSameLine": false,
+ "jsxSingleQuote": false,
+ "proseWrap": "preserve",
+ "htmlWhitespaceSensitivity": "css"
+}
\ No newline at end of file
diff --git a/trigo-web/README.md b/trigo-web/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e80cb99eea6ad5497b0ab43482b009402440684a
--- /dev/null
+++ b/trigo-web/README.md
@@ -0,0 +1,227 @@
+# Trigo - 3D Go Board Game
+
+A modern web implementation of Go played on a three-dimensional board, built with Vue 3, TypeScript, Node.js, and Three.js.
+
+## Features
+
+- **3D Board**: Play Go on a 5x5x5 three-dimensional board
+- **Multiplayer**: Real-time online gameplay via WebSocket
+- **Single Player**: Play against AI opponents
+- **Game Replay**: Review and analyze completed games
+- **Modern Tech Stack**: Vue 3, TypeScript, Three.js for WebGL rendering
+
+## Project Structure
+
+```
+trigo-web/
+โโโ app/ # Frontend application (Vue 3)
+โ โโโ src/
+โ โ โโโ components/ # Vue components
+โ โ โโโ views/ # Page views
+โ โ โโโ game/ # Game logic and 3D rendering
+โ โ โโโ store/ # Pinia state management
+โ โ โโโ services/ # API and WebSocket services
+โ โโโ package.json
+โโโ backend/ # Backend server (Node.js)
+โ โโโ src/
+โ โ โโโ controllers/
+โ โ โโโ services/ # Game logic and room management
+โ โ โโโ sockets/ # Socket.io handlers
+โ โ โโโ server.ts # Main server entry
+โ โโโ package.json
+โโโ inc/ # Shared code between frontend and backend
+โ โโโ trigo/ # Core game logic and types
+โ โ โโโ types.ts # TypeScript interfaces
+โ โ โโโ gameUtils.ts # Game utility functions (capture, Ko, territory)
+โ โ โโโ game.ts # TrigoGame class - main game state management
+โ โ โโโ ab0yz.ts # TGN coordinate encoding
+โโโ tests/ # Test files
+โ โโโ game/ # Game logic tests (TrigoGame)
+โโโ vitest.config.ts # Vitest test configuration
+โโโ tsconfig.json # TypeScript configuration
+โโโ package.json # Root package.json with dev scripts
+```
+
+## Getting Started
+
+### Prerequisites
+
+- Node.js (v18+ recommended)
+- npm or yarn
+
+### Installation
+
+1. Clone the repository:
+```bash
+git clone [repository-url]
+cd trigo-web
+```
+
+2. Install all dependencies:
+```bash
+npm run install:all
+```
+
+This will install dependencies for the root, frontend, and backend.
+
+### Development
+
+Run both frontend and backend in development mode:
+
+```bash
+npm run dev
+```
+
+Or run them separately:
+
+```bash
+# Frontend only (runs on http://localhost:5173)
+npm run dev:app
+
+# Backend only (runs on http://localhost:3000)
+npm run dev:backend
+```
+
+### Building for Production
+
+Build both frontend and backend:
+
+```bash
+npm run build
+```
+
+### Testing
+
+The project includes comprehensive unit tests for the game logic (TrigoGame class).
+
+**Run all tests once:**
+```bash
+npm run test:run
+```
+
+**Run tests in watch mode (auto-rerun on file changes):**
+```bash
+npm test
+```
+
+**Run tests with UI dashboard:**
+```bash
+npm run test:ui
+```
+
+**Run specific test file:**
+```bash
+npm exec vitest -- run tests/game/trigoGame.core.test.ts
+```
+
+**Test Coverage:**
+- **109/109 tests passing (100%)**
+- Core functionality (35 tests) - Drop, pass, surrender, reset
+- History management (21 tests) - Undo, redo, jump to step
+- Game rules (18 tests) - Capture, Ko rule, suicide, territory calculation
+- State management (32 tests) - Serialization, callbacks, session storage
+
+**Test Files Location:** `tests/game/`
+- `trigoGame.core.test.ts` - Basic game operations
+- `trigoGame.history.test.ts` - History and navigation
+- `trigoGame.rules.test.ts` - Go game rules implementation
+- `trigoGame.state.test.ts` - State persistence and callbacks
+
+### Code Formatting
+
+Format all code with Prettier:
+
+```bash
+npm run format
+```
+
+Check formatting without making changes:
+
+```bash
+npm run format:check
+```
+
+## Technology Stack
+
+### Frontend
+- **Vue 3** - Progressive JavaScript framework
+- **TypeScript** - Type-safe JavaScript
+- **Three.js** - 3D graphics library for WebGL
+- **Pinia** - State management
+- **Vue Router** - Client-side routing
+- **Socket.io Client** - WebSocket communication
+- **Vite** - Fast build tool
+- **SASS** - CSS preprocessor
+
+### Backend
+- **Node.js** - JavaScript runtime
+- **Express** - Web framework
+- **Socket.io** - Real-time bidirectional communication
+- **TypeScript** - Type-safe JavaScript
+- **UUID** - Unique ID generation
+
+## Game Rules
+
+Trigo extends the traditional Go game into three dimensions:
+
+1. The game is played on a 5x5x5 cubic board
+2. Players take turns placing stones (black and white)
+3. Stones are captured when completely surrounded in 3D space
+4. The goal is to control more territory than your opponent
+
+## API Endpoints
+
+### REST API
+- `GET /health` - Health check
+- `GET /api/rooms` - List active game rooms
+- `GET /api/rooms/:roomId` - Get specific room details
+
+### WebSocket Events
+
+#### Client โ Server
+- `joinRoom` - Join or create a game room
+- `leaveRoom` - Leave current room
+- `makeMove` - Make a game move
+- `pass` - Pass turn
+- `resign` - Resign from game
+- `chatMessage` - Send chat message
+
+#### Server โ Client
+- `roomJoined` - Successfully joined room
+- `gameUpdate` - Game state update
+- `playerJoined` - Another player joined
+- `playerLeft` - Player left room
+- `chatMessage` - Incoming chat message
+- `gameEnded` - Game finished
+
+## Development Guidelines
+
+### Code Style
+- Uses Prettier for consistent formatting
+- Tab indentation (following prototype style)
+- Double quotes for strings
+- No trailing commas
+- Semicolons always
+
+### Git Workflow
+1. Create feature branch from `main`
+2. Make changes and test locally
+3. Format code with `npm run format`
+4. Commit with descriptive messages
+5. Create pull request
+
+## Based On
+
+This project is a modern reimplementation of the original Trigo prototype found in `third_party/klstrigo/`, updating the technology stack while preserving the core game mechanics and 3D gameplay experience.
+
+## License
+
+MIT License - See LICENSE file for details
+
+## Contributing
+
+Contributions are welcome! Please read the contributing guidelines before submitting pull requests.
+
+## Support
+
+For issues, questions, or suggestions, please open an issue on GitHub.
\ No newline at end of file
diff --git a/trigo-web/app/.env b/trigo-web/app/.env
new file mode 100644
index 0000000000000000000000000000000000000000..0c3534bf1d4197aebf30668fd9e897dc253084bc
--- /dev/null
+++ b/trigo-web/app/.env
@@ -0,0 +1,3 @@
+VITE_SERVER_URL=http://localhost:3000
+VITE_HOST=0.0.0.0
+VITE_PORT=5173
\ No newline at end of file
diff --git a/trigo-web/app/.env.local.example b/trigo-web/app/.env.local.example
new file mode 100644
index 0000000000000000000000000000000000000000..d61235d2eef552fe58413ef085525ea068a1e1d0
--- /dev/null
+++ b/trigo-web/app/.env.local.example
@@ -0,0 +1,11 @@
+# Local environment variables (not committed to git)
+# Override these values for your local development setup
+
+# Example: To expose the dev server to your network:
+# VITE_HOST=0.0.0.0
+
+# Example: To use a different port:
+# VITE_PORT=3001
+
+# Example: To connect to a different backend:
+# VITE_SERVER_URL=http://192.168.1.100:3000
\ No newline at end of file
diff --git a/trigo-web/app/.gitignore b/trigo-web/app/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e61e33f669ec1fb15a51f23a79acd052239f57c2
--- /dev/null
+++ b/trigo-web/app/.gitignore
@@ -0,0 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Environment files
+.env.local
+.env.*.local
\ No newline at end of file
diff --git a/trigo-web/app/index.html b/trigo-web/app/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..140426b3a21b8869ea68ed9d47793ebea98ff135
--- /dev/null
+++ b/trigo-web/app/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Trigo - 3D Go Game
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trigo-web/app/package-lock.json b/trigo-web/app/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..033dd000e8dd8d58a95d48f6a3948eb16fe6d2a3
--- /dev/null
+++ b/trigo-web/app/package-lock.json
@@ -0,0 +1,3888 @@
+{
+ "name": "trigo-app",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "trigo-app",
+ "version": "0.0.0",
+ "dependencies": {
+ "pinia": "^2.1.6",
+ "socket.io-client": "^4.5.2",
+ "three": "^0.156.1",
+ "vue": "^3.3.4",
+ "vue-router": "^4.2.4"
+ },
+ "devDependencies": {
+ "@types/three": "^0.156.0",
+ "@vitejs/plugin-vue": "^5.2.4",
+ "@vitest/ui": "^4.0.6",
+ "jsdom": "^27.1.0",
+ "sass-embedded": "^1.93.2",
+ "typescript": "^5.2.2",
+ "vite": "^5.4.21",
+ "vitest": "^4.0.6",
+ "vue-tsc": "^2.2.12"
+ }
+ },
+ "node_modules/@acemir/cssom": {
+ "version": "0.9.19",
+ "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.19.tgz",
+ "integrity": "sha512-Pp2gAQXPZ2o7lt4j0IMwNRXqQ3pagxtDj5wctL5U2Lz4oV0ocDNlkgx4DpxfyKav4S/bePuI+SMqcBSUHLy9kg==",
+ "dev": true
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
+ "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
+ "dev": true,
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-color-parser": "^3.1.0",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "lru-cache": "^11.2.1"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "6.7.4",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz",
+ "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==",
+ "dev": true,
+ "dependencies": {
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.1.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.2"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true
+ },
+ "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==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
+ "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+ "dependencies": {
+ "@babel/types": "^7.28.4"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
+ "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bufbuild/protobuf": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.0.tgz",
+ "integrity": "sha512-fdRs9PSrBF7QUntpZpq6BTw58fhgGJojgg39m9oFOJGZT+nip9b0so5cYY1oWl5pvemDLr0cPPsH46vwThEbpQ==",
+ "dev": true
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz",
+ "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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=="
+ },
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
+ "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
+ "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
+ "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
+ "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
+ "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
+ "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
+ "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
+ "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
+ "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
+ "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
+ "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
+ "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
+ "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
+ "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
+ "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
+ "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
+ "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
+ "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
+ "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
+ "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
+ "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
+ "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "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=="
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "dev": true
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true
+ },
+ "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
+ },
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
+ "dev": true
+ },
+ "node_modules/@types/three": {
+ "version": "0.156.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.156.0.tgz",
+ "integrity": "sha512-733bXDSRdlrxqOmQuOmfC1UBRuJ2pREPk8sWnx9MtIJEVDQMx8U0NQO5MVVaOrjzDPyLI+cFPim2X/ss9v0+LQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/stats.js": "*",
+ "@types/webxr": "*",
+ "fflate": "~0.6.10",
+ "meshoptimizer": "~0.18.1"
+ }
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+ "dev": true,
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.6.tgz",
+ "integrity": "sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg==",
+ "dev": true,
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.0.6",
+ "@vitest/utils": "4.0.6",
+ "chai": "^6.0.1",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.6.tgz",
+ "integrity": "sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g==",
+ "dev": true,
+ "dependencies": {
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.6.tgz",
+ "integrity": "sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "4.0.6",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.6.tgz",
+ "integrity": "sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.6",
+ "magic-string": "^0.30.19",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.6.tgz",
+ "integrity": "sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/ui": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.6.tgz",
+ "integrity": "sha512-1ekpBsYNUm0Xv/0YsTvoSRmiRkmzz9Pma7qQ3Ui76sg2gwp2/ewSWqx4W/HfaN5dF0E8iBbidFo1wGaeqXYIrQ==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "4.0.6",
+ "fflate": "^0.8.2",
+ "flatted": "^3.3.3",
+ "pathe": "^2.0.3",
+ "sirv": "^3.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "4.0.6"
+ }
+ },
+ "node_modules/@vitest/ui/node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.6.tgz",
+ "integrity": "sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.6",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@volar/language-core": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz",
+ "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==",
+ "dev": true,
+ "dependencies": {
+ "@volar/source-map": "2.4.15"
+ }
+ },
+ "node_modules/@volar/source-map": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz",
+ "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==",
+ "dev": true
+ },
+ "node_modules/@volar/typescript": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz",
+ "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==",
+ "dev": true,
+ "dependencies": {
+ "@volar/language-core": "2.4.15",
+ "path-browserify": "^1.0.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
+ "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==",
+ "dependencies": {
+ "@babel/parser": "^7.28.4",
+ "@vue/shared": "3.5.22",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz",
+ "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.22",
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz",
+ "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==",
+ "dependencies": {
+ "@babel/parser": "^7.28.4",
+ "@vue/compiler-core": "3.5.22",
+ "@vue/compiler-dom": "3.5.22",
+ "@vue/compiler-ssr": "3.5.22",
+ "@vue/shared": "3.5.22",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.19",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz",
+ "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.22",
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/compiler-vue2": {
+ "version": "2.7.16",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
+ "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
+ "dev": true,
+ "dependencies": {
+ "de-indent": "^1.0.2",
+ "he": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+ },
+ "node_modules/@vue/language-core": {
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz",
+ "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==",
+ "dev": true,
+ "dependencies": {
+ "@volar/language-core": "2.4.15",
+ "@vue/compiler-dom": "^3.5.0",
+ "@vue/compiler-vue2": "^2.7.16",
+ "@vue/shared": "^3.5.0",
+ "alien-signals": "^1.0.3",
+ "minimatch": "^9.0.3",
+ "muggle-string": "^0.4.1",
+ "path-browserify": "^1.0.1"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz",
+ "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==",
+ "dependencies": {
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz",
+ "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.22",
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz",
+ "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.22",
+ "@vue/runtime-core": "3.5.22",
+ "@vue/shared": "3.5.22",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz",
+ "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.22",
+ "@vue/shared": "3.5.22"
+ },
+ "peerDependencies": {
+ "vue": "3.5.22"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz",
+ "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w=="
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/alien-signals": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
+ "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==",
+ "dev": true
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer-builder": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
+ "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
+ "dev": true
+ },
+ "node_modules/chai": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz",
+ "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/colorjs.io": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
+ "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
+ "dev": true
+ },
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "dev": true,
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/cssstyle": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.2.tgz",
+ "integrity": "sha512-zDMqXh8Vs1CdRYZQ2M633m/SFgcjlu8RB8b/1h82i+6vpArF507NSYIWJHGlJaTWoS+imcnctmEz43txhbVkOw==",
+ "dev": true,
+ "dependencies": {
+ "@asamuzakjp/css-color": "^4.0.3",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.14",
+ "css-tree": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/data-urls": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
+ "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/de-indent": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true
+ },
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/engine.io-client": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1",
+ "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==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/expect-type": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
+ "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
+ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
+ "dev": true
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": 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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "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,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
+ "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
+ "dev": true
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true
+ },
+ "node_modules/jsdom": {
+ "version": "27.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz",
+ "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==",
+ "dev": true,
+ "dependencies": {
+ "@acemir/cssom": "^0.9.19",
+ "@asamuzakjp/dom-selector": "^6.7.3",
+ "cssstyle": "^5.3.2",
+ "data-urls": "^6.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "parse5": "^8.0.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.1.0",
+ "ws": "^8.18.3",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsdom/node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "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/lru-cache": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
+ "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
+ "dev": true,
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.19",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true
+ },
+ "node_modules/meshoptimizer": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
+ "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
+ "dev": true
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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=="
+ },
+ "node_modules/muggle-string": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
+ "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
+ "dev": true,
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+ "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "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"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
+ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
+ "dev": true,
+ "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.52.5",
+ "@rollup/rollup-android-arm64": "4.52.5",
+ "@rollup/rollup-darwin-arm64": "4.52.5",
+ "@rollup/rollup-darwin-x64": "4.52.5",
+ "@rollup/rollup-freebsd-arm64": "4.52.5",
+ "@rollup/rollup-freebsd-x64": "4.52.5",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
+ "@rollup/rollup-linux-arm-musleabihf": "4.52.5",
+ "@rollup/rollup-linux-arm64-gnu": "4.52.5",
+ "@rollup/rollup-linux-arm64-musl": "4.52.5",
+ "@rollup/rollup-linux-loong64-gnu": "4.52.5",
+ "@rollup/rollup-linux-ppc64-gnu": "4.52.5",
+ "@rollup/rollup-linux-riscv64-gnu": "4.52.5",
+ "@rollup/rollup-linux-riscv64-musl": "4.52.5",
+ "@rollup/rollup-linux-s390x-gnu": "4.52.5",
+ "@rollup/rollup-linux-x64-gnu": "4.52.5",
+ "@rollup/rollup-linux-x64-musl": "4.52.5",
+ "@rollup/rollup-openharmony-arm64": "4.52.5",
+ "@rollup/rollup-win32-arm64-msvc": "4.52.5",
+ "@rollup/rollup-win32-ia32-msvc": "4.52.5",
+ "@rollup/rollup-win32-x64-gnu": "4.52.5",
+ "@rollup/rollup-win32-x64-msvc": "4.52.5",
+ "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,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/sass": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
+ "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
+ }
+ },
+ "node_modules/sass-embedded": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz",
+ "integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==",
+ "dev": true,
+ "dependencies": {
+ "@bufbuild/protobuf": "^2.5.0",
+ "buffer-builder": "^0.2.0",
+ "colorjs.io": "^0.5.0",
+ "immutable": "^5.0.2",
+ "rxjs": "^7.4.0",
+ "supports-color": "^8.1.1",
+ "sync-child-process": "^1.0.2",
+ "varint": "^6.0.0"
+ },
+ "bin": {
+ "sass": "dist/bin/sass.js"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "optionalDependencies": {
+ "sass-embedded-all-unknown": "1.93.2",
+ "sass-embedded-android-arm": "1.93.2",
+ "sass-embedded-android-arm64": "1.93.2",
+ "sass-embedded-android-riscv64": "1.93.2",
+ "sass-embedded-android-x64": "1.93.2",
+ "sass-embedded-darwin-arm64": "1.93.2",
+ "sass-embedded-darwin-x64": "1.93.2",
+ "sass-embedded-linux-arm": "1.93.2",
+ "sass-embedded-linux-arm64": "1.93.2",
+ "sass-embedded-linux-musl-arm": "1.93.2",
+ "sass-embedded-linux-musl-arm64": "1.93.2",
+ "sass-embedded-linux-musl-riscv64": "1.93.2",
+ "sass-embedded-linux-musl-x64": "1.93.2",
+ "sass-embedded-linux-riscv64": "1.93.2",
+ "sass-embedded-linux-x64": "1.93.2",
+ "sass-embedded-unknown-all": "1.93.2",
+ "sass-embedded-win32-arm64": "1.93.2",
+ "sass-embedded-win32-x64": "1.93.2"
+ }
+ },
+ "node_modules/sass-embedded-all-unknown": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.2.tgz",
+ "integrity": "sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q==",
+ "cpu": [
+ "!arm",
+ "!arm64",
+ "!riscv64",
+ "!x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "sass": "1.93.2"
+ }
+ },
+ "node_modules/sass-embedded-android-arm": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz",
+ "integrity": "sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-android-arm64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.2.tgz",
+ "integrity": "sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-android-riscv64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.2.tgz",
+ "integrity": "sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-android-x64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.2.tgz",
+ "integrity": "sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-darwin-arm64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.2.tgz",
+ "integrity": "sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-darwin-x64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.2.tgz",
+ "integrity": "sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-arm": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.2.tgz",
+ "integrity": "sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-arm64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.2.tgz",
+ "integrity": "sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-arm": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.2.tgz",
+ "integrity": "sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-arm64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.2.tgz",
+ "integrity": "sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-riscv64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.2.tgz",
+ "integrity": "sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-x64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.2.tgz",
+ "integrity": "sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-riscv64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.2.tgz",
+ "integrity": "sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-x64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.2.tgz",
+ "integrity": "sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-unknown-all": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.2.tgz",
+ "integrity": "sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==",
+ "dev": true,
+ "optional": true,
+ "os": [
+ "!android",
+ "!darwin",
+ "!linux",
+ "!win32"
+ ],
+ "dependencies": {
+ "sass": "1.93.2"
+ }
+ },
+ "node_modules/sass-embedded-win32-arm64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz",
+ "integrity": "sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-win32-x64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz",
+ "integrity": "sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
+ "node_modules/sirv": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
+ "dev": true,
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/socket.io-client": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.2",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true
+ },
+ "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,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
+ },
+ "node_modules/sync-child-process": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
+ "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
+ "dev": true,
+ "dependencies": {
+ "sync-message-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/sync-message-port": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
+ "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/three": {
+ "version": "0.156.1",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.156.1.tgz",
+ "integrity": "sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ=="
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/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,
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
+ "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz",
+ "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==",
+ "dev": true,
+ "dependencies": {
+ "tldts-core": "^7.0.17"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz",
+ "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
+ "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+ "dev": true,
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "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
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "devOptional": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/varint": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
+ "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
+ "dev": true
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.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",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.6.tgz",
+ "integrity": "sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/expect": "4.0.6",
+ "@vitest/mocker": "4.0.6",
+ "@vitest/pretty-format": "4.0.6",
+ "@vitest/runner": "4.0.6",
+ "@vitest/snapshot": "4.0.6",
+ "@vitest/spy": "4.0.6",
+ "@vitest/utils": "4.0.6",
+ "debug": "^4.4.3",
+ "es-module-lexer": "^1.7.0",
+ "expect-type": "^1.2.2",
+ "magic-string": "^0.30.19",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3",
+ "vite": "^6.0.0 || ^7.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.0.6",
+ "@vitest/browser-preview": "4.0.6",
+ "@vitest/browser-webdriverio": "4.0.6",
+ "@vitest/ui": "4.0.6",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@vitest/mocker": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.6.tgz",
+ "integrity": "sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/spy": "4.0.6",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.19"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/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,
+ "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/vitest/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/vitest/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,
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest/node_modules/vite": {
+ "version": "7.1.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
+ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "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/vscode-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+ "dev": true
+ },
+ "node_modules/vue": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
+ "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.22",
+ "@vue/compiler-sfc": "3.5.22",
+ "@vue/runtime-dom": "3.5.22",
+ "@vue/server-renderer": "3.5.22",
+ "@vue/shared": "3.5.22"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz",
+ "integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/vue-tsc": {
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz",
+ "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==",
+ "dev": true,
+ "dependencies": {
+ "@volar/typescript": "2.4.15",
+ "@vue/language-core": "2.2.12"
+ },
+ "bin": {
+ "vue-tsc": "bin/vue-tsc.js"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
+ "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
+ "dev": true,
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "dev": true,
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
+ "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "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/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": 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"
+ }
+ }
+ }
+}
diff --git a/trigo-web/app/package.json b/trigo-web/app/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..49ceb9d197041aa0a9b08bf5c51d6f5947ea8666
--- /dev/null
+++ b/trigo-web/app/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "trigo-app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "dev:host": "vite --host",
+ "build": "vue-tsc --noEmit && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "pinia": "^2.1.6",
+ "socket.io-client": "^4.5.2",
+ "three": "^0.156.1",
+ "vue": "^3.3.4",
+ "vue-router": "^4.2.4"
+ },
+ "devDependencies": {
+ "@types/three": "^0.156.0",
+ "@vitejs/plugin-vue": "^5.2.4",
+ "sass-embedded": "^1.93.2",
+ "typescript": "^5.2.2",
+ "vite": "^5.4.21",
+ "vue-tsc": "^2.2.12"
+ }
+}
diff --git a/trigo-web/app/src/App.vue b/trigo-web/app/src/App.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5aff9c0c96996bb5cc71879a957758b80a5b17dd
--- /dev/null
+++ b/trigo-web/app/src/App.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trigo-web/app/src/assets/logo.png b/trigo-web/app/src/assets/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..4840e976b734cd1ffbc26a0565a099afa4407d6b
Binary files /dev/null and b/trigo-web/app/src/assets/logo.png differ
diff --git a/trigo-web/app/src/main.ts b/trigo-web/app/src/main.ts
new file mode 100644
index 0000000000000000000000000000000000000000..72bc14ca804ef082ab9796be76dcf3897e14dda5
--- /dev/null
+++ b/trigo-web/app/src/main.ts
@@ -0,0 +1,23 @@
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+
+import App from "./App.vue";
+import router from "./router";
+import { initializeParsers } from "@inc/trigo/parserInit";
+
+
+const app = createApp(App);
+const pinia = createPinia();
+
+app.use(pinia);
+app.use(router);
+
+// Initialize parsers before mounting (required for TGN functionality)
+initializeParsers().then(() => {
+ app.mount("#app");
+}).catch((error) => {
+ console.error("Failed to initialize parsers:", error);
+ // Still mount app even if parser initialization fails
+ // (parser will throw error when TGN features are used)
+ app.mount("#app");
+});
\ No newline at end of file
diff --git a/trigo-web/app/src/router/index.ts b/trigo-web/app/src/router/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4ce4690239e7015bc2da82ca05c2a0aa547dfbb4
--- /dev/null
+++ b/trigo-web/app/src/router/index.ts
@@ -0,0 +1,17 @@
+import { createRouter, createWebHistory } from "vue-router";
+import TrigoView from "@/views/TrigoView.vue";
+
+
+const router = createRouter({
+ history: createWebHistory(import.meta.env.BASE_URL),
+ routes: [
+ {
+ path: "/",
+ name: "home",
+ component: TrigoView
+ }
+ ]
+});
+
+
+export default router;
\ No newline at end of file
diff --git a/trigo-web/app/src/services/trigoViewport.ts b/trigo-web/app/src/services/trigoViewport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c732e66c4cf07e7ce4a07bba95e0b120d8d06cf3
--- /dev/null
+++ b/trigo-web/app/src/services/trigoViewport.ts
@@ -0,0 +1,1679 @@
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import type { BoardShape } from "../../../inc/trigo";
+
+// Color constants
+const COLORS = {
+ // Scene colors
+ SCENE_BACKGROUND: 0x505055,
+ SCENE_CLEAR: 0x505055,
+
+ // Chess frame colors (three-tiered system)
+ FRAME_CREST: 0xff4d4d, // Red-tinted for edges/corners
+ FRAME_SURFACE: 0xe6b380, // Orange/yellow-tinted for face edges
+ FRAME_INTERIOR: 0x999999, // Gray for interior lines
+
+ // intersection point colors
+ POINT_DEFAULT: 0x4a90e2,
+ POINT_HOVERED: 0x00ff00,
+ POINT_HOVERED_DISABLED: 0xff0000,
+ POINT_AXIS_ALIGNED: 0xffaa00,
+ POINT_AIR_PATCH: 0x80e680, // Semi-transparent green for liberties in inspect mode
+
+ // Stone colors
+ STONE_BLACK: 0x070707,
+ STONE_WHITE: 0xf0f0f0,
+
+ // Stone specular highlights
+ STONE_BLACK_SPECULAR: 0x445577,
+ STONE_WHITE_SPECULAR: 0xeeeedd,
+
+ // Lighting colors
+ AMBIENT_LIGHT: 0xffffff,
+ DIRECTIONAL_LIGHT: 0xffffff,
+ HEMISPHERE_LIGHT_SKY: 0xeefaff,
+ HEMISPHERE_LIGHT_GROUND: 0x20201a,
+} as const;
+
+// Opacity constants
+const OPACITY = {
+ // Chess frame opacities
+ FRAME_CREST: 0.64,
+ FRAME_SURFACE: 0.12,
+ FRAME_INTERIOR: 0.04,
+
+ // Grid and point opacities
+ POINT_DEFAULT: 0.1,
+ POINT_HOVERED: 0.8,
+ POINT_AXIS_ALIGNED: 0.8,
+ POINT_AIR_PATCH: 0.24, // Semi-transparent for liberty visualization
+ PREVIEW_STONE: 0.5,
+ PREVIEW_JOINT_BLACK: 0.5,
+ PREVIEW_JOINT_WHITE: 0.6,
+ DIMMED: 0.3,
+ DOMAIN_BLACK: 0.3,
+ DOMAIN_WHITE: 0.3,
+} as const;
+
+// Shininess constants for stone materials
+const SHININESS = {
+ STONE_BLACK: 120,
+ STONE_WHITE: 30,
+} as const;
+
+
+// Geometric size constants
+const SIZES = {
+ // Stone and point sizes (relative to grid spacing)
+ STONE_RADIUS: 0.28,
+ INTERSECTION_POINT_RADIUS: 0.16,
+ JOINT_RADIUS: 0.12,
+ JOINT_LENGTH: 0.47,
+ DOMAIN_CUBE_SIZE: 0.6,
+
+ // Sphere detail (number of segments)
+ STONE_SEGMENTS: 32,
+ POINT_SEGMENTS: 8,
+ JOINT_SEGMENTS: 6,
+} as const;
+
+
+// Camera and scene constants
+const CAMERA = {
+ FOV: 70,
+ NEAR: 0.1,
+ FAR: 1000,
+ DISTANCE_MULTIPLIER: 1.1,
+ HEIGHT_RATIO: 0.8,
+} as const;
+
+
+// Lighting intensity constants
+const LIGHTING = {
+ AMBIENT_INTENSITY: 0.2,
+ DIRECTIONAL_MAIN_INTENSITY: 0.8,
+ DIRECTIONAL_FILL_INTENSITY: 0.3,
+ HEMISPHERE_INTENSITY: 0.8,
+} as const;
+
+
+// Fog constants
+const FOG = {
+ NEAR_FACTOR: 0.2,
+ FAR_FACTOR: 0.8,
+ MIN_NEAR: 0.1,
+} as const;
+
+
+// Last stone highlight constants
+const SHINING = {
+ FLICKER_SPEED: 0.0048,
+ EMISSIVE_COLOR: [0.03, 0.32, 0.6],
+ BASE_INTENSITY_WHITE: 0.2,
+ BASE_INTENSITY_BLACK: 0.06,
+ FLICKER_INTENSITY_WHITE: 0.6,
+ FLICKER_INTENSITY_BLACK: 0.1,
+} as const;
+
+
+export interface Stone {
+ position: { x: number; y: number; z: number };
+ color: "black" | "white";
+ mesh?: THREE.Mesh;
+}
+
+export interface ViewportCallbacks {
+ onStoneClick?: (x: number, y: number, z: number) => void;
+ onPositionHover?: (x: number | null, y: number | null, z: number | null) => void;
+ isPositionDroppable?: (x: number, y: number, z: number) => boolean;
+ onInspectGroup?: (groupSize: number, liberties: number) => void;
+}
+
+export class TrigoViewport {
+ private canvas: HTMLCanvasElement;
+ private scene: THREE.Scene;
+ private camera: THREE.PerspectiveCamera;
+ private renderer: THREE.WebGLRenderer;
+ private controls!: OrbitControls;
+ private raycaster: THREE.Raycaster;
+ private mouse: THREE.Vector2;
+
+ private boardShape: BoardShape;
+ private gridSpacing: number = 2;
+ private gridGroup: THREE.Group;
+ private stonesGroup: THREE.Group;
+ private jointsGroup: THREE.Group;
+ private intersectionPoints: THREE.Group;
+ private domainCubesGroup: THREE.Group;
+ private highlightedPoint: THREE.Mesh | null = null;
+ private highlightedAxisPoints: THREE.Mesh[] = [];
+ private previewStone: THREE.Mesh | null = null;
+ private lastPlacedStone: { x: number; y: number; z: number } | null = null;
+ private hoveredPosition: { x: number; y: number; z: number } | null = null;
+
+ private stones: Map = new Map();
+ private joints: Map = new Map();
+ private domainCubes: Map = new Map();
+ private callbacks: ViewportCallbacks;
+
+ private animationId: number | null = null;
+ private isDestroyed: boolean = false;
+ private currentPlayerColor: "black" | "white" = "black";
+ private isGameActive: boolean = false;
+ private lastCameraDistance: number = 0;
+
+ // Mouse drag detection
+ private isMouseDown: boolean = false;
+ private mouseDownPosition: { x: number; y: number } | null = null;
+ private hasDragged: boolean = false;
+ private dragThreshold: number = 5; // pixels
+
+ // Inspect mode for analyzing stone groups
+ private inspectMode: boolean = false;
+ private ctrlKeyDown: boolean = false;
+ private highlightedGroup: Set | null = null;
+ private airPatch: Set | null = null; // Liberty positions for highlighted group
+ private lastMouseEvent: MouseEvent | null = null;
+
+ // Domain visibility for territory display
+ private blackDomainVisible: boolean = false;
+ private whiteDomainVisible: boolean = false;
+ private blackDomain: Set | null = null;
+ private whiteDomain: Set | null = null;
+
+ constructor(canvas: HTMLCanvasElement, boardShape: BoardShape = { x: 5, y: 5, z: 5 }, callbacks: ViewportCallbacks = {}) {
+ this.canvas = canvas;
+ this.boardShape = boardShape;
+ this.callbacks = callbacks;
+
+ // Initialize Three.js components
+ this.scene = new THREE.Scene();
+ this.camera = new THREE.PerspectiveCamera(
+ CAMERA.FOV,
+ canvas.clientWidth / canvas.clientHeight,
+ CAMERA.NEAR,
+ CAMERA.FAR
+ );
+ this.renderer = new THREE.WebGLRenderer({
+ canvas,
+ antialias: true,
+ alpha: true
+ });
+ this.raycaster = new THREE.Raycaster();
+ this.mouse = new THREE.Vector2();
+
+ // Groups for organizing scene objects
+ this.gridGroup = new THREE.Group();
+ this.stonesGroup = new THREE.Group();
+ this.jointsGroup = new THREE.Group();
+ this.intersectionPoints = new THREE.Group();
+ this.domainCubesGroup = new THREE.Group();
+
+ this.initialize();
+ }
+
+ private initialize(): void {
+ // Setup renderer - use getBoundingClientRect for accurate CSS size
+ const rect = this.canvas.getBoundingClientRect();
+ this.renderer.setSize(rect.width, rect.height, false);
+ this.renderer.setPixelRatio(window.devicePixelRatio);
+ this.renderer.setClearColor(COLORS.SCENE_CLEAR, 1);
+
+ // Setup camera - use max dimension for distance calculation
+ const maxDim = Math.max(this.boardShape.x, this.boardShape.y, this.boardShape.z);
+ const distance = maxDim * this.gridSpacing * CAMERA.DISTANCE_MULTIPLIER;
+ this.camera.position.set(distance, distance * CAMERA.HEIGHT_RATIO, distance);
+ this.camera.lookAt(0, 0, 0);
+
+ // Setup controls
+ this.controls = new OrbitControls(this.camera, this.canvas);
+ this.controls.enableDamping = true;
+ this.controls.dampingFactor = 0.05;
+ this.controls.minDistance = 5;
+ this.controls.maxDistance = 100;
+ this.controls.maxPolarAngle = Math.PI / 2 + Math.PI / 4;
+ this.controls.enablePan = false; // Disable camera panning with right mouse button
+
+ // Setup scene
+ this.scene.background = new THREE.Color(COLORS.SCENE_BACKGROUND);
+ this.setupFog();
+ this.setupLighting();
+ this.createGrid();
+ this.createIntersectionPoints();
+ this.createJoints();
+ this.createDomainCubes();
+ this.createPreviewStone();
+
+ // Add groups to scene
+ this.scene.add(this.gridGroup);
+ this.scene.add(this.stonesGroup);
+ this.scene.add(this.jointsGroup);
+ this.scene.add(this.intersectionPoints);
+ this.scene.add(this.domainCubesGroup);
+
+ // Event listeners
+ this.canvas.addEventListener("mousemove", this.onMouseMove.bind(this));
+ this.canvas.addEventListener("mousedown", this.onMouseDown.bind(this));
+ this.canvas.addEventListener("mouseup", this.onMouseUp.bind(this));
+ this.canvas.addEventListener("click", this.onClick.bind(this));
+ window.addEventListener("resize", this.onWindowResize.bind(this));
+ window.addEventListener("keydown", this.onKeyDown.bind(this));
+ window.addEventListener("keyup", this.onKeyUp.bind(this));
+
+ // Start animation loop
+ this.animate();
+ }
+
+ private createPreviewStone(): void {
+ const geometry = new THREE.SphereGeometry(SIZES.STONE_RADIUS * this.gridSpacing, SIZES.STONE_SEGMENTS, SIZES.STONE_SEGMENTS);
+ const material = new THREE.MeshPhongMaterial({
+ color: this.currentPlayerColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE,
+ transparent: true,
+ opacity: OPACITY.PREVIEW_STONE,
+ shininess: this.currentPlayerColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE
+ });
+ this.previewStone = new THREE.Mesh(geometry, material);
+ this.previewStone.visible = false; // Hidden by default
+ this.scene.add(this.previewStone);
+ }
+
+ private updatePreviewStoneColor(): void {
+ if (!this.previewStone) return;
+ const material = this.previewStone.material as THREE.MeshPhongMaterial;
+ material.color.set(this.currentPlayerColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = this.currentPlayerColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ }
+
+ private setupFog(): void {
+ // Use scene background color for fog
+ this.scene.fog = new THREE.Fog(COLORS.SCENE_BACKGROUND, 0, 1);
+
+ // Update fog parameters based on current camera position
+ this.updateFog(true);
+ }
+
+ private highlightAxisPoints(gridX: number, gridY: number, gridZ: number): void {
+ // Clear previous axis highlights
+ this.clearAxisHighlights();
+
+ // Highlight points along the same axes
+ this.intersectionPoints.children.forEach((child) => {
+ const point = child as THREE.Mesh;
+ const { gridX: px, gridY: py, gridZ: pz } = point.userData;
+
+ // Check if point is on the same X, Y, or Z axis
+ const alignedXAxis = px === gridX;
+ const alignedYAxis = py === gridY;
+ const alignedZAxis = pz === gridZ;
+ const aligned = Number(alignedXAxis) + Number(alignedYAxis) + Number(alignedZAxis);
+
+ // Highlight if on one axis
+ if (aligned == 2) {
+ const material = point.material as THREE.MeshBasicMaterial;
+ material.color.set(COLORS.POINT_AXIS_ALIGNED);
+ material.opacity = OPACITY.POINT_AXIS_ALIGNED;
+ this.highlightedAxisPoints.push(point);
+ }
+ });
+ }
+
+ private clearAxisHighlights(): void {
+ // Reset all previously highlighted axis points
+ this.highlightedAxisPoints.forEach((point) => {
+ const material = point.material as THREE.MeshBasicMaterial;
+ material.color.set(COLORS.POINT_DEFAULT);
+ material.opacity = OPACITY.POINT_DEFAULT;
+ });
+ this.highlightedAxisPoints = [];
+ }
+
+ private setupLighting(): void {
+ // Ambient light
+ const ambientLight = new THREE.AmbientLight(COLORS.AMBIENT_LIGHT, LIGHTING.AMBIENT_INTENSITY);
+ this.scene.add(ambientLight);
+
+ // Directional light (main)
+ const directionalLight1 = new THREE.DirectionalLight(COLORS.DIRECTIONAL_LIGHT, LIGHTING.DIRECTIONAL_MAIN_INTENSITY);
+ directionalLight1.position.set(10, 20, 10);
+ directionalLight1.castShadow = true;
+ this.scene.add(directionalLight1);
+
+ // Directional light (fill)
+ const directionalLight2 = new THREE.DirectionalLight(COLORS.DIRECTIONAL_LIGHT, LIGHTING.DIRECTIONAL_FILL_INTENSITY);
+ directionalLight2.position.set(-10, -10, -10);
+ this.scene.add(directionalLight2);
+
+ // Hemisphere light for softer ambient
+ const hemisphereLight = new THREE.HemisphereLight(COLORS.HEMISPHERE_LIGHT_SKY, COLORS.HEMISPHERE_LIGHT_GROUND, LIGHTING.HEMISPHERE_INTENSITY);
+ hemisphereLight.position.set(0, 20, 0);
+ this.scene.add(hemisphereLight);
+ }
+
+ private createGrid(): void {
+ const { x: sizeX, y: sizeY, z: sizeZ } = this.boardShape;
+ const spacing = this.gridSpacing;
+ const offsetX = ((sizeX - 1) * spacing) / 2;
+ const offsetY = ((sizeY - 1) * spacing) / 2;
+ const offsetZ = ((sizeZ - 1) * spacing) / 2;
+
+ // Chess frame materials - three-tiered system from prototype
+ // Crest: edges/corners (most visible)
+ const crestMaterial = new THREE.LineBasicMaterial({
+ color: COLORS.FRAME_CREST,
+ opacity: OPACITY.FRAME_CREST,
+ transparent: true
+ });
+
+ // Surface: edges on faces (medium visibility)
+ const surfaceMaterial = new THREE.LineBasicMaterial({
+ color: COLORS.FRAME_SURFACE,
+ opacity: OPACITY.FRAME_SURFACE,
+ transparent: true
+ });
+
+ // Interior: inner lines (least visible)
+ const interiorMaterial = new THREE.LineBasicMaterial({
+ color: COLORS.FRAME_INTERIOR,
+ opacity: OPACITY.FRAME_INTERIOR,
+ transparent: true
+ });
+
+ // Helper function to determine material based on border conditions
+ const getLineMaterial = (border1: boolean, border2: boolean): THREE.LineBasicMaterial => {
+ if (border1 && border2) return crestMaterial; // Both borders -> crest
+ if (border1 || border2) return surfaceMaterial; // One border -> surface
+ return interiorMaterial; // No borders -> interior
+ };
+
+ // X-axis lines (parallel to X)
+ for (let y = 0; y < sizeY; y++) {
+ for (let z = 0; z < sizeZ; z++) {
+ const border1 = (y === 0) || (y === sizeY - 1);
+ const border2 = (z === 0) || (z === sizeZ - 1);
+ const material = getLineMaterial(border1, border2);
+
+ const points = [];
+ for (let x = 0; x < sizeX; x++) {
+ points.push(
+ new THREE.Vector3(
+ x * spacing - offsetX,
+ y * spacing - offsetY,
+ z * spacing - offsetZ
+ )
+ );
+ }
+ const geometry = new THREE.BufferGeometry().setFromPoints(points);
+ const line = new THREE.Line(geometry, material);
+ this.gridGroup.add(line);
+ }
+ }
+
+ // Y-axis lines (parallel to Y)
+ for (let x = 0; x < sizeX; x++) {
+ for (let z = 0; z < sizeZ; z++) {
+ const border1 = (x === 0) || (x === sizeX - 1);
+ const border2 = (z === 0) || (z === sizeZ - 1);
+ const material = getLineMaterial(border1, border2);
+
+ const points = [];
+ for (let y = 0; y < sizeY; y++) {
+ points.push(
+ new THREE.Vector3(
+ x * spacing - offsetX,
+ y * spacing - offsetY,
+ z * spacing - offsetZ
+ )
+ );
+ }
+ const geometry = new THREE.BufferGeometry().setFromPoints(points);
+ const line = new THREE.Line(geometry, material);
+ this.gridGroup.add(line);
+ }
+ }
+
+ // Z-axis lines (parallel to Z)
+ if (sizeZ >= 3) {
+ for (let x = 0; x < sizeX; x++) {
+ for (let y = 0; y < sizeY; y++) {
+ const border1 = (x === 0) || (x === sizeX - 1);
+ const border2 = (y === 0) || (y === sizeY - 1);
+ const material = getLineMaterial(border1, border2);
+
+ const points = [];
+ for (let z = 0; z < sizeZ; z++) {
+ points.push(
+ new THREE.Vector3(
+ x * spacing - offsetX,
+ y * spacing - offsetY,
+ z * spacing - offsetZ
+ )
+ );
+ }
+ const geometry = new THREE.BufferGeometry().setFromPoints(points);
+ const line = new THREE.Line(geometry, material);
+ this.gridGroup.add(line);
+ }
+ }
+ }
+
+ // Add axes helper for orientation
+ const maxOffset = Math.max(offsetX, offsetY, offsetZ);
+
+ if (sizeZ >= 3) {
+ // Show all three axes for 3D boards
+ const axesHelper = new THREE.AxesHelper(maxOffset * 1.2);
+ this.gridGroup.add(axesHelper);
+ } else {
+ // Show only X and Y axes for 2D boards (hide Z axis)
+ const axisLength = maxOffset * 1.2;
+ const axesMaterial = [
+ new THREE.LineBasicMaterial({ color: 0xff0000 }), // X axis - red
+ new THREE.LineBasicMaterial({ color: 0x00ff00 }) // Y axis - green
+ ];
+
+ // X axis
+ const xPoints = [
+ new THREE.Vector3(0, 0, 0),
+ new THREE.Vector3(axisLength, 0, 0)
+ ];
+ const xGeometry = new THREE.BufferGeometry().setFromPoints(xPoints);
+ const xLine = new THREE.Line(xGeometry, axesMaterial[0]);
+ this.gridGroup.add(xLine);
+
+ // Y axis
+ const yPoints = [
+ new THREE.Vector3(0, 0, 0),
+ new THREE.Vector3(0, axisLength, 0)
+ ];
+ const yGeometry = new THREE.BufferGeometry().setFromPoints(yPoints);
+ const yLine = new THREE.Line(yGeometry, axesMaterial[1]);
+ this.gridGroup.add(yLine);
+ }
+ }
+
+ private createIntersectionPoints(): void {
+ const { x: sizeX, y: sizeY, z: sizeZ } = this.boardShape;
+ const spacing = this.gridSpacing;
+ const offsetX = ((sizeX - 1) * spacing) / 2;
+ const offsetY = ((sizeY - 1) * spacing) / 2;
+ const offsetZ = ((sizeZ - 1) * spacing) / 2;
+
+ // Create small spheres at each grid intersection
+ const pointGeometry = new THREE.SphereGeometry(SIZES.INTERSECTION_POINT_RADIUS, SIZES.POINT_SEGMENTS, SIZES.POINT_SEGMENTS);
+
+ for (let x = 0; x < sizeX; x++) {
+ for (let y = 0; y < sizeY; y++) {
+ for (let z = 0; z < sizeZ; z++) {
+ // Create a unique material for each point so they can be styled independently
+ const pointMaterial = new THREE.MeshBasicMaterial({
+ color: COLORS.POINT_DEFAULT,
+ opacity: OPACITY.POINT_DEFAULT,
+ transparent: true
+ });
+ const point = new THREE.Mesh(pointGeometry, pointMaterial);
+ point.position.set(
+ x * spacing - offsetX,
+ y * spacing - offsetY,
+ z * spacing - offsetZ
+ );
+ point.userData = { gridX: x, gridY: y, gridZ: z };
+ this.intersectionPoints.add(point);
+ }
+ }
+ }
+ }
+
+
+ private createJoints(): void {
+ const { x: sizeX, y: sizeY, z: sizeZ } = this.boardShape;
+ const spacing = this.gridSpacing;
+ const offsetX = ((sizeX - 1) * spacing) / 2;
+ const offsetY = ((sizeY - 1) * spacing) / 2;
+ const offsetZ = ((sizeZ - 1) * spacing) / 2;
+
+ // Joint dimensions from prototype: scale (0.06, 0.47, 0.06)
+ const jointRadius = SIZES.JOINT_RADIUS;
+ const jointLength = SIZES.JOINT_LENGTH * spacing; // Scale by grid spacing
+
+ // Create joints for each grid position
+ for (let x = 0; x < sizeX; x++) {
+ for (let y = 0; y < sizeY; y++) {
+ for (let z = 0; z < sizeZ; z++) {
+ const key = this.getStoneKey(x, y, z);
+ const jointNodes: { X?: THREE.Mesh; Y?: THREE.Mesh; Z?: THREE.Mesh } = {};
+
+ // Create X-axis joint (between current and next X position)
+ if (x < sizeX - 1) {
+ const geometry = new THREE.CylinderGeometry(jointRadius, jointRadius, jointLength, SIZES.JOINT_SEGMENTS);
+ const material = new THREE.MeshPhongMaterial({
+ color: COLORS.STONE_BLACK,
+ shininess: SHININESS.STONE_BLACK,
+ specular: COLORS.STONE_BLACK_SPECULAR
+ });
+ const joint = new THREE.Mesh(geometry, material);
+
+ // Position at midpoint between stones, rotate to align with X-axis
+ joint.position.set(
+ (x + 0.5) * spacing - offsetX,
+ y * spacing - offsetY,
+ z * spacing - offsetZ
+ );
+ joint.rotation.set(0, 0, Math.PI / 2); // Rotate to X-axis
+
+ joint.visible = false; // Hidden by default
+ this.jointsGroup.add(joint);
+ jointNodes.X = joint;
+ }
+
+ // Create Y-axis joint (between current and next Y position)
+ if (y < sizeY - 1) {
+ const geometry = new THREE.CylinderGeometry(jointRadius, jointRadius, jointLength, SIZES.JOINT_SEGMENTS);
+ const material = new THREE.MeshPhongMaterial({
+ color: COLORS.STONE_BLACK,
+ shininess: SHININESS.STONE_BLACK,
+ specular: COLORS.STONE_BLACK_SPECULAR
+ });
+ const joint = new THREE.Mesh(geometry, material);
+
+ // Position at midpoint between stones (Y-axis is already aligned)
+ joint.position.set(
+ x * spacing - offsetX,
+ (y + 0.5) * spacing - offsetY,
+ z * spacing - offsetZ
+ );
+ // No rotation needed for Y-axis (cylinder default orientation)
+
+ joint.visible = false; // Hidden by default
+ this.jointsGroup.add(joint);
+ jointNodes.Y = joint;
+ }
+
+ // Create Z-axis joint (between current and next Z position)
+ if (z < sizeZ - 1) {
+ const geometry = new THREE.CylinderGeometry(jointRadius, jointRadius, jointLength, SIZES.JOINT_SEGMENTS);
+ const material = new THREE.MeshPhongMaterial({
+ color: COLORS.STONE_BLACK,
+ shininess: SHININESS.STONE_BLACK,
+ specular: COLORS.STONE_BLACK_SPECULAR
+ });
+ const joint = new THREE.Mesh(geometry, material);
+
+ // Position at midpoint between stones, rotate to align with Z-axis
+ joint.position.set(
+ x * spacing - offsetX,
+ y * spacing - offsetY,
+ (z + 0.5) * spacing - offsetZ
+ );
+ joint.rotation.set(Math.PI / 2, 0, 0); // Rotate to Z-axis
+
+ joint.visible = false; // Hidden by default
+ this.jointsGroup.add(joint);
+ jointNodes.Z = joint;
+ }
+
+ this.joints.set(key, jointNodes);
+ }
+ }
+ }
+ }
+
+
+ private createDomainCubes(): void {
+ const { x: sizeX, y: sizeY, z: sizeZ } = this.boardShape;
+ const spacing = this.gridSpacing;
+ const offsetX = ((sizeX - 1) * spacing) / 2;
+ const offsetY = ((sizeY - 1) * spacing) / 2;
+ const offsetZ = ((sizeZ - 1) * spacing) / 2;
+
+ // Domain cube size from prototype: scale 0.6
+ const cubeSize = SIZES.DOMAIN_CUBE_SIZE * spacing;
+
+ // Create domain cubes for each grid position
+ for (let x = 0; x < sizeX; x++) {
+ for (let y = 0; y < sizeY; y++) {
+ for (let z = 0; z < sizeZ; z++) {
+ const key = this.getStoneKey(x, y, z);
+
+ // Create cube geometry
+ const geometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
+
+ // Create material (will be updated dynamically based on domain type)
+ const material = new THREE.MeshBasicMaterial({
+ color: COLORS.STONE_BLACK,
+ transparent: true,
+ opacity: OPACITY.DOMAIN_BLACK,
+ depthWrite: false // Prevent z-fighting with stones
+ });
+
+ const cube = new THREE.Mesh(geometry, material);
+
+ // Position at grid intersection
+ cube.position.set(
+ x * spacing - offsetX,
+ y * spacing - offsetY,
+ z * spacing - offsetZ
+ );
+
+ cube.visible = false; // Hidden by default
+ this.domainCubesGroup.add(cube);
+ this.domainCubes.set(key, cube);
+ }
+ }
+ }
+ }
+
+ private getStoneKey(x: number, y: number, z: number): string {
+ return `${x},${y},${z}`;
+ }
+
+ public addStone(x: number, y: number, z: number, color: "black" | "white"): void {
+ const key = this.getStoneKey(x, y, z);
+ if (this.stones.has(key)) {
+ console.warn(`Stone already exists at (${x}, ${y}, ${z})`);
+ return;
+ }
+
+ // Hide preview stone immediately when adding a new stone
+ this.hidePreviewStone();
+
+ const spacing = this.gridSpacing;
+ const offsetX = ((this.boardShape.x - 1) * spacing) / 2;
+ const offsetY = ((this.boardShape.y - 1) * spacing) / 2;
+ const offsetZ = ((this.boardShape.z - 1) * spacing) / 2;
+
+ // Create stone geometry
+ const geometry = new THREE.SphereGeometry(SIZES.STONE_RADIUS * this.gridSpacing, SIZES.STONE_SEGMENTS, SIZES.STONE_SEGMENTS);
+ const material = new THREE.MeshPhongMaterial({
+ color: color === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE,
+ shininess: color === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE,
+ specular: color === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR,
+ emissive: 0x000000, // Will be animated for last placed stone
+ emissiveIntensity: 0
+ });
+
+ const stoneMesh = new THREE.Mesh(geometry, material);
+ stoneMesh.position.set(
+ x * spacing - offsetX,
+ y * spacing - offsetY,
+ z * spacing - offsetZ
+ );
+
+ const stone: Stone = {
+ position: { x, y, z },
+ color,
+ mesh: stoneMesh
+ };
+
+ this.stones.set(key, stone);
+ this.stonesGroup.add(stoneMesh);
+
+ // Clear emissive from previous last placed stone
+ if (this.lastPlacedStone) {
+ const prevKey = this.getStoneKey(
+ this.lastPlacedStone.x,
+ this.lastPlacedStone.y,
+ this.lastPlacedStone.z
+ );
+ const prevStone = this.stones.get(prevKey);
+ if (prevStone && prevStone.mesh) {
+ const prevMaterial = prevStone.mesh.material as THREE.MeshPhongMaterial;
+ prevMaterial.emissive.set(0x000000);
+ prevMaterial.emissiveIntensity = 0;
+ }
+ }
+
+ // Track this as the last placed stone
+ this.lastPlacedStone = { x, y, z };
+
+ // Update joints to show connections
+ this.refreshJoints();
+ }
+
+ public removeStone(x: number, y: number, z: number): void {
+ const key = this.getStoneKey(x, y, z);
+ const stone = this.stones.get(key);
+
+ if (stone && stone.mesh) {
+ this.stonesGroup.remove(stone.mesh);
+ stone.mesh.geometry.dispose();
+ if (stone.mesh.material instanceof THREE.Material) {
+ stone.mesh.material.dispose();
+ }
+ this.stones.delete(key);
+
+ // Update joints after removing stone
+ this.refreshJoints();
+ }
+ }
+
+ public clearBoard(): void {
+ // Remove all stones
+ this.stones.forEach((stone) => {
+ if (stone.mesh) {
+ this.stonesGroup.remove(stone.mesh);
+ stone.mesh.geometry.dispose();
+ if (stone.mesh.material instanceof THREE.Material) {
+ stone.mesh.material.dispose();
+ }
+ }
+ });
+ this.stones.clear();
+ this.lastPlacedStone = null;
+
+ // Hide all joints
+ this.refreshJoints();
+ }
+
+ public hasStone(x: number, y: number, z: number): boolean {
+ return this.stones.has(this.getStoneKey(x, y, z));
+ }
+
+
+ private refreshJoints(): void {
+ const { x: sizeX, y: sizeY, z: sizeZ } = this.boardShape;
+
+ for (let x = 0; x < sizeX; x++) {
+ for (let y = 0; y < sizeY; y++) {
+ for (let z = 0; z < sizeZ; z++) {
+ const key = this.getStoneKey(x, y, z);
+ const jointNodes = this.joints.get(key);
+
+ if (!jointNodes) continue;
+
+ const centerStone = this.stones.get(key);
+
+ // X-axis joint: check if current and (x+1) have same color
+ if (jointNodes.X) {
+ if (centerStone && x + 1 < sizeX) {
+ const nextKey = this.getStoneKey(x + 1, y, z);
+ const nextStone = this.stones.get(nextKey);
+
+ if (nextStone && nextStone.color === centerStone.color) {
+ const material = jointNodes.X.material as THREE.MeshPhongMaterial;
+ material.color.set(centerStone.color === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = centerStone.color === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(centerStone.color === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = 1.0;
+ material.transparent = false;
+ jointNodes.X.visible = true;
+ } else {
+ jointNodes.X.visible = false;
+ }
+ } else {
+ jointNodes.X.visible = false;
+ }
+ }
+
+ // Y-axis joint: check if current and (y+1) have same color
+ if (jointNodes.Y) {
+ if (centerStone && y + 1 < sizeY) {
+ const nextKey = this.getStoneKey(x, y + 1, z);
+ const nextStone = this.stones.get(nextKey);
+
+ if (nextStone && nextStone.color === centerStone.color) {
+ const material = jointNodes.Y.material as THREE.MeshPhongMaterial;
+ material.color.set(centerStone.color === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = centerStone.color === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(centerStone.color === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = 1.0;
+ material.transparent = false;
+ jointNodes.Y.visible = true;
+ } else {
+ jointNodes.Y.visible = false;
+ }
+ } else {
+ jointNodes.Y.visible = false;
+ }
+ }
+
+ // Z-axis joint: check if current and (z+1) have same color
+ if (jointNodes.Z) {
+ if (centerStone && z + 1 < sizeZ) {
+ const nextKey = this.getStoneKey(x, y, z + 1);
+ const nextStone = this.stones.get(nextKey);
+
+ if (nextStone && nextStone.color === centerStone.color) {
+ const material = jointNodes.Z.material as THREE.MeshPhongMaterial;
+ material.color.set(centerStone.color === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = centerStone.color === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(centerStone.color === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = 1.0;
+ material.transparent = false;
+ jointNodes.Z.visible = true;
+ } else {
+ jointNodes.Z.visible = false;
+ }
+ } else {
+ jointNodes.Z.visible = false;
+ }
+ }
+
+ // Show preview joints if hovering over this position
+ const isHovered = this.hoveredPosition &&
+ this.hoveredPosition.x === x &&
+ this.hoveredPosition.y === y &&
+ this.hoveredPosition.z === z;
+
+ if (isHovered && this.isGameActive && !centerStone) {
+ // Preview joints connecting to adjacent stones of current player's color
+ const previewColor = this.currentPlayerColor;
+ const previewOpacity = previewColor === "black" ? OPACITY.PREVIEW_JOINT_BLACK : OPACITY.PREVIEW_JOINT_WHITE;
+
+ // Check -X direction (left neighbor)
+ if (x > 0) {
+ const leftKey = this.getStoneKey(x - 1, y, z);
+ const leftStone = this.stones.get(leftKey);
+ if (leftStone && leftStone.color === previewColor) {
+ const leftJointNodes = this.joints.get(leftKey);
+ if (leftJointNodes?.X) {
+ const material = leftJointNodes.X.material as THREE.MeshPhongMaterial;
+ material.color.set(previewColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = previewColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(previewColor === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = previewOpacity;
+ material.transparent = true;
+ leftJointNodes.X.visible = true;
+ }
+ }
+ }
+
+ // Check -Y direction (bottom neighbor)
+ if (y > 0) {
+ const bottomKey = this.getStoneKey(x, y - 1, z);
+ const bottomStone = this.stones.get(bottomKey);
+ if (bottomStone && bottomStone.color === previewColor) {
+ const bottomJointNodes = this.joints.get(bottomKey);
+ if (bottomJointNodes?.Y) {
+ const material = bottomJointNodes.Y.material as THREE.MeshPhongMaterial;
+ material.color.set(previewColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = previewColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(previewColor === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = previewOpacity;
+ material.transparent = true;
+ bottomJointNodes.Y.visible = true;
+ }
+ }
+ }
+
+ // Check -Z direction (back neighbor)
+ if (z > 0) {
+ const backKey = this.getStoneKey(x, y, z - 1);
+ const backStone = this.stones.get(backKey);
+ if (backStone && backStone.color === previewColor) {
+ const backJointNodes = this.joints.get(backKey);
+ if (backJointNodes?.Z) {
+ const material = backJointNodes.Z.material as THREE.MeshPhongMaterial;
+ material.color.set(previewColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = previewColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(previewColor === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = previewOpacity;
+ material.transparent = true;
+ backJointNodes.Z.visible = true;
+ }
+ }
+ }
+
+ // Check +X direction (right neighbor)
+ if (x + 1 < sizeX && jointNodes.X) {
+ const rightKey = this.getStoneKey(x + 1, y, z);
+ const rightStone = this.stones.get(rightKey);
+ if (rightStone && rightStone.color === previewColor) {
+ const material = jointNodes.X.material as THREE.MeshPhongMaterial;
+ material.color.set(previewColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = previewColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(previewColor === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = previewOpacity;
+ material.transparent = true;
+ jointNodes.X.visible = true;
+ }
+ }
+
+ // Check +Y direction (top neighbor)
+ if (y + 1 < sizeY && jointNodes.Y) {
+ const topKey = this.getStoneKey(x, y + 1, z);
+ const topStone = this.stones.get(topKey);
+ if (topStone && topStone.color === previewColor) {
+ const material = jointNodes.Y.material as THREE.MeshPhongMaterial;
+ material.color.set(previewColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = previewColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(previewColor === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = previewOpacity;
+ material.transparent = true;
+ jointNodes.Y.visible = true;
+ }
+ }
+
+ // Check +Z direction (front neighbor)
+ if (z + 1 < sizeZ && jointNodes.Z) {
+ const frontKey = this.getStoneKey(x, y, z + 1);
+ const frontStone = this.stones.get(frontKey);
+ if (frontStone && frontStone.color === previewColor) {
+ const material = jointNodes.Z.material as THREE.MeshPhongMaterial;
+ material.color.set(previewColor === "black" ? COLORS.STONE_BLACK : COLORS.STONE_WHITE);
+ material.shininess = previewColor === "black" ? SHININESS.STONE_BLACK : SHININESS.STONE_WHITE;
+ material.specular.set(previewColor === "black" ? COLORS.STONE_BLACK_SPECULAR : COLORS.STONE_WHITE_SPECULAR);
+ material.opacity = previewOpacity;
+ material.transparent = true;
+ jointNodes.Z.visible = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ public setCurrentPlayer(color: "black" | "white"): void {
+ this.currentPlayerColor = color;
+ this.updatePreviewStoneColor();
+ }
+
+ public setGameActive(active: boolean): void {
+ this.isGameActive = active;
+ }
+
+
+ public hidePreviewStone(): void {
+ if (this.previewStone) {
+ this.previewStone.visible = false;
+ }
+ this.hoveredPosition = null;
+ this.refreshJoints();
+ }
+
+
+ public setLastPlacedStone(x: number | null, y: number | null, z: number | null): void {
+ // Clear previous stone's emissive glow
+ if (this.lastPlacedStone) {
+ const prevKey = this.getStoneKey(this.lastPlacedStone.x, this.lastPlacedStone.y, this.lastPlacedStone.z);
+ const prevStone = this.stones.get(prevKey);
+ if (prevStone && prevStone.mesh) {
+ const prevMaterial = prevStone.mesh.material as THREE.MeshPhongMaterial;
+ prevMaterial.emissive.set(0x000000);
+ prevMaterial.emissiveIntensity = 0;
+ }
+ }
+
+ // Set new last placed stone
+ if (x !== null && y !== null && z !== null) {
+ this.lastPlacedStone = { x, y, z };
+ } else {
+ this.lastPlacedStone = null;
+ }
+ }
+
+
+ // Domain visibility methods (for territory display)
+ public setBlackDomainVisible(visible: boolean): void {
+ if (this.blackDomainVisible !== visible) {
+ this.blackDomainVisible = visible;
+ this.refreshDomainVisualization();
+ }
+ }
+
+ public setWhiteDomainVisible(visible: boolean): void {
+ if (this.whiteDomainVisible !== visible) {
+ this.whiteDomainVisible = visible;
+ this.refreshDomainVisualization();
+ }
+ }
+
+ public setDomainData(blackDomain: Set | null, whiteDomain: Set | null): void {
+ this.blackDomain = blackDomain;
+ this.whiteDomain = whiteDomain;
+ this.refreshDomainVisualization();
+ }
+
+ private refreshDomainVisualization(): void {
+ // In inspect mode, air patch takes priority over domain visualization
+ if (this.inspectMode && this.airPatch) {
+ this.updateDomainCubesVisualization(null, null);
+ } else {
+ // Normal mode: show domains based on visibility flags
+ const black = this.blackDomainVisible ? this.blackDomain : null;
+ const white = this.whiteDomainVisible ? this.whiteDomain : null;
+ this.updateDomainCubesVisualization(black, white);
+ }
+ }
+
+ public showDomainCubes(blackDomain: Set | null, whiteDomain: Set | null): void {
+ this.setDomainData(blackDomain, whiteDomain);
+ this.setBlackDomainVisible(true);
+ this.setWhiteDomainVisible(true);
+ }
+
+ public hideDomainCubes(): void {
+ this.setBlackDomainVisible(false);
+ this.setWhiteDomainVisible(false);
+ }
+
+
+ public setBoardShape(shape: BoardShape): void {
+ if (shape.x === this.boardShape.x && shape.y === this.boardShape.y && shape.z === this.boardShape.z) return;
+
+ this.boardShape = shape;
+
+ // Clear existing grid, points, and joints
+ this.gridGroup.clear();
+ this.intersectionPoints.clear();
+ this.jointsGroup.clear();
+ this.joints.clear();
+ this.domainCubesGroup.clear();
+ this.domainCubes.clear();
+ this.clearBoard();
+
+ // Recreate grid, points, and joints
+ this.createGrid();
+ this.createIntersectionPoints();
+ this.createJoints();
+ this.createDomainCubes();
+
+ // Update fog for new board size
+ this.setupFog();
+
+ // Adjust camera position
+ const maxDim = Math.max(shape.x, shape.y, shape.z);
+ const distance = maxDim * this.gridSpacing * CAMERA.DISTANCE_MULTIPLIER;
+ this.camera.position.set(distance, distance * CAMERA.HEIGHT_RATIO, distance);
+ this.camera.lookAt(0, 0, 0);
+ }
+
+ private onMouseDown(event: MouseEvent): void {
+ // Handle middle button (button 1) for inspect mode
+ if (event.button === 1) {
+ event.preventDefault();
+ this.inspectMode = true;
+ this.updateHighlightedGroup(event);
+ this.updateStoneOpacity();
+ return;
+ }
+
+ // Handle left button (button 0)
+ if (event.button === 0) {
+ this.isMouseDown = true;
+ this.mouseDownPosition = { x: event.clientX, y: event.clientY };
+ this.hasDragged = false;
+
+ // Exit inspect mode on left click
+ if (this.inspectMode && !this.ctrlKeyDown) {
+ this.inspectMode = false;
+ this.highlightedGroup = null;
+ this.airPatch = null;
+ this.updateStoneOpacity();
+ // Clear tooltip by calling callback with 0, 0
+ if (this.callbacks.onInspectGroup) {
+ this.callbacks.onInspectGroup(0, 0);
+ }
+ }
+ }
+ }
+
+ private onMouseUp(event: MouseEvent): void {
+ // Handle middle button release
+ if (event.button === 1) {
+ event.preventDefault();
+ if (this.inspectMode && !this.ctrlKeyDown) {
+ this.inspectMode = false;
+ this.highlightedGroup = null;
+ this.airPatch = null;
+ this.updateStoneOpacity();
+ // Clear tooltip by calling callback with 0, 0
+ if (this.callbacks.onInspectGroup) {
+ this.callbacks.onInspectGroup(0, 0);
+ }
+ }
+ return;
+ }
+
+ // Handle left button release
+ if (event.button === 0) {
+ this.isMouseDown = false;
+ this.mouseDownPosition = null;
+ // Note: hasDragged will be reset on next mousedown
+ }
+ }
+
+ private onMouseMove(event: MouseEvent): void {
+ // Store last mouse event for Ctrl key inspection
+ this.lastMouseEvent = event;
+
+ // If Ctrl is held and inspect mode is active, update highlighted group
+ if (this.ctrlKeyDown && this.inspectMode) {
+ this.updateHighlightedGroup(event);
+ this.updateStoneOpacity();
+ }
+
+ // Check if mouse is not hovering over canvas and cleanup
+ if (!this.canvas.matches(':hover')) {
+ // Hide preview stone when mouse is outside canvas
+ if (this.previewStone) {
+ this.previewStone.visible = false;
+ }
+ this.hoveredPosition = null;
+ this.refreshJoints();
+ // Clear highlights when mouse is outside
+ if (this.highlightedPoint) {
+ const material = this.highlightedPoint.material as THREE.MeshBasicMaterial;
+ material.color.set(COLORS.POINT_DEFAULT);
+ material.opacity = OPACITY.POINT_DEFAULT;
+ this.highlightedPoint = null;
+ }
+ this.clearAxisHighlights();
+ return;
+ }
+
+ // Check if we're dragging
+ if (this.isMouseDown && this.mouseDownPosition) {
+ const dx = event.clientX - this.mouseDownPosition.x;
+ const dy = event.clientY - this.mouseDownPosition.y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance > this.dragThreshold) {
+ this.hasDragged = true;
+ }
+ }
+
+ // Hide preview stone if dragging
+ if (this.isMouseDown || this.hasDragged) {
+ if (this.previewStone) {
+ this.previewStone.visible = false;
+ }
+ this.hoveredPosition = null;
+ this.refreshJoints();
+ // Clear highlights when dragging
+ if (this.highlightedPoint) {
+ const material = this.highlightedPoint.material as THREE.MeshBasicMaterial;
+ material.color.set(COLORS.POINT_DEFAULT);
+ material.opacity = OPACITY.POINT_DEFAULT;
+ this.highlightedPoint = null;
+ }
+ this.clearAxisHighlights();
+ return; // Don't process hover logic when dragging
+ }
+
+ const rect = this.canvas.getBoundingClientRect();
+ this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+
+ // Raycast to find intersection points
+ this.raycaster.setFromCamera(this.mouse, this.camera);
+ const intersects = this.raycaster.intersectObjects(this.intersectionPoints.children);
+
+ // Remove previous highlight
+ if (this.highlightedPoint) {
+ (this.highlightedPoint.material as THREE.MeshBasicMaterial).color.set(COLORS.POINT_DEFAULT);
+ (this.highlightedPoint.material as THREE.MeshBasicMaterial).opacity = OPACITY.POINT_DEFAULT;
+ this.highlightedPoint = null;
+ }
+
+ // Clear previous axis highlights
+ this.clearAxisHighlights();
+
+ if (intersects.length > 0) {
+ const intersect = intersects[0];
+ const point = intersect.object as THREE.Mesh;
+ const { gridX, gridY, gridZ } = point.userData;
+
+ // Check if there's already a stone at this position
+ if (!this.hasStone(gridX, gridY, gridZ)) {
+ // Check if position is droppable using game logic validation
+ const isDroppable =
+ this.isGameActive &&
+ (!this.callbacks.isPositionDroppable ||
+ this.callbacks.isPositionDroppable(gridX, gridY, gridZ));
+
+ this.highlightedPoint = point;
+ // Use green for valid/droppable, red for invalid (game inactive or violates rules)
+ const hoverColor = isDroppable ? COLORS.POINT_HOVERED : COLORS.POINT_HOVERED_DISABLED;
+ (point.material as THREE.MeshBasicMaterial).color.set(hoverColor);
+ (point.material as THREE.MeshBasicMaterial).opacity = OPACITY.POINT_HOVERED;
+
+ // Highlight axis-aligned points
+ this.highlightAxisPoints(gridX, gridY, gridZ);
+
+ // Show preview stone only at droppable positions
+ if (this.previewStone && isDroppable) {
+ const spacing = this.gridSpacing;
+ const offsetX = ((this.boardShape.x - 1) * spacing) / 2;
+ const offsetY = ((this.boardShape.y - 1) * spacing) / 2;
+ const offsetZ = ((this.boardShape.z - 1) * spacing) / 2;
+
+ this.previewStone.position.set(
+ gridX * spacing - offsetX,
+ gridY * spacing - offsetY,
+ gridZ * spacing - offsetZ
+ );
+ this.previewStone.visible = true;
+
+ // Update hovered position and refresh joints for preview
+ this.hoveredPosition = { x: gridX, y: gridY, z: gridZ };
+ this.refreshJoints();
+ } else if (this.previewStone) {
+ // Hide preview stone if position is not droppable
+ this.previewStone.visible = false;
+ this.hoveredPosition = null;
+ this.refreshJoints();
+ }
+
+ if (this.callbacks.onPositionHover) {
+ this.callbacks.onPositionHover(gridX, gridY, gridZ);
+ }
+ } else {
+ // Hide preview stone if position is occupied
+ if (this.previewStone) {
+ this.previewStone.visible = false;
+ }
+ this.hoveredPosition = null;
+ this.refreshJoints();
+ if (this.callbacks.onPositionHover) {
+ this.callbacks.onPositionHover(null, null, null);
+ }
+ }
+ } else {
+ // Hide preview stone when not hovering over grid
+ if (this.previewStone) {
+ this.previewStone.visible = false;
+ }
+ this.hoveredPosition = null;
+ this.refreshJoints();
+ if (this.callbacks.onPositionHover) {
+ this.callbacks.onPositionHover(null, null, null);
+ }
+ }
+
+ // Only show pointer cursor when game is active and hovering over valid position
+ const canPlaceStone = intersects.length > 0 && this.isGameActive;
+ this.canvas.style.cursor = canPlaceStone ? "pointer" : "default";
+ }
+
+ private onClick(event: MouseEvent): void {
+ // Don't place stone if we've been dragging
+ if (this.hasDragged) {
+ this.hasDragged = false; // Reset for next interaction
+ return;
+ }
+
+ const rect = this.canvas.getBoundingClientRect();
+ this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+
+ this.raycaster.setFromCamera(this.mouse, this.camera);
+ const intersects = this.raycaster.intersectObjects(this.intersectionPoints.children);
+
+ if (intersects.length > 0) {
+ const intersect = intersects[0];
+ const point = intersect.object as THREE.Mesh;
+ const { gridX, gridY, gridZ } = point.userData;
+
+ if (!this.hasStone(gridX, gridY, gridZ)) {
+ if (this.callbacks.onStoneClick) {
+ this.callbacks.onStoneClick(gridX, gridY, gridZ);
+ }
+ }
+ }
+ }
+
+ private onWindowResize(): void {
+ if (this.isDestroyed) return;
+
+ // Use getBoundingClientRect to get actual CSS size
+ const rect = this.canvas.getBoundingClientRect();
+ const width = rect.width;
+ const height = rect.height;
+
+ this.camera.aspect = width / height;
+ this.camera.updateProjectionMatrix();
+ // Use false as third parameter to prevent updating canvas style
+ this.renderer.setSize(width, height, false);
+ }
+
+ private animate(): void {
+ if (this.isDestroyed) return;
+
+ this.animationId = requestAnimationFrame(() => this.animate());
+
+ // Update fog based on camera distance
+ this.updateFog();
+
+ // Update last placed stone highlight effect only when mouse is over canvas
+ // Using :hover pseudo-class check for better accuracy
+ if (this.canvas.matches && this.canvas.matches(':hover')) {
+ this.updateLastStoneHighlight();
+ }
+
+ this.controls.update();
+ this.renderer.render(this.scene, this.camera);
+ }
+
+ private updateFog(forceUpdate: boolean = false): void {
+ if (!this.scene.fog) return;
+
+ // Get current camera distance from origin
+ const cameraDistance = this.camera.position.length();
+
+ // Only update if camera distance changed (unless forced)
+ if (!forceUpdate && Math.abs(cameraDistance - this.lastCameraDistance) < 0.01) return;
+
+ this.lastCameraDistance = cameraDistance;
+
+ // Calculate diagonal distance of the board
+ const diagonal = Math.sqrt(this.boardShape.x ** 2 + this.boardShape.y ** 2 + this.boardShape.z ** 2);
+ const boardDiagonal = diagonal * this.gridSpacing;
+
+ // Update fog near and far based on camera distance +/- diagonal
+ const fog = this.scene.fog as THREE.Fog;
+ fog.near = Math.max(FOG.MIN_NEAR, cameraDistance - boardDiagonal * FOG.NEAR_FACTOR);
+ fog.far = cameraDistance + boardDiagonal * FOG.FAR_FACTOR;
+ }
+
+ private updateLastStoneHighlight(): void {
+ if (!this.lastPlacedStone) return;
+
+ // Flicker function similar to prototype: sine wave animation
+ const time = Date.now();
+ const flicker = Math.sin(time * SHINING.FLICKER_SPEED) / 2 + 0.5;
+
+ // Get the last placed stone
+ const key = this.getStoneKey(
+ this.lastPlacedStone.x,
+ this.lastPlacedStone.y,
+ this.lastPlacedStone.z
+ );
+ const stone = this.stones.get(key);
+
+ if (stone && stone.mesh) {
+ const material = stone.mesh.material as THREE.MeshPhongMaterial;
+
+ // Cyan/blue glow color (matching prototype)
+ const emissiveColor = new THREE.Color(...SHINING.EMISSIVE_COLOR);
+
+ // Scale intensity based on stone color (white stones get brighter glow)
+ const baseIntensity = stone.color === "white" ? SHINING.BASE_INTENSITY_WHITE : SHINING.BASE_INTENSITY_BLACK;
+ const flickerIntensity = stone.color === "white" ? SHINING.FLICKER_INTENSITY_WHITE : SHINING.FLICKER_INTENSITY_BLACK;
+ const intensity = flicker * flickerIntensity + baseIntensity;
+
+ material.emissive = emissiveColor;
+ material.emissiveIntensity = intensity;
+ }
+ }
+
+ private updateHighlightedGroup(event: MouseEvent): void {
+ // Find the stone group under the mouse cursor
+ const rect = this.canvas.getBoundingClientRect();
+ const mouse = new THREE.Vector2();
+ mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+
+ // Raycast to find clicked stone
+ this.raycaster.setFromCamera(mouse, this.camera);
+ const intersects = this.raycaster.intersectObjects(this.stonesGroup.children);
+
+ if (intersects.length > 0) {
+ // Find the position of the clicked stone
+ let clickedPosition: {x: number, y: number, z: number} | null = null;
+
+ for (const [, stone] of this.stones.entries()) {
+ if (stone.mesh === intersects[0].object) {
+ clickedPosition = stone.position;
+ break;
+ }
+ }
+
+ if (clickedPosition) {
+ // Find the connected group using flood fill
+ this.highlightedGroup = this.findConnectedGroup(clickedPosition);
+
+ // Calculate liberties for the group
+ const libertiesResult = this.calculateLiberties(this.highlightedGroup);
+ this.airPatch = libertiesResult.positions;
+
+ // Notify callback with group info
+ if (this.callbacks.onInspectGroup) {
+ this.callbacks.onInspectGroup(this.highlightedGroup.size, libertiesResult.count);
+ }
+ }
+ } else {
+ this.highlightedGroup = null;
+ this.airPatch = null;
+ // Clear inspect info
+ if (this.callbacks.onInspectGroup) {
+ this.callbacks.onInspectGroup(0, 0);
+ }
+ }
+ }
+
+ private findConnectedGroup(startPos: {x: number, y: number, z: number}): Set {
+ const group = new Set();
+ const startKey = this.getStoneKey(startPos.x, startPos.y, startPos.z);
+ const startStone = this.stones.get(startKey);
+
+ if (!startStone) return group;
+
+ const color = startStone.color;
+ const queue: {x: number, y: number, z: number}[] = [startPos];
+ const visited = new Set();
+
+ while (queue.length > 0) {
+ const pos = queue.shift()!;
+ const key = this.getStoneKey(pos.x, pos.y, pos.z);
+
+ if (visited.has(key)) continue;
+ visited.add(key);
+
+ const stone = this.stones.get(key);
+ if (!stone || stone.color !== color) continue;
+
+ group.add(key);
+
+ // Check all 6 neighbors in 3D space
+ const neighbors = [
+ {x: pos.x + 1, y: pos.y, z: pos.z},
+ {x: pos.x - 1, y: pos.y, z: pos.z},
+ {x: pos.x, y: pos.y + 1, z: pos.z},
+ {x: pos.x, y: pos.y - 1, z: pos.z},
+ {x: pos.x, y: pos.y, z: pos.z + 1},
+ {x: pos.x, y: pos.y, z: pos.z - 1}
+ ];
+
+ for (const neighbor of neighbors) {
+ // Check if neighbor is within board bounds
+ if (neighbor.x >= 0 && neighbor.x < this.boardShape.x &&
+ neighbor.y >= 0 && neighbor.y < this.boardShape.y &&
+ neighbor.z >= 0 && neighbor.z < this.boardShape.z) {
+ const neighborKey = this.getStoneKey(neighbor.x, neighbor.y, neighbor.z);
+ if (!visited.has(neighborKey)) {
+ queue.push(neighbor);
+ }
+ }
+ }
+ }
+
+ return group;
+ }
+
+ private calculateLiberties(group: Set): { count: number; positions: Set } {
+ const liberties = new Set();
+
+ // For each stone in the group, check its neighbors for empty positions
+ for (const key of group) {
+ const parts = key.split(',').map(Number);
+ const pos = {x: parts[0], y: parts[1], z: parts[2]};
+
+ // Check all 6 neighbors
+ const neighbors = [
+ {x: pos.x + 1, y: pos.y, z: pos.z},
+ {x: pos.x - 1, y: pos.y, z: pos.z},
+ {x: pos.x, y: pos.y + 1, z: pos.z},
+ {x: pos.x, y: pos.y - 1, z: pos.z},
+ {x: pos.x, y: pos.y, z: pos.z + 1},
+ {x: pos.x, y: pos.y, z: pos.z - 1}
+ ];
+
+ for (const neighbor of neighbors) {
+ // Check if neighbor is within bounds
+ if (neighbor.x >= 0 && neighbor.x < this.boardShape.x &&
+ neighbor.y >= 0 && neighbor.y < this.boardShape.y &&
+ neighbor.z >= 0 && neighbor.z < this.boardShape.z) {
+ const neighborKey = this.getStoneKey(neighbor.x, neighbor.y, neighbor.z);
+ // If neighbor is empty (not in stones map), it's a liberty
+ if (!this.stones.has(neighborKey)) {
+ liberties.add(neighborKey);
+ }
+ }
+ }
+ }
+
+ return { count: liberties.size, positions: liberties };
+ }
+
+
+ private updateStoneOpacity(): void {
+ // Update opacity of all stones based on inspect mode
+ this.stones.forEach((stone, key) => {
+ if (!stone.mesh) return;
+ const material = stone.mesh.material as THREE.MeshPhongMaterial;
+
+ if (this.inspectMode && this.highlightedGroup) {
+ // Dim stones not in highlighted group
+ if (this.highlightedGroup.has(key)) {
+ material.opacity = 1.0;
+ material.transparent = false;
+ } else {
+ material.opacity = OPACITY.DIMMED;
+ material.transparent = true;
+ }
+ } else {
+ // Normal opacity
+ material.opacity = 1.0;
+ material.transparent = false;
+ }
+ });
+
+ // Update domain cubes first (respects visibility flags and inspect mode)
+ this.refreshDomainVisualization();
+
+ // Update intersection point colors to show air patch (liberties)
+ // This must come after domain cubes so it can check if cubes are visible
+ this.updateAirPatchVisualization();
+ }
+
+
+ private updateAirPatchVisualization(): void {
+ // Reset all intersection points to default color
+ this.intersectionPoints.children.forEach((child) => {
+ const point = child as THREE.Mesh;
+ const material = point.material as THREE.MeshBasicMaterial;
+ const { gridX, gridY, gridZ } = point.userData;
+ const key = this.getStoneKey(gridX, gridY, gridZ);
+
+ // Check if domain cube is visible at this position
+ const domainCube = this.domainCubes.get(key);
+ const hasDomainCube = domainCube && domainCube.visible;
+
+ // Check if this position is a liberty (air patch)
+ if (this.inspectMode && this.airPatch && this.airPatch.has(key)) {
+ if (hasDomainCube) {
+ // Hide intersection point when domain cube is visible
+ point.visible = false;
+ } else {
+ // Show liberty highlight when no domain cube
+ point.visible = true;
+ material.color.set(COLORS.POINT_AIR_PATCH);
+ material.opacity = OPACITY.POINT_AIR_PATCH;
+ }
+ } else if (!this.stones.has(key)) {
+ // Empty position not in air patch - reset to default
+ point.visible = true;
+ material.color.set(COLORS.POINT_DEFAULT);
+ material.opacity = OPACITY.POINT_DEFAULT;
+ }
+ });
+ }
+
+
+ private updateDomainCubesVisualization(blackDomain: Set | null, whiteDomain: Set | null): void {
+ // Update domain cube visibility based on territory and air patch
+ this.domainCubes.forEach((cube, key) => {
+ const material = cube.material as THREE.MeshBasicMaterial;
+
+ // Priority: Air patch > Black domain > White domain > Hidden
+ if (this.inspectMode && this.airPatch && this.airPatch.has(key)) {
+ // Show air patch (liberty) with green color
+ material.color.set(COLORS.POINT_AIR_PATCH);
+ material.opacity = OPACITY.POINT_AIR_PATCH;
+ cube.visible = true;
+ } else if (blackDomain && blackDomain.has(key)) {
+ // Show black territory
+ material.color.set(COLORS.STONE_BLACK);
+ material.opacity = OPACITY.DOMAIN_BLACK;
+ cube.visible = true;
+ } else if (whiteDomain && whiteDomain.has(key)) {
+ // Show white territory
+ material.color.set(COLORS.STONE_WHITE);
+ material.opacity = OPACITY.DOMAIN_WHITE;
+ cube.visible = true;
+ } else {
+ // Hide cube
+ cube.visible = false;
+ }
+ });
+ }
+
+
+ private onKeyDown(event: KeyboardEvent): void {
+ // Ctrl key (17) or Meta key (91/93) for Mac
+ if (event.ctrlKey || event.metaKey) {
+ this.ctrlKeyDown = true;
+ this.inspectMode = true;
+ // Update highlighted group based on last mouse position if available
+ if (this.lastMouseEvent) {
+ this.updateHighlightedGroup(this.lastMouseEvent);
+ }
+ this.updateStoneOpacity();
+ }
+ }
+
+ private onKeyUp(event: KeyboardEvent): void {
+ // Ctrl key release
+ if (!event.ctrlKey && !event.metaKey) {
+ this.ctrlKeyDown = false;
+ if (this.inspectMode) {
+ this.inspectMode = false;
+ this.highlightedGroup = null;
+ this.airPatch = null;
+ this.updateStoneOpacity();
+ // Clear tooltip by calling callback with 0, 0
+ if (this.callbacks.onInspectGroup) {
+ this.callbacks.onInspectGroup(0, 0);
+ }
+ }
+ }
+ }
+
+
+ public destroy(): void {
+ this.isDestroyed = true;
+
+ // Cancel animation
+ if (this.animationId !== null) {
+ cancelAnimationFrame(this.animationId);
+ }
+
+ // Remove event listeners
+ this.canvas.removeEventListener("mousemove", this.onMouseMove.bind(this));
+ this.canvas.removeEventListener("mousedown", this.onMouseDown.bind(this));
+ this.canvas.removeEventListener("mouseup", this.onMouseUp.bind(this));
+ this.canvas.removeEventListener("click", this.onClick.bind(this));
+ window.removeEventListener("resize", this.onWindowResize.bind(this));
+ window.removeEventListener("keydown", this.onKeyDown.bind(this));
+ window.removeEventListener("keyup", this.onKeyUp.bind(this));
+
+ // Dispose of Three.js resources
+ this.clearBoard();
+ this.gridGroup.clear();
+ this.intersectionPoints.clear();
+ this.renderer.dispose();
+ this.controls.dispose();
+ }
+}
diff --git a/trigo-web/app/src/stores/gameStore.ts b/trigo-web/app/src/stores/gameStore.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7ab6da0a87746bc62018477ede1831c24e3dd104
--- /dev/null
+++ b/trigo-web/app/src/stores/gameStore.ts
@@ -0,0 +1,310 @@
+import { defineStore } from "pinia";
+import { TrigoGameFrontend } from "../utils/TrigoGameFrontend";
+import type {
+ Stone,
+ BoardShape,
+ Player,
+ Move,
+ Position,
+ GameConfig,
+ TerritoryResult
+} from "../../../inc/trigo";
+
+
+/**
+ * Game Store State
+ *
+ * Refactored to use TrigoGameFrontend as the single source of truth
+ */
+export interface GameState {
+ game: TrigoGameFrontend;
+ config: GameConfig;
+}
+
+
+/**
+ * Pinia store for game state management
+ *
+ * Now delegates all game logic to TrigoGameFrontend
+ */
+export const useGameStore = defineStore("game", {
+ state: (): GameState => ({
+ game: new TrigoGameFrontend({ x: 5, y: 5, z: 5 }),
+ config: {
+ boardShape: { x: 5, y: 5, z: 5 },
+ allowUndo: true
+ }
+ }),
+
+
+ getters: {
+ // Board state
+ board: (state): Stone[][][] => state.game.getBoard(),
+ boardShape: (state): BoardShape => state.game.getShape(),
+
+ // Current player
+ currentPlayer: (state): Player => state.game.getCurrentPlayerString(),
+ currentPlayerColor: (state): Player => state.game.getCurrentPlayerString(),
+ opponentPlayer: (state): Player => state.game.getOpponentPlayer(),
+
+ // Move history
+ moveHistory: (state): Move[] => state.game.getMoveHistory(),
+ moveCount: (state): number => state.game.getMoveCount(),
+ currentMoveIndex: (state): number => state.game.getCurrentMoveIndex(),
+ currentMoves: (state): Move[] => {
+ const history = state.game.getMoveHistory();
+ const currentIndex = state.game.getCurrentMoveIndex();
+ return history.slice(0, currentIndex);
+ },
+
+ // Game status
+ gameStatus: (state) => state.game.getStatus(),
+ gameResult: (state) => state.game.getResult(),
+ isGameActive: (state): boolean => state.game.isGameActive(),
+
+ // Captured stones
+ capturedStones: (state): { black: number; white: number } => {
+ const counts = state.game.getCapturedCounts();
+ return {
+ black: counts.white, // Black captured white stones
+ white: counts.black // White captured black stones
+ };
+ },
+
+ // Pass tracking
+ passCount: (state): number => state.game.getConsecutivePassCount(),
+
+ // Undo/Redo capabilities
+ canUndo: (state): boolean => {
+ return state.config.allowUndo && state.game.canUndo();
+ },
+ canRedo: (state): boolean => {
+ return state.config.allowUndo && state.game.canRedo();
+ },
+
+ // Position checks
+ isValidPosition: (state) => (x: number, y: number, z: number): boolean => {
+ return state.game.isValidPosition(x, y, z);
+ },
+ isEmpty: (state) => (x: number, y: number, z: number): boolean => {
+ return state.game.isEmpty(x, y, z);
+ },
+ getStone: (state) => (x: number, y: number, z: number): Stone => {
+ return state.game.getStoneAt(x, y, z) as Stone;
+ }
+ },
+
+
+ actions: {
+ /**
+ * Initialize a new game
+ */
+ initializeGame(boardShape: BoardShape = { x: 5, y: 5, z: 5 }): void {
+ this.game = new TrigoGameFrontend(boardShape);
+ this.config.boardShape = boardShape;
+ this.saveToSessionStorage();
+ },
+
+
+ /**
+ * Start the game
+ */
+ startGame(): void {
+ this.game.startGame();
+ this.saveToSessionStorage();
+ },
+
+
+ /**
+ * Make a move
+ */
+ makeMove(x: number, y: number, z: number): { success: boolean; capturedPositions?: Position[] } {
+ // Check if game is active
+ if (!this.game.isGameActive()) {
+ console.warn("Game is not active");
+ return { success: false };
+ }
+
+ // Attempt to make the move
+ const success = this.game.makeMove(x, y, z);
+
+ if (success) {
+ // Get captured positions from last step
+ const lastStep = this.game.getLastStep();
+ const capturedPositions = lastStep?.capturedPositions;
+
+ // Save to storage
+ this.saveToSessionStorage();
+
+ return { success: true, capturedPositions };
+ }
+
+ return { success: false };
+ },
+
+
+ /**
+ * Pass turn
+ */
+ pass(): boolean {
+ if (!this.game.isGameActive()) {
+ return false;
+ }
+
+ const success = this.game.pass();
+ if (success) {
+ this.saveToSessionStorage();
+ }
+ return success;
+ },
+
+
+ /**
+ * Resign/surrender
+ */
+ resign(): boolean {
+ if (!this.game.isGameActive()) {
+ return false;
+ }
+
+ const success = this.game.surrender();
+ if (success) {
+ this.saveToSessionStorage();
+ }
+ return success;
+ },
+
+
+ /**
+ * Undo last move
+ */
+ undoMove(): boolean {
+ if (!this.canUndo) {
+ return false;
+ }
+
+ const success = this.game.undoMove();
+ if (success) {
+ this.saveToSessionStorage();
+ }
+ return success;
+ },
+
+
+ /**
+ * Redo next move
+ */
+ redoMove(): boolean {
+ if (!this.canRedo) {
+ return false;
+ }
+
+ const success = this.game.redoMove();
+ if (success) {
+ this.saveToSessionStorage();
+ }
+ return success;
+ },
+
+
+ /**
+ * Jump to specific move in history
+ */
+ jumpToMove(index: number): boolean {
+ const success = this.game.jumpToMove(index);
+ if (success) {
+ this.saveToSessionStorage();
+ }
+ return success;
+ },
+
+
+ /**
+ * Reset game to initial state
+ */
+ resetGame(): void {
+ this.game.reset();
+ this.saveToSessionStorage();
+ },
+
+
+ /**
+ * Change board shape (only when game is idle)
+ */
+ setBoardShape(shape: BoardShape): boolean {
+ if (this.game.getStatus() !== "idle") {
+ console.warn("Cannot change board shape while game is active");
+ return false;
+ }
+
+ this.initializeGame(shape);
+ return true;
+ },
+
+
+ /**
+ * Calculate territory
+ */
+ computeTerritory(): TerritoryResult {
+ return this.game.computeTerritory();
+ },
+
+
+ /**
+ * Get neighboring positions (6 directions in 3D)
+ */
+ getNeighbors(x: number, y: number, z: number): Array<{ x: number; y: number; z: number }> {
+ const neighbors = [
+ { x: x + 1, y, z },
+ { x: x - 1, y, z },
+ { x, y: y + 1, z },
+ { x, y: y - 1, z },
+ { x, y, z: z + 1 },
+ { x, y, z: z - 1 }
+ ];
+
+ return neighbors.filter((pos) => this.isValidPosition(pos.x, pos.y, pos.z));
+ },
+
+
+ /**
+ * Save game state to session storage
+ */
+ saveToSessionStorage(): void {
+ try {
+ this.game.saveToSessionStorage("trigoGameState");
+ } catch (error) {
+ console.error("Failed to save game state:", error);
+ }
+ },
+
+
+ /**
+ * Load game state from session storage
+ */
+ loadFromSessionStorage(): boolean {
+ try {
+ const success = this.game.loadFromSessionStorage("trigoGameState");
+ if (success) {
+ console.log("Game state restored from session storage");
+ }
+ return success;
+ } catch (error) {
+ console.error("Failed to load game state:", error);
+ return false;
+ }
+ },
+
+
+ /**
+ * Clear saved game state
+ */
+ clearSessionStorage(): void {
+ try {
+ this.game.clearSessionStorage("trigoGameState");
+ } catch (error) {
+ console.error("Failed to clear session storage:", error);
+ }
+ }
+ }
+});
diff --git a/trigo-web/app/src/utils/TrigoGameFrontend.ts b/trigo-web/app/src/utils/TrigoGameFrontend.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c7bd438a733700e31d32ea159d94cefbe536304
--- /dev/null
+++ b/trigo-web/app/src/utils/TrigoGameFrontend.ts
@@ -0,0 +1,258 @@
+/**
+ * TrigoGameFrontend - Frontend wrapper for TrigoGame
+ *
+ * Provides convenient string-based interfaces for frontend use
+ * Wraps TrigoGame's number-based types with string types
+ */
+
+import {
+ TrigoGame,
+ type BoardShape,
+ type Move,
+ type Player,
+ type TerritoryResult,
+ stoneToPlayer,
+ stepsToMoves,
+ makePosition,
+ type GameStatus,
+ type GameResult
+} from "../../../inc/trigo";
+
+
+/**
+ * TrigoGameFrontend - Extended TrigoGame with frontend-friendly interfaces
+ */
+export class TrigoGameFrontend extends TrigoGame {
+ /**
+ * Make a move with coordinates (convenience method)
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param z Z coordinate
+ * @returns true if move was successful
+ */
+ makeMove(x: number, y: number, z: number): boolean {
+ return this.drop(makePosition(x, y, z));
+ }
+
+
+ /**
+ * Get current player as string
+ *
+ * @returns "black" | "white"
+ */
+ getCurrentPlayerString(): Player {
+ return stoneToPlayer(this.getCurrentPlayer());
+ }
+
+
+ /**
+ * Get move history in frontend format
+ *
+ * @returns Array of Move objects
+ */
+ getMoveHistory(): Move[] {
+ return stepsToMoves(this.getHistory());
+ }
+
+
+ /**
+ * Get stone at position, returns number format for frontend
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param z Z coordinate
+ * @returns 0 = Empty, 1 = Black, 2 = White
+ */
+ getStoneAt(x: number, y: number, z: number): number {
+ return this.getStone(makePosition(x, y, z)) as number;
+ }
+
+
+ /**
+ * Check if position is valid
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param z Z coordinate
+ * @returns true if position is on the board
+ */
+ isValidPosition(x: number, y: number, z: number): boolean {
+ const shape = this.getShape();
+ return (
+ x >= 0 &&
+ x < shape.x &&
+ y >= 0 &&
+ y < shape.y &&
+ z >= 0 &&
+ z < shape.z
+ );
+ }
+
+
+ /**
+ * Check if position is empty
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param z Z coordinate
+ * @returns true if position is empty
+ */
+ isEmpty(x: number, y: number, z: number): boolean {
+ if (!this.isValidPosition(x, y, z)) {
+ return false;
+ }
+ return this.getStoneAt(x, y, z) === 0;
+ }
+
+
+ /**
+ * Check if a move is valid at position
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param z Z coordinate
+ * @returns Object with valid flag and optional reason
+ */
+ isValidMoveAt(x: number, y: number, z: number): { valid: boolean; reason?: string } {
+ return this.isValidMove(makePosition(x, y, z));
+ }
+
+
+ /**
+ * Get opponent player
+ *
+ * @returns Opponent player color
+ */
+ getOpponentPlayer(): Player {
+ const current = this.getCurrentPlayerString();
+ return current === "black" ? "white" : "black";
+ }
+
+
+ /**
+ * Check if undo is available
+ *
+ * @returns true if can undo
+ */
+ canUndo(): boolean {
+ return this.getCurrentStep() > 0;
+ }
+
+
+ /**
+ * Undo move (convenience wrapper)
+ *
+ * @returns true if undo was successful
+ */
+ undoMove(): boolean {
+ return this.undo();
+ }
+
+
+ /**
+ * Redo move (convenience wrapper)
+ *
+ * @returns true if redo was successful
+ */
+ redoMove(): boolean {
+ return this.redo();
+ }
+
+
+ /**
+ * Jump to specific move in history
+ *
+ * @param index Move index (-1 for initial state)
+ * @returns true if jump was successful
+ */
+ jumpToMove(index: number): boolean {
+ return this.jumpToStep(index);
+ }
+
+
+ /**
+ * Get total move count
+ *
+ * @returns Number of moves played
+ */
+ getMoveCount(): number {
+ return this.getCurrentStep();
+ }
+
+
+ /**
+ * Get current move index
+ *
+ * @returns Current step index
+ */
+ getCurrentMoveIndex(): number {
+ return this.getCurrentStep();
+ }
+
+
+ /**
+ * Get board shape
+ *
+ * @returns Board dimensions
+ */
+ getBoardShape(): BoardShape {
+ return this.getShape();
+ }
+
+
+ /**
+ * Initialize game with board shape
+ *
+ * @param shape Board dimensions
+ */
+ initializeGame(shape?: BoardShape): void {
+ if (shape) {
+ // Would need to recreate the game instance with new shape
+ // For now, just reset
+ this.reset();
+ } else {
+ this.reset();
+ }
+ }
+
+
+ /**
+ * Compute territory (convenience wrapper)
+ *
+ * @returns Territory calculation result
+ */
+ computeTerritory(): TerritoryResult {
+ return this.getTerritory();
+ }
+
+
+ /**
+ * Get game status
+ *
+ * @returns Game status
+ */
+ getStatus(): GameStatus {
+ return this.getGameStatus();
+ }
+
+
+ /**
+ * Get game result
+ *
+ * @returns Game result if available
+ */
+ getResult(): GameResult | undefined {
+ return this.getGameResult();
+ }
+
+
+ /**
+ * Get consecutive pass count
+ *
+ * @returns Number of consecutive passes
+ */
+ getConsecutivePassCount(): number {
+ return this.getPassCount();
+ }
+}
diff --git a/trigo-web/app/src/views/TrigoView.vue b/trigo-web/app/src/views/TrigoView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..69441452b77bf9bd31c09cc582823e3e8456866c
--- /dev/null
+++ b/trigo-web/app/src/views/TrigoView.vue
@@ -0,0 +1,1604 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ showTerritoryMode ? 'Territory' : 'Captured' }}
+
+
+
+
+
+
+
+
+
+
+ Move History
+
+
+
+
+
+
+
+ OPEN
+
+
+
+
{{ roundIndex + 1 }}.
+
+
+ {{ formatMoveCoords(round.black) }}
+ pass
+
+
+
+ {{ formatMoveCoords(round.white) }}
+ pass
+
+
+
+
+
+
+
+
+
+
+
+
+
Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/trigo-web/app/test_capture.js b/trigo-web/app/test_capture.js
new file mode 100644
index 0000000000000000000000000000000000000000..83d89a910350b18161567c0045f15f165db417e7
--- /dev/null
+++ b/trigo-web/app/test_capture.js
@@ -0,0 +1,23 @@
+const { TrigoGame } = require('../../inc/trigo/trigoGame.js');
+const { StoneType } = require('../../inc/trigo/game.js');
+
+const game = new TrigoGame({ x: 5, y: 5, z: 1 });
+game.startGame();
+
+console.log("=== Testing 2D Capture ===");
+game.drop({ x: 2, y: 2, z: 0 }); // Black
+console.log("After move 1, board[3][2][0]:", game.getBoard()[3][2][0]);
+game.drop({ x: 3, y: 2, z: 0 }); // White (target)
+console.log("After move 2, board[3][2][0]:", game.getBoard()[3][2][0]);
+game.drop({ x: 4, y: 2, z: 0 }); // Black
+console.log("After move 3, board[3][2][0]:", game.getBoard()[3][2][0]);
+game.drop({ x: 3, y: 1, z: 0 }); // White elsewhere
+game.drop({ x: 3, y: 3, z: 0 }); // Black
+console.log("After move 5, board[3][2][0]:", game.getBoard()[3][2][0]);
+game.drop({ x: 1, y: 1, z: 0 }); // White elsewhere
+game.drop({ x: 2, y: 1, z: 0 }); // Black - should capture
+
+console.log("\nAfter final move:");
+console.log("board[3][2][0]:", game.getBoard()[3][2][0], "(should be 0/EMPTY if captured)");
+console.log("Last step capturedPositions:", game.getLastStep()?.capturedPositions);
+console.log("Captured counts:", game.getCapturedCounts());
diff --git a/trigo-web/app/vite.config.ts b/trigo-web/app/vite.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7f540b9d16373ffe26935880e9c7409beb0d4ca9
--- /dev/null
+++ b/trigo-web/app/vite.config.ts
@@ -0,0 +1,42 @@
+import { defineConfig, loadEnv } from "vite";
+import vue from "@vitejs/plugin-vue";
+import { fileURLToPath, URL } from "node:url";
+
+// https://vitejs.dev/config/
+export default defineConfig(({ mode }) => {
+ // Load env file based on `mode` in the current working directory.
+ const env = loadEnv(mode, process.cwd(), "");
+
+ return {
+ plugins: [vue()],
+ // Point to parent project's public directory
+ publicDir: fileURLToPath(new URL("../public", import.meta.url)),
+ resolve: {
+ alias: {
+ "@": fileURLToPath(new URL("./src", import.meta.url)),
+ "@inc": fileURLToPath(new URL("../inc", import.meta.url))
+ }
+ },
+ server: {
+ host: env.VITE_HOST || "localhost",
+ port: parseInt(env.VITE_PORT) || 5173,
+ strictPort: true,
+ open: false
+ },
+ build: {
+ rollupOptions: {
+ external: ["/lib/tgnParser.cjs"]
+ }
+ },
+ css: {
+ preprocessorOptions: {
+ scss: {
+ api: "modern" // Use modern Sass API
+ }
+ }
+ },
+ define: {
+ "process.env": {}
+ }
+ };
+});
\ No newline at end of file
diff --git a/trigo-web/backend/.env b/trigo-web/backend/.env
new file mode 100644
index 0000000000000000000000000000000000000000..bf323eefdfed33a090fa024a1f3108ddbe53f893
--- /dev/null
+++ b/trigo-web/backend/.env
@@ -0,0 +1,3 @@
+PORT=3000
+CLIENT_URL=http://localhost:5173
+NODE_ENV=development
\ No newline at end of file
diff --git a/trigo-web/backend/package-lock.json b/trigo-web/backend/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..5f37579f14a180c63bacb57346764020a8610558
--- /dev/null
+++ b/trigo-web/backend/package-lock.json
@@ -0,0 +1,1663 @@
+{
+ "name": "trigo-backend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "trigo-backend",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@types/uuid": "^11.0.0",
+ "cors": "^2.8.5",
+ "dotenv": "^16.6.1",
+ "express": "^4.21.2",
+ "socket.io": "^4.8.1",
+ "uuid": "^13.0.0"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.13",
+ "@types/express": "^4.17.17",
+ "@types/node": "^20.5.0",
+ "nodemon": "^3.0.1",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.2.2"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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,
+ "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
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "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=="
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+ "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true
+ },
+ "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,
+ "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,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
+ "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.7",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz",
+ "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
+ "dev": true,
+ "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
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.23",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz",
+ "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "dev": true
+ },
+ "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
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz",
+ "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==",
+ "dev": true,
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.5",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
+ "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
+ "dev": true,
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/uuid": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz",
+ "integrity": "sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==",
+ "deprecated": "This is a stub types definition. uuid provides its own type definitions, so you do not need this installed.",
+ "dependencies": {
+ "uuid": "*"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
+ "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=="
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "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==",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "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.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "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==",
+ "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/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "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==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "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=="
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/engine.io": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+ "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+ "dependencies": {
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.7.2",
+ "cors": "~2.8.5",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/engine.io/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/engine.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io/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=="
+ },
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "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=="
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "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.13.0",
+ "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/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "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.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "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==",
+ "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,
+ "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==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "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==",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
+ "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==",
+ "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==",
+ "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==",
+ "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==",
+ "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==",
+ "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==",
+ "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==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
+ "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nodemon/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
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.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"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/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=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "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==",
+ "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==",
+ "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/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/socket.io": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+ "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.3.2",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+ "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+ "dependencies": {
+ "debug": "~4.3.4",
+ "ws": "~8.17.1"
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-adapter/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=="
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-parser/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=="
+ },
+ "node_modules/socket.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io/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=="
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "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==",
+ "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==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true
+ },
+ "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=="
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
+ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "bin": {
+ "uuid": "dist-node/bin/uuid"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "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/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ }
+ }
+}
diff --git a/trigo-web/backend/package.json b/trigo-web/backend/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..acb4aea8b7d295d904faa77cd90bc7e927519ad1
--- /dev/null
+++ b/trigo-web/backend/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "trigo-backend",
+ "version": "1.0.0",
+ "description": "Backend server for Trigo game",
+ "main": "dist/server.js",
+ "scripts": {
+ "dev": "nodemon --watch src --exec ts-node src/server.ts",
+ "build": "tsc",
+ "start": "node dist/server.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [
+ "game",
+ "go",
+ "websocket",
+ "nodejs"
+ ],
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "@types/uuid": "^11.0.0",
+ "cors": "^2.8.5",
+ "dotenv": "^16.6.1",
+ "express": "^4.21.2",
+ "socket.io": "^4.8.1",
+ "uuid": "^13.0.0"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.13",
+ "@types/express": "^4.17.17",
+ "@types/node": "^20.5.0",
+ "nodemon": "^3.0.1",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.2.2"
+ }
+}
diff --git a/trigo-web/backend/src/server.ts b/trigo-web/backend/src/server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e538f29a9715e8ed30094f1d0067a2aefc882ed
--- /dev/null
+++ b/trigo-web/backend/src/server.ts
@@ -0,0 +1,59 @@
+const express = require("express");
+const { createServer } = require("http");
+const { Server } = require("socket.io");
+const cors = require("cors");
+const dotenv = require("dotenv");
+const path = require("path");
+
+dotenv.config();
+
+const app = express();
+const httpServer = createServer(app);
+const io = new Server(httpServer, {
+ cors: {
+ origin: process.env.CLIENT_URL || "http://localhost:5173",
+ methods: ["GET", "POST"]
+ }
+});
+
+const PORT = process.env.PORT || 3000;
+const HOST = process.env.HOST || "localhost";
+
+// Middleware
+app.use(cors());
+app.use(express.json());
+
+// Serve static files from frontend build (for production)
+if (process.env.NODE_ENV === "production") {
+ const frontendPath = path.join(__dirname, "../../app/dist");
+ app.use(express.static(frontendPath));
+
+ // Serve index.html for all routes (SPA support)
+ app.get("*", (req: any, res: any, next: any) => {
+ if (req.path.startsWith("/health") || req.path.startsWith("/socket.io")) {
+ return next();
+ }
+ res.sendFile(path.join(frontendPath, "index.html"));
+ });
+}
+
+// Health check endpoint
+app.get("/health", (_req: any, res: any) => {
+ res.json({ status: "ok", timestamp: new Date().toISOString() });
+});
+
+// Socket.io connection handling
+io.on("connection", (socket: any) => {
+ console.log(`New client connected: ${socket.id}`);
+
+ socket.on("disconnect", () => {
+ console.log(`Client disconnected: ${socket.id}`);
+ });
+});
+
+// Start server
+httpServer.listen(PORT, HOST, () => {
+ console.log(`Server running on ${HOST}:${PORT}`);
+ console.log(`Health check: http://${HOST}:${PORT}/health`);
+ console.log(`Environment: ${process.env.NODE_ENV || "development"}`);
+});
\ No newline at end of file
diff --git a/trigo-web/backend/src/services/gameManager.ts b/trigo-web/backend/src/services/gameManager.ts
new file mode 100644
index 0000000000000000000000000000000000000000..af9cee4552d5c2bc21046cb3d6d33def57dda1de
--- /dev/null
+++ b/trigo-web/backend/src/services/gameManager.ts
@@ -0,0 +1,329 @@
+import { v4 as uuidv4 } from "uuid";
+import type { BoardShape, Position } from "../../../inc/trigo";
+import { TrigoGame, StepType, StoneType } from "../../../inc/trigo/game";
+
+export interface Player {
+ id: string;
+ nickname: string;
+ color: "black" | "white";
+ connected: boolean;
+}
+
+export interface GameState {
+ gameStatus: "waiting" | "playing" | "finished";
+ winner: "black" | "white" | null;
+}
+
+export interface GameRoom {
+ id: string;
+ players: { [playerId: string]: Player };
+ game: TrigoGame; // The actual game instance
+ gameState: GameState; // Game status metadata
+ createdAt: Date;
+ startedAt: Date | null;
+}
+
+export class GameManager {
+ private rooms: Map = new Map();
+ private playerRoomMap: Map = new Map();
+ private defaultBoardShape: BoardShape = { x: 5, y: 5, z: 5 }; // Default 5x5x5 board
+
+ constructor() {
+ console.log("GameManager initialized");
+ }
+
+ createRoom(playerId: string, nickname: string, boardShape?: BoardShape): GameRoom | null {
+ const roomId = this.generateRoomId();
+ const shape = boardShape || this.defaultBoardShape;
+
+ const room: GameRoom = {
+ id: roomId,
+ players: {
+ [playerId]: {
+ id: playerId,
+ nickname,
+ color: "black",
+ connected: true
+ }
+ },
+ game: new TrigoGame(shape, {
+ onStepAdvance: (_step, history) => {
+ console.log(`Step ${history.length}: Player made move`);
+ },
+ onCapture: (captured) => {
+ console.log(`Captured ${captured.length} stones`);
+ },
+ onWin: (winner) => {
+ console.log(`Game won by ${winner}`);
+ }
+ }),
+ gameState: {
+ gameStatus: "waiting",
+ winner: null
+ },
+ createdAt: new Date(),
+ startedAt: null
+ };
+
+ this.rooms.set(roomId, room);
+ this.playerRoomMap.set(playerId, roomId);
+
+ console.log(`Room ${roomId} created by ${playerId}`);
+ return room;
+ }
+
+ joinRoom(roomId: string, playerId: string, nickname: string): GameRoom | null {
+ const room = this.rooms.get(roomId);
+ if (!room) {
+ return null;
+ }
+
+ const playerCount = Object.keys(room.players).length;
+ if (playerCount >= 2) {
+ return null; // Room is full
+ }
+
+ // Assign white color to the second player
+ room.players[playerId] = {
+ id: playerId,
+ nickname,
+ color: "white",
+ connected: true
+ };
+
+ this.playerRoomMap.set(playerId, roomId);
+
+ // Start the game when second player joins
+ if (playerCount === 1) {
+ room.gameState.gameStatus = "playing";
+ room.startedAt = new Date();
+ }
+
+ return room;
+ }
+
+ leaveRoom(roomId: string, playerId: string): void {
+ const room = this.rooms.get(roomId);
+ if (!room) return;
+
+ if (room.players[playerId]) {
+ room.players[playerId].connected = false;
+ }
+
+ this.playerRoomMap.delete(playerId);
+
+ // Check if room should be deleted
+ const connectedPlayers = Object.values(room.players).filter((p) => p.connected);
+ if (connectedPlayers.length === 0) {
+ this.rooms.delete(roomId);
+ console.log(`Room ${roomId} deleted - no players remaining`);
+ }
+ }
+
+ makeMove(
+ roomId: string,
+ playerId: string,
+ move: { x: number; y: number; z: number }
+ ): boolean {
+ const room = this.rooms.get(roomId);
+ if (!room) return false;
+
+ const player = room.players[playerId];
+ if (!player) return false;
+
+ // Check game status
+ if (room.gameState.gameStatus !== "playing") {
+ return false;
+ }
+
+ // Convert player color to Stone type
+ const expectedPlayer = player.color === "black" ? StoneType.BLACK : StoneType.WHITE;
+ const currentPlayer = room.game.getCurrentPlayer();
+
+ // Check if it's the player's turn
+ if (currentPlayer !== expectedPlayer) {
+ return false;
+ }
+
+ // Attempt to make the move using TrigoGame
+ const position: Position = { x: move.x, y: move.y, z: move.z };
+ const success = room.game.drop(position);
+
+ return success;
+ }
+
+ passTurn(roomId: string, playerId: string): boolean {
+ const room = this.rooms.get(roomId);
+ if (!room) return false;
+
+ const player = room.players[playerId];
+ if (!player) return false;
+
+ // Check game status
+ if (room.gameState.gameStatus !== "playing") {
+ return false;
+ }
+
+ // Convert player color to Stone type
+ const expectedPlayer = player.color === "black" ? StoneType.BLACK : StoneType.WHITE;
+ const currentPlayer = room.game.getCurrentPlayer();
+
+ // Check if it's the player's turn
+ if (currentPlayer !== expectedPlayer) {
+ return false;
+ }
+
+ return room.game.pass();
+ }
+
+ resign(roomId: string, playerId: string): boolean {
+ const room = this.rooms.get(roomId);
+ if (!room) return false;
+
+ const player = room.players[playerId];
+ if (!player) return false;
+
+ // Surrender the game
+ room.game.surrender();
+
+ // Update room state
+ room.gameState.gameStatus = "finished";
+ room.gameState.winner = player.color === "black" ? "white" : "black";
+
+ return true;
+ }
+
+ /**
+ * Undo the last move (ๆๆฃ)
+ */
+ undoMove(roomId: string, playerId: string): boolean {
+ const room = this.rooms.get(roomId);
+ if (!room) return false;
+
+ const player = room.players[playerId];
+ if (!player) return false;
+
+ // Check game status
+ if (room.gameState.gameStatus !== "playing") {
+ return false;
+ }
+
+ return room.game.undo();
+ }
+
+ /**
+ * Get game board state for a room
+ */
+ getGameBoard(roomId: string): number[][][] | null {
+ const room = this.rooms.get(roomId);
+ if (!room) return null;
+
+ return room.game.getBoard();
+ }
+
+ /**
+ * Get game statistics for a room
+ */
+ getGameStats(roomId: string) {
+ const room = this.rooms.get(roomId);
+ if (!room) return null;
+
+ return room.game.getStats();
+ }
+
+ /**
+ * Get current player for a room
+ */
+ getCurrentPlayer(roomId: string): "black" | "white" | null {
+ const room = this.rooms.get(roomId);
+ if (!room) return null;
+
+ const currentStone = room.game.getCurrentPlayer();
+ return currentStone === StoneType.BLACK ? "black" : "white";
+ }
+
+ /**
+ * Calculate and get territory for a room
+ */
+ getTerritory(roomId: string) {
+ const room = this.rooms.get(roomId);
+ if (!room) return null;
+
+ return room.game.getTerritory();
+ }
+
+ /**
+ * End the game and determine winner based on territory
+ */
+ endGameByTerritory(roomId: string): boolean {
+ const room = this.rooms.get(roomId);
+ if (!room) return false;
+
+ if (room.gameState.gameStatus !== "playing") {
+ return false;
+ }
+
+ // Calculate final territory
+ const territory = room.game.getTerritory();
+
+ // Determine winner
+ if (territory.black > territory.white) {
+ room.gameState.winner = "black";
+ } else if (territory.white > territory.black) {
+ room.gameState.winner = "white";
+ } else {
+ // Draw - could set winner to null or handle differently
+ room.gameState.winner = null;
+ }
+
+ room.gameState.gameStatus = "finished";
+ console.log(`Game ${roomId} ended. Black: ${territory.black}, White: ${territory.white}, Winner: ${room.gameState.winner}`);
+
+ return true;
+ }
+
+ /**
+ * Check if both players passed consecutively (game should end)
+ * Returns true if game was ended
+ */
+ checkConsecutivePasses(roomId: string): boolean {
+ const room = this.rooms.get(roomId);
+ if (!room) return false;
+
+ const history = room.game.getHistory();
+ if (history.length < 2) return false;
+
+ // Get last two moves
+ const lastMove = history[history.length - 1];
+ const secondLastMove = history[history.length - 2];
+
+ // Check if both were passes
+ if (lastMove.type === StepType.PASS && secondLastMove.type === StepType.PASS) {
+ // Two consecutive passes - end the game
+ this.endGameByTerritory(roomId);
+ return true;
+ }
+
+ return false;
+ }
+
+ getRoom(roomId: string): GameRoom | undefined {
+ return this.rooms.get(roomId);
+ }
+
+ getPlayerRoom(playerId: string): GameRoom | undefined {
+ const roomId = this.playerRoomMap.get(playerId);
+ if (!roomId) return undefined;
+ return this.rooms.get(roomId);
+ }
+
+ getActiveRooms(): GameRoom[] {
+ return Array.from(this.rooms.values()).filter(
+ (room) => room.gameState.gameStatus !== "finished"
+ );
+ }
+
+ private generateRoomId(): string {
+ return uuidv4().substring(0, 8).toUpperCase();
+ }
+}
\ No newline at end of file
diff --git a/trigo-web/backend/src/sockets/gameSocket.ts b/trigo-web/backend/src/sockets/gameSocket.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bde19188d498b85c090fa81b69e03a9b09d9bcd0
--- /dev/null
+++ b/trigo-web/backend/src/sockets/gameSocket.ts
@@ -0,0 +1,155 @@
+import { Server, Socket } from "socket.io";
+import { GameManager } from "../services/gameManager";
+
+export function setupSocketHandlers(io: Server, socket: Socket, gameManager: GameManager) {
+ console.log(`Setting up socket handlers for ${socket.id}`);
+
+ // Join room
+ socket.on("joinRoom", (data: { roomId?: string; nickname: string }) => {
+ const { roomId, nickname } = data;
+
+ // Create or join room
+ const room = roomId ? gameManager.joinRoom(roomId, socket.id, nickname) : gameManager.createRoom(socket.id, nickname);
+
+ if (room) {
+ socket.join(room.id);
+
+ // Get complete game data for frontend
+ const board = gameManager.getGameBoard(room.id);
+ const currentPlayer = gameManager.getCurrentPlayer(room.id);
+ const stats = gameManager.getGameStats(room.id);
+
+ socket.emit("roomJoined", {
+ roomId: room.id,
+ playerId: socket.id,
+ playerColor: room.players[socket.id]?.color,
+ gameState: {
+ board,
+ boardShape: room.game.getShape(),
+ currentPlayer,
+ moveHistory: room.game.getHistory(),
+ currentMoveIndex: room.game.getCurrentStep(),
+ capturedStones: {
+ black: stats?.capturedByBlack || 0,
+ white: stats?.capturedByWhite || 0
+ },
+ gameStatus: room.gameState.gameStatus,
+ winner: room.gameState.winner
+ }
+ });
+
+ // Notify other players
+ socket.to(room.id).emit("playerJoined", {
+ playerId: socket.id,
+ nickname: nickname
+ });
+
+ console.log(`Player ${socket.id} joined room ${room.id}`);
+ } else {
+ socket.emit("error", { message: "Failed to join room" });
+ }
+ });
+
+ // Leave room
+ socket.on("leaveRoom", () => {
+ const room = gameManager.getPlayerRoom(socket.id);
+ if (room) {
+ socket.leave(room.id);
+ gameManager.leaveRoom(room.id, socket.id);
+
+ // Notify others
+ socket.to(room.id).emit("playerLeft", {
+ playerId: socket.id
+ });
+ }
+ });
+
+ // Game moves
+ socket.on("makeMove", (data: { x: number; y: number; z: number }) => {
+ const room = gameManager.getPlayerRoom(socket.id);
+ if (room && gameManager.makeMove(room.id, socket.id, data)) {
+ // Get updated game data
+ const board = gameManager.getGameBoard(room.id);
+ const currentPlayer = gameManager.getCurrentPlayer(room.id);
+ const stats = gameManager.getGameStats(room.id);
+ const lastStep = room.game.getLastStep();
+
+ // Broadcast game update to all players in the room
+ io.to(room.id).emit("gameUpdate", {
+ board,
+ currentPlayer,
+ lastMove: data,
+ capturedStones: {
+ black: stats?.capturedByBlack || 0,
+ white: stats?.capturedByWhite || 0
+ },
+ capturedPositions: lastStep?.capturedPositions,
+ moveHistory: room.game.getHistory(),
+ currentMoveIndex: room.game.getCurrentStep()
+ });
+ } else {
+ socket.emit("error", { message: "Invalid move" });
+ }
+ });
+
+ // Pass turn
+ socket.on("pass", () => {
+ const room = gameManager.getPlayerRoom(socket.id);
+ if (room && gameManager.passTurn(room.id, socket.id)) {
+ const currentPlayer = gameManager.getCurrentPlayer(room.id);
+
+ io.to(room.id).emit("gameUpdate", {
+ currentPlayer,
+ action: "pass",
+ moveHistory: room.game.getHistory(),
+ currentMoveIndex: room.game.getCurrentStep()
+ });
+
+ // Check for consecutive passes (game end)
+ if (gameManager.checkConsecutivePasses(room.id)) {
+ const territory = gameManager.getTerritory(room.id);
+ io.to(room.id).emit("gameEnded", {
+ winner: room.gameState.winner,
+ reason: "double-pass",
+ territory
+ });
+ }
+ }
+ });
+
+ // Resign
+ socket.on("resign", () => {
+ const room = gameManager.getPlayerRoom(socket.id);
+ if (room && gameManager.resign(room.id, socket.id)) {
+ io.to(room.id).emit("gameEnded", {
+ winner: room.gameState.winner,
+ reason: "resignation"
+ });
+ }
+ });
+
+ // Chat messages
+ socket.on("chatMessage", (data: { content: string }) => {
+ const room = gameManager.getPlayerRoom(socket.id);
+ if (room) {
+ const player = room.players[socket.id];
+ io.to(room.id).emit("chatMessage", {
+ author: player?.nickname || "Unknown",
+ content: data.content,
+ playerId: socket.id
+ });
+ }
+ });
+
+ // Handle disconnection
+ socket.on("disconnect", () => {
+ console.log(`Client disconnected: ${socket.id}`);
+ const room = gameManager.getPlayerRoom(socket.id);
+ if (room) {
+ gameManager.leaveRoom(room.id, socket.id);
+ socket.to(room.id).emit("playerDisconnected", {
+ playerId: socket.id
+ });
+ }
+ });
+}
\ No newline at end of file
diff --git a/trigo-web/backend/tsconfig.json b/trigo-web/backend/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..d3c23c1a1db1b0e895f96f72fbef45abe876f967
--- /dev/null
+++ b/trigo-web/backend/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "commonjs",
+ "lib": ["ES2020"],
+ "outDir": "./dist",
+ "rootDir": "../",
+ "strict": false,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "types": ["node"],
+ "allowJs": true,
+ "noImplicitAny": false
+ },
+ "include": ["src/**/*", "../inc/**/*"],
+ "exclude": [
+ "node_modules",
+ "dist",
+ "../inc/trigo/parserInit.ts",
+ "../inc/tgn/tgn.jison.cjs"
+ ],
+ "ts-node": {
+ "esm": false,
+ "compilerOptions": {
+ "module": "commonjs"
+ }
+ }
+}
\ No newline at end of file
diff --git a/trigo-web/inc/tgn/README.md b/trigo-web/inc/tgn/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..516ddf227d137d4e8a812ee5bc9e34c7e1fc64c8
--- /dev/null
+++ b/trigo-web/inc/tgn/README.md
@@ -0,0 +1,109 @@
+# TGN (Trigo Game Notation) Parser
+
+This directory contains the TGN parser implementation for reading and writing Trigo game notation files.
+
+## Files
+
+- **`tgn.jison`** - Grammar definition for TGN format (similar to PGN for chess)
+- **`tgnParser.ts`** - TypeScript wrapper providing type-safe API for the parser
+- **`tgn.jison.cjs`** - Generated parser (auto-generated, gitignored)
+
+## Building the Parser
+
+The parser is generated from the jison grammar file. To rebuild:
+
+```bash
+npm run build:parsers
+```
+
+Or specifically for TGN:
+
+```bash
+npm run build:parser:tgn
+```
+
+This will generate `tgn.jison.cjs` from `tgn.jison`.
+
+## Usage
+
+Import the parser functions from the game module:
+
+```typescript
+import { TrigoGame, validateTGN, TGNParseError } from '@inc/trigo/game';
+
+// Parse TGN and create game instance
+const tgn = `
+[Event "Test Game"]
+[Board 5x5x5]
+
+1. 000 y00
+2. 0y0 pass
+`;
+
+try {
+ const game = TrigoGame.fromTGN(tgn);
+ console.log('Game loaded successfully!');
+} catch (error) {
+ if (error instanceof TGNParseError) {
+ console.error('Parse error at line', error.line);
+ }
+}
+
+// Validate TGN without parsing
+const result = validateTGN(tgn);
+if (result.valid) {
+ console.log('Valid TGN');
+} else {
+ console.error('Invalid TGN:', result.error);
+}
+```
+
+## TGN Format
+
+TGN format consists of:
+
+1. **Metadata tags** - Key-value pairs in square brackets
+2. **Move sequence** - Numbered rounds with moves in ab0yz coordinate notation
+
+Example:
+
+```tgn
+[Event "World Championship"]
+[Site "Tokyo"]
+[Date "2025.10.31"]
+[Black "Alice"]
+[White "Bob"]
+[Board 5x5x5]
+[Rules "Chinese"]
+
+1. 000 y00
+2. 0y0 yy0
+3. aaa pass
+```
+
+### Coordinate Notation (ab0yz)
+
+- `0` = center position
+- `a`, `b`, `c`, ... = positions from one edge toward center
+- `z`, `y`, `x`, ... = positions from opposite edge toward center
+
+Examples for 5ร5ร5 board:
+- `000` = center (2,2,2)
+- `aaa` = corner (0,0,0)
+- `zzz` = opposite corner (4,4,4)
+- `y00` = (4,2,2)
+
+For 2D boards (e.g., 19ร19ร1), only two coordinates are used:
+- `00` = center
+- `aa` = corner
+
+## Development
+
+The parser is built using [Jison](https://github.com/zaach/jison), a JavaScript parser generator similar to Bison/Yacc.
+
+**Note:** Generated files (`*.jison.cjs`, `*.jison.js`) are gitignored and must be built locally or in CI/CD.
+
+## Related Documentation
+
+- [TGN Format Specification](../../docs/tgn-format-spec.md)
+- [Parser Development Guide](../../docs/README.tgn.md)
diff --git a/trigo-web/inc/tgn/tgn.jison b/trigo-web/inc/tgn/tgn.jison
new file mode 100644
index 0000000000000000000000000000000000000000..066ad43d7f8d9d8e524438a31125290050d9cb89
--- /dev/null
+++ b/trigo-web/inc/tgn/tgn.jison
@@ -0,0 +1,224 @@
+/* Trigo Game Notation Parser
+ * Similar as PGN (Portable Game Notation) format
+ */
+
+/* ========== Lexical Grammar ========== */
+%lex
+
+%%
+
+\s+ /* skip whitespace */
+\n /* skip newlines */
+";"[^\n]* /* skip line comments */
+"{"[^}]*"}" /* skip block comments */
+
+/* Brackets */
+"[" return '['
+"]" return ']'
+
+/* String literals */
+\"([^\\\"]|\\.)*\" return 'STRING'
+
+/* Tag names - Common PGN tags */
+"Event" return 'TAG_EVENT'
+"Site" return 'TAG_SITE'
+"Date" return 'TAG_DATE'
+"Round" return 'TAG_ROUND'
+"Black" return 'TAG_BLACK'
+"White" return 'TAG_WHITE'
+"Result" return 'TAG_RESULT'
+
+/* Trigo-specific tags */
+"Board" return 'TAG_BOARD'
+"Handicap" return 'TAG_HANDICAP'
+"Rules" return 'TAG_RULES'
+"TimeControl" return 'TAG_TIMECONTROL'
+"Annotator" return 'TAG_ANNOTATOR'
+"Application" return 'TAG_APPLICATION'
+
+/* Game result symbols */
+"B+" return 'RESULT_BLACK'
+"W+" return 'RESULT_WHITE'
+"=" return '=' /* draw */
+"*" return '*' /* unknown */
+
+/* Move notation placeholders - will be expanded later */
+[1-9][0-9]* return 'NUMBER'
+"." return 'DOT'
+"pass" return 'PASS'
+"resign" return 'RESIGN'
+"points" return 'POINTS'
+"stones" return 'STONES'
+
+[x](?=[1-9]) return 'TIMES'
+
+/* Coordinates for moves - will be refined */
+[a-z0]+ return 'COORDINATE'
+
+/* Generic tag name for extensibility */
+[A-Z][A-Za-z0-9_]* return 'TAG_NAME'
+
+/* End of file */
+<> return 'EOF'
+
+/* Catch-all for unexpected characters */
+. return 'INVALID'
+
+/lex
+
+/* ========== Grammar Rules ========== */
+
+%%
+
+game
+ : tag_section move_section EOF
+ {
+ return {
+ tags: $1,
+ moves: $2,
+ success: true
+ };
+ }
+ | tag_section EOF
+ {
+ return {
+ tags: $1,
+ moves: null,
+ success: true
+ };
+ }
+ | move_section EOF
+ {
+ return {
+ tags: {},
+ moves: $1,
+ success: true
+ };
+ }
+ | EOF
+ {
+ return {
+ tags: {},
+ moves: null,
+ success: true
+ };
+ }
+ ;
+
+/* ========== Tag Section (Metadata) ========== */
+
+tag_section
+ : tag_pair -> $1
+ | tag_section tag_pair -> Object.assign({}, $1, $2)
+ ;
+
+tag_pair
+ : "[" tag_name STRING "]"
+ {
+ const tagName = $2;
+ const tagValue = $3.slice(1, -1); // Remove quotes
+ $$ = { [tagName]: tagValue };
+ }
+ | "[" TAG_RESULT game_result "]" -> $3
+ | "[" TAG_BOARD board_shape "]" -> ({[$2]: $3})
+ ;
+
+tag_name
+ : TAG_EVENT
+ | TAG_SITE
+ | TAG_DATE
+ | TAG_ROUND
+ | TAG_BLACK
+ | TAG_WHITE
+ | TAG_HANDICAP
+ | TAG_RULES
+ | TAG_TIMECONTROL
+ | TAG_ANNOTATOR
+ | TAG_APPLICATION
+ | TAG_NAME { $$ = yytext; }
+ ;
+
+/* ========== Movetext Section (Game Body) ========== */
+/* PLACEHOLDER: Will be implemented in future iterations */
+
+move_section
+ : move_sequence
+ //| move_sequence game_termination
+ ;
+
+move_sequence
+ : move_sequence_intact -> $1
+ | move_sequence_truncated -> $1
+ ;
+
+move_sequence_intact
+ : /* empty */ -> []
+ | move_sequence_intact move_round -> $1.concat([$2])
+ ;
+
+move_sequence_truncated
+ : move_sequence_intact move_round_half -> $1.concat([$2])
+ ;
+
+move_round
+ : number DOT move_action move_action -> ({ round: $1, action_black: $3, action_white: $4 })
+ ;
+
+move_round_half
+ : number DOT move_action -> ({ round: $1, action_black: $3 })
+ ;
+
+move_action
+ : PASS -> ({ type: 'pass' })
+ | RESIGN -> ({ type: 'resign' })
+ | COORDINATE
+ {
+ // Placeholder: Parse coordinate notation
+ $$ = {
+ type: 'move',
+ position: yytext
+ };
+ }
+ ;
+
+game_result
+ : win -> ({Result: $1})
+ | win conquer -> ({Result: $1, Conquer: $2})
+ | "=" -> ({Result: "draw"})
+ | "*" -> ({Result: "unknown"})
+ ;
+
+win
+ : RESULT_BLACK -> "black win"
+ | RESULT_WHITE -> "white win"
+ ;
+
+conquer
+ : number conquer_unit -> ({n: $1, unit: $2})
+ ;
+
+conquer_unit
+ : POINTS
+ | STONES
+ ;
+
+board_shape
+ : number -> [$1]
+ | board_shape TIMES number -> $1.concat($3)
+ ;
+
+number
+ : NUMBER -> parseInt($1)
+ ;
+
+%%
+
+/* ========== Additional JavaScript Code ========== */
+
+// Parser configuration
+parser.yy = {
+ // Helper functions can be added here
+ parseError: function(str, hash) {
+ throw new Error('Parse error: ' + str);
+ }
+};
diff --git a/trigo-web/inc/tgn/tgn.jison.cjs b/trigo-web/inc/tgn/tgn.jison.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..3127db322b851d71ad368830b2b39eea32c49f7a
--- /dev/null
+++ b/trigo-web/inc/tgn/tgn.jison.cjs
@@ -0,0 +1,791 @@
+/* parser generated by jison 0.4.18 */
+/*
+ Returns a Parser object of the following structure:
+
+ Parser: {
+ yy: {}
+ }
+
+ Parser.prototype: {
+ yy: {},
+ trace: function(),
+ symbols_: {associative list: name ==> number},
+ terminals_: {associative list: number ==> name},
+ productions_: [...],
+ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
+ table: [...],
+ defaultActions: {...},
+ parseError: function(str, hash),
+ parse: function(input),
+
+ lexer: {
+ EOF: 1,
+ parseError: function(str, hash),
+ setInput: function(input),
+ input: function(),
+ unput: function(str),
+ more: function(),
+ less: function(n),
+ pastInput: function(),
+ upcomingInput: function(),
+ showPosition: function(),
+ test_match: function(regex_match_array, rule_index),
+ next: function(),
+ lex: function(),
+ begin: function(condition),
+ popState: function(),
+ _currentRules: function(),
+ topState: function(),
+ pushState: function(condition),
+
+ options: {
+ ranges: boolean (optional: true ==> token location info will include a .range[] member)
+ flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
+ backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
+ },
+
+ performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
+ rules: [...],
+ conditions: {associative list: name ==> set},
+ }
+ }
+
+
+ token location info (@$, _$, etc.): {
+ first_line: n,
+ last_line: n,
+ first_column: n,
+ last_column: n,
+ range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
+ }
+
+
+ the parseError function receives a 'hash' object with these members for lexer and parser errors: {
+ text: (matched text)
+ token: (the produced terminal token, if any)
+ line: (yylineno)
+ }
+ while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
+ loc: (yylloc)
+ expected: (string describing the set of expected tokens)
+ recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
+ }
+*/
+var tgn = (function(){
+var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,7],$V1=[2,25],$V2=[6,8,49],$V3=[1,32],$V4=[6,49],$V5=[11,49],$V6=[11,48],$V7=[1,51],$V8=[1,52],$V9=[1,53],$Va=[6,36,37,38,49];
+var parser = {trace: function trace () { },
+yy: {},
+symbols_: {"error":2,"game":3,"tag_section":4,"move_section":5,"EOF":6,"tag_pair":7,"[":8,"tag_name":9,"STRING":10,"]":11,"TAG_RESULT":12,"game_result":13,"TAG_BOARD":14,"board_shape":15,"TAG_EVENT":16,"TAG_SITE":17,"TAG_DATE":18,"TAG_ROUND":19,"TAG_BLACK":20,"TAG_WHITE":21,"TAG_HANDICAP":22,"TAG_RULES":23,"TAG_TIMECONTROL":24,"TAG_ANNOTATOR":25,"TAG_APPLICATION":26,"TAG_NAME":27,"move_sequence":28,"move_sequence_intact":29,"move_sequence_truncated":30,"move_round":31,"move_round_half":32,"number":33,"DOT":34,"move_action":35,"PASS":36,"RESIGN":37,"COORDINATE":38,"win":39,"conquer":40,"=":41,"*":42,"RESULT_BLACK":43,"RESULT_WHITE":44,"conquer_unit":45,"POINTS":46,"STONES":47,"TIMES":48,"NUMBER":49,"$accept":0,"$end":1},
+terminals_: {2:"error",6:"EOF",8:"[",10:"STRING",11:"]",12:"TAG_RESULT",14:"TAG_BOARD",16:"TAG_EVENT",17:"TAG_SITE",18:"TAG_DATE",19:"TAG_ROUND",20:"TAG_BLACK",21:"TAG_WHITE",22:"TAG_HANDICAP",23:"TAG_RULES",24:"TAG_TIMECONTROL",25:"TAG_ANNOTATOR",26:"TAG_APPLICATION",27:"TAG_NAME",34:"DOT",36:"PASS",37:"RESIGN",38:"COORDINATE",41:"=",42:"*",43:"RESULT_BLACK",44:"RESULT_WHITE",46:"POINTS",47:"STONES",48:"TIMES",49:"NUMBER"},
+productions_: [0,[3,3],[3,2],[3,2],[3,1],[4,1],[4,2],[7,4],[7,4],[7,4],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[5,1],[28,1],[28,1],[29,0],[29,2],[30,2],[31,4],[32,3],[35,1],[35,1],[35,1],[13,1],[13,2],[13,1],[13,1],[39,1],[39,1],[40,2],[45,1],[45,1],[15,1],[15,3],[33,1]],
+performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
+/* this == yyval */
+
+var $0 = $$.length - 1;
+switch (yystate) {
+case 1:
+
+ return {
+ tags: $$[$0-2],
+ moves: $$[$0-1],
+ success: true
+ };
+
+break;
+case 2:
+
+ return {
+ tags: $$[$0-1],
+ moves: null,
+ success: true
+ };
+
+break;
+case 3:
+
+ return {
+ tags: {},
+ moves: $$[$0-1],
+ success: true
+ };
+
+break;
+case 4:
+
+ return {
+ tags: {},
+ moves: null,
+ success: true
+ };
+
+break;
+case 5: case 23: case 24:
+this.$ = $$[$0];
+break;
+case 6:
+this.$ = Object.assign({}, $$[$0-1], $$[$0]);
+break;
+case 7:
+
+ const tagName = $$[$0-2];
+ const tagValue = $$[$0-1].slice(1, -1); // Remove quotes
+ this.$ = { [tagName]: tagValue };
+
+break;
+case 8:
+this.$ = $$[$0-1];
+break;
+case 9:
+this.$ = ({[$$[$0-2]]: $$[$0-1]});
+break;
+case 21:
+ this.$ = yytext;
+break;
+case 25:
+this.$ = [];
+break;
+case 26: case 27:
+this.$ = $$[$0-1].concat([$$[$0]]);
+break;
+case 28:
+this.$ = ({ round: $$[$0-3], action_black: $$[$0-1], action_white: $$[$0] });
+break;
+case 29:
+this.$ = ({ round: $$[$0-2], action_black: $$[$0] });
+break;
+case 30:
+this.$ = ({ type: 'pass' });
+break;
+case 31:
+this.$ = ({ type: 'resign' });
+break;
+case 32:
+
+ // Placeholder: Parse coordinate notation
+ this.$ = {
+ type: 'move',
+ position: yytext
+ };
+
+break;
+case 33:
+this.$ = ({Result: $$[$0]});
+break;
+case 34:
+this.$ = ({Result: $$[$0-1], Conquer: $$[$0]});
+break;
+case 35:
+this.$ = ({Result: "draw"});
+break;
+case 36:
+this.$ = ({Result: "unknown"});
+break;
+case 37:
+this.$ = "black win";
+break;
+case 38:
+this.$ = "white win";
+break;
+case 39:
+this.$ = ({n: $$[$0-1], unit: $$[$0]});
+break;
+case 42:
+this.$ = [$$[$0]];
+break;
+case 43:
+this.$ = $$[$0-2].concat($$[$0]);
+break;
+case 44:
+this.$ = parseInt($$[$0]);
+break;
+}
+},
+table: [{3:1,4:2,5:3,6:[1,4],7:5,8:$V0,28:6,29:8,30:9,49:$V1},{1:[3]},{5:10,6:[1,11],7:12,8:$V0,28:6,29:8,30:9,49:$V1},{6:[1,13]},{1:[2,4]},o($V2,[2,5]),{6:[2,22]},{9:14,12:[1,15],14:[1,16],16:[1,17],17:[1,18],18:[1,19],19:[1,20],20:[1,21],21:[1,22],22:[1,23],23:[1,24],24:[1,25],25:[1,26],26:[1,27],27:[1,28]},{6:[2,23],31:29,32:30,33:31,49:$V3},{6:[2,24]},{6:[1,33]},{1:[2,2]},o($V2,[2,6]),{1:[2,3]},{10:[1,34]},{13:35,39:36,41:[1,37],42:[1,38],43:[1,39],44:[1,40]},{15:41,33:42,49:$V3},{10:[2,10]},{10:[2,11]},{10:[2,12]},{10:[2,13]},{10:[2,14]},{10:[2,15]},{10:[2,16]},{10:[2,17]},{10:[2,18]},{10:[2,19]},{10:[2,20]},{10:[2,21]},o($V4,[2,26]),{6:[2,27]},{34:[1,43]},o([11,34,46,47,48],[2,44]),{1:[2,1]},{11:[1,44]},{11:[1,45]},{11:[2,33],33:47,40:46,49:$V3},{11:[2,35]},{11:[2,36]},o($V5,[2,37]),o($V5,[2,38]),{11:[1,48],48:[1,49]},o($V6,[2,42]),{35:50,36:$V7,37:$V8,38:$V9},o($V2,[2,7]),o($V2,[2,8]),{11:[2,34]},{45:54,46:[1,55],47:[1,56]},o($V2,[2,9]),{33:57,49:$V3},{6:[2,29],35:58,36:$V7,37:$V8,38:$V9},o($Va,[2,30]),o($Va,[2,31]),o($Va,[2,32]),{11:[2,39]},{11:[2,40]},{11:[2,41]},o($V6,[2,43]),o($V4,[2,28])],
+defaultActions: {4:[2,4],6:[2,22],9:[2,24],11:[2,2],13:[2,3],17:[2,10],18:[2,11],19:[2,12],20:[2,13],21:[2,14],22:[2,15],23:[2,16],24:[2,17],25:[2,18],26:[2,19],27:[2,20],28:[2,21],30:[2,27],33:[2,1],37:[2,35],38:[2,36],46:[2,34],54:[2,39],55:[2,40],56:[2,41]},
+parseError: function parseError (str, hash) {
+ if (hash.recoverable) {
+ this.trace(str);
+ } else {
+ var error = new Error(str);
+ error.hash = hash;
+ throw error;
+ }
+},
+parse: function parse(input) {
+ var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+ var args = lstack.slice.call(arguments, 1);
+ var lexer = Object.create(this.lexer);
+ var sharedState = { yy: {} };
+ for (var k in this.yy) {
+ if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
+ sharedState.yy[k] = this.yy[k];
+ }
+ }
+ lexer.setInput(input, sharedState.yy);
+ sharedState.yy.lexer = lexer;
+ sharedState.yy.parser = this;
+ if (typeof lexer.yylloc == 'undefined') {
+ lexer.yylloc = {};
+ }
+ var yyloc = lexer.yylloc;
+ lstack.push(yyloc);
+ var ranges = lexer.options && lexer.options.ranges;
+ if (typeof sharedState.yy.parseError === 'function') {
+ this.parseError = sharedState.yy.parseError;
+ } else {
+ this.parseError = Object.getPrototypeOf(this).parseError;
+ }
+ function popStack(n) {
+ stack.length = stack.length - 2 * n;
+ vstack.length = vstack.length - n;
+ lstack.length = lstack.length - n;
+ }
+ _token_stack:
+ var lex = function () {
+ var token;
+ token = lexer.lex() || EOF;
+ if (typeof token !== 'number') {
+ token = self.symbols_[token] || token;
+ }
+ return token;
+ };
+ var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+ while (true) {
+ state = stack[stack.length - 1];
+ if (this.defaultActions[state]) {
+ action = this.defaultActions[state];
+ } else {
+ if (symbol === null || typeof symbol == 'undefined') {
+ symbol = lex();
+ }
+ action = table[state] && table[state][symbol];
+ }
+ if (typeof action === 'undefined' || !action.length || !action[0]) {
+ var errStr = '';
+ expected = [];
+ for (p in table[state]) {
+ if (this.terminals_[p] && p > TERROR) {
+ expected.push('\'' + this.terminals_[p] + '\'');
+ }
+ }
+ if (lexer.showPosition) {
+ errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
+ } else {
+ errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
+ }
+ this.parseError(errStr, {
+ text: lexer.match,
+ token: this.terminals_[symbol] || symbol,
+ line: lexer.yylineno,
+ loc: yyloc,
+ expected: expected
+ });
+ }
+ if (action[0] instanceof Array && action.length > 1) {
+ throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
+ }
+ switch (action[0]) {
+ case 1:
+ stack.push(symbol);
+ vstack.push(lexer.yytext);
+ lstack.push(lexer.yylloc);
+ stack.push(action[1]);
+ symbol = null;
+ if (!preErrorSymbol) {
+ yyleng = lexer.yyleng;
+ yytext = lexer.yytext;
+ yylineno = lexer.yylineno;
+ yyloc = lexer.yylloc;
+ if (recovering > 0) {
+ recovering--;
+ }
+ } else {
+ symbol = preErrorSymbol;
+ preErrorSymbol = null;
+ }
+ break;
+ case 2:
+ len = this.productions_[action[1]][1];
+ yyval.$ = vstack[vstack.length - len];
+ yyval._$ = {
+ first_line: lstack[lstack.length - (len || 1)].first_line,
+ last_line: lstack[lstack.length - 1].last_line,
+ first_column: lstack[lstack.length - (len || 1)].first_column,
+ last_column: lstack[lstack.length - 1].last_column
+ };
+ if (ranges) {
+ yyval._$.range = [
+ lstack[lstack.length - (len || 1)].range[0],
+ lstack[lstack.length - 1].range[1]
+ ];
+ }
+ r = this.performAction.apply(yyval, [
+ yytext,
+ yyleng,
+ yylineno,
+ sharedState.yy,
+ action[1],
+ vstack,
+ lstack
+ ].concat(args));
+ if (typeof r !== 'undefined') {
+ return r;
+ }
+ if (len) {
+ stack = stack.slice(0, -1 * len * 2);
+ vstack = vstack.slice(0, -1 * len);
+ lstack = lstack.slice(0, -1 * len);
+ }
+ stack.push(this.productions_[action[1]][0]);
+ vstack.push(yyval.$);
+ lstack.push(yyval._$);
+ newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+ stack.push(newState);
+ break;
+ case 3:
+ return true;
+ }
+ }
+ return true;
+}};
+
+
+/* ========== Additional JavaScript Code ========== */
+
+// Parser configuration
+parser.yy = {
+ // Helper functions can be added here
+ parseError: function(str, hash) {
+ throw new Error('Parse error: ' + str);
+ }
+};
+/* generated by jison-lex 0.3.4 */
+var lexer = (function(){
+var lexer = ({
+
+EOF:1,
+
+parseError:function parseError(str, hash) {
+ if (this.yy.parser) {
+ this.yy.parser.parseError(str, hash);
+ } else {
+ throw new Error(str);
+ }
+ },
+
+// resets the lexer, sets new input
+setInput:function (input, yy) {
+ this.yy = yy || this.yy || {};
+ this._input = input;
+ this._more = this._backtrack = this.done = false;
+ this.yylineno = this.yyleng = 0;
+ this.yytext = this.matched = this.match = '';
+ this.conditionStack = ['INITIAL'];
+ this.yylloc = {
+ first_line: 1,
+ first_column: 0,
+ last_line: 1,
+ last_column: 0
+ };
+ if (this.options.ranges) {
+ this.yylloc.range = [0,0];
+ }
+ this.offset = 0;
+ return this;
+ },
+
+// consumes and returns one char from the input
+input:function () {
+ var ch = this._input[0];
+ this.yytext += ch;
+ this.yyleng++;
+ this.offset++;
+ this.match += ch;
+ this.matched += ch;
+ var lines = ch.match(/(?:\r\n?|\n).*/g);
+ if (lines) {
+ this.yylineno++;
+ this.yylloc.last_line++;
+ } else {
+ this.yylloc.last_column++;
+ }
+ if (this.options.ranges) {
+ this.yylloc.range[1]++;
+ }
+
+ this._input = this._input.slice(1);
+ return ch;
+ },
+
+// unshifts one char (or a string) into the input
+unput:function (ch) {
+ var len = ch.length;
+ var lines = ch.split(/(?:\r\n?|\n)/g);
+
+ this._input = ch + this._input;
+ this.yytext = this.yytext.substr(0, this.yytext.length - len);
+ //this.yyleng -= len;
+ this.offset -= len;
+ var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+ this.match = this.match.substr(0, this.match.length - 1);
+ this.matched = this.matched.substr(0, this.matched.length - 1);
+
+ if (lines.length - 1) {
+ this.yylineno -= lines.length - 1;
+ }
+ var r = this.yylloc.range;
+
+ this.yylloc = {
+ first_line: this.yylloc.first_line,
+ last_line: this.yylineno + 1,
+ first_column: this.yylloc.first_column,
+ last_column: lines ?
+ (lines.length === oldLines.length ? this.yylloc.first_column : 0)
+ + oldLines[oldLines.length - lines.length].length - lines[0].length :
+ this.yylloc.first_column - len
+ };
+
+ if (this.options.ranges) {
+ this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+ }
+ this.yyleng = this.yytext.length;
+ return this;
+ },
+
+// When called from action, caches matched text and appends it on next action
+more:function () {
+ this._more = true;
+ return this;
+ },
+
+// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
+reject:function () {
+ if (this.options.backtrack_lexer) {
+ this._backtrack = true;
+ } else {
+ return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
+ text: "",
+ token: null,
+ line: this.yylineno
+ });
+
+ }
+ return this;
+ },
+
+// retain first n characters of the match
+less:function (n) {
+ this.unput(this.match.slice(n));
+ },
+
+// displays already matched input, i.e. for error messages
+pastInput:function () {
+ var past = this.matched.substr(0, this.matched.length - this.match.length);
+ return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+ },
+
+// displays upcoming input, i.e. for error messages
+upcomingInput:function () {
+ var next = this.match;
+ if (next.length < 20) {
+ next += this._input.substr(0, 20-next.length);
+ }
+ return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
+ },
+
+// displays the character position where the lexing error occurred, i.e. for error messages
+showPosition:function () {
+ var pre = this.pastInput();
+ var c = new Array(pre.length + 1).join("-");
+ return pre + this.upcomingInput() + "\n" + c + "^";
+ },
+
+// test the lexed token: return FALSE when not a match, otherwise return token
+test_match:function(match, indexed_rule) {
+ var token,
+ lines,
+ backup;
+
+ if (this.options.backtrack_lexer) {
+ // save context
+ backup = {
+ yylineno: this.yylineno,
+ yylloc: {
+ first_line: this.yylloc.first_line,
+ last_line: this.last_line,
+ first_column: this.yylloc.first_column,
+ last_column: this.yylloc.last_column
+ },
+ yytext: this.yytext,
+ match: this.match,
+ matches: this.matches,
+ matched: this.matched,
+ yyleng: this.yyleng,
+ offset: this.offset,
+ _more: this._more,
+ _input: this._input,
+ yy: this.yy,
+ conditionStack: this.conditionStack.slice(0),
+ done: this.done
+ };
+ if (this.options.ranges) {
+ backup.yylloc.range = this.yylloc.range.slice(0);
+ }
+ }
+
+ lines = match[0].match(/(?:\r\n?|\n).*/g);
+ if (lines) {
+ this.yylineno += lines.length;
+ }
+ this.yylloc = {
+ first_line: this.yylloc.last_line,
+ last_line: this.yylineno + 1,
+ first_column: this.yylloc.last_column,
+ last_column: lines ?
+ lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
+ this.yylloc.last_column + match[0].length
+ };
+ this.yytext += match[0];
+ this.match += match[0];
+ this.matches = match;
+ this.yyleng = this.yytext.length;
+ if (this.options.ranges) {
+ this.yylloc.range = [this.offset, this.offset += this.yyleng];
+ }
+ this._more = false;
+ this._backtrack = false;
+ this._input = this._input.slice(match[0].length);
+ this.matched += match[0];
+ token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
+ if (this.done && this._input) {
+ this.done = false;
+ }
+ if (token) {
+ return token;
+ } else if (this._backtrack) {
+ // recover context
+ for (var k in backup) {
+ this[k] = backup[k];
+ }
+ return false; // rule action called reject() implying the next rule should be tested instead.
+ }
+ return false;
+ },
+
+// return next match in input
+next:function () {
+ if (this.done) {
+ return this.EOF;
+ }
+ if (!this._input) {
+ this.done = true;
+ }
+
+ var token,
+ match,
+ tempMatch,
+ index;
+ if (!this._more) {
+ this.yytext = '';
+ this.match = '';
+ }
+ var rules = this._currentRules();
+ for (var i = 0; i < rules.length; i++) {
+ tempMatch = this._input.match(this.rules[rules[i]]);
+ if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+ match = tempMatch;
+ index = i;
+ if (this.options.backtrack_lexer) {
+ token = this.test_match(tempMatch, rules[i]);
+ if (token !== false) {
+ return token;
+ } else if (this._backtrack) {
+ match = false;
+ continue; // rule action called reject() implying a rule MISmatch.
+ } else {
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+ return false;
+ }
+ } else if (!this.options.flex) {
+ break;
+ }
+ }
+ }
+ if (match) {
+ token = this.test_match(match, rules[index]);
+ if (token !== false) {
+ return token;
+ }
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+ return false;
+ }
+ if (this._input === "") {
+ return this.EOF;
+ } else {
+ return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
+ text: "",
+ token: null,
+ line: this.yylineno
+ });
+ }
+ },
+
+// return next match that has a token
+lex:function lex () {
+ var r = this.next();
+ if (r) {
+ return r;
+ } else {
+ return this.lex();
+ }
+ },
+
+// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
+begin:function begin (condition) {
+ this.conditionStack.push(condition);
+ },
+
+// pop the previously active lexer condition state off the condition stack
+popState:function popState () {
+ var n = this.conditionStack.length - 1;
+ if (n > 0) {
+ return this.conditionStack.pop();
+ } else {
+ return this.conditionStack[0];
+ }
+ },
+
+// produce the lexer rule set which is active for the currently active lexer condition state
+_currentRules:function _currentRules () {
+ if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
+ return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
+ } else {
+ return this.conditions["INITIAL"].rules;
+ }
+ },
+
+// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
+topState:function topState (n) {
+ n = this.conditionStack.length - 1 - Math.abs(n || 0);
+ if (n >= 0) {
+ return this.conditionStack[n];
+ } else {
+ return "INITIAL";
+ }
+ },
+
+// alias for begin(condition)
+pushState:function pushState (condition) {
+ this.begin(condition);
+ },
+
+// return the number of states currently on the stack
+stateStackSize:function stateStackSize() {
+ return this.conditionStack.length;
+ },
+options: {},
+performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+var YYSTATE=YY_START;
+switch($avoiding_name_collisions) {
+case 0:/* skip whitespace */
+break;
+case 1:/* skip newlines */
+break;
+case 2:/* skip line comments */
+break;
+case 3:/* skip block comments */
+break;
+case 4:return 8
+break;
+case 5:return 11
+break;
+case 6:return 10
+break;
+case 7:return 16
+break;
+case 8:return 17
+break;
+case 9:return 18
+break;
+case 10:return 19
+break;
+case 11:return 20
+break;
+case 12:return 21
+break;
+case 13:return 12
+break;
+case 14:return 14
+break;
+case 15:return 22
+break;
+case 16:return 23
+break;
+case 17:return 24
+break;
+case 18:return 25
+break;
+case 19:return 26
+break;
+case 20:return 43
+break;
+case 21:return 44
+break;
+case 22:return 41 /* draw */
+break;
+case 23:return 42 /* unknown */
+break;
+case 24:return 49
+break;
+case 25:return 34
+break;
+case 26:return 36
+break;
+case 27:return 37
+break;
+case 28:return 46
+break;
+case 29:return 47
+break;
+case 30:return 48
+break;
+case 31:return 38
+break;
+case 32:return 27
+break;
+case 33:return 6
+break;
+case 34:return 'INVALID'
+break;
+}
+},
+rules: [/^(?:\s+)/,/^(?:\n)/,/^(?:;[^\n]*)/,/^(?:\{[^}]*\})/,/^(?:\[)/,/^(?:\])/,/^(?:"([^\\\"]|\\.)*")/,/^(?:Event\b)/,/^(?:Site\b)/,/^(?:Date\b)/,/^(?:Round\b)/,/^(?:Black\b)/,/^(?:White\b)/,/^(?:Result\b)/,/^(?:Board\b)/,/^(?:Handicap\b)/,/^(?:Rules\b)/,/^(?:TimeControl\b)/,/^(?:Annotator\b)/,/^(?:Application\b)/,/^(?:B\+)/,/^(?:W\+)/,/^(?:=)/,/^(?:\*)/,/^(?:[1-9][0-9]*)/,/^(?:\.)/,/^(?:pass\b)/,/^(?:resign\b)/,/^(?:points\b)/,/^(?:stones\b)/,/^(?:[x](?=[1-9]))/,/^(?:[a-z0]+)/,/^(?:[A-Z][A-Za-z0-9_]*)/,/^(?:$)/,/^(?:.)/],
+conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],"inclusive":true}}
+});
+return lexer;
+})();
+parser.lexer = lexer;
+function Parser () {
+ this.yy = {};
+}
+Parser.prototype = parser;parser.Parser = Parser;
+return new Parser;
+})();
+
+
+if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
+exports.parser = tgn;
+exports.Parser = tgn.Parser;
+exports.parse = function () { return tgn.parse.apply(tgn, arguments); };
+exports.main = function commonjsMain (args) {
+ if (!args[1]) {
+ console.log('Usage: '+args[0]+' FILE');
+ process.exit(1);
+ }
+ var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
+ return exports.parser.parse(source);
+};
+if (typeof module !== 'undefined' && require.main === module) {
+ exports.main(process.argv.slice(1));
+}
+}
\ No newline at end of file
diff --git a/trigo-web/inc/tgn/tgnParser.ts b/trigo-web/inc/tgn/tgnParser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c23cb30960bb1b75e6f9e51c6fb46163e163d732
--- /dev/null
+++ b/trigo-web/inc/tgn/tgnParser.ts
@@ -0,0 +1,166 @@
+/**
+ * TGN Parser TypeScript Wrapper
+ *
+ * Wraps the jison-generated parser with TypeScript types
+ *
+ * Based on lotus project architecture:
+ * - Use jison npm package for grammar compilation at build time
+ * - Generate parser to .js file in build phase
+ * - Use synchronous parsing (no async needed)
+ * - Works in both browser and Node.js environments
+ */
+
+/**
+ * Parsed move action - represents a single player's action in a round
+ */
+export interface ParsedMoveAction {
+ type: 'move' | 'pass' | 'resign';
+ position?: string; // ab0yz coordinate notation
+}
+
+
+/**
+ * Parsed move round - contains both black and white moves
+ */
+export interface ParsedMoveRound {
+ round: number;
+ action_black: ParsedMoveAction;
+ action_white?: ParsedMoveAction;
+}
+
+
+/**
+ * Parsed game result
+ */
+export interface ParsedGameResult {
+ Result: string; // "black win" | "white win" | "draw" | "unknown"
+ Conquer?: {
+ n: number;
+ unit: string; // "points" | "stones"
+ };
+}
+
+
+/**
+ * Parsed TGN tags (metadata)
+ */
+export interface ParsedTags {
+ Event?: string;
+ Site?: string;
+ Date?: string;
+ Round?: string;
+ Black?: string;
+ White?: string;
+ Result?: string;
+ Board?: number[]; // [x, y, z] or [x, y]
+ Handicap?: string;
+ Rules?: string;
+ TimeControl?: string;
+ Annotator?: string;
+ Application?: string;
+ [key: string]: string | number[] | ParsedGameResult | undefined;
+}
+
+
+/**
+ * Parser output structure
+ */
+export interface TGNParseResult {
+ tags: ParsedTags;
+ moves: ParsedMoveRound[] | null;
+ success: boolean;
+}
+
+
+/**
+ * Parser error with position information
+ */
+export class TGNParseError extends Error {
+ constructor(
+ message: string,
+ public line?: number,
+ public column?: number,
+ public hash?: any
+ ) {
+ super(message);
+ this.name = 'TGNParseError';
+ }
+}
+
+
+// Will be set by initialization code or build process
+let parserModule: any = null;
+
+
+/**
+ * Set the parser module (called by initialization code)
+ * This allows the pre-built parser to be used
+ */
+export function setParserModule(module: any): void {
+ parserModule = module;
+}
+
+
+/**
+ * Get the parser module
+ * Throws error if parser not loaded
+ */
+function getParser() {
+ if (!parserModule) {
+ throw new Error(
+ 'TGN parser not loaded. Please ensure the parser has been built.\n' +
+ 'Run: npm run build:parsers'
+ );
+ }
+ return parserModule;
+}
+
+
+/**
+ * Parse TGN string and return structured data
+ * Synchronous parsing (no async needed)
+ *
+ * @param tgnString - TGN formatted game notation
+ * @returns Parsed game data with tags and moves
+ * @throws TGNParseError if parsing fails
+ */
+export function parseTGN(tgnString: string): TGNParseResult {
+ const parser = getParser();
+
+ if (!parser.parse) {
+ throw new Error('TGN parser parse method not available');
+ }
+
+ try {
+ const result = parser.parse(tgnString);
+ return result as TGNParseResult;
+ } catch (error: any) {
+ // Wrap jison errors with our custom error type
+ throw new TGNParseError(
+ error.message || 'Unknown parse error',
+ error.hash?.line,
+ error.hash?.loc?.first_column,
+ error.hash
+ );
+ }
+}
+
+
+/**
+ * Validate TGN string without fully parsing
+ * Synchronous validation (no async needed)
+ *
+ * @param tgnString - TGN formatted game notation
+ * @returns Object with valid flag and error message if invalid
+ */
+export function validateTGN(tgnString: string): { valid: boolean; error?: string } {
+ try {
+ parseTGN(tgnString);
+ return { valid: true };
+ } catch (error: any) {
+ return {
+ valid: false,
+ error: error.message || 'Unknown validation error'
+ };
+ }
+}
diff --git a/trigo-web/inc/trigo/ab0yz.ts b/trigo-web/inc/trigo/ab0yz.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ebb472915db444a6721663bcc84f68b78d36c2e1
--- /dev/null
+++ b/trigo-web/inc/trigo/ab0yz.ts
@@ -0,0 +1,120 @@
+
+// remove ones at tail
+const compactShape = (shape: number[]): number[] => shape[shape.length - 1] === 1 ? compactShape(shape.slice(0, shape.length - 1)) : shape;
+
+
+/**
+ * Encode a position to TGN coordinate string.
+ *
+ * Coordinate system:
+ * - '0' represents the center position on an axis
+ * - 'a', 'b', 'c', ... from one edge toward center
+ * - 'z', 'y', 'x', ... from opposite edge toward center
+ *
+ * @param pos - Position array [x, y, z, ...] with 0-based indices
+ * @param boardShape - Board dimensions [sizeX, sizeY, sizeZ, ...]
+ * @returns TGN coordinate string (e.g., "000", "aa0", "bzz", "aa", "0")
+ *
+ * @example
+ * encodeAb0yz([2, 2, 2], [5, 5, 5]) // "000" - center of 5x5x5 board
+ * encodeAb0yz([0, 0, 2], [5, 5, 5]) // "aa0" - corner
+ * encodeAb0yz([4, 2, 2], [5, 5, 5]) // "z00"
+ * encodeAb0yz([0, 0, 0], [19, 19, 1]) // "aa" - 2D board (trailing 1 ignored)
+ */
+const encodeAb0yz = (pos: number[], boardShape: number[]): string => {
+ const compactedShape = compactShape(boardShape);
+ const result: string[] = [];
+
+ for (let i = 0; i < compactedShape.length; i++) {
+ const size = compactedShape[i];
+ const center = (size - 1) / 2;
+ const index = pos[i];
+
+ if (index === center) {
+ // Center position
+ result.push('0');
+ } else if (index < center) {
+ // Left side: a, b, c, ...
+ result.push(String.fromCharCode(97 + index)); // 'a' = 97
+ } else {
+ // Right side: z, y, x, ...
+ const offset = size - 1 - index;
+ result.push(String.fromCharCode(122 - offset)); // 'z' = 122
+ }
+ }
+
+ return result.join('');
+};
+
+
+/**
+ * Decode a TGN coordinate string to position array.
+ *
+ * @param code - TGN coordinate string (e.g., "000", "aa0", "bzz", "aa", "0")
+ * @param boardShape - Board dimensions [sizeX, sizeY, sizeZ, ...]
+ * @returns Position array [x, y, z, ...] with 0-based indices
+ *
+ * @example
+ * decodeAb0yz("000", [5, 5, 5]) // [2, 2, 2] - center
+ * decodeAb0yz("aa0", [5, 5, 5]) // [0, 0, 2]
+ * decodeAb0yz("z00", [5, 5, 5]) // [4, 2, 2]
+ * decodeAb0yz("aa", [19, 19, 1]) // [0, 0, 0] - 2D board
+ */
+const decodeAb0yz = (code: string, boardShape: number[]): number[] => {
+ const compactedShape = compactShape(boardShape);
+
+ if (code.length !== compactedShape.length) {
+ throw new Error(`Invalid TGN coordinate: "${code}" (must be ${compactedShape.length} characters for board shape ${boardShape.join('x')})`);
+ }
+
+ const result: number[] = [];
+
+ for (let i = 0; i < compactedShape.length; i++) {
+ const char = code[i];
+ const size = compactedShape[i];
+ const center = (size - 1) / 2;
+
+ if (char === '0') {
+ // Center position
+ console.assert(Number.isInteger(center));
+ result.push(center);
+ } else {
+ const charCode = char.charCodeAt(0);
+
+ if (charCode >= 97 && charCode <= 122) { // 'a' to 'z'
+ // Calculate distance from 'a' and 'z'
+ const distFromA = charCode - 97;
+ const distFromZ = 122 - charCode;
+
+ // Determine if it's left side (closer to 'a') or right side (closer to 'z')
+ if (distFromA < distFromZ) {
+ // Left side: a=0, b=1, c=2, ...
+ const index = distFromA;
+ if (index >= center) {
+ throw new Error(`Invalid TGN coordinate: "${code}" (position ${index} >= center ${center} on axis ${i})`);
+ }
+ result.push(index);
+ } else {
+ // Right side: z=size-1, y=size-2, x=size-3, ...
+ const index = size - 1 - distFromZ;
+ if (index <= center) {
+ throw new Error(`Invalid TGN coordinate: "${code}" (position ${index} <= center ${center} on axis ${i})`);
+ }
+ result.push(index);
+ }
+ } else {
+ throw new Error(`Invalid TGN coordinate: "${code}" (character '${char}' at position ${i} must be '0' or a-z)`);
+ }
+ }
+ }
+
+ // Fill remaining dimensions with 0 if boardShape has trailing 1s
+ while (result.length < boardShape.length) {
+ result.push(0);
+ }
+
+ return result;
+};
+
+
+export { encodeAb0yz, decodeAb0yz };
diff --git a/trigo-web/inc/trigo/game.ts b/trigo-web/inc/trigo/game.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f36de21e8b1f49448c2f65f2719915fc4beef2d8
--- /dev/null
+++ b/trigo-web/inc/trigo/game.ts
@@ -0,0 +1,1137 @@
+/**
+ * TrigoGame Class - Main Game State Management
+ *
+ * Modern reimplementation of prototype's trigo.Game
+ * Integrates game state, move history, and game logic in a single class
+ *
+ * Equivalent to: third_party/klstrigo/Source/static/js/trigo.game.js:75-601
+ */
+
+import type { Position, Stone, BoardShape } from "./types";
+import {
+ StoneType,
+ getEnemyColor,
+ validateMove,
+ findCapturedGroups,
+ executeCaptures,
+ calculateTerritory,
+ type TerritoryResult
+} from "./gameUtils";
+import { encodeAb0yz, decodeAb0yz } from "./ab0yz";
+import { parseTGN, validateTGN, TGNParseError } from "../tgn/tgnParser";
+
+
+// Re-export StoneType for convenient access with TrigoGame
+export { StoneType } from "./gameUtils";
+
+
+/**
+ * Step Types - Different types of moves in the game
+ * Equivalent to trigo.Game.StepType in prototype
+ */
+export enum StepType {
+ DROP = 0, // Place a stone
+ PASS = 1, // Pass turn
+ SURRENDER = 2, // Resign/surrender
+ UNDO = 3 // Undo last move (called "REPENT" in prototype)
+}
+
+
+/**
+ * Game Status enumeration
+ */
+export type GameStatus = "idle" | "playing" | "paused" | "finished";
+
+
+/**
+ * Game Result information
+ */
+export interface GameResult {
+ winner: "black" | "white" | "draw";
+ reason: "resignation" | "timeout" | "completion" | "double-pass";
+ score?: TerritoryResult;
+}
+
+
+/**
+ * Step - Represents a single move in the game
+ * Equivalent to step objects in prototype's StepHistory
+ */
+export interface Step {
+ type: StepType;
+ position?: Position; // Only for DROP moves
+ player: Stone; // Which player made this move
+ capturedPositions?: Position[]; // Stones captured by this move
+ timestamp: number; // When the move was made
+}
+
+
+/**
+ * Game Callbacks - Event handlers for game state changes
+ * Equivalent to Callbacks in prototype's trigo.Game constructor
+ */
+export interface GameCallbacks {
+ onStepAdvance?: (step: Step, history: Step[]) => void;
+ onStepBack?: (step: Step, history: Step[]) => void;
+ onCapture?: (capturedPositions: Position[]) => void;
+ onWin?: (winner: Stone) => void;
+ onTerritoryChange?: (territory: TerritoryResult) => void;
+}
+
+
+/**
+ * TrigoGame - Main game class managing state, history, and logic
+ *
+ * Equivalent to trigo.Game in prototype (lines 75-395)
+ *
+ * Key features:
+ * - Maintains game board state
+ * - Tracks complete move history
+ * - Implements Go rules (capture, Ko, suicide)
+ * - Supports undo/redo functionality
+ * - Territory calculation
+ */
+export class TrigoGame {
+ // Game configuration
+ private shape: BoardShape;
+ private callbacks: GameCallbacks;
+
+ // Game state
+ private board: Stone[][][];
+ private currentPlayer: Stone;
+ private stepHistory: Step[];
+ private currentStepIndex: number;
+
+ // Game status management
+ private gameStatus: GameStatus;
+ private gameResult?: GameResult;
+ private passCount: number;
+
+ // Last captured stones for Ko rule detection
+ private lastCapturedPositions: Position[] | null = null;
+
+ // Territory cache
+ private territoryDirty: boolean = true;
+ private cachedTerritory: TerritoryResult | null = null;
+
+
+ /**
+ * Constructor
+ * Equivalent to trigo.Game constructor (lines 75-85)
+ */
+ constructor(shape: BoardShape = { x: 5, y: 5, z: 5 }, callbacks: GameCallbacks = {}) {
+ this.shape = shape;
+ this.callbacks = callbacks;
+ this.board = this.createEmptyBoard();
+ this.currentPlayer = StoneType.BLACK;
+ this.stepHistory = [];
+ this.currentStepIndex = 0;
+ this.gameStatus = "idle";
+ this.gameResult = undefined;
+ this.passCount = 0;
+ }
+
+
+ /**
+ * Create an empty board
+ */
+ private createEmptyBoard(): Stone[][][] {
+ const board: Stone[][][] = [];
+ for (let x = 0; x < this.shape.x; x++) {
+ board[x] = [];
+ for (let y = 0; y < this.shape.y; y++) {
+ board[x][y] = [];
+ for (let z = 0; z < this.shape.z; z++) {
+ board[x][y][z] = StoneType.EMPTY;
+ }
+ }
+ }
+ return board;
+ }
+
+
+ /**
+ * Reset the game to initial state
+ * Equivalent to Game.reset() (lines 153-163)
+ */
+ reset(): void {
+ this.board = this.createEmptyBoard();
+ this.currentPlayer = StoneType.BLACK;
+ this.stepHistory = [];
+ this.currentStepIndex = 0;
+ this.lastCapturedPositions = null;
+ this.territoryDirty = true;
+ this.cachedTerritory = null;
+ this.gameStatus = "idle";
+ this.gameResult = undefined;
+ this.passCount = 0;
+ }
+
+
+ /**
+ * Get current board state (read-only)
+ */
+ getBoard(): Stone[][][] {
+ // Return a deep copy to prevent external modification
+ return this.board.map(plane => plane.map(row => [...row]));
+ }
+
+
+ /**
+ * Get stone at specific position
+ * Equivalent to Game.stone() (lines 95-97)
+ */
+ getStone(pos: Position): Stone {
+ return this.board[pos.x][pos.y][pos.z];
+ }
+
+
+ /**
+ * Get current player
+ */
+ getCurrentPlayer(): Stone {
+ return this.currentPlayer;
+ }
+
+
+ /**
+ * Get current step number
+ * Equivalent to Game.currentStep() (lines 99-101)
+ */
+ getCurrentStep(): number {
+ return this.currentStepIndex;
+ }
+
+
+ /**
+ * Get move history
+ * Equivalent to Game.routine() (lines 103-105)
+ */
+ getHistory(): Step[] {
+ return [...this.stepHistory];
+ }
+
+
+ /**
+ * Get last move
+ * Equivalent to Game.lastStep() (lines 107-110)
+ */
+ getLastStep(): Step | null {
+ if (this.currentStepIndex > 0) {
+ return this.stepHistory[this.currentStepIndex - 1];
+ }
+ return null;
+ }
+
+
+ /**
+ * Get board shape
+ * Equivalent to Game.shape() (lines 87-89)
+ */
+ getShape(): BoardShape {
+ return { ...this.shape };
+ }
+
+
+ /**
+ * Get game status
+ */
+ getGameStatus(): GameStatus {
+ return this.gameStatus;
+ }
+
+
+ /**
+ * Set game status
+ */
+ setGameStatus(status: GameStatus): void {
+ this.gameStatus = status;
+ }
+
+
+ /**
+ * Get game result
+ */
+ getGameResult(): GameResult | undefined {
+ return this.gameResult;
+ }
+
+
+ /**
+ * Get consecutive pass count
+ */
+ getPassCount(): number {
+ return this.passCount;
+ }
+
+
+ /**
+ * Recalculate consecutive pass count based on current history
+ * Counts consecutive PASS steps from the end of current history
+ */
+ private recalculatePassCount(): void {
+ this.passCount = 0;
+
+ // Count backwards from current position to find consecutive passes
+ for (let i = this.currentStepIndex - 1; i >= 0; i--) {
+ if (this.stepHistory[i].type === StepType.PASS) {
+ this.passCount++;
+ } else {
+ break; // Stop at first non-pass move
+ }
+ }
+ }
+
+
+ /**
+ * Start the game
+ */
+ startGame(): void {
+ if (this.gameStatus === "idle") {
+ this.gameStatus = "playing";
+ }
+ }
+
+
+ /**
+ * Check if game is active
+ */
+ isGameActive(): boolean {
+ return this.gameStatus === "playing";
+ }
+
+
+ /**
+ * Check if a move is valid
+ * Equivalent to Game.isDropable() and Game.isValidStep() (lines 112-151)
+ */
+ isValidMove(pos: Position, player?: Stone): { valid: boolean; reason?: string } {
+ const playerColor = player || this.currentPlayer;
+ return validateMove(pos, playerColor, this.board, this.shape, this.lastCapturedPositions);
+ }
+
+
+ /**
+ * Reset pass count (called when a stone is placed)
+ */
+ private resetPassCount(): void {
+ this.passCount = 0;
+ }
+
+
+ /**
+ * Place a stone (drop move)
+ * Equivalent to Game.drop() and Game.appendStone() (lines 181-273)
+ *
+ * @returns true if move was successful, false otherwise
+ */
+ drop(pos: Position): boolean {
+ // Validate the move
+ const validation = this.isValidMove(pos);
+ if (!validation.valid) {
+ console.warn(`Invalid move at (${pos.x}, ${pos.y}, ${pos.z}): ${validation.reason}`);
+ return false;
+ }
+
+ // Find captured groups BEFORE placing the stone
+ const capturedGroups = findCapturedGroups(pos, this.currentPlayer, this.board, this.shape);
+
+ // Place the stone on the board
+ this.board[pos.x][pos.y][pos.z] = this.currentPlayer;
+
+ // Execute captures
+ const capturedPositions = executeCaptures(capturedGroups, this.board);
+
+ // Store captured positions for Ko rule
+ this.lastCapturedPositions = capturedPositions.length > 0 ? capturedPositions : null;
+
+ // Mark territory as dirty
+ this.territoryDirty = true;
+
+ // Reset pass count when a stone is placed
+ this.resetPassCount();
+
+ // Create step record
+ const step: Step = {
+ type: StepType.DROP,
+ position: pos,
+ player: this.currentPlayer,
+ capturedPositions: capturedPositions.length > 0 ? capturedPositions : undefined,
+ timestamp: Date.now()
+ };
+
+ // Advance to next step
+ this.advanceStep(step);
+
+ // Trigger callbacks
+ if (capturedPositions.length > 0 && this.callbacks.onCapture) {
+ this.callbacks.onCapture(capturedPositions);
+ }
+
+ if (this.territoryDirty && this.callbacks.onTerritoryChange) {
+ this.callbacks.onTerritoryChange(this.getTerritory());
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Pass turn
+ * Equivalent to PASS step type in prototype
+ */
+ pass(): boolean {
+ const step: Step = {
+ type: StepType.PASS,
+ player: this.currentPlayer,
+ timestamp: Date.now()
+ };
+
+ this.lastCapturedPositions = null;
+
+ // Increment pass count
+ this.passCount++;
+
+ // Advance step
+ this.advanceStep(step);
+
+ // Check for double pass (game end condition)
+ if (this.passCount >= 2) {
+ // Calculate territory to determine winner
+ const territory = this.getTerritory();
+ const capturedCounts = this.getCapturedCounts();
+ const blackTotal = territory.black + capturedCounts.white; // black's territory + white stones captured
+ const whiteTotal = territory.white + capturedCounts.black; // white's territory + black stones captured
+
+ let winner: "black" | "white" | "draw";
+ if (blackTotal > whiteTotal) {
+ winner = "black";
+ } else if (whiteTotal > blackTotal) {
+ winner = "white";
+ } else {
+ winner = "draw";
+ }
+
+ this.gameResult = {
+ winner,
+ reason: "double-pass",
+ score: territory
+ };
+
+ this.gameStatus = "finished";
+
+ // Trigger win callback
+ if (this.callbacks.onWin) {
+ const winnerStone = winner === "black" ? StoneType.BLACK :
+ winner === "white" ? StoneType.WHITE : StoneType.EMPTY;
+ this.callbacks.onWin(winnerStone);
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Surrender/resign
+ * Equivalent to Game.step() with SURRENDER type (lines 176-178)
+ */
+ surrender(): boolean {
+ const surrenderingPlayer = this.currentPlayer; // Remember who surrendered
+
+ const step: Step = {
+ type: StepType.SURRENDER,
+ player: this.currentPlayer,
+ timestamp: Date.now()
+ };
+
+ this.advanceStep(step);
+
+ // Set game result - opponent of surrendering player wins
+ const winner = surrenderingPlayer === StoneType.BLACK ? "white" : "black";
+ this.gameResult = {
+ winner,
+ reason: "resignation"
+ };
+ this.gameStatus = "finished";
+
+ // Trigger win callback for the opponent
+ const winnerStone = getEnemyColor(surrenderingPlayer);
+ if (this.callbacks.onWin) {
+ this.callbacks.onWin(winnerStone);
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Undo last move
+ * Equivalent to Game.repent() (lines 197-230)
+ *
+ * @returns true if undo was successful, false if no moves to undo
+ */
+ undo(): boolean {
+ if (this.currentStepIndex === 0 || this.stepHistory.length === 0) {
+ return false;
+ }
+
+ const lastStep = this.stepHistory[this.currentStepIndex - 1];
+
+ // Revert the move
+ if (lastStep.type === StepType.DROP && lastStep.position) {
+ // Remove the placed stone
+ this.board[lastStep.position.x][lastStep.position.y][lastStep.position.z] = StoneType.EMPTY;
+
+ // Restore captured stones
+ if (lastStep.capturedPositions) {
+ const enemyColor = getEnemyColor(lastStep.player);
+ for (const pos of lastStep.capturedPositions) {
+ this.board[pos.x][pos.y][pos.z] = enemyColor;
+ }
+ }
+ }
+
+ // Move back in history
+ this.currentStepIndex--;
+ this.currentPlayer = lastStep.player; // Restore player who made that move
+
+ // Recalculate pass count based on new history position
+ this.recalculatePassCount();
+
+ // Update last captured positions for Ko rule
+ // Need to check the step before this one
+ if (this.currentStepIndex > 0) {
+ const previousStep = this.stepHistory[this.currentStepIndex - 1];
+ this.lastCapturedPositions = previousStep.capturedPositions || null;
+ } else {
+ this.lastCapturedPositions = null;
+ }
+
+ // Mark territory as dirty
+ this.territoryDirty = true;
+
+ // Trigger callback
+ if (this.callbacks.onStepBack) {
+ this.callbacks.onStepBack(lastStep, this.stepHistory.slice(0, this.currentStepIndex));
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Redo next move (after undo)
+ *
+ * @returns true if redo was successful, false if no moves to redo
+ */
+ redo(): boolean {
+ // Check if we can redo (not at the end of history)
+ if (this.currentStepIndex >= this.stepHistory.length) {
+ return false;
+ }
+
+ const nextStep = this.stepHistory[this.currentStepIndex];
+
+ // Re-apply the move
+ if (nextStep.type === StepType.DROP && nextStep.position) {
+ // Place the stone
+ this.board[nextStep.position.x][nextStep.position.y][nextStep.position.z] = nextStep.player;
+
+ // Re-execute captures if there were any
+ if (nextStep.capturedPositions) {
+ for (const pos of nextStep.capturedPositions) {
+ this.board[pos.x][pos.y][pos.z] = StoneType.EMPTY;
+ }
+ }
+
+ // Update last captured positions
+ this.lastCapturedPositions = nextStep.capturedPositions || null;
+ } else if (nextStep.type === StepType.PASS) {
+ this.lastCapturedPositions = null;
+ }
+
+ // Move forward in history
+ this.currentStepIndex++;
+ this.currentPlayer = getEnemyColor(nextStep.player); // Switch to next player
+
+ // Mark territory as dirty
+ this.territoryDirty = true;
+
+ // Trigger callback
+ if (this.callbacks.onStepAdvance) {
+ this.callbacks.onStepAdvance(nextStep, this.stepHistory.slice(0, this.currentStepIndex));
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Check if redo is available
+ */
+ canRedo(): boolean {
+ return this.currentStepIndex < this.stepHistory.length;
+ }
+
+
+ /**
+ * Jump to specific step in history
+ * Rebuilds board state after applying the first 'index' moves
+ *
+ * @param index Number of moves to apply from history (0 for initial state, 1 for after first move, etc.)
+ * @returns true if jump was successful
+ */
+ jumpToStep(index: number): boolean {
+ // Validate index: allow 0 (initial state) up to stepHistory.length (all moves applied)
+ if (index < 0 || index > this.stepHistory.length) {
+ return false;
+ }
+
+ // If already at target index, return false (no change made)
+ if (index === this.currentStepIndex) {
+ return false;
+ }
+
+ // Rebuild board from scratch
+ this.board = this.createEmptyBoard();
+ this.lastCapturedPositions = null;
+
+ // Replay all moves up to (but not including) target index
+ // After this loop, we'll have applied 'index' number of moves
+ for (let i = 0; i < index; i++) {
+ const step = this.stepHistory[i];
+
+ if (step.type === StepType.DROP && step.position) {
+ const pos = step.position;
+
+ // Place the stone
+ this.board[pos.x][pos.y][pos.z] = step.player;
+
+ // Re-execute captures
+ if (step.capturedPositions) {
+ for (const capturedPos of step.capturedPositions) {
+ this.board[capturedPos.x][capturedPos.y][capturedPos.z] = StoneType.EMPTY;
+ }
+ }
+ }
+ }
+
+ // Set last captured positions from the last applied move (if any)
+ if (index > 0) {
+ const lastAppliedStep = this.stepHistory[index - 1];
+ if (lastAppliedStep.type === StepType.DROP) {
+ this.lastCapturedPositions = lastAppliedStep.capturedPositions || null;
+ } else if (lastAppliedStep.type === StepType.PASS) {
+ this.lastCapturedPositions = null;
+ }
+ } else {
+ this.lastCapturedPositions = null;
+ }
+
+ // Update current index
+ const oldStepIndex = this.currentStepIndex;
+ this.currentStepIndex = index;
+
+ // Set current player based on number of moves played
+ // currentStepIndex represents the number of moves applied
+ const movesPlayed = index;
+ this.currentPlayer = movesPlayed % 2 === 0 ? StoneType.BLACK : StoneType.WHITE;
+
+ // Recalculate pass count based on new history position
+ this.recalculatePassCount();
+
+ // Mark territory as dirty
+ this.territoryDirty = true;
+
+ // Trigger callback based on direction
+ if (index < oldStepIndex && this.callbacks.onStepBack) {
+ const currentStep = this.stepHistory[index];
+ this.callbacks.onStepBack(currentStep, this.stepHistory.slice(0, index + 1));
+ } else if (index > oldStepIndex && this.callbacks.onStepAdvance) {
+ const currentStep = this.stepHistory[index];
+ this.callbacks.onStepAdvance(currentStep, this.stepHistory.slice(0, index + 1));
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Advance to next step
+ * Equivalent to Game.stepAdvance() (lines 279-287)
+ */
+ private advanceStep(step: Step): void {
+ // If we're not at the end of history, truncate future steps
+ if (this.currentStepIndex < this.stepHistory.length) {
+ this.stepHistory = this.stepHistory.slice(0, this.currentStepIndex);
+ }
+
+ // Add the new step
+ this.stepHistory.push(step);
+ this.currentStepIndex++;
+
+ // Switch player
+ this.currentPlayer = getEnemyColor(this.currentPlayer);
+
+ // Trigger callback
+ if (this.callbacks.onStepAdvance) {
+ this.callbacks.onStepAdvance(step, this.stepHistory);
+ }
+ }
+
+
+ /**
+ * Get territory calculation
+ * Equivalent to Game.blackDomain() and Game.whiteDomain() (lines 232-244)
+ *
+ * Returns cached result if territory hasn't changed
+ */
+ getTerritory(): TerritoryResult {
+ if (this.territoryDirty || !this.cachedTerritory) {
+ this.cachedTerritory = calculateTerritory(this.board, this.shape);
+ this.territoryDirty = false;
+ }
+ return this.cachedTerritory;
+ }
+
+
+ /**
+ * Get captured stone counts up to current position in history
+ * Only counts captures that have been played (up to currentStepIndex)
+ */
+ getCapturedCounts(): { black: number; white: number } {
+ const counts = { black: 0, white: 0 };
+
+ // Only count captures up to current step index
+ for (let i = 0; i < this.currentStepIndex; i++) {
+ const step = this.stepHistory[i];
+ if (step.capturedPositions && step.capturedPositions.length > 0) {
+ // Captured stones belong to the enemy of the player who made the move
+ const enemyColor = getEnemyColor(step.player);
+ if (enemyColor === StoneType.BLACK) {
+ counts.black += step.capturedPositions.length;
+ } else if (enemyColor === StoneType.WHITE) {
+ counts.white += step.capturedPositions.length;
+ }
+ }
+ }
+
+ return counts;
+ }
+
+
+ /**
+ * Serialize game state to JSON
+ * Equivalent to Game.serialize() (lines 250-252)
+ */
+ toJSON(): object {
+ return {
+ shape: this.shape,
+ currentPlayer: this.currentPlayer,
+ currentStepIndex: this.currentStepIndex,
+ history: this.stepHistory,
+ board: this.board,
+ gameStatus: this.gameStatus,
+ gameResult: this.gameResult,
+ passCount: this.passCount
+ };
+ }
+
+
+ /**
+ * Load game state from JSON
+ */
+ fromJSON(data: any): boolean {
+ try {
+ // Validate required fields
+ if (!data || typeof data !== "object") {
+ return false;
+ }
+
+ if (!data.shape || !data.board || !Array.isArray(data.history)) {
+ return false;
+ }
+
+ this.shape = data.shape;
+ this.currentPlayer = data.currentPlayer;
+ this.currentStepIndex = data.currentStepIndex;
+ this.stepHistory = data.history || [];
+ this.board = data.board;
+ this.gameStatus = data.gameStatus || "idle";
+ this.gameResult = data.gameResult;
+ this.passCount = data.passCount || 0;
+
+ // Recalculate last captured positions
+ if (this.currentStepIndex > 0) {
+ const lastStep = this.stepHistory[this.currentStepIndex - 1];
+ this.lastCapturedPositions = lastStep.capturedPositions || null;
+ } else {
+ this.lastCapturedPositions = null;
+ }
+
+ this.territoryDirty = true;
+ this.cachedTerritory = null;
+
+ return true;
+ } catch (error) {
+ console.error("Failed to load game state:", error);
+ return false;
+ }
+ }
+
+
+ /**
+ * Get game statistics
+ */
+ getStats(): {
+ totalMoves: number;
+ blackMoves: number;
+ whiteMoves: number;
+ capturedByBlack: number;
+ capturedByWhite: number;
+ territory: TerritoryResult;
+ } {
+ const captured = this.getCapturedCounts();
+ const territory = this.getTerritory();
+
+ let blackMoves = 0;
+ let whiteMoves = 0;
+
+ for (const step of this.stepHistory.slice(0, this.currentStepIndex)) {
+ if (step.type === StepType.DROP) {
+ if (step.player === StoneType.BLACK) {
+ blackMoves++;
+ } else if (step.player === StoneType.WHITE) {
+ whiteMoves++;
+ }
+ }
+ }
+
+ return {
+ totalMoves: this.currentStepIndex,
+ blackMoves,
+ whiteMoves,
+ capturedByBlack: captured.white, // Black captures white stones
+ capturedByWhite: captured.black, // White captures black stones
+ territory
+ };
+ }
+
+
+ /**
+ * Save game state to sessionStorage
+ *
+ * @param key Storage key (default: "trigoGameState")
+ * @returns true if save was successful
+ */
+ saveToSessionStorage(key: string = "trigoGameState"): boolean {
+ // Check if sessionStorage is available (browser environment)
+ if (typeof globalThis !== "undefined" && (globalThis as any).sessionStorage) {
+ try {
+ const gameState = this.toJSON();
+ (globalThis as any).sessionStorage.setItem(key, JSON.stringify(gameState));
+ return true;
+ } catch (error) {
+ console.error("Failed to save game state to sessionStorage:", error);
+ return false;
+ }
+ }
+
+ console.warn("sessionStorage is not available");
+ return false;
+ }
+
+
+ /**
+ * Load game state from sessionStorage
+ *
+ * @param key Storage key (default: "trigoGameState")
+ * @returns true if load was successful
+ */
+ loadFromSessionStorage(key: string = "trigoGameState"): boolean {
+ // Check if sessionStorage is available (browser environment)
+ if (typeof globalThis !== "undefined" && (globalThis as any).sessionStorage) {
+ try {
+ const savedState = (globalThis as any).sessionStorage.getItem(key);
+ if (!savedState) {
+ console.log("No saved game state found");
+ return false;
+ }
+
+ const data = JSON.parse(savedState);
+ return this.fromJSON(data);
+ } catch (error) {
+ console.error("Failed to load game state from sessionStorage:", error);
+ return false;
+ }
+ }
+
+ console.warn("sessionStorage is not available");
+ return false;
+ }
+
+
+ /**
+ * Clear saved game state from sessionStorage
+ *
+ * @param key Storage key (default: "trigoGameState")
+ */
+ clearSessionStorage(key: string = "trigoGameState"): void {
+ // Check if sessionStorage is available (browser environment)
+ if (typeof globalThis !== "undefined" && (globalThis as any).sessionStorage) {
+ try {
+ (globalThis as any).sessionStorage.removeItem(key);
+ } catch (error) {
+ console.error("Failed to clear sessionStorage:", error);
+ }
+ } else {
+ console.warn("sessionStorage is not available");
+ }
+ }
+
+
+ /**
+ * Export game to TGN (Trigo Game Notation) format
+ *
+ * TGN format is similar to PGN (Portable Game Notation) for chess.
+ * It includes metadata tags and move sequence using ab0yz coordinate notation.
+ *
+ * @param metadata Optional metadata for the game (Event, Site, Date, Players, etc.)
+ * @returns TGN-formatted string
+ *
+ * @example
+ * const tgn = game.toTGN({
+ * event: "World Championship",
+ * site: "Tokyo",
+ * date: "2025.10.31",
+ * black: "Alice",
+ * white: "Bob"
+ * });
+ */
+ toTGN(metadata?: {
+ event?: string;
+ site?: string;
+ date?: string;
+ round?: string;
+ black?: string;
+ white?: string;
+ rules?: string;
+ timeControl?: string;
+ application?: string;
+ [key: string]: string | undefined;
+ }): string {
+ const lines: string[] = [];
+
+ // Add metadata tags
+ if (metadata) {
+ if (metadata.event) lines.push(`[Event "${metadata.event}"]`);
+ if (metadata.site) lines.push(`[Site "${metadata.site}"]`);
+ if (metadata.date) lines.push(`[Date "${metadata.date}"]`);
+ if (metadata.round) lines.push(`[Round "${metadata.round}"]`);
+ if (metadata.black) lines.push(`[Black "${metadata.black}"]`);
+ if (metadata.white) lines.push(`[White "${metadata.white}"]`);
+ }
+
+ // Add result if game is finished
+ if (this.gameStatus === "finished" && this.gameResult) {
+ let resultStr = "";
+ if (this.gameResult.winner === "black") {
+ resultStr = "B+";
+ } else if (this.gameResult.winner === "white") {
+ resultStr = "W+";
+ } else {
+ resultStr = "=";
+ }
+
+ // Add score information if available
+ if (this.gameResult.score) {
+ const { black, white } = this.gameResult.score;
+ const diff = Math.abs(black - white);
+ resultStr += `${diff}points`;
+ } else if (this.gameResult.reason === "resignation") {
+ resultStr += "resign";
+ }
+
+ lines.push(`[Result "${resultStr}"]`);
+ }
+
+ // Add board size (without quotes - parser expects unquoted board shape)
+ const boardStr = this.shape.z === 1
+ ? `${this.shape.x}x${this.shape.y}` // 2D board
+ : `${this.shape.x}x${this.shape.y}x${this.shape.z}`; // 3D board
+ lines.push(`[Board ${boardStr}]`);
+
+ // Add optional metadata
+ if (metadata) {
+ if (metadata.rules) lines.push(`[Rules "${metadata.rules}"]`);
+ if (metadata.timeControl) lines.push(`[TimeControl "${metadata.timeControl}"]`);
+ if (metadata.application) lines.push(`[Application "${metadata.application}"]`);
+ }
+
+ // Add empty line after metadata
+ lines.push("");
+
+ // Generate move sequence
+ const moves: string[] = [];
+ let moveNumber = 1;
+
+ for (let i = 0; i < this.stepHistory.length; i++) {
+ const step = this.stepHistory[i];
+ let moveStr = "";
+
+ // Add move number at the start of each round (black's move)
+ if (step.player === StoneType.BLACK) {
+ moveStr = `${moveNumber}. `;
+ }
+
+ // Format the move
+ if (step.type === StepType.DROP && step.position) {
+ // Convert position to TGN coordinate
+ const pos = [step.position.x, step.position.y, step.position.z];
+ const boardShape = [this.shape.x, this.shape.y, this.shape.z];
+ const coord = encodeAb0yz(pos, boardShape);
+ moveStr += coord;
+ } else if (step.type === StepType.PASS) {
+ moveStr += "pass";
+ } else if (step.type === StepType.SURRENDER) {
+ moveStr += "resign";
+ }
+
+ moves.push(moveStr);
+
+ // Increment move number after white's move
+ if (step.player === StoneType.WHITE) {
+ moveNumber++;
+ }
+ }
+
+ // Format moves with proper line breaks
+ // Group moves in pairs (black, white) on the same line
+ let currentLine = "";
+ for (let i = 0; i < moves.length; i++) {
+ const move = moves[i];
+
+ if (move.match(/^\d+\./)) {
+ // Start of a new round (black's move)
+ if (currentLine) {
+ lines.push(currentLine);
+ }
+ currentLine = move;
+ } else {
+ // White's move - add to current line
+ currentLine += " " + move;
+ }
+ }
+
+ // Add the last line if it exists
+ if (currentLine) {
+ lines.push(currentLine);
+ }
+
+ // Add empty line at the end
+ lines.push("");
+
+ return lines.join("\n");
+ }
+
+
+ /**
+ * Import game from TGN (Trigo Game Notation) format
+ *
+ * Static factory method that parses a TGN string and creates a new TrigoGame instance
+ * with the board configuration and moves from the TGN file.
+ *
+ * Synchronous operation - requires parser to be loaded via setParserModule()
+ *
+ * @param tgnString TGN-formatted game notation string
+ * @param callbacks Optional game callbacks
+ * @returns New TrigoGame instance with the imported game state
+ * @throws TGNParseError if the TGN string is invalid
+ *
+ * @example
+ * const tgnString = `
+ * [Event "World Championship"]
+ * [Board "5x5x5"]
+ * [Black "Alice"]
+ * [White "Bob"]
+ *
+ * 1. 000 y00
+ * 2. 0y0 pass
+ * `;
+ * const game = TrigoGame.fromTGN(tgnString);
+ */
+ static fromTGN(tgnString: string, callbacks?: GameCallbacks): TrigoGame {
+ // Parse the TGN string (synchronous)
+ const parsed = parseTGN(tgnString);
+
+ // Extract board shape from tags
+ let boardShape: BoardShape;
+ if (parsed.tags.Board && Array.isArray(parsed.tags.Board)) {
+ const shape = parsed.tags.Board;
+ // Ensure we have at least 2 dimensions, default to 1 for z if not 3D
+ boardShape = {
+ x: shape[0] || 5,
+ y: shape[1] || 5,
+ z: shape[2] || 1
+ };
+ } else {
+ // Default to 5x5x5 if no board shape specified
+ boardShape = { x: 5, y: 5, z: 5 };
+ }
+
+ // Create new game instance using 'this' constructor
+ // This allows subclasses to automatically get their own type
+ const game = new this(boardShape, callbacks);
+ game.startGame();
+
+ // Replay all moves from the parsed data
+ if (parsed.moves && parsed.moves.length > 0) {
+ for (const round of parsed.moves) {
+ // Play black's move
+ if (round.action_black) {
+ game._applyParsedMove(round.action_black, boardShape);
+ }
+
+ // Play white's move if it exists
+ if (round.action_white) {
+ game._applyParsedMove(round.action_white, boardShape);
+ }
+ }
+ }
+
+ return game;
+ }
+
+
+ /**
+ * Apply a parsed move action to the game
+ * Private helper method for fromTGN
+ *
+ * @param action Parsed move action from TGN parser
+ * @param boardShape Board dimensions for coordinate decoding
+ */
+ private _applyParsedMove(action: { type: string; position?: string }, boardShape: BoardShape): void {
+ if (action.type === 'pass') {
+ this.pass();
+ } else if (action.type === 'resign') {
+ this.surrender();
+ } else if (action.type === 'move' && action.position) {
+ // Decode ab0yz coordinate to Position
+ const coords = decodeAb0yz(action.position, [boardShape.x, boardShape.y, boardShape.z]);
+ const position: Position = {
+ x: coords[0],
+ y: coords[1],
+ z: coords[2]
+ };
+
+ // Make the move
+ this.drop(position);
+ }
+ }
+}
+
+
+// Re-export parser utilities for convenience
+export { validateTGN, TGNParseError } from "../tgn/tgnParser";
+
diff --git a/trigo-web/inc/trigo/gameUtils.ts b/trigo-web/inc/trigo/gameUtils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4628d70c5e9da1300335eab8695dbbfc2693ddff
--- /dev/null
+++ b/trigo-web/inc/trigo/gameUtils.ts
@@ -0,0 +1,600 @@
+/**
+ * Game Logic Service - Core Go Rules Implementation
+ *
+ * Implements capture detection, Ko rule, and territory counting
+ * Ported from prototype trigo.game.js to modern TypeScript
+ */
+
+import type { Position, Stone, BoardShape } from "./types";
+
+export const StoneType = {
+ EMPTY: 0 as Stone,
+ BLACK: 1 as Stone,
+ WHITE: 2 as Stone
+};
+
+
+/**
+ * Get the enemy color of a given stone
+ *
+ * Equivalent to trigo.enemyColor() in prototype
+ */
+export function getEnemyColor(color: Stone): Stone {
+ if (color === StoneType.BLACK) return StoneType.WHITE;
+ if (color === StoneType.WHITE) return StoneType.BLACK;
+ return StoneType.EMPTY;
+}
+
+
+/**
+ * Check if a position is within board bounds
+ */
+export function isInBounds(pos: Position, shape: BoardShape): boolean {
+ return (
+ pos.x >= 0 && pos.x < shape.x &&
+ pos.y >= 0 && pos.y < shape.y &&
+ pos.z >= 0 && pos.z < shape.z
+ );
+}
+
+
+/**
+ * Get all neighboring positions (up to 6 in 3D: ยฑx, ยฑy, ยฑz)
+ *
+ * Equivalent to StoneArray.stoneNeighbors() in prototype
+ */
+export function getNeighbors(pos: Position, shape: BoardShape): Position[] {
+ const neighbors: Position[] = [];
+
+ // Check all 6 directions in 3D space
+ const directions = [
+ { x: 1, y: 0, z: 0 },
+ { x: -1, y: 0, z: 0 },
+ { x: 0, y: 1, z: 0 },
+ { x: 0, y: -1, z: 0 },
+ { x: 0, y: 0, z: 1 },
+ { x: 0, y: 0, z: -1 }
+ ];
+
+ for (const dir of directions) {
+ const neighbor: Position = {
+ x: pos.x + dir.x,
+ y: pos.y + dir.y,
+ z: pos.z + dir.z
+ };
+
+ if (isInBounds(neighbor, shape)) {
+ neighbors.push(neighbor);
+ }
+ }
+
+ return neighbors;
+}
+
+
+/**
+ * Compare two positions for equality
+ */
+export function positionsEqual(p1: Position, p2: Position): boolean {
+ return p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
+}
+
+
+/**
+ * Coordinate Set - manages a set of positions (stones in a group or liberties)
+ */
+export class CoordSet {
+ private positions: Position[] = [];
+
+ has(pos: Position): boolean {
+ return this.positions.some((p) => positionsEqual(p, pos));
+ }
+
+ insert(pos: Position): boolean {
+ if (!this.has(pos)) {
+ this.positions.push(pos);
+ return true;
+ }
+ return false;
+ }
+
+ remove(pos: Position): void {
+ this.positions = this.positions.filter((p) => !positionsEqual(p, pos));
+ }
+
+ size(): number {
+ return this.positions.length;
+ }
+
+ empty(): boolean {
+ return this.positions.length === 0;
+ }
+
+ forEach(callback: (pos: Position, index: number) => void): void {
+ this.positions.forEach(callback);
+ }
+
+ toArray(): Position[] {
+ return [...this.positions];
+ }
+
+ clear(): void {
+ this.positions = [];
+ }
+}
+
+
+/**
+ * Patch - represents a connected group of same-colored stones
+ *
+ * Note: Called "Patch" in the original prototype (trigo.game.js)
+ * Equivalent to trigo.Game.Patch which extends trigo.CoordSet
+ */
+export class Patch {
+ positions: CoordSet = new CoordSet();
+ color: Stone = StoneType.EMPTY;
+
+ constructor(color: Stone = StoneType.EMPTY) {
+ this.color = color;
+ }
+
+ addStone(pos: Position): void {
+ this.positions.insert(pos);
+ }
+
+ size(): number {
+ return this.positions.size();
+ }
+
+ /**
+ * Get all liberties (empty adjacent positions) for this group
+ *
+ * Equivalent to StoneArray.patchAir() in prototype
+ * Returns a CoordSet of empty positions adjacent to this patch
+ */
+ getLiberties(board: Stone[][][], shape: BoardShape): CoordSet {
+ const liberties = new CoordSet();
+
+ this.positions.forEach((stonePos) => {
+ const neighbors = getNeighbors(stonePos, shape);
+ for (const neighbor of neighbors) {
+ if (board[neighbor.x][neighbor.y][neighbor.z] === StoneType.EMPTY) {
+ liberties.insert(neighbor);
+ }
+ }
+ });
+
+ return liberties;
+ }
+}
+
+
+/**
+ * Find the connected group of stones at a given position
+ */
+export function findGroup(
+ pos: Position,
+ board: Stone[][][],
+ shape: BoardShape
+): Patch {
+ const color = board[pos.x][pos.y][pos.z];
+ const group = new Patch(color);
+
+ if (color === StoneType.EMPTY) {
+ return group;
+ }
+
+ // Flood fill to find all connected stones of the same color
+ const visited = new CoordSet();
+ const stack: Position[] = [pos];
+
+ while (stack.length > 0) {
+ const current = stack.pop()!;
+
+ if (visited.has(current)) {
+ continue;
+ }
+
+ visited.insert(current);
+
+ if (board[current.x][current.y][current.z] === color) {
+ group.addStone(current);
+
+ const neighbors = getNeighbors(current, shape);
+ for (const neighbor of neighbors) {
+ if (!visited.has(neighbor)) {
+ stack.push(neighbor);
+ }
+ }
+ }
+ }
+
+ return group;
+}
+
+
+/**
+ * Get all neighboring groups (different from current position's color)
+ */
+export function getNeighborGroups(
+ pos: Position,
+ board: Stone[][][],
+ shape: BoardShape,
+ excludeEmpty: boolean = false
+): Patch[] {
+ const neighbors = getNeighbors(pos, shape);
+ const groups: Patch[] = [];
+ const processedPositions = new CoordSet();
+
+ for (const neighbor of neighbors) {
+ if (processedPositions.has(neighbor)) {
+ continue;
+ }
+
+ const stone = board[neighbor.x][neighbor.y][neighbor.z];
+
+ if (excludeEmpty && stone === StoneType.EMPTY) {
+ continue;
+ }
+
+ if (stone !== StoneType.EMPTY) {
+ const group = findGroup(neighbor, board, shape);
+ group.positions.forEach((p) => processedPositions.insert(p));
+ groups.push(group);
+ }
+ }
+
+ return groups;
+}
+
+
+/**
+ * Check if a group would be captured (has no liberties)
+ */
+export function isGroupCaptured(
+ group: Patch,
+ board: Stone[][][],
+ shape: BoardShape
+): boolean {
+ const liberties = group.getLiberties(board, shape);
+ return liberties.size() === 0;
+}
+
+
+/**
+ * Find all groups that would be captured by placing a stone at position
+ *
+ * Equivalent to Game.killedPatches() in prototype
+ *
+ * Note: Prototype checks patchAir <= 1 BEFORE placement.
+ * We create a temp board with stone placed, then check for 0 liberties AFTER.
+ * Both approaches produce the same result.
+ */
+export function findCapturedGroups(
+ pos: Position,
+ playerColor: Stone,
+ board: Stone[][][],
+ shape: BoardShape
+): Patch[] {
+ const enemyColor = getEnemyColor(playerColor);
+ const captured: Patch[] = [];
+
+ // Create a temporary board with the new stone placed
+ const tempBoard = board.map((plane) => plane.map((row) => [...row]));
+ tempBoard[pos.x][pos.y][pos.z] = playerColor;
+
+ // Check all neighboring enemy groups
+ const neighborGroups = getNeighborGroups(pos, tempBoard, shape, true);
+
+ for (const group of neighborGroups) {
+ if (group.color === enemyColor) {
+ if (isGroupCaptured(group, tempBoard, shape)) {
+ captured.push(group);
+ }
+ }
+ }
+
+ return captured;
+}
+
+
+/**
+ * Check if placing a stone at position would result in self-capture (suicide)
+ *
+ * Equivalent to Game.isDeadStone() in prototype
+ *
+ * Exception: Move is allowed if it captures enemy stones first
+ */
+export function isSuicideMove(
+ pos: Position,
+ playerColor: Stone,
+ board: Stone[][][],
+ shape: BoardShape
+): boolean {
+ // Create temporary board with the new stone
+ const tempBoard = board.map((plane) => plane.map((row) => [...row]));
+ tempBoard[pos.x][pos.y][pos.z] = playerColor;
+
+ // If this move captures enemy stones, it's not suicide
+ const capturedGroups = findCapturedGroups(pos, playerColor, board, shape);
+ if (capturedGroups.length > 0) {
+ return false;
+ }
+
+ // Check if the placed stone's group has any liberties
+ const placedGroup = findGroup(pos, tempBoard, shape);
+ const liberties = placedGroup.getLiberties(tempBoard, shape);
+
+ return liberties.size() === 0;
+}
+
+
+/**
+ * Ko Detection - check if move would recreate previous board state
+ *
+ * Equivalent to "rob" check in Game.isDropable() (lines 127-132)
+ * Note: Ko rule is called "rob" (ๆๅซ dวjiรฉ) in the prototype
+ *
+ * Ko rule: Cannot immediately recapture a single stone if it would
+ * return the board to the previous position
+ */
+export function isKoViolation(
+ pos: Position,
+ playerColor: Stone,
+ board: Stone[][][],
+ shape: BoardShape,
+ lastCapturedPositions: Position[] | null
+): boolean {
+ // Ko only applies when:
+ // 1. We would capture exactly one stone
+ // 2. The previous move also captured exactly one stone
+ // 3. We're placing at the position of the previously captured stone
+
+ if (!lastCapturedPositions || lastCapturedPositions.length !== 1) {
+ return false;
+ }
+
+ const capturedGroups = findCapturedGroups(pos, playerColor, board, shape);
+
+ // Check if this move would capture exactly one stone
+ if (capturedGroups.length !== 1 || capturedGroups[0].size() !== 1) {
+ return false;
+ }
+
+ // Check if we're placing at the position that was just captured
+ const previouslyCaptured = lastCapturedPositions[0];
+ if (positionsEqual(pos, previouslyCaptured)) {
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Execute captures on the board
+ * Returns the positions of captured stones
+ */
+export function executeCaptures(
+ capturedGroups: Patch[],
+ board: Stone[][][]
+): Position[] {
+ const capturedPositions: Position[] = [];
+
+ for (const group of capturedGroups) {
+ group.positions.forEach((pos) => {
+ board[pos.x][pos.y][pos.z] = StoneType.EMPTY;
+ capturedPositions.push(pos);
+ });
+ }
+
+ return capturedPositions;
+}
+
+
+/**
+ * Territory Calculation
+ *
+ * Determines which empty regions belong to which player
+ * An empty region belongs to a player if it's completely surrounded
+ * by that player's stones
+ */
+export interface TerritoryResult {
+ black: number;
+ white: number;
+ neutral: number;
+ blackTerritory: Position[];
+ whiteTerritory: Position[];
+ neutralTerritory: Position[];
+}
+
+
+/**
+ * Find all connected empty positions starting from a position
+ */
+function findEmptyRegion(
+ startPos: Position,
+ board: Stone[][][],
+ shape: BoardShape,
+ visited: CoordSet
+): CoordSet {
+ const region = new CoordSet();
+ const stack: Position[] = [startPos];
+
+ while (stack.length > 0) {
+ const pos = stack.pop()!;
+
+ if (visited.has(pos)) {
+ continue;
+ }
+
+ visited.insert(pos);
+
+ if (board[pos.x][pos.y][pos.z] === StoneType.EMPTY) {
+ region.insert(pos);
+
+ const neighbors = getNeighbors(pos, shape);
+ for (const neighbor of neighbors) {
+ if (!visited.has(neighbor)) {
+ stack.push(neighbor);
+ }
+ }
+ }
+ }
+
+ return region;
+}
+
+
+/**
+ * Determine which player owns an empty region
+ * Returns BLACK, WHITE, or EMPTY (neutral/dame)
+ *
+ * Equivalent to PatchList.spaceDomain() in prototype (lines 561-585)
+ * An empty region belongs to a player if ALL bordering stones are that color
+ */
+function determineRegionOwner(
+ region: CoordSet,
+ board: Stone[][][],
+ shape: BoardShape
+): Stone {
+ let owner: Stone = StoneType.EMPTY;
+ let solved = false; // Flag to break out when we find mixed colors
+
+ region.forEach((pos) => {
+ if (solved) return; // Skip if already determined to be neutral
+
+ const neighbors = getNeighbors(pos, shape);
+
+ for (const neighbor of neighbors) {
+ if (solved) break; // Skip if already determined to be neutral
+
+ const stone = board[neighbor.x][neighbor.y][neighbor.z];
+
+ if (stone !== StoneType.EMPTY) {
+ if (owner === StoneType.EMPTY) {
+ // First colored stone we've found
+ owner = stone;
+ } else if (owner !== stone) {
+ // Found a different colored stone - region is neutral
+ owner = StoneType.EMPTY;
+ solved = true; // Mark as solved so we stop checking
+ }
+ }
+ }
+ });
+
+ return owner;
+}
+
+
+/**
+ * Calculate territory for both players
+ *
+ * Equivalent to Game.calculateDomainSize() in prototype (lines 305-343)
+ * Uses PatchList.spaceDomain() logic to determine region ownership
+ *
+ * Algorithm:
+ * 1. First pass: Add all stones to their player's territory, collect empty regions
+ * 2. Second pass: Determine ownership of each empty region
+ */
+export function calculateTerritory(
+ board: Stone[][][],
+ shape: BoardShape
+): TerritoryResult {
+ const result: TerritoryResult = {
+ black: 0,
+ white: 0,
+ neutral: 0,
+ blackTerritory: [],
+ whiteTerritory: [],
+ neutralTerritory: []
+ };
+
+ const visited = new CoordSet();
+ const emptyRegions: CoordSet[] = [];
+
+ // FIRST PASS: Count all stones and find all empty regions
+ for (let x = 0; x < shape.x; x++) {
+ for (let y = 0; y < shape.y; y++) {
+ for (let z = 0; z < shape.z; z++) {
+ const pos: Position = { x, y, z };
+ const stone = board[x][y][z];
+
+ if (stone === StoneType.BLACK) {
+ result.black++;
+ result.blackTerritory.push(pos);
+ } else if (stone === StoneType.WHITE) {
+ result.white++;
+ result.whiteTerritory.push(pos);
+ } else if (!visited.has(pos)) {
+ // Found an empty position - explore the region and save it
+ const region = findEmptyRegion(pos, board, shape, visited);
+ emptyRegions.push(region);
+ }
+ }
+ }
+ }
+
+ // SECOND PASS: Determine ownership of each empty region
+ for (const region of emptyRegions) {
+ const owner = determineRegionOwner(region, board, shape);
+ const regionArray = region.toArray();
+
+ if (owner === StoneType.BLACK) {
+ result.black += region.size();
+ result.blackTerritory.push(...regionArray);
+ } else if (owner === StoneType.WHITE) {
+ result.white += region.size();
+ result.whiteTerritory.push(...regionArray);
+ } else {
+ result.neutral += region.size();
+ result.neutralTerritory.push(...regionArray);
+ }
+ }
+
+ return result;
+}
+
+
+/**
+ * Validate if a move is legal
+ *
+ * Equivalent to Game.isDropable() in prototype
+ * Checks: bounds, occupation, Ko rule ("rob"), suicide rule
+ */
+export interface MoveValidation {
+ valid: boolean;
+ reason?: string;
+}
+
+
+export function validateMove(
+ pos: Position,
+ playerColor: Stone,
+ board: Stone[][][],
+ shape: BoardShape,
+ lastCapturedPositions: Position[] | null = null
+): MoveValidation {
+ // Check bounds
+ if (!isInBounds(pos, shape)) {
+ return { valid: false, reason: "Position out of bounds" };
+ }
+
+ // Check if position is empty
+ if (board[pos.x][pos.y][pos.z] !== StoneType.EMPTY) {
+ return { valid: false, reason: "Position already occupied" };
+ }
+
+ // Check for Ko violation
+ if (isKoViolation(pos, playerColor, board, shape, lastCapturedPositions)) {
+ return { valid: false, reason: "Ko rule violation" };
+ }
+
+ // Check for suicide (self-capture)
+ if (isSuicideMove(pos, playerColor, board, shape)) {
+ return { valid: false, reason: "suicide move not allowed" };
+ }
+
+ return { valid: true };
+}
diff --git a/trigo-web/inc/trigo/index.ts b/trigo-web/inc/trigo/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8bb8bca7f59af82c85554c09db4322361714017f
--- /dev/null
+++ b/trigo-web/inc/trigo/index.ts
@@ -0,0 +1,5 @@
+
+export * from "./types";
+export * from "./gameUtils";
+export * from "./game";
+export * from "./typeAdapters";
diff --git a/trigo-web/inc/trigo/parserInit.ts b/trigo-web/inc/trigo/parserInit.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7f6324f316ed7c661443c51cf47c1afb9fcd3075
--- /dev/null
+++ b/trigo-web/inc/trigo/parserInit.ts
@@ -0,0 +1,135 @@
+/**
+ * Parser Initialization Module
+ *
+ * Loads pre-built parsers from public/lib and injects them into parser wrappers
+ * This allows the same synchronous API to work in both browser and Node.js
+ *
+ * Usage in Node.js tests:
+ * ```
+ * import { initializeParsers } from "@inc/trigo/parserInit"
+ * await initializeParsers()
+ * ```
+ *
+ * Usage in Vue app:
+ * Add to main.ts before using any game functionality
+ */
+
+import { setParserModule } from "../tgn/tgnParser";
+
+
+/**
+ * Check if we're in a browser environment
+ */
+function isBrowser(): boolean {
+ return typeof window !== "undefined" && typeof document !== "undefined";
+}
+
+
+/**
+ * Check if we're in a Node.js environment
+ */
+function isNode(): boolean {
+ return typeof process !== "undefined" && !!(process as any).versions && !!(process as any).versions.node;
+}
+
+
+/**
+ * Initialize all parsers for use in the application
+ * Should be called once at application startup
+ *
+ * In browser: Dynamically imports from /lib/
+ * In Node.js: Loads from project's public/lib directory
+ */
+export async function initializeParsers(): Promise {
+ try {
+ if (isBrowser()) {
+ await initializeParsersForBrowser();
+ } else if (isNode()) {
+ await initializeParsersForNode();
+ } else {
+ throw new Error("Unable to determine runtime environment");
+ }
+ } catch (error) {
+ console.error("โ Failed to initialize parsers:", error);
+ throw error;
+ }
+}
+
+
+/**
+ * Initialize parsers for browser environment
+ * Loads parsers from /lib/ which is served by Vite in dev and included in build
+ */
+async function initializeParsersForBrowser(): Promise {
+ try {
+ // In browser, fetch the parser from the public directory
+ // Vite serves /public as / so /lib/tgnParser.cjs maps to public/lib/tgnParser.cjs
+ const libPath = "/" + "lib/tgnParser.cjs";
+
+ const response = await fetch(libPath);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch parser: ${response.status} ${response.statusText}`);
+ }
+
+ const code = await response.text();
+
+ // Create a CommonJS module environment
+ // The jison parser checks: if (typeof require !== 'undefined' && typeof exports !== 'undefined')
+ // So we need to make these available as variables in the eval scope
+ const module: any = { exports: {} };
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const exports = module.exports;
+
+ // We need 'require' to be defined (but can be a dummy)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const require = function() { throw new Error("require not available in browser"); };
+
+ // Eval in a scope where module, exports, and require are defined
+ // eslint-disable-next-line no-eval
+ eval(code);
+
+ // The jison parser exports: exports.parser = tgnParser
+ // So module.exports.parser is the actual parser instance
+ const parser = module.exports.parser;
+
+ if (!parser || typeof parser.parse !== 'function') {
+ throw new Error("Parser loaded but parse method not available");
+ }
+
+ setParserModule(parser);
+ } catch (error) {
+ console.error("โ Failed to load TGN parser:", error);
+ throw error;
+ }
+}
+
+
+/**
+ * Initialize parsers for Node.js environment
+ * Loads parsers from project's public/lib directory
+ */
+async function initializeParsersForNode(): Promise {
+ try {
+ // Import Node.js modules dynamically
+ const pathModule = await import("path");
+ const urlModule = await import("url");
+
+ const path = pathModule.default;
+ const { fileURLToPath, pathToFileURL } = urlModule;
+
+ // Get project root directory
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
+ const projectRoot = path.resolve(__dirname, "../..");
+ const libPath = path.resolve(projectRoot, "public/lib");
+
+ // Load TGN parser
+ const tgnParserPath = path.resolve(libPath, "tgnParser.cjs");
+ const tgnParserModule = await import(/* @vite-ignore */ pathToFileURL(tgnParserPath).href);
+ const tgnParser = (tgnParserModule as any).parser;
+
+ setParserModule(tgnParser);
+ } catch (error) {
+ console.error("โ Failed to load TGN parser:", error);
+ throw error;
+ }
+}
diff --git a/trigo-web/inc/trigo/typeAdapters.ts b/trigo-web/inc/trigo/typeAdapters.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3eb4ac46b7d97d909a9d5620c22b112cb8a72b06
--- /dev/null
+++ b/trigo-web/inc/trigo/typeAdapters.ts
@@ -0,0 +1,110 @@
+/**
+ * Type Adapters - Convert between frontend string types and TrigoGame number types
+ *
+ * Frontend gameStore uses string types like "black"|"white" for better readability
+ * TrigoGame uses number enum (StoneType) for better performance and compatibility with legacy code
+ *
+ * This module provides conversion functions between these two systems
+ */
+
+import type { Stone, Position, Player, Move } from "./types";
+import { StoneType } from "./game";
+import type { Step } from "./game";
+
+
+/**
+ * Convert Player string to Stone number
+ *
+ * @param player "black" | "white"
+ * @returns StoneType.BLACK | StoneType.WHITE
+ */
+export function playerToStone(player: Player): Stone {
+ return player === "black" ? StoneType.BLACK : StoneType.WHITE;
+}
+
+
+/**
+ * Convert Stone number to Player string
+ *
+ * @param stone StoneType enum value
+ * @returns "black" | "white"
+ */
+export function stoneToPlayer(stone: Stone): Player {
+ if (stone === StoneType.BLACK) {
+ return "black";
+ } else if (stone === StoneType.WHITE) {
+ return "white";
+ }
+ // Default to black if EMPTY or invalid
+ return "black";
+}
+
+
+/**
+ * Convert Step (TrigoGame format) to Move (frontend format)
+ *
+ * @param step Step from TrigoGame history
+ * @returns Move for frontend gameStore
+ */
+export function stepToMove(step: Step): Move {
+ const move: Move = {
+ player: stoneToPlayer(step.player),
+ timestamp: new Date(step.timestamp)
+ };
+
+ // If it's a DROP move, include position
+ if (step.position) {
+ move.x = step.position.x;
+ move.y = step.position.y;
+ move.z = step.position.z;
+ }
+
+ // If it's a PASS move, set flag
+ if (step.type === 1) { // StepType.PASS
+ move.isPass = true;
+ }
+
+ return move;
+}
+
+
+/**
+ * Convert array of Steps to array of Moves
+ *
+ * @param steps Array of Steps from TrigoGame
+ * @returns Array of Moves for frontend
+ */
+export function stepsToMoves(steps: Step[]): Move[] {
+ return steps.map(stepToMove);
+}
+
+
+/**
+ * Convert board (Stone[][][]) to frontend format (number[][][])
+ *
+ * Frontend may prefer explicit number format
+ * 0 = Empty, 1 = Black, 2 = White
+ *
+ * @param board TrigoGame board
+ * @returns Frontend board format
+ */
+export function boardToNumbers(board: Stone[][][]): number[][][] {
+ return board.map(plane =>
+ plane.map(row =>
+ row.map(cell => cell as number)
+ )
+ );
+}
+
+
+/**
+ * Helper: Create Position object from coordinates
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param z Z coordinate
+ * @returns Position object
+ */
+export function makePosition(x: number, y: number, z: number): Position {
+ return { x, y, z };
+}
diff --git a/trigo-web/inc/trigo/types.ts b/trigo-web/inc/trigo/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b70017125a76b2d6abd658467f8ac549293dcd9e
--- /dev/null
+++ b/trigo-web/inc/trigo/types.ts
@@ -0,0 +1,48 @@
+export interface Position {
+ x: number;
+ y: number;
+ z: number;
+}
+
+export interface Move {
+ x?: number;
+ y?: number;
+ z?: number;
+ player: "black" | "white";
+ timestamp?: Date;
+ isPass?: boolean;
+}
+
+export interface BoardShape {
+ x: number;
+ y: number;
+ z: number;
+}
+
+export interface GameConfig {
+ boardShape: BoardShape;
+ timeLimit?: number;
+ allowUndo: boolean;
+}
+
+export enum Stone {
+ Empty = 0,
+ Black = 1,
+ White = 2
+}
+
+export type Player = "black" | "white";
+
+export interface GameRecord {
+ id: string;
+ moves: Move[];
+ result?: {
+ winner: Player | "draw";
+ reason: "resignation" | "timeout" | "completion" | "double-pass";
+ };
+ createdAt: Date;
+ players: {
+ black: string;
+ white: string;
+ };
+}
diff --git a/trigo-web/inc/tsconfig.json b/trigo-web/inc/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..51c6c4591b4105ab61c5399ef5a8b5e6c70cdba4
--- /dev/null
+++ b/trigo-web/inc/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM"],
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "noEmit": true
+ },
+ "include": ["**/*.ts", "**/*.d.ts"],
+ "exclude": ["node_modules", "dist"]
+}
\ No newline at end of file
diff --git a/trigo-web/package-lock.json b/trigo-web/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..5834b883b793da155e6cc0f94fcad5f9d4038e55
--- /dev/null
+++ b/trigo-web/package-lock.json
@@ -0,0 +1,4833 @@
+{
+ "name": "trigo-web",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "trigo-web",
+ "version": "1.0.0",
+ "license": "MIT",
+ "devDependencies": {
+ "@types/node": "^24.10.0",
+ "@types/yargs": "^17.0.34",
+ "@vitejs/plugin-vue": "^5.2.4",
+ "@vitest/ui": "^4.0.6",
+ "concurrently": "^7.6.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "jison": "^0.4.18",
+ "jsdom": "^27.1.0",
+ "prettier": "^3.6.2",
+ "tsx": "^4.20.6",
+ "typescript": "^5.2.2",
+ "vite": "^5.4.21",
+ "vitest": "^4.0.6",
+ "vue": "^3.3.4",
+ "yargs": "^18.0.0"
+ }
+ },
+ "node_modules/@acemir/cssom": {
+ "version": "0.9.19",
+ "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.19.tgz",
+ "integrity": "sha512-Pp2gAQXPZ2o7lt4j0IMwNRXqQ3pagxtDj5wctL5U2Lz4oV0ocDNlkgx4DpxfyKav4S/bePuI+SMqcBSUHLy9kg==",
+ "dev": true
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
+ "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
+ "dev": true,
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-color-parser": "^3.1.0",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "lru-cache": "^11.2.1"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "6.7.4",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz",
+ "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==",
+ "dev": true,
+ "dependencies": {
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.1.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.2"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true
+ },
+ "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,
+ "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,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz",
+ "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz",
+ "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@eslint/core": "^0.16.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
+ "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.38.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
+ "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
+ "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@eslint/core": "^0.16.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "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
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
+ "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
+ "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
+ "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
+ "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
+ "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
+ "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
+ "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
+ "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
+ "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
+ "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
+ "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
+ "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
+ "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
+ "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
+ "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
+ "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
+ "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
+ "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
+ "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
+ "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
+ "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
+ "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "dev": true
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true
+ },
+ "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
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
+ "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.34",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz",
+ "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+ "dev": true,
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.6.tgz",
+ "integrity": "sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg==",
+ "dev": true,
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.0.6",
+ "@vitest/utils": "4.0.6",
+ "chai": "^6.0.1",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.6.tgz",
+ "integrity": "sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g==",
+ "dev": true,
+ "dependencies": {
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.6.tgz",
+ "integrity": "sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "4.0.6",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.6.tgz",
+ "integrity": "sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.6",
+ "magic-string": "^0.30.19",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.6.tgz",
+ "integrity": "sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/ui": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.6.tgz",
+ "integrity": "sha512-1ekpBsYNUm0Xv/0YsTvoSRmiRkmzz9Pma7qQ3Ui76sg2gwp2/ewSWqx4W/HfaN5dF0E8iBbidFo1wGaeqXYIrQ==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "4.0.6",
+ "fflate": "^0.8.2",
+ "flatted": "^3.3.3",
+ "pathe": "^2.0.3",
+ "sirv": "^3.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "4.0.6"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.6.tgz",
+ "integrity": "sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.6",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
+ "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.28.4",
+ "@vue/shared": "3.5.22",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-core/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz",
+ "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==",
+ "dev": true,
+ "dependencies": {
+ "@vue/compiler-core": "3.5.22",
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz",
+ "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.28.4",
+ "@vue/compiler-core": "3.5.22",
+ "@vue/compiler-dom": "3.5.22",
+ "@vue/compiler-ssr": "3.5.22",
+ "@vue/shared": "3.5.22",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.19",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz",
+ "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==",
+ "dev": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.22",
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz",
+ "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==",
+ "dev": true,
+ "dependencies": {
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz",
+ "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==",
+ "dev": true,
+ "dependencies": {
+ "@vue/reactivity": "3.5.22",
+ "@vue/shared": "3.5.22"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz",
+ "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==",
+ "dev": true,
+ "dependencies": {
+ "@vue/reactivity": "3.5.22",
+ "@vue/runtime-core": "3.5.22",
+ "@vue/shared": "3.5.22",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz",
+ "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==",
+ "dev": true,
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.22",
+ "@vue/shared": "3.5.22"
+ },
+ "peerDependencies": {
+ "vue": "3.5.22"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz",
+ "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peer": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=0.4.2"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "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,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz",
+ "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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,
+ "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/cjson": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz",
+ "integrity": "sha512-bBRQcCIHzI1IVH59fR0bwGrFmi3Btb/JNwM/n401i1DnYgWndpsUBiQRAddLflkZage20A2d25OAWZZk0vBRlA==",
+ "dev": true,
+ "dependencies": {
+ "jsonlint": "1.6.0"
+ },
+ "engines": {
+ "node": ">= 0.3.0"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "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,
+ "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
+ },
+ "node_modules/colors": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
+ "integrity": "sha512-XjsuUwpDeY98+yz959OlUK6m7mLBM+1MEG5oaenfuQnNnrQk1WvtcvFgN3FNDP3f2NmZ211t0mNEfSEN1h0eIg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/concurrently": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz",
+ "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "date-fns": "^2.29.1",
+ "lodash": "^4.17.21",
+ "rxjs": "^7.0.0",
+ "shell-quote": "^1.7.3",
+ "spawn-command": "^0.0.2-1",
+ "supports-color": "^8.1.0",
+ "tree-kill": "^1.2.2",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/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,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/concurrently/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,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/concurrently/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
+ },
+ "node_modules/concurrently/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,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/concurrently/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,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/concurrently/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,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/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,
+ "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/concurrently/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,
+ "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/concurrently/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,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "dev": true,
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/cssstyle": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.2.tgz",
+ "integrity": "sha512-zDMqXh8Vs1CdRYZQ2M633m/SFgcjlu8RB8b/1h82i+6vpArF507NSYIWJHGlJaTWoS+imcnctmEz43txhbVkOw==",
+ "dev": true,
+ "dependencies": {
+ "@asamuzakjp/css-color": "^4.0.3",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.14",
+ "css-tree": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true
+ },
+ "node_modules/data-urls": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
+ "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/ebnf-parser": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz",
+ "integrity": "sha512-urvSxVQ6XJcoTpc+/x2pWhhuOX4aljCNQpwzw+ifZvV1andZkAmiJc3Rq1oGEAQmcjiLceyMXOy1l8ms8qs2fQ==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "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,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz",
+ "integrity": "sha512-z9FWgKc48wjMlpzF5ymKS1AF8OIgnKLp9VyN7KbdtyrP/9lndwUFqCtMm+TAJmJf7KJFFYc4cFJfVTTGkKEwsA==",
+ "dev": true,
+ "dependencies": {
+ "esprima": "~1.1.1",
+ "estraverse": "~1.5.0",
+ "esutils": "~1.0.0"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.1.33"
+ }
+ },
+ "node_modules/escodegen/node_modules/estraverse": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz",
+ "integrity": "sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/escodegen/node_modules/esutils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz",
+ "integrity": "sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.38.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
+ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.1",
+ "@eslint/core": "^0.16.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.38.0",
+ "@eslint/plugin-kit": "^0.4.0",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
+ "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.11.7"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz",
+ "integrity": "sha512-qxxB994/7NtERxgXdFgLHIs9M6bhLXc6qtUmWZ3L8+gTQ9qaoyki2887P2IqAYsoENyr8SUbTutStDniOHSDHg==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
+ "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "peer": true
+ },
+ "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,
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": 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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.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,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
+ "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
+ "dev": true,
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/jison": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.18.tgz",
+ "integrity": "sha512-FKkCiJvozgC7VTHhMJ00a0/IApSxhlGsFIshLW6trWJ8ONX2TQJBBz6DlcO1Gffy4w9LT+uL+PA+CVnUSJMF7w==",
+ "dev": true,
+ "dependencies": {
+ "cjson": "0.3.0",
+ "ebnf-parser": "0.1.10",
+ "escodegen": "1.3.x",
+ "esprima": "1.1.x",
+ "jison-lex": "0.3.x",
+ "JSONSelect": "0.4.0",
+ "lex-parser": "~0.1.3",
+ "nomnom": "1.5.2"
+ },
+ "bin": {
+ "jison": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/jison-lex": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.3.4.tgz",
+ "integrity": "sha512-EBh5wrXhls1cUwROd5DcDHR1sG7CdsCFSqY1027+YA1RGxz+BX2TDLAhdsQf40YEtFDGoiO0Qm8PpnBl2EzDJw==",
+ "dev": true,
+ "dependencies": {
+ "lex-parser": "0.1.x",
+ "nomnom": "1.5.2"
+ },
+ "bin": {
+ "jison-lex": "cli.js"
+ },
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "27.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz",
+ "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==",
+ "dev": true,
+ "dependencies": {
+ "@acemir/cssom": "^0.9.19",
+ "@asamuzakjp/dom-selector": "^6.7.3",
+ "cssstyle": "^5.3.2",
+ "data-urls": "^6.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "parse5": "^8.0.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.1.0",
+ "ws": "^8.18.3",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/jsonlint": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz",
+ "integrity": "sha512-x6YLBe6NjdpmIeiklwQOxsZuYj/SOWkT33GlTpaG1UdFGjdWjPcxJ1CWZAX3wA7tarz8E2YHF6KiW5HTapPlXw==",
+ "dev": true,
+ "dependencies": {
+ "JSV": ">= 4.0.x",
+ "nomnom": ">= 1.5.x"
+ },
+ "bin": {
+ "jsonlint": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/JSONSelect": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz",
+ "integrity": "sha512-VRLR3Su35MH+XV2lrvh9O7qWoug/TUyj9tLDjn9rtpUCNnILLrHjgd/tB0KrhugCxUpj3UqoLqfYb3fLJdIQQQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.7"
+ }
+ },
+ "node_modules/JSV": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz",
+ "integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lex-parser": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz",
+ "integrity": "sha512-DuAEISsr1H4LOpmFLkyMc8YStiRWZCO8hMsoXAXSbgyfvs2WQhSt0+/FBv3ZU/JBFZMGcE+FWzEBSzwUU7U27w==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/lru-cache": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
+ "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
+ "dev": true,
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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
+ },
+ "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"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/nomnom": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz",
+ "integrity": "sha512-fiVbT7BqxiQqjlR9U3FDGOSERFCKoXVCdxV2FwZuNN7/cmJ42iQx35nUFOAFDcyvemu9Adp+IlsCGlKQYLmBKw==",
+ "deprecated": "Package no longer supported. Contact support@npmjs.com for more info.",
+ "dev": true,
+ "dependencies": {
+ "colors": "0.5.x",
+ "underscore": "1.1.x"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
+ "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
+ "dev": true,
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true
+ },
+ "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
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "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"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
+ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
+ "dev": true,
+ "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.52.5",
+ "@rollup/rollup-android-arm64": "4.52.5",
+ "@rollup/rollup-darwin-arm64": "4.52.5",
+ "@rollup/rollup-darwin-x64": "4.52.5",
+ "@rollup/rollup-freebsd-arm64": "4.52.5",
+ "@rollup/rollup-freebsd-x64": "4.52.5",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
+ "@rollup/rollup-linux-arm-musleabihf": "4.52.5",
+ "@rollup/rollup-linux-arm64-gnu": "4.52.5",
+ "@rollup/rollup-linux-arm64-musl": "4.52.5",
+ "@rollup/rollup-linux-loong64-gnu": "4.52.5",
+ "@rollup/rollup-linux-ppc64-gnu": "4.52.5",
+ "@rollup/rollup-linux-riscv64-gnu": "4.52.5",
+ "@rollup/rollup-linux-riscv64-musl": "4.52.5",
+ "@rollup/rollup-linux-s390x-gnu": "4.52.5",
+ "@rollup/rollup-linux-x64-gnu": "4.52.5",
+ "@rollup/rollup-linux-x64-musl": "4.52.5",
+ "@rollup/rollup-openharmony-arm64": "4.52.5",
+ "@rollup/rollup-win32-arm64-msvc": "4.52.5",
+ "@rollup/rollup-win32-ia32-msvc": "4.52.5",
+ "@rollup/rollup-win32-x64-gnu": "4.52.5",
+ "@rollup/rollup-win32-x64-msvc": "4.52.5",
+ "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,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
+ "node_modules/sirv": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
+ "dev": true,
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.1.43",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+ "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "amdefine": ">=0.0.4"
+ },
+ "engines": {
+ "node": ">=0.8.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,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/spawn-command": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
+ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
+ "dev": true
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true
+ },
+ "node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
+ },
+ "node_modules/synckit": {
+ "version": "0.11.11",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
+ "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
+ "dev": true,
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
+ "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz",
+ "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==",
+ "dev": true,
+ "dependencies": {
+ "tldts-core": "^7.0.17"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz",
+ "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==",
+ "dev": true
+ },
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
+ "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+ "dev": true,
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "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,
+ "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
+ },
+ "node_modules/tsx": {
+ "version": "4.20.6",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz",
+ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "~0.25.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/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,
+ "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/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/underscore": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz",
+ "integrity": "sha512-w4QtCHoLBXw1mjofIDoMyexaEdWGMedWNDhlWTtT1V1lCRqi65Pnoygkh6+WRdr+Bm8ldkBNkNeCsXGMlQS9HQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.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",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.6.tgz",
+ "integrity": "sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/expect": "4.0.6",
+ "@vitest/mocker": "4.0.6",
+ "@vitest/pretty-format": "4.0.6",
+ "@vitest/runner": "4.0.6",
+ "@vitest/snapshot": "4.0.6",
+ "@vitest/spy": "4.0.6",
+ "@vitest/utils": "4.0.6",
+ "debug": "^4.4.3",
+ "es-module-lexer": "^1.7.0",
+ "expect-type": "^1.2.2",
+ "magic-string": "^0.30.19",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3",
+ "vite": "^6.0.0 || ^7.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.0.6",
+ "@vitest/browser-preview": "4.0.6",
+ "@vitest/browser-webdriverio": "4.0.6",
+ "@vitest/ui": "4.0.6",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/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,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vitest/node_modules/@vitest/mocker": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.6.tgz",
+ "integrity": "sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/spy": "4.0.6",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.19"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/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,
+ "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/vitest/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/vitest/node_modules/vite": {
+ "version": "7.1.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
+ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "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/vue": {
+ "version": "3.5.22",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
+ "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
+ "dev": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.22",
+ "@vue/compiler-sfc": "3.5.22",
+ "@vue/runtime-dom": "3.5.22",
+ "@vue/server-renderer": "3.5.22",
+ "@vue/shared": "3.5.22"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
+ "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
+ "dev": true,
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "dev": true,
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
+ "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "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/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true
+ },
+ "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,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
+ "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^9.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "string-width": "^7.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^22.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
+ "dev": true,
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/trigo-web/package.json b/trigo-web/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..3e4045b74d0c51d8626be83ffbb90e8c96d1d7c6
--- /dev/null
+++ b/trigo-web/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "trigo-web",
+ "version": "1.0.0",
+ "type": "module",
+ "description": "3D Go board game with Vue3 and Node.js",
+ "scripts": {
+ "dev": "concurrently \"npm run dev:backend\" \"npm run dev:app\"",
+ "dev:app": "cd app && npm run dev",
+ "dev:backend": "cd backend && npm run dev",
+ "build": "npm run build:app && npm run build:backend",
+ "build:app": "cd app && npm run build",
+ "build:backend": "cd backend && npm run build",
+ "build:parsers": "npm run build:parser:tgn",
+ "build:parser:tgn": "tsx tools/buildJisonParser.ts",
+ "install:all": "npm install && cd app && npm install && cd ../backend && npm install",
+ "start:prod": "cd backend && npm start",
+ "format": "prettier --write \"**/*.{js,ts,vue,json,md,scss,css}\"",
+ "format:check": "prettier --check \"**/*.{js,ts,vue,json,md,scss,css}\"",
+ "test": "vitest",
+ "test:ui": "vitest --ui",
+ "test:run": "vitest run",
+ "generate:games": "tsx tools/generateRandomGames.ts"
+ },
+ "keywords": [
+ "game",
+ "go",
+ "3d",
+ "vue",
+ "nodejs",
+ "websocket"
+ ],
+ "author": "",
+ "license": "MIT",
+ "devDependencies": {
+ "@types/node": "^24.10.0",
+ "@types/yargs": "^17.0.34",
+ "@vitejs/plugin-vue": "^5.2.4",
+ "@vitest/ui": "^4.0.6",
+ "concurrently": "^7.6.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "jison": "^0.4.18",
+ "jsdom": "^27.1.0",
+ "prettier": "^3.6.2",
+ "tsx": "^4.20.6",
+ "typescript": "^5.2.2",
+ "vite": "^5.4.21",
+ "vitest": "^4.0.6",
+ "vue": "^3.3.4",
+ "yargs": "^18.0.0"
+ }
+}
diff --git a/trigo-web/public/lib/tgnParser.cjs b/trigo-web/public/lib/tgnParser.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..4ec300ed795b45197bcb7fc4ef1804abc8636634
--- /dev/null
+++ b/trigo-web/public/lib/tgnParser.cjs
@@ -0,0 +1,791 @@
+/* parser generated by jison 0.4.18 */
+/*
+ Returns a Parser object of the following structure:
+
+ Parser: {
+ yy: {}
+ }
+
+ Parser.prototype: {
+ yy: {},
+ trace: function(),
+ symbols_: {associative list: name ==> number},
+ terminals_: {associative list: number ==> name},
+ productions_: [...],
+ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
+ table: [...],
+ defaultActions: {...},
+ parseError: function(str, hash),
+ parse: function(input),
+
+ lexer: {
+ EOF: 1,
+ parseError: function(str, hash),
+ setInput: function(input),
+ input: function(),
+ unput: function(str),
+ more: function(),
+ less: function(n),
+ pastInput: function(),
+ upcomingInput: function(),
+ showPosition: function(),
+ test_match: function(regex_match_array, rule_index),
+ next: function(),
+ lex: function(),
+ begin: function(condition),
+ popState: function(),
+ _currentRules: function(),
+ topState: function(),
+ pushState: function(condition),
+
+ options: {
+ ranges: boolean (optional: true ==> token location info will include a .range[] member)
+ flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
+ backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
+ },
+
+ performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
+ rules: [...],
+ conditions: {associative list: name ==> set},
+ }
+ }
+
+
+ token location info (@$, _$, etc.): {
+ first_line: n,
+ last_line: n,
+ first_column: n,
+ last_column: n,
+ range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
+ }
+
+
+ the parseError function receives a 'hash' object with these members for lexer and parser errors: {
+ text: (matched text)
+ token: (the produced terminal token, if any)
+ line: (yylineno)
+ }
+ while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
+ loc: (yylloc)
+ expected: (string describing the set of expected tokens)
+ recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
+ }
+*/
+var tgnParser = (function(){
+var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,7],$V1=[2,25],$V2=[6,8,49],$V3=[1,32],$V4=[6,49],$V5=[11,49],$V6=[11,48],$V7=[1,51],$V8=[1,52],$V9=[1,53],$Va=[6,36,37,38,49];
+var parser = {trace: function trace () { },
+yy: {},
+symbols_: {"error":2,"game":3,"tag_section":4,"move_section":5,"EOF":6,"tag_pair":7,"[":8,"tag_name":9,"STRING":10,"]":11,"TAG_RESULT":12,"game_result":13,"TAG_BOARD":14,"board_shape":15,"TAG_EVENT":16,"TAG_SITE":17,"TAG_DATE":18,"TAG_ROUND":19,"TAG_BLACK":20,"TAG_WHITE":21,"TAG_HANDICAP":22,"TAG_RULES":23,"TAG_TIMECONTROL":24,"TAG_ANNOTATOR":25,"TAG_APPLICATION":26,"TAG_NAME":27,"move_sequence":28,"move_sequence_intact":29,"move_sequence_truncated":30,"move_round":31,"move_round_half":32,"number":33,"DOT":34,"move_action":35,"PASS":36,"RESIGN":37,"COORDINATE":38,"win":39,"conquer":40,"=":41,"*":42,"RESULT_BLACK":43,"RESULT_WHITE":44,"conquer_unit":45,"POINTS":46,"STONES":47,"TIMES":48,"NUMBER":49,"$accept":0,"$end":1},
+terminals_: {2:"error",6:"EOF",8:"[",10:"STRING",11:"]",12:"TAG_RESULT",14:"TAG_BOARD",16:"TAG_EVENT",17:"TAG_SITE",18:"TAG_DATE",19:"TAG_ROUND",20:"TAG_BLACK",21:"TAG_WHITE",22:"TAG_HANDICAP",23:"TAG_RULES",24:"TAG_TIMECONTROL",25:"TAG_ANNOTATOR",26:"TAG_APPLICATION",27:"TAG_NAME",34:"DOT",36:"PASS",37:"RESIGN",38:"COORDINATE",41:"=",42:"*",43:"RESULT_BLACK",44:"RESULT_WHITE",46:"POINTS",47:"STONES",48:"TIMES",49:"NUMBER"},
+productions_: [0,[3,3],[3,2],[3,2],[3,1],[4,1],[4,2],[7,4],[7,4],[7,4],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[5,1],[28,1],[28,1],[29,0],[29,2],[30,2],[31,4],[32,3],[35,1],[35,1],[35,1],[13,1],[13,2],[13,1],[13,1],[39,1],[39,1],[40,2],[45,1],[45,1],[15,1],[15,3],[33,1]],
+performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
+/* this == yyval */
+
+var $0 = $$.length - 1;
+switch (yystate) {
+case 1:
+
+ return {
+ tags: $$[$0-2],
+ moves: $$[$0-1],
+ success: true
+ };
+
+break;
+case 2:
+
+ return {
+ tags: $$[$0-1],
+ moves: null,
+ success: true
+ };
+
+break;
+case 3:
+
+ return {
+ tags: {},
+ moves: $$[$0-1],
+ success: true
+ };
+
+break;
+case 4:
+
+ return {
+ tags: {},
+ moves: null,
+ success: true
+ };
+
+break;
+case 5: case 23: case 24:
+this.$ = $$[$0];
+break;
+case 6:
+this.$ = Object.assign({}, $$[$0-1], $$[$0]);
+break;
+case 7:
+
+ const tagName = $$[$0-2];
+ const tagValue = $$[$0-1].slice(1, -1); // Remove quotes
+ this.$ = { [tagName]: tagValue };
+
+break;
+case 8:
+this.$ = $$[$0-1];
+break;
+case 9:
+this.$ = ({[$$[$0-2]]: $$[$0-1]});
+break;
+case 21:
+ this.$ = yytext;
+break;
+case 25:
+this.$ = [];
+break;
+case 26: case 27:
+this.$ = $$[$0-1].concat([$$[$0]]);
+break;
+case 28:
+this.$ = ({ round: $$[$0-3], action_black: $$[$0-1], action_white: $$[$0] });
+break;
+case 29:
+this.$ = ({ round: $$[$0-2], action_black: $$[$0] });
+break;
+case 30:
+this.$ = ({ type: 'pass' });
+break;
+case 31:
+this.$ = ({ type: 'resign' });
+break;
+case 32:
+
+ // Placeholder: Parse coordinate notation
+ this.$ = {
+ type: 'move',
+ position: yytext
+ };
+
+break;
+case 33:
+this.$ = ({Result: $$[$0]});
+break;
+case 34:
+this.$ = ({Result: $$[$0-1], Conquer: $$[$0]});
+break;
+case 35:
+this.$ = ({Result: "draw"});
+break;
+case 36:
+this.$ = ({Result: "unknown"});
+break;
+case 37:
+this.$ = "black win";
+break;
+case 38:
+this.$ = "white win";
+break;
+case 39:
+this.$ = ({n: $$[$0-1], unit: $$[$0]});
+break;
+case 42:
+this.$ = [$$[$0]];
+break;
+case 43:
+this.$ = $$[$0-2].concat($$[$0]);
+break;
+case 44:
+this.$ = parseInt($$[$0]);
+break;
+}
+},
+table: [{3:1,4:2,5:3,6:[1,4],7:5,8:$V0,28:6,29:8,30:9,49:$V1},{1:[3]},{5:10,6:[1,11],7:12,8:$V0,28:6,29:8,30:9,49:$V1},{6:[1,13]},{1:[2,4]},o($V2,[2,5]),{6:[2,22]},{9:14,12:[1,15],14:[1,16],16:[1,17],17:[1,18],18:[1,19],19:[1,20],20:[1,21],21:[1,22],22:[1,23],23:[1,24],24:[1,25],25:[1,26],26:[1,27],27:[1,28]},{6:[2,23],31:29,32:30,33:31,49:$V3},{6:[2,24]},{6:[1,33]},{1:[2,2]},o($V2,[2,6]),{1:[2,3]},{10:[1,34]},{13:35,39:36,41:[1,37],42:[1,38],43:[1,39],44:[1,40]},{15:41,33:42,49:$V3},{10:[2,10]},{10:[2,11]},{10:[2,12]},{10:[2,13]},{10:[2,14]},{10:[2,15]},{10:[2,16]},{10:[2,17]},{10:[2,18]},{10:[2,19]},{10:[2,20]},{10:[2,21]},o($V4,[2,26]),{6:[2,27]},{34:[1,43]},o([11,34,46,47,48],[2,44]),{1:[2,1]},{11:[1,44]},{11:[1,45]},{11:[2,33],33:47,40:46,49:$V3},{11:[2,35]},{11:[2,36]},o($V5,[2,37]),o($V5,[2,38]),{11:[1,48],48:[1,49]},o($V6,[2,42]),{35:50,36:$V7,37:$V8,38:$V9},o($V2,[2,7]),o($V2,[2,8]),{11:[2,34]},{45:54,46:[1,55],47:[1,56]},o($V2,[2,9]),{33:57,49:$V3},{6:[2,29],35:58,36:$V7,37:$V8,38:$V9},o($Va,[2,30]),o($Va,[2,31]),o($Va,[2,32]),{11:[2,39]},{11:[2,40]},{11:[2,41]},o($V6,[2,43]),o($V4,[2,28])],
+defaultActions: {4:[2,4],6:[2,22],9:[2,24],11:[2,2],13:[2,3],17:[2,10],18:[2,11],19:[2,12],20:[2,13],21:[2,14],22:[2,15],23:[2,16],24:[2,17],25:[2,18],26:[2,19],27:[2,20],28:[2,21],30:[2,27],33:[2,1],37:[2,35],38:[2,36],46:[2,34],54:[2,39],55:[2,40],56:[2,41]},
+parseError: function parseError (str, hash) {
+ if (hash.recoverable) {
+ this.trace(str);
+ } else {
+ var error = new Error(str);
+ error.hash = hash;
+ throw error;
+ }
+},
+parse: function parse(input) {
+ var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+ var args = lstack.slice.call(arguments, 1);
+ var lexer = Object.create(this.lexer);
+ var sharedState = { yy: {} };
+ for (var k in this.yy) {
+ if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
+ sharedState.yy[k] = this.yy[k];
+ }
+ }
+ lexer.setInput(input, sharedState.yy);
+ sharedState.yy.lexer = lexer;
+ sharedState.yy.parser = this;
+ if (typeof lexer.yylloc == 'undefined') {
+ lexer.yylloc = {};
+ }
+ var yyloc = lexer.yylloc;
+ lstack.push(yyloc);
+ var ranges = lexer.options && lexer.options.ranges;
+ if (typeof sharedState.yy.parseError === 'function') {
+ this.parseError = sharedState.yy.parseError;
+ } else {
+ this.parseError = Object.getPrototypeOf(this).parseError;
+ }
+ function popStack(n) {
+ stack.length = stack.length - 2 * n;
+ vstack.length = vstack.length - n;
+ lstack.length = lstack.length - n;
+ }
+ _token_stack:
+ var lex = function () {
+ var token;
+ token = lexer.lex() || EOF;
+ if (typeof token !== 'number') {
+ token = self.symbols_[token] || token;
+ }
+ return token;
+ };
+ var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+ while (true) {
+ state = stack[stack.length - 1];
+ if (this.defaultActions[state]) {
+ action = this.defaultActions[state];
+ } else {
+ if (symbol === null || typeof symbol == 'undefined') {
+ symbol = lex();
+ }
+ action = table[state] && table[state][symbol];
+ }
+ if (typeof action === 'undefined' || !action.length || !action[0]) {
+ var errStr = '';
+ expected = [];
+ for (p in table[state]) {
+ if (this.terminals_[p] && p > TERROR) {
+ expected.push('\'' + this.terminals_[p] + '\'');
+ }
+ }
+ if (lexer.showPosition) {
+ errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
+ } else {
+ errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
+ }
+ this.parseError(errStr, {
+ text: lexer.match,
+ token: this.terminals_[symbol] || symbol,
+ line: lexer.yylineno,
+ loc: yyloc,
+ expected: expected
+ });
+ }
+ if (action[0] instanceof Array && action.length > 1) {
+ throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
+ }
+ switch (action[0]) {
+ case 1:
+ stack.push(symbol);
+ vstack.push(lexer.yytext);
+ lstack.push(lexer.yylloc);
+ stack.push(action[1]);
+ symbol = null;
+ if (!preErrorSymbol) {
+ yyleng = lexer.yyleng;
+ yytext = lexer.yytext;
+ yylineno = lexer.yylineno;
+ yyloc = lexer.yylloc;
+ if (recovering > 0) {
+ recovering--;
+ }
+ } else {
+ symbol = preErrorSymbol;
+ preErrorSymbol = null;
+ }
+ break;
+ case 2:
+ len = this.productions_[action[1]][1];
+ yyval.$ = vstack[vstack.length - len];
+ yyval._$ = {
+ first_line: lstack[lstack.length - (len || 1)].first_line,
+ last_line: lstack[lstack.length - 1].last_line,
+ first_column: lstack[lstack.length - (len || 1)].first_column,
+ last_column: lstack[lstack.length - 1].last_column
+ };
+ if (ranges) {
+ yyval._$.range = [
+ lstack[lstack.length - (len || 1)].range[0],
+ lstack[lstack.length - 1].range[1]
+ ];
+ }
+ r = this.performAction.apply(yyval, [
+ yytext,
+ yyleng,
+ yylineno,
+ sharedState.yy,
+ action[1],
+ vstack,
+ lstack
+ ].concat(args));
+ if (typeof r !== 'undefined') {
+ return r;
+ }
+ if (len) {
+ stack = stack.slice(0, -1 * len * 2);
+ vstack = vstack.slice(0, -1 * len);
+ lstack = lstack.slice(0, -1 * len);
+ }
+ stack.push(this.productions_[action[1]][0]);
+ vstack.push(yyval.$);
+ lstack.push(yyval._$);
+ newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+ stack.push(newState);
+ break;
+ case 3:
+ return true;
+ }
+ }
+ return true;
+}};
+
+
+/* ========== Additional JavaScript Code ========== */
+
+// Parser configuration
+parser.yy = {
+ // Helper functions can be added here
+ parseError: function(str, hash) {
+ throw new Error('Parse error: ' + str);
+ }
+};
+/* generated by jison-lex 0.3.4 */
+var lexer = (function(){
+var lexer = ({
+
+EOF:1,
+
+parseError:function parseError(str, hash) {
+ if (this.yy.parser) {
+ this.yy.parser.parseError(str, hash);
+ } else {
+ throw new Error(str);
+ }
+ },
+
+// resets the lexer, sets new input
+setInput:function (input, yy) {
+ this.yy = yy || this.yy || {};
+ this._input = input;
+ this._more = this._backtrack = this.done = false;
+ this.yylineno = this.yyleng = 0;
+ this.yytext = this.matched = this.match = '';
+ this.conditionStack = ['INITIAL'];
+ this.yylloc = {
+ first_line: 1,
+ first_column: 0,
+ last_line: 1,
+ last_column: 0
+ };
+ if (this.options.ranges) {
+ this.yylloc.range = [0,0];
+ }
+ this.offset = 0;
+ return this;
+ },
+
+// consumes and returns one char from the input
+input:function () {
+ var ch = this._input[0];
+ this.yytext += ch;
+ this.yyleng++;
+ this.offset++;
+ this.match += ch;
+ this.matched += ch;
+ var lines = ch.match(/(?:\r\n?|\n).*/g);
+ if (lines) {
+ this.yylineno++;
+ this.yylloc.last_line++;
+ } else {
+ this.yylloc.last_column++;
+ }
+ if (this.options.ranges) {
+ this.yylloc.range[1]++;
+ }
+
+ this._input = this._input.slice(1);
+ return ch;
+ },
+
+// unshifts one char (or a string) into the input
+unput:function (ch) {
+ var len = ch.length;
+ var lines = ch.split(/(?:\r\n?|\n)/g);
+
+ this._input = ch + this._input;
+ this.yytext = this.yytext.substr(0, this.yytext.length - len);
+ //this.yyleng -= len;
+ this.offset -= len;
+ var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+ this.match = this.match.substr(0, this.match.length - 1);
+ this.matched = this.matched.substr(0, this.matched.length - 1);
+
+ if (lines.length - 1) {
+ this.yylineno -= lines.length - 1;
+ }
+ var r = this.yylloc.range;
+
+ this.yylloc = {
+ first_line: this.yylloc.first_line,
+ last_line: this.yylineno + 1,
+ first_column: this.yylloc.first_column,
+ last_column: lines ?
+ (lines.length === oldLines.length ? this.yylloc.first_column : 0)
+ + oldLines[oldLines.length - lines.length].length - lines[0].length :
+ this.yylloc.first_column - len
+ };
+
+ if (this.options.ranges) {
+ this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+ }
+ this.yyleng = this.yytext.length;
+ return this;
+ },
+
+// When called from action, caches matched text and appends it on next action
+more:function () {
+ this._more = true;
+ return this;
+ },
+
+// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
+reject:function () {
+ if (this.options.backtrack_lexer) {
+ this._backtrack = true;
+ } else {
+ return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
+ text: "",
+ token: null,
+ line: this.yylineno
+ });
+
+ }
+ return this;
+ },
+
+// retain first n characters of the match
+less:function (n) {
+ this.unput(this.match.slice(n));
+ },
+
+// displays already matched input, i.e. for error messages
+pastInput:function () {
+ var past = this.matched.substr(0, this.matched.length - this.match.length);
+ return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+ },
+
+// displays upcoming input, i.e. for error messages
+upcomingInput:function () {
+ var next = this.match;
+ if (next.length < 20) {
+ next += this._input.substr(0, 20-next.length);
+ }
+ return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
+ },
+
+// displays the character position where the lexing error occurred, i.e. for error messages
+showPosition:function () {
+ var pre = this.pastInput();
+ var c = new Array(pre.length + 1).join("-");
+ return pre + this.upcomingInput() + "\n" + c + "^";
+ },
+
+// test the lexed token: return FALSE when not a match, otherwise return token
+test_match:function(match, indexed_rule) {
+ var token,
+ lines,
+ backup;
+
+ if (this.options.backtrack_lexer) {
+ // save context
+ backup = {
+ yylineno: this.yylineno,
+ yylloc: {
+ first_line: this.yylloc.first_line,
+ last_line: this.last_line,
+ first_column: this.yylloc.first_column,
+ last_column: this.yylloc.last_column
+ },
+ yytext: this.yytext,
+ match: this.match,
+ matches: this.matches,
+ matched: this.matched,
+ yyleng: this.yyleng,
+ offset: this.offset,
+ _more: this._more,
+ _input: this._input,
+ yy: this.yy,
+ conditionStack: this.conditionStack.slice(0),
+ done: this.done
+ };
+ if (this.options.ranges) {
+ backup.yylloc.range = this.yylloc.range.slice(0);
+ }
+ }
+
+ lines = match[0].match(/(?:\r\n?|\n).*/g);
+ if (lines) {
+ this.yylineno += lines.length;
+ }
+ this.yylloc = {
+ first_line: this.yylloc.last_line,
+ last_line: this.yylineno + 1,
+ first_column: this.yylloc.last_column,
+ last_column: lines ?
+ lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
+ this.yylloc.last_column + match[0].length
+ };
+ this.yytext += match[0];
+ this.match += match[0];
+ this.matches = match;
+ this.yyleng = this.yytext.length;
+ if (this.options.ranges) {
+ this.yylloc.range = [this.offset, this.offset += this.yyleng];
+ }
+ this._more = false;
+ this._backtrack = false;
+ this._input = this._input.slice(match[0].length);
+ this.matched += match[0];
+ token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
+ if (this.done && this._input) {
+ this.done = false;
+ }
+ if (token) {
+ return token;
+ } else if (this._backtrack) {
+ // recover context
+ for (var k in backup) {
+ this[k] = backup[k];
+ }
+ return false; // rule action called reject() implying the next rule should be tested instead.
+ }
+ return false;
+ },
+
+// return next match in input
+next:function () {
+ if (this.done) {
+ return this.EOF;
+ }
+ if (!this._input) {
+ this.done = true;
+ }
+
+ var token,
+ match,
+ tempMatch,
+ index;
+ if (!this._more) {
+ this.yytext = '';
+ this.match = '';
+ }
+ var rules = this._currentRules();
+ for (var i = 0; i < rules.length; i++) {
+ tempMatch = this._input.match(this.rules[rules[i]]);
+ if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+ match = tempMatch;
+ index = i;
+ if (this.options.backtrack_lexer) {
+ token = this.test_match(tempMatch, rules[i]);
+ if (token !== false) {
+ return token;
+ } else if (this._backtrack) {
+ match = false;
+ continue; // rule action called reject() implying a rule MISmatch.
+ } else {
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+ return false;
+ }
+ } else if (!this.options.flex) {
+ break;
+ }
+ }
+ }
+ if (match) {
+ token = this.test_match(match, rules[index]);
+ if (token !== false) {
+ return token;
+ }
+ // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+ return false;
+ }
+ if (this._input === "") {
+ return this.EOF;
+ } else {
+ return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
+ text: "",
+ token: null,
+ line: this.yylineno
+ });
+ }
+ },
+
+// return next match that has a token
+lex:function lex () {
+ var r = this.next();
+ if (r) {
+ return r;
+ } else {
+ return this.lex();
+ }
+ },
+
+// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
+begin:function begin (condition) {
+ this.conditionStack.push(condition);
+ },
+
+// pop the previously active lexer condition state off the condition stack
+popState:function popState () {
+ var n = this.conditionStack.length - 1;
+ if (n > 0) {
+ return this.conditionStack.pop();
+ } else {
+ return this.conditionStack[0];
+ }
+ },
+
+// produce the lexer rule set which is active for the currently active lexer condition state
+_currentRules:function _currentRules () {
+ if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
+ return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
+ } else {
+ return this.conditions["INITIAL"].rules;
+ }
+ },
+
+// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
+topState:function topState (n) {
+ n = this.conditionStack.length - 1 - Math.abs(n || 0);
+ if (n >= 0) {
+ return this.conditionStack[n];
+ } else {
+ return "INITIAL";
+ }
+ },
+
+// alias for begin(condition)
+pushState:function pushState (condition) {
+ this.begin(condition);
+ },
+
+// return the number of states currently on the stack
+stateStackSize:function stateStackSize() {
+ return this.conditionStack.length;
+ },
+options: {},
+performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+var YYSTATE=YY_START;
+switch($avoiding_name_collisions) {
+case 0:/* skip whitespace */
+break;
+case 1:/* skip newlines */
+break;
+case 2:/* skip line comments */
+break;
+case 3:/* skip block comments */
+break;
+case 4:return 8
+break;
+case 5:return 11
+break;
+case 6:return 10
+break;
+case 7:return 16
+break;
+case 8:return 17
+break;
+case 9:return 18
+break;
+case 10:return 19
+break;
+case 11:return 20
+break;
+case 12:return 21
+break;
+case 13:return 12
+break;
+case 14:return 14
+break;
+case 15:return 22
+break;
+case 16:return 23
+break;
+case 17:return 24
+break;
+case 18:return 25
+break;
+case 19:return 26
+break;
+case 20:return 43
+break;
+case 21:return 44
+break;
+case 22:return 41 /* draw */
+break;
+case 23:return 42 /* unknown */
+break;
+case 24:return 49
+break;
+case 25:return 34
+break;
+case 26:return 36
+break;
+case 27:return 37
+break;
+case 28:return 46
+break;
+case 29:return 47
+break;
+case 30:return 48
+break;
+case 31:return 38
+break;
+case 32:return 27
+break;
+case 33:return 6
+break;
+case 34:return 'INVALID'
+break;
+}
+},
+rules: [/^(?:\s+)/,/^(?:\n)/,/^(?:;[^\n]*)/,/^(?:\{[^}]*\})/,/^(?:\[)/,/^(?:\])/,/^(?:"([^\\\"]|\\.)*")/,/^(?:Event\b)/,/^(?:Site\b)/,/^(?:Date\b)/,/^(?:Round\b)/,/^(?:Black\b)/,/^(?:White\b)/,/^(?:Result\b)/,/^(?:Board\b)/,/^(?:Handicap\b)/,/^(?:Rules\b)/,/^(?:TimeControl\b)/,/^(?:Annotator\b)/,/^(?:Application\b)/,/^(?:B\+)/,/^(?:W\+)/,/^(?:=)/,/^(?:\*)/,/^(?:[1-9][0-9]*)/,/^(?:\.)/,/^(?:pass\b)/,/^(?:resign\b)/,/^(?:points\b)/,/^(?:stones\b)/,/^(?:[x](?=[1-9]))/,/^(?:[a-z0]+)/,/^(?:[A-Z][A-Za-z0-9_]*)/,/^(?:$)/,/^(?:.)/],
+conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],"inclusive":true}}
+});
+return lexer;
+})();
+parser.lexer = lexer;
+function Parser () {
+ this.yy = {};
+}
+Parser.prototype = parser;parser.Parser = Parser;
+return new Parser;
+})();
+
+
+if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
+exports.parser = tgnParser;
+exports.Parser = tgnParser.Parser;
+exports.parse = function () { return tgnParser.parse.apply(tgnParser, arguments); };
+exports.main = function commonjsMain (args) {
+ if (!args[1]) {
+ console.log('Usage: '+args[0]+' FILE');
+ process.exit(1);
+ }
+ var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
+ return exports.parser.parse(source);
+};
+if (typeof module !== 'undefined' && require.main === module) {
+ exports.main(process.argv.slice(1));
+}
+}
\ No newline at end of file
diff --git a/trigo-web/tools/README.md b/trigo-web/tools/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a13c949c08f4d1eaa11add07f30251f6277c4885
--- /dev/null
+++ b/trigo-web/tools/README.md
@@ -0,0 +1,152 @@
+# Trigo Tools
+
+Development tools for the Trigo project.
+
+## Random Game Generator
+
+Generate random Trigo games in TGN (Trigo Game Notation) format for testing, development, and analysis.
+
+### Usage
+
+```bash
+# Generate 10 games with default settings (play until territory settled)
+npm run generate:games
+
+# Generate 100 games with custom move range
+npm run generate:games -- --count 100 --moves 20-80
+
+# Generate games with exactly 30 moves each
+npm run generate:games -- --moves 30 --count 50
+
+# Generate games on 3x3x3 board (plays until settled)
+npm run generate:games -- --board "3*3*3" --count 50
+
+# Generate 2D games (9x9x1 board)
+npm run generate:games -- --board "9*9*1" --count 20
+
+# Higher pass probability
+npm run generate:games -- --pass-chance 0.3
+
+# Custom output directory
+npm run generate:games -- --output "my_games" --count 50
+```
+
+### Options
+
+| Option | Short | Default | Description |
+|--------|-------|---------|-------------|
+| `--count` | `-c` | 10 | Number of games to generate |
+| `--moves` | `-m` | (settled) | Moves per game as "MIN-MAX" or single number. Default: play until neutral territory = 0 |
+| `--pass-chance` | | 0 | Probability of passing (0.0-1.0) |
+| `--board` | `-b` | "random" | Board size as "X*Y*Z" or "random" |
+| `--output` | `-o` | "output" | Output directory (relative to tools/) |
+| `--help` | `-h` | - | Show help message |
+
+### How It Works
+
+**Default Mode (no --moves specified):**
+1. Creates a new Trigo game with the specified board shape
+2. Plays random valid moves until the board reaches 90% coverage
+3. After 90% coverage, checks territory after each move
+4. Stops when neutral territory reaches 0 (game is fully settled)
+5. Results in complete, finished games with all territory claimed
+
+**Custom Move Mode (--moves specified):**
+1. Creates a new Trigo game with the specified board shape
+2. Randomly selects valid moves for the specified number of moves
+3. Respects Go rules: Ko, suicide prevention, capture
+4. Games may end early on double-pass
+5. Exports each game to TGN format with metadata
+
+### Output Format
+
+Generated files are named using SHA-256 hash of the TGN content: `game_.tgn`
+
+This ensures:
+- **Content-based naming**: Same game content = same filename
+- **Duplicate detection**: Identical games won't overwrite each other
+- **Deterministic**: Reproducible filenames independent of generation time
+
+Example output:
+```
+tools/output/
+โโโ game_a3f5c8d912e4b6f1.tgn
+โโโ game_7b2d9e1a4c8f3605.tgn
+โโโ game_9e4b6f1a3f5c8d91.tgn
+```
+
+### TGN Format Example
+
+```tgn
+[Event "Random Generated Game"]
+[Site "Batch Generator"]
+[Date "2025.11.03"]
+[Black "Random Black"]
+[White "Random White"]
+[Board "5x5x5"]
+[Rules "Chinese"]
+[Application "Trigo Random Generator v1.0 (5ร5ร5)"]
+
+1. 000 y00
+2. 0y0 yy0
+3. aaa zzz
+4. pass 0az
+5. z0z pass
+```
+
+### Performance
+
+- **Small boards (3ร3ร3)**: ~250 games/second
+- **Standard boards (5ร5ร5)**: ~75 games/second
+- **Large boards (9ร9ร1)**: ~33 games/second
+
+Generating 1000 games on a 5ร5ร5 board takes approximately 13 seconds.
+
+### Use Cases
+
+1. **Testing TGN parser** - Create diverse test files for import functionality
+2. **Performance testing** - Generate large datasets for board rendering tests
+3. **AI training** - Create training data for future AI opponent
+4. **Game analysis** - Study patterns in random gameplay
+5. **Format validation** - Verify TGN export works correctly across scenarios
+
+### Board Sizes
+
+**3D Boards:**
+- `3*3*3` - Tiny cube (27 positions)
+- `5*5*5` - Standard cube (125 positions)
+- `7*7*7` - Large cube (343 positions)
+- `9*9*2` - Wide board (162 positions)
+
+**2D Boards (Traditional Go):**
+- `9*9*1` - Small board (81 positions)
+- `13*13*1` - Medium board (169 positions)
+- `19*19*1` - Standard board (361 positions)
+
+### Implementation Details
+
+**File:** `tools/generateRandomGames.ts`
+
+**Key Functions:**
+- `generateRandomGame()` - Simulates a complete random game
+- `getValidMoves()` - Finds all legal moves for current player
+- `selectRandomMove()` - Randomly picks from valid moves
+- `generateBatch()` - Generates multiple games in parallel
+
+**Safety Features:**
+- Validates all moves using game rules (Ko, suicide, occupation)
+- Handles cases where no valid moves exist (must pass)
+- Respects double-pass game end condition
+- Progress indicator for large batches
+- Error handling and validation
+
+### Dependencies
+
+- **tsx** - TypeScript execution
+- **yargs** - Advanced command-line argument parsing with validation
+- **TrigoGame** - Core game logic from `inc/trigo/game.ts`
+- **Node.js fs/path** - File system operations
+
+---
+
+For more information about TGN format, see `/tests/game/trigoGame.tgn.test.ts`
diff --git a/trigo-web/tools/buildJisonParser.ts b/trigo-web/tools/buildJisonParser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e1fe6abd1ec0a05e0b111b3b136c4207a00c41ce
--- /dev/null
+++ b/trigo-web/tools/buildJisonParser.ts
@@ -0,0 +1,74 @@
+/**
+ * Build script for Jison parser generation
+ *
+ * Generates pre-compiled parser to public/lib/ directory for use in browser
+ * Following lotus project architecture:
+ * - Use jison npm package CLI to compile grammar
+ * - Pre-build parsers to static .js files
+ * - Serve from public directory
+ * - Works in both browser and Node.js
+ */
+
+import { execSync } from "child_process";
+import fs from "fs";
+import path from "path";
+import { fileURLToPath } from "url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const projectRoot = path.resolve(__dirname, "..");
+
+
+/**
+ * Build a single parser from grammar file
+ * @param grammarPath - Path to .jison grammar file
+ * @param targetPath - Where to write the compiled parser
+ */
+function buildParser(grammarPath: string, targetPath: string): void {
+ console.log(`Building parser: ${grammarPath} โ ${targetPath}`);
+
+ // Ensure output directory exists
+ const targetDir = path.dirname(targetPath);
+ if (!fs.existsSync(targetDir)) {
+ fs.mkdirSync(targetDir, { recursive: true });
+ console.log(`Created directory: ${targetDir}`);
+ }
+
+ // Run jison CLI to generate parser
+ // Using -m commonjs for CommonJS module format (works in Node.js and Vite)
+ // Outputs to .cjs file which will be treated as CommonJS
+ try {
+ execSync(`npx jison "${grammarPath}" -o "${targetPath.replace(".js", ".cjs")}" -m commonjs`, {
+ cwd: projectRoot,
+ stdio: "inherit"
+ });
+ console.log(`โ Parser written to: ${targetPath.replace(".js", ".cjs")}`);
+ } catch (error) {
+ console.error(`โ Failed to build parser:`, error);
+ throw error;
+ }
+}
+
+
+/**
+ * Main build function
+ */
+function main(): void {
+ try {
+ console.log("๐จ Building Jison parsers...\n");
+
+ // Build TGN parser to public/lib/
+ const tgnGrammarPath = path.resolve(projectRoot, "inc/tgn/tgn.jison");
+ const tgnOutputPath = path.resolve(projectRoot, "public/lib/tgnParser.js");
+
+ buildParser(tgnGrammarPath, tgnOutputPath);
+
+ console.log("\nโ
Done building parsers!");
+ } catch (error) {
+ console.error("โ Error building parsers:", error);
+ process.exit(1);
+ }
+}
+
+
+// Run main function
+main();
diff --git a/trigo-web/tools/generateRandomGames.ts b/trigo-web/tools/generateRandomGames.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3d774190620a8018c4c1c8d840219a2c52c3b0ef
--- /dev/null
+++ b/trigo-web/tools/generateRandomGames.ts
@@ -0,0 +1,465 @@
+#!/usr/bin/env node
+/**
+ * Random Trigo Game Generator
+ *
+ * Generates random Trigo games and exports them as TGN files.
+ * Useful for testing, development, and creating training data.
+ *
+ * Usage:
+ * npm run generate:games
+ * npm run generate:games -- --count 100 --min-moves 20 --max-moves 80
+ * npm run generate:games -- --board "3*3*3" --count 50
+ */
+
+import { TrigoGame, StoneType } from "../inc/trigo/game.js";
+import type { BoardShape, Position } from "../inc/trigo/types.js";
+import { calculateTerritory } from "../inc/trigo/gameUtils.js";
+import * as fs from "fs";
+import * as path from "path";
+import * as crypto from "crypto";
+import { fileURLToPath } from "url";
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+
+type BoardShapeTuple = [number, number, number];
+
+
+const arangeShape = (min: BoardShapeTuple, max: BoardShapeTuple): BoardShapeTuple[] => {
+ // traverse all shapes in the range between min & max (boundary includes)
+ const result: BoardShapeTuple[] = [];
+
+ for (let x = min[0]; x <= max[0]; x++) {
+ for (let y = min[1]; y <= max[1]; y++) {
+ for (let z = min[2]; z <= max[2]; z++) {
+ result.push([x, y, z]);
+ }
+ }
+ }
+
+ return result;
+};
+
+
+const CANDIDATE_BOARD_SHAPES = [
+ ...arangeShape([2, 1, 1], [19, 19, 1]),
+ ...arangeShape([2, 2, 2], [9, 9, 9]),
+];
+
+
+/**
+ * Generator configuration options
+ */
+interface GeneratorOptions {
+ minMoves: number;
+ maxMoves: number;
+ passChance: number;
+ boardShape?: BoardShape;
+ outputDir: string;
+ moveToEndGame: boolean; // true if using default, false if user-specified
+}
+
+
+/**
+ * Parse board shape string (e.g., "5*5*5" or "9*9*1")
+ * Special value "random" selects randomly from CANDIDATE_BOARD_SHAPES
+ */
+function parseBoardShape(shapeStr: string): BoardShape {
+ // Handle random selection
+ if (shapeStr.toLowerCase() === "random") {
+ const randomIndex = Math.floor(Math.random() * CANDIDATE_BOARD_SHAPES.length);
+ const [x, y, z] = CANDIDATE_BOARD_SHAPES[randomIndex];
+ console.log(` [Random board selected: ${x}ร${y}ร${z}]`);
+ return { x, y, z };
+ }
+
+ // Parse explicit board shape
+ const parts = shapeStr.split(/[^0-9]+/).filter(Boolean).map(Number);
+ if (parts.length !== 3) {
+ throw new Error(`Invalid board shape: ${shapeStr}. Expected format: "X*Y*Z" or "random"`);
+ }
+ return { x: parts[0], y: parts[1], z: parts[2] };
+}
+
+
+/**
+ * Get all empty positions on the board
+ */
+function getAllEmptyPositions(game: TrigoGame): Position[] {
+ const board = game.getBoard();
+ const shape = game.getShape();
+ const emptyPositions: Position[] = [];
+
+ for (let x = 0; x < shape.x; x++) {
+ for (let y = 0; y < shape.y; y++) {
+ for (let z = 0; z < shape.z; z++) {
+ if (board[x][y][z] === StoneType.EMPTY) {
+ emptyPositions.push({ x, y, z });
+ }
+ }
+ }
+ }
+
+ return emptyPositions;
+}
+
+
+/**
+ * Get all valid moves for the current player
+ */
+function getValidMoves(game: TrigoGame): Position[] {
+ const emptyPositions = getAllEmptyPositions(game);
+ return emptyPositions.filter((pos) => game.isValidMove(pos).valid);
+}
+
+
+/**
+ * Select a random move from available positions
+ */
+function selectRandomMove(validMoves: Position[]): Position {
+ const randomIndex = Math.floor(Math.random() * validMoves.length);
+ return validMoves[randomIndex];
+}
+
+
+/**
+ * Generate a single random game
+ */
+function generateRandomGame(options: GeneratorOptions): string {
+ // Select board shape (random if undefined)
+ const boardShape = options.boardShape || selectRandomBoardShape();
+
+ const game = new TrigoGame(boardShape);
+ game.startGame();
+
+ const totalPositions = boardShape.x * boardShape.y * boardShape.z;
+ const coverageThreshold = Math.floor(totalPositions * 0.9); // 90% coverage
+
+ let moveCount = 0;
+ let consecutivePasses = 0;
+
+ if (options.moveToEndGame) {
+ // Default mode: Play until neutral territory is zero
+ // Start checking territory after 90% coverage
+ let territoryCheckStarted = false;
+
+ while (game.getGameStatus() === "playing") {
+ // Check if we should start territory checking
+ if (!territoryCheckStarted && moveCount >= coverageThreshold) {
+ territoryCheckStarted = true;
+ }
+
+ // Random chance to pass (if configured)
+ if (options.passChance > 0 && Math.random() < options.passChance) {
+ game.pass();
+ consecutivePasses++;
+ moveCount++;
+
+ // Game ends on double pass
+ if (consecutivePasses >= 2) {
+ break;
+ }
+ continue;
+ }
+
+ // Try to make a move
+ const validMoves = getValidMoves(game);
+
+ if (validMoves.length === 0) {
+ // No valid moves available, must pass
+ game.pass();
+ if (options.passChance <= 0)
+ break;
+
+ consecutivePasses++;
+ moveCount++;
+
+ if (consecutivePasses >= 2)
+ break;
+ } else {
+ // Make a random valid move
+ const move = selectRandomMove(validMoves);
+ const success = game.drop(move);
+
+ if (success) {
+ consecutivePasses = 0;
+ moveCount++;
+
+ // Check territory after 90% coverage
+ if (territoryCheckStarted) {
+ const board = game.getBoard();
+ const territory = calculateTerritory(board, boardShape);
+
+ // Stop if neutral territory is zero (game is settled)
+ if (territory.neutral === 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ // User-specified move count: Play for target number of moves
+ const targetMoves =
+ Math.floor(Math.random() * (options.maxMoves - options.minMoves)) + options.minMoves;
+
+ while (moveCount < targetMoves && game.getGameStatus() === "playing") {
+ // Random chance to pass
+ if (Math.random() < options.passChance) {
+ game.pass();
+ consecutivePasses++;
+ moveCount++;
+
+ // Game ends on double pass
+ if (consecutivePasses >= 2) {
+ break;
+ }
+ continue;
+ }
+
+ // Try to make a move
+ const validMoves = getValidMoves(game);
+
+ if (validMoves.length === 0) {
+ // No valid moves available, must pass
+ game.pass();
+ consecutivePasses++;
+ moveCount++;
+
+ if (consecutivePasses >= 2) {
+ break;
+ }
+ } else {
+ // Make a random valid move
+ const move = selectRandomMove(validMoves);
+ const success = game.drop(move);
+
+ if (success) {
+ consecutivePasses = 0;
+ moveCount++;
+ }
+ }
+ }
+ }
+
+ // Generate TGN
+ const tgn = game.toTGN();
+
+ return tgn;
+}
+
+
+/**
+ * Select a random board shape from CANDIDATE_BOARD_SHAPES
+ */
+function selectRandomBoardShape(): BoardShape {
+ const randomIndex = Math.floor(Math.random() * CANDIDATE_BOARD_SHAPES.length);
+ const [x, y, z] = CANDIDATE_BOARD_SHAPES[randomIndex];
+ return { x, y, z };
+}
+
+
+/**
+ * Generate a batch of random games
+ */
+function generateBatch(count: number, options: GeneratorOptions): void {
+ // Create output directory if it doesn't exist
+ const outputPath = path.resolve(__dirname, options.outputDir);
+ if (!fs.existsSync(outputPath)) {
+ fs.mkdirSync(outputPath, { recursive: true });
+ console.log(`Created output directory: ${outputPath}`);
+ }
+
+ console.log(`\nGenerating ${count} random games...`);
+ if (options.boardShape) {
+ console.log(`Board: ${options.boardShape.x}ร${options.boardShape.y}ร${options.boardShape.z}`);
+ } else {
+ console.log(`Board: Random selection from ${CANDIDATE_BOARD_SHAPES.length} candidates`);
+ }
+
+ if (options.moveToEndGame) {
+ console.log(`Mode: Play until neutral territory = 0 (check after 90% coverage)`);
+ } else {
+ console.log(`Moves: ${options.minMoves}-${options.maxMoves}`);
+ }
+ console.log(`Pass chance: ${(options.passChance * 100).toFixed(0)}%`);
+ console.log(`Output: ${outputPath}\n`);
+
+ const startTime = Date.now();
+
+ process.stdout.write(".".repeat(count));
+ process.stdout.write("\r");
+
+ for (let i = 0; i < count; i++) {
+ try {
+ const tgn = generateRandomGame(options);
+
+ // Generate filename based on content hash
+ const hash = crypto.createHash('sha256').update(tgn).digest('hex');
+ const filename = `game_${hash.substring(0, 16)}.tgn`;
+ const filepath = path.join(outputPath, filename);
+
+ // Write TGN file
+ fs.writeFileSync(filepath, tgn, "utf-8");
+
+ process.stdout.write("+");
+ } catch (error) {
+ console.error(`\nError generating game ${i}:`, error);
+ }
+ }
+
+ const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
+ console.log(`\n\nGeneration complete!`);
+ console.log(`Time: ${elapsedTime}s`);
+ console.log(`Rate: ${(count / parseFloat(elapsedTime)).toFixed(2)} games/second`);
+ console.log(`Output: ${outputPath}`);
+}
+
+
+/**
+ * Parse moves range string (e.g., "10-50" or "20")
+ * Returns [min, max] tuple
+ */
+function parseMovesRange(movesStr: string): [number, number] {
+ // Check if it's a range (e.g., "10-50")
+ if (movesStr.includes("-")) {
+ const parts = movesStr.split("-").map(s => s.trim());
+ if (parts.length !== 2) {
+ throw new Error(`Invalid moves range: ${movesStr}. Expected format: "MIN-MAX" or "N"`);
+ }
+ const min = parseInt(parts[0], 10);
+ const max = parseInt(parts[1], 10);
+
+ if (isNaN(min) || isNaN(max)) {
+ throw new Error(`Invalid moves range: ${movesStr}. Values must be numbers`);
+ }
+ if (min < 0 || max < 0) {
+ throw new Error("moves must be non-negative");
+ }
+ if (max < min) {
+ throw new Error("max moves must be greater than or equal to min moves");
+ }
+
+ return [min, max];
+ }
+
+ // Single number means exact move count (min = max)
+ const moves = parseInt(movesStr, 10);
+ if (isNaN(moves)) {
+ throw new Error(`Invalid moves value: ${movesStr}. Must be a number or range like "10-50"`);
+ }
+ if (moves < 0) {
+ throw new Error("moves must be non-negative");
+ }
+
+ return [moves, moves];
+}
+
+
+/**
+ * Parse command line arguments using yargs
+ */
+function parseArgs(): { count: number; options: GeneratorOptions } {
+ const argv = yargs(hideBin(process.argv))
+ .scriptName("npm run generate:games --")
+ .usage("Usage: $0 [options]")
+ .option("count", {
+ alias: "c",
+ type: "number",
+ default: 10,
+ description: "Number of games to generate",
+ coerce: (value) => {
+ if (value <= 0) {
+ throw new Error("count must be greater than 0");
+ }
+ return value;
+ }
+ })
+ .option("moves", {
+ alias: "m",
+ type: "string",
+ description: "Moves per game as \"MIN-MAX\" or single number (default: play until settled)",
+ })
+ .option("pass-chance", {
+ type: "number",
+ default: 0,
+ description: "Probability of passing (0.0-1.0)",
+ coerce: (value) => {
+ if (value < 0 || value > 1) {
+ throw new Error("pass-chance must be between 0.0 and 1.0");
+ }
+ return value;
+ }
+ })
+ .option("board", {
+ alias: "b",
+ type: "string",
+ default: "random",
+ description: 'Board size as "X*Y*Z" or "random" for random selection',
+ })
+ .option("output", {
+ alias: "o",
+ type: "string",
+ default: "output",
+ description: "Output directory (relative to tools/)"
+ })
+ .example([
+ ["$0", "Generate 10 games until territory settled"],
+ ["$0 --count 100 --moves 20-80", "Generate 100 games with 20-80 moves"],
+ ["$0 --board '5*5*5' --count 50", "Generate 50 games on 5ร5ร5 board"],
+ ["$0 --moves 30 --count 20", "Generate 20 games with exactly 30 moves"],
+ ["$0 --pass-chance 0.3 --output test_games", "Generate games with 30% pass chance"]
+ ])
+ .help("h")
+ .alias("h", "help")
+ .version(false)
+ .strict()
+ .parseSync();
+
+ // Check if moves was user-specified or default
+ const moveToEndGame = !argv.moves;
+
+ // Parse board shape: undefined for random mode, otherwise parse the string
+ const boardShape = argv.board.toLowerCase() === "random"
+ ? undefined
+ : parseBoardShape(argv.board);
+
+ // Parse moves range (use dummy range for default mode, will be ignored)
+ const [minMoves, maxMoves] = moveToEndGame ? [0, 0] : parseMovesRange(argv.moves as string);
+
+ return {
+ count: argv.count,
+ options: {
+ minMoves,
+ maxMoves,
+ passChance: argv["pass-chance"],
+ boardShape,
+ outputDir: argv.output,
+ moveToEndGame
+ }
+ };
+}
+
+
+/**
+ * Main entry point
+ */
+function main(): void {
+ try {
+ console.log("=== Trigo Random Game Generator ===\n");
+
+ const { count, options } = parseArgs();
+ generateBatch(count, options);
+ } catch (error) {
+ console.error("\nError:", error instanceof Error ? error.message : error);
+ process.exit(1);
+ }
+}
+
+
+// Run the generator
+main();
diff --git a/trigo-web/tools/output/.gitignore b/trigo-web/tools/output/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e4249c202cd68c9e858ec0f3700511a967978944
--- /dev/null
+++ b/trigo-web/tools/output/.gitignore
@@ -0,0 +1 @@
+*.tgn
diff --git a/trigo-web/tsconfig.json b/trigo-web/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..5cbf9716e40e58200a39f06709a661b3b2bcb0ee
--- /dev/null
+++ b/trigo-web/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM"],
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "types": ["vitest/globals", "node"],
+ "baseUrl": ".",
+ "paths": {
+ "@inc/*": ["./inc/*"]
+ }
+ },
+ "include": ["tests/**/*.ts", "inc/**/*.ts", "tools/**/*.ts"],
+ "exclude": ["node_modules", "dist", "app", "backend"]
+}
diff --git a/trigo-web/vitest.config.ts b/trigo-web/vitest.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..08b756820dce41850538545c3fe9ea508ad28558
--- /dev/null
+++ b/trigo-web/vitest.config.ts
@@ -0,0 +1,41 @@
+import { defineConfig, Plugin } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import { fileURLToPath } from 'node:url'
+
+// Plugin to handle /lib/tgnParser.cjs imports in Node.js environment
+const libParserPlugin: Plugin = {
+ name: 'lib-parser-bypass',
+ enforce: 'pre',
+ transform(code, id) {
+ // Only transform parserInit.ts
+ if (id.includes('parserInit')) {
+ // Replace import calls of /lib/tgnParser.cjs with a marker that won't be analyzed
+ return code.replace(
+ /await import\(.*?\/lib\/tgnParser\.cjs/g,
+ 'await import("" + "/lib/tgnParser.cjs'
+ )
+ }
+ }
+}
+
+export default defineConfig({
+ plugins: [libParserPlugin, vue()],
+ test: {
+ globals: true,
+ environment: 'node', // Changed from jsdom to node since we're testing pure logic
+ include: ['tests/game/**/*.test.ts'], // Game logic tests
+ },
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./app/src', import.meta.url)),
+ '@inc': fileURLToPath(new URL('./inc', import.meta.url))
+ }
+ },
+ ssr: {
+ external: ['/lib/tgnParser.cjs']
+ }
+})
+
+
+
+
diff --git a/trigo-web/yarn.lock b/trigo-web/yarn.lock
new file mode 100644
index 0000000000000000000000000000000000000000..e02e143077c88da462749518ba84920536934008
--- /dev/null
+++ b/trigo-web/yarn.lock
@@ -0,0 +1,1858 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@acemir/cssom@^0.9.19":
+ version "0.9.19"
+ resolved "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.19.tgz"
+ integrity sha512-Pp2gAQXPZ2o7lt4j0IMwNRXqQ3pagxtDj5wctL5U2Lz4oV0ocDNlkgx4DpxfyKav4S/bePuI+SMqcBSUHLy9kg==
+
+"@asamuzakjp/css-color@^4.0.3":
+ version "4.0.5"
+ resolved "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz"
+ integrity sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==
+ dependencies:
+ "@csstools/css-calc" "^2.1.4"
+ "@csstools/css-color-parser" "^3.1.0"
+ "@csstools/css-parser-algorithms" "^3.0.5"
+ "@csstools/css-tokenizer" "^3.0.4"
+ lru-cache "^11.2.1"
+
+"@asamuzakjp/dom-selector@^6.7.3":
+ version "6.7.4"
+ resolved "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz"
+ integrity sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==
+ dependencies:
+ "@asamuzakjp/nwsapi" "^2.3.9"
+ bidi-js "^1.0.3"
+ css-tree "^3.1.0"
+ is-potential-custom-element-name "^1.0.1"
+ lru-cache "^11.2.2"
+
+"@asamuzakjp/nwsapi@^2.3.9":
+ version "2.3.9"
+ resolved "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz"
+ integrity sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==
+
+"@babel/helper-string-parser@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
+ integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
+
+"@babel/helper-validator-identifier@^7.28.5":
+ version "7.28.5"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"
+ integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
+
+"@babel/parser@^7.28.4":
+ version "7.28.5"
+ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz"
+ integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==
+ dependencies:
+ "@babel/types" "^7.28.5"
+
+"@babel/runtime@^7.21.0":
+ version "7.28.4"
+ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz"
+ integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
+
+"@babel/types@^7.28.5":
+ version "7.28.5"
+ resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz"
+ integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==
+ dependencies:
+ "@babel/helper-string-parser" "^7.27.1"
+ "@babel/helper-validator-identifier" "^7.28.5"
+
+"@csstools/color-helpers@^5.1.0":
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz"
+ integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==
+
+"@csstools/css-calc@^2.1.4":
+ version "2.1.4"
+ resolved "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz"
+ integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==
+
+"@csstools/css-color-parser@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz"
+ integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==
+ dependencies:
+ "@csstools/color-helpers" "^5.1.0"
+ "@csstools/css-calc" "^2.1.4"
+
+"@csstools/css-parser-algorithms@^3.0.5":
+ version "3.0.5"
+ resolved "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz"
+ integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==
+
+"@csstools/css-syntax-patches-for-csstree@^1.0.14":
+ version "1.0.15"
+ resolved "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz"
+ integrity sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==
+
+"@csstools/css-tokenizer@^3.0.4":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz"
+ integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==
+
+"@esbuild/linux-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz"
+ integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
+
+"@esbuild/linux-x64@0.25.12":
+ version "0.25.12"
+ resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz"
+ integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==
+
+"@eslint-community/eslint-utils@^4.8.0":
+ version "4.9.0"
+ resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz"
+ integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==
+ dependencies:
+ eslint-visitor-keys "^3.4.3"
+
+"@eslint-community/regexpp@^4.12.1":
+ version "4.12.2"
+ resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz"
+ integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
+
+"@eslint/config-array@^0.21.1":
+ version "0.21.1"
+ resolved "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz"
+ integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==
+ dependencies:
+ "@eslint/object-schema" "^2.1.7"
+ debug "^4.3.1"
+ minimatch "^3.1.2"
+
+"@eslint/config-helpers@^0.4.1":
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz"
+ integrity sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==
+ dependencies:
+ "@eslint/core" "^0.16.0"
+
+"@eslint/core@^0.16.0":
+ version "0.16.0"
+ resolved "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz"
+ integrity sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==
+ dependencies:
+ "@types/json-schema" "^7.0.15"
+
+"@eslint/eslintrc@^3.3.1":
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz"
+ integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^10.0.1"
+ globals "^14.0.0"
+ ignore "^5.2.0"
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ minimatch "^3.1.2"
+ strip-json-comments "^3.1.1"
+
+"@eslint/js@9.38.0":
+ version "9.38.0"
+ resolved "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz"
+ integrity sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==
+
+"@eslint/object-schema@^2.1.7":
+ version "2.1.7"
+ resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz"
+ integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==
+
+"@eslint/plugin-kit@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz"
+ integrity sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==
+ dependencies:
+ "@eslint/core" "^0.16.0"
+ levn "^0.4.1"
+
+"@humanfs/core@^0.19.1":
+ version "0.19.1"
+ resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz"
+ integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==
+
+"@humanfs/node@^0.16.6":
+ version "0.16.7"
+ resolved "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz"
+ integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==
+ dependencies:
+ "@humanfs/core" "^0.19.1"
+ "@humanwhocodes/retry" "^0.4.0"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2":
+ version "0.4.3"
+ resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz"
+ integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
+
+"@jridgewell/sourcemap-codec@^1.5.5":
+ version "1.5.5"
+ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
+ integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
+
+"@pkgr/core@^0.2.9":
+ version "0.2.9"
+ resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz"
+ integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
+
+"@polka/url@^1.0.0-next.24":
+ version "1.0.0-next.29"
+ resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz"
+ integrity sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==
+
+"@rollup/rollup-linux-x64-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz"
+ integrity sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==
+
+"@rollup/rollup-linux-x64-musl@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz"
+ integrity sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==
+
+"@standard-schema/spec@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz"
+ integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==
+
+"@types/chai@^5.2.2":
+ version "5.2.3"
+ resolved "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz"
+ integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==
+ dependencies:
+ "@types/deep-eql" "*"
+ assertion-error "^2.0.1"
+
+"@types/deep-eql@*":
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz"
+ integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==
+
+"@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@1.0.8":
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
+ integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+
+"@types/json-schema@^7.0.15":
+ version "7.0.15"
+ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
+ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
+
+"@types/node@^18.0.0 || >=20.0.0", "@types/node@^20.0.0 || ^22.0.0 || >=24.0.0", "@types/node@^20.19.0 || >=22.12.0", "@types/node@^24.10.0":
+ version "24.10.0"
+ resolved "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz"
+ integrity sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==
+ dependencies:
+ undici-types "~7.16.0"
+
+"@types/yargs-parser@*":
+ version "21.0.3"
+ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz"
+ integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==
+
+"@types/yargs@^17.0.34":
+ version "17.0.34"
+ resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz"
+ integrity sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==
+ dependencies:
+ "@types/yargs-parser" "*"
+
+"@vitejs/plugin-vue@^5.2.4":
+ version "5.2.4"
+ resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz"
+ integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==
+
+"@vitest/expect@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.6.tgz"
+ integrity sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg==
+ dependencies:
+ "@standard-schema/spec" "^1.0.0"
+ "@types/chai" "^5.2.2"
+ "@vitest/spy" "4.0.6"
+ "@vitest/utils" "4.0.6"
+ chai "^6.0.1"
+ tinyrainbow "^3.0.3"
+
+"@vitest/mocker@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.6.tgz"
+ integrity sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==
+ dependencies:
+ "@vitest/spy" "4.0.6"
+ estree-walker "^3.0.3"
+ magic-string "^0.30.19"
+
+"@vitest/pretty-format@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.6.tgz"
+ integrity sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g==
+ dependencies:
+ tinyrainbow "^3.0.3"
+
+"@vitest/runner@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.6.tgz"
+ integrity sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og==
+ dependencies:
+ "@vitest/utils" "4.0.6"
+ pathe "^2.0.3"
+
+"@vitest/snapshot@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.6.tgz"
+ integrity sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g==
+ dependencies:
+ "@vitest/pretty-format" "4.0.6"
+ magic-string "^0.30.19"
+ pathe "^2.0.3"
+
+"@vitest/spy@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.6.tgz"
+ integrity sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ==
+
+"@vitest/ui@^4.0.6", "@vitest/ui@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.6.tgz"
+ integrity sha512-1ekpBsYNUm0Xv/0YsTvoSRmiRkmzz9Pma7qQ3Ui76sg2gwp2/ewSWqx4W/HfaN5dF0E8iBbidFo1wGaeqXYIrQ==
+ dependencies:
+ "@vitest/utils" "4.0.6"
+ fflate "^0.8.2"
+ flatted "^3.3.3"
+ pathe "^2.0.3"
+ sirv "^3.0.2"
+ tinyglobby "^0.2.15"
+ tinyrainbow "^3.0.3"
+
+"@vitest/utils@4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.6.tgz"
+ integrity sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A==
+ dependencies:
+ "@vitest/pretty-format" "4.0.6"
+ tinyrainbow "^3.0.3"
+
+"@vue/compiler-core@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz"
+ integrity sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==
+ dependencies:
+ "@babel/parser" "^7.28.4"
+ "@vue/shared" "3.5.22"
+ entities "^4.5.0"
+ estree-walker "^2.0.2"
+ source-map-js "^1.2.1"
+
+"@vue/compiler-dom@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz"
+ integrity sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==
+ dependencies:
+ "@vue/compiler-core" "3.5.22"
+ "@vue/shared" "3.5.22"
+
+"@vue/compiler-sfc@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz"
+ integrity sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==
+ dependencies:
+ "@babel/parser" "^7.28.4"
+ "@vue/compiler-core" "3.5.22"
+ "@vue/compiler-dom" "3.5.22"
+ "@vue/compiler-ssr" "3.5.22"
+ "@vue/shared" "3.5.22"
+ estree-walker "^2.0.2"
+ magic-string "^0.30.19"
+ postcss "^8.5.6"
+ source-map-js "^1.2.1"
+
+"@vue/compiler-ssr@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz"
+ integrity sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==
+ dependencies:
+ "@vue/compiler-dom" "3.5.22"
+ "@vue/shared" "3.5.22"
+
+"@vue/reactivity@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz"
+ integrity sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==
+ dependencies:
+ "@vue/shared" "3.5.22"
+
+"@vue/runtime-core@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz"
+ integrity sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==
+ dependencies:
+ "@vue/reactivity" "3.5.22"
+ "@vue/shared" "3.5.22"
+
+"@vue/runtime-dom@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz"
+ integrity sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==
+ dependencies:
+ "@vue/reactivity" "3.5.22"
+ "@vue/runtime-core" "3.5.22"
+ "@vue/shared" "3.5.22"
+ csstype "^3.1.3"
+
+"@vue/server-renderer@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz"
+ integrity sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==
+ dependencies:
+ "@vue/compiler-ssr" "3.5.22"
+ "@vue/shared" "3.5.22"
+
+"@vue/shared@3.5.22":
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz"
+ integrity sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==
+
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0:
+ version "8.15.0"
+ resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
+ integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
+
+agent-base@^7.1.0, agent-base@^7.1.2:
+ version "7.1.4"
+ resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"
+ integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
+
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+amdefine@>=0.0.4:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz"
+ integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-regex@^6.0.1:
+ version "6.2.2"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz"
+ integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+ansi-styles@^6.2.1:
+ version "6.2.3"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz"
+ integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+assertion-error@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz"
+ integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+bidi-js@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz"
+ integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==
+ dependencies:
+ require-from-string "^2.0.2"
+
+brace-expansion@^1.1.7:
+ version "1.1.12"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz"
+ integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+chai@^6.0.1:
+ version "6.2.0"
+ resolved "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz"
+ integrity sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==
+
+chalk@^4.0.0, chalk@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+cjson@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz"
+ integrity sha512-bBRQcCIHzI1IVH59fR0bwGrFmi3Btb/JNwM/n401i1DnYgWndpsUBiQRAddLflkZage20A2d25OAWZZk0vBRlA==
+ dependencies:
+ jsonlint "1.6.0"
+
+cliui@^8.0.1:
+ 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==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
+cliui@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz"
+ integrity sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==
+ dependencies:
+ string-width "^7.2.0"
+ strip-ansi "^7.1.0"
+ wrap-ansi "^9.0.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colors@0.5.x:
+ version "0.5.1"
+ resolved "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz"
+ integrity sha512-XjsuUwpDeY98+yz959OlUK6m7mLBM+1MEG5oaenfuQnNnrQk1WvtcvFgN3FNDP3f2NmZ211t0mNEfSEN1h0eIg==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+concurrently@^7.6.0:
+ version "7.6.0"
+ resolved "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz"
+ integrity sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==
+ dependencies:
+ chalk "^4.1.0"
+ date-fns "^2.29.1"
+ lodash "^4.17.21"
+ rxjs "^7.0.0"
+ shell-quote "^1.7.3"
+ spawn-command "^0.0.2-1"
+ supports-color "^8.1.0"
+ tree-kill "^1.2.2"
+ yargs "^17.3.1"
+
+cross-spawn@^7.0.6:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+css-tree@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz"
+ integrity sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==
+ dependencies:
+ mdn-data "2.12.2"
+ source-map-js "^1.0.1"
+
+cssstyle@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.2.tgz"
+ integrity sha512-zDMqXh8Vs1CdRYZQ2M633m/SFgcjlu8RB8b/1h82i+6vpArF507NSYIWJHGlJaTWoS+imcnctmEz43txhbVkOw==
+ dependencies:
+ "@asamuzakjp/css-color" "^4.0.3"
+ "@csstools/css-syntax-patches-for-csstree" "^1.0.14"
+ css-tree "^3.1.0"
+
+csstype@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+data-urls@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz"
+ integrity sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==
+ dependencies:
+ whatwg-mimetype "^4.0.0"
+ whatwg-url "^15.0.0"
+
+date-fns@^2.29.1:
+ version "2.30.0"
+ resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz"
+ integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
+
+debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.3, debug@4:
+ version "4.4.3"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
+decimal.js@^10.6.0:
+ version "10.6.0"
+ resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz"
+ integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==
+
+deep-is@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
+ integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
+ebnf-parser@0.1.10:
+ version "0.1.10"
+ resolved "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz"
+ integrity sha512-urvSxVQ6XJcoTpc+/x2pWhhuOX4aljCNQpwzw+ifZvV1andZkAmiJc3Rq1oGEAQmcjiLceyMXOy1l8ms8qs2fQ==
+
+emoji-regex@^10.3.0:
+ version "10.6.0"
+ resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz"
+ integrity sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+entities@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+entities@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz"
+ integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==
+
+es-module-lexer@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz"
+ integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
+
+esbuild@^0.21.3:
+ version "0.21.5"
+ resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz"
+ integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.21.5"
+ "@esbuild/android-arm" "0.21.5"
+ "@esbuild/android-arm64" "0.21.5"
+ "@esbuild/android-x64" "0.21.5"
+ "@esbuild/darwin-arm64" "0.21.5"
+ "@esbuild/darwin-x64" "0.21.5"
+ "@esbuild/freebsd-arm64" "0.21.5"
+ "@esbuild/freebsd-x64" "0.21.5"
+ "@esbuild/linux-arm" "0.21.5"
+ "@esbuild/linux-arm64" "0.21.5"
+ "@esbuild/linux-ia32" "0.21.5"
+ "@esbuild/linux-loong64" "0.21.5"
+ "@esbuild/linux-mips64el" "0.21.5"
+ "@esbuild/linux-ppc64" "0.21.5"
+ "@esbuild/linux-riscv64" "0.21.5"
+ "@esbuild/linux-s390x" "0.21.5"
+ "@esbuild/linux-x64" "0.21.5"
+ "@esbuild/netbsd-x64" "0.21.5"
+ "@esbuild/openbsd-x64" "0.21.5"
+ "@esbuild/sunos-x64" "0.21.5"
+ "@esbuild/win32-arm64" "0.21.5"
+ "@esbuild/win32-ia32" "0.21.5"
+ "@esbuild/win32-x64" "0.21.5"
+
+esbuild@^0.25.0:
+ version "0.25.12"
+ resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz"
+ integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==
+ 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"
+
+esbuild@~0.25.0:
+ version "0.25.12"
+ resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz"
+ integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==
+ 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"
+
+escalade@^3.1.1:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+escodegen@1.3.x:
+ version "1.3.3"
+ resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz"
+ integrity sha512-z9FWgKc48wjMlpzF5ymKS1AF8OIgnKLp9VyN7KbdtyrP/9lndwUFqCtMm+TAJmJf7KJFFYc4cFJfVTTGkKEwsA==
+ dependencies:
+ esprima "~1.1.1"
+ estraverse "~1.5.0"
+ esutils "~1.0.0"
+ optionalDependencies:
+ source-map "~0.1.33"
+
+eslint-config-prettier@^10.1.8, "eslint-config-prettier@>= 7.0.0 <10.0.0 || >=10.1.0":
+ version "10.1.8"
+ resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz"
+ integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==
+
+eslint-plugin-prettier@^5.5.4:
+ version "5.5.4"
+ resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz"
+ integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==
+ dependencies:
+ prettier-linter-helpers "^1.0.0"
+ synckit "^0.11.7"
+
+eslint-scope@^8.4.0:
+ version "8.4.0"
+ resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz"
+ integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+eslint-visitor-keys@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
+ integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
+
+"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@>=7.0.0, eslint@>=8.0.0:
+ version "9.38.0"
+ resolved "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz"
+ integrity sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.8.0"
+ "@eslint-community/regexpp" "^4.12.1"
+ "@eslint/config-array" "^0.21.1"
+ "@eslint/config-helpers" "^0.4.1"
+ "@eslint/core" "^0.16.0"
+ "@eslint/eslintrc" "^3.3.1"
+ "@eslint/js" "9.38.0"
+ "@eslint/plugin-kit" "^0.4.0"
+ "@humanfs/node" "^0.16.6"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@humanwhocodes/retry" "^0.4.2"
+ "@types/estree" "^1.0.6"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.6"
+ debug "^4.3.2"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^8.4.0"
+ eslint-visitor-keys "^4.2.1"
+ espree "^10.4.0"
+ esquery "^1.5.0"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^8.0.0"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ ignore "^5.2.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+
+espree@^10.0.1, espree@^10.4.0:
+ version "10.4.0"
+ resolved "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz"
+ integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==
+ dependencies:
+ acorn "^8.15.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^4.2.1"
+
+esprima@~1.1.1, esprima@1.1.x:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz"
+ integrity sha512-qxxB994/7NtERxgXdFgLHIs9M6bhLXc6qtUmWZ3L8+gTQ9qaoyki2887P2IqAYsoENyr8SUbTutStDniOHSDHg==
+
+esquery@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estraverse@~1.5.0:
+ version "1.5.1"
+ resolved "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz"
+ integrity sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==
+
+estree-walker@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+estree-walker@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz"
+ integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+esutils@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz"
+ integrity sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==
+
+expect-type@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz"
+ integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-diff@^1.1.2:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz"
+ integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fdir@^6.5.0:
+ version "6.5.0"
+ resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
+ integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+
+fflate@^0.8.2:
+ version "0.8.2"
+ resolved "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz"
+ integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
+
+file-entry-cache@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz"
+ integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
+ dependencies:
+ flat-cache "^4.0.0"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+flat-cache@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz"
+ integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.4"
+
+flatted@^3.2.9, flatted@^3.3.3:
+ version "3.3.3"
+ resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz"
+ integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-east-asian-width@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz"
+ integrity sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==
+
+get-tsconfig@^4.7.5:
+ version "4.13.0"
+ resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz"
+ integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==
+ dependencies:
+ resolve-pkg-maps "^1.0.0"
+
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+globals@^14.0.0:
+ version "14.0.0"
+ resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz"
+ integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+html-encoding-sniffer@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz"
+ integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==
+ dependencies:
+ whatwg-encoding "^3.1.1"
+
+http-proxy-agent@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"
+ integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==
+ dependencies:
+ agent-base "^7.1.0"
+ debug "^4.3.4"
+
+https-proxy-agent@^7.0.6:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"
+ integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
+ dependencies:
+ agent-base "^7.1.2"
+ debug "4"
+
+iconv-lite@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+ignore@^5.2.0:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
+ integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
+
+import-fresh@^3.2.1:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
+ integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ 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==
+
+is-glob@^4.0.0, is-glob@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-potential-custom-element-name@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz"
+ integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+jison-lex@0.3.x:
+ version "0.3.4"
+ resolved "https://registry.npmjs.org/jison-lex/-/jison-lex-0.3.4.tgz"
+ integrity sha512-EBh5wrXhls1cUwROd5DcDHR1sG7CdsCFSqY1027+YA1RGxz+BX2TDLAhdsQf40YEtFDGoiO0Qm8PpnBl2EzDJw==
+ dependencies:
+ lex-parser "0.1.x"
+ nomnom "1.5.2"
+
+jison@^0.4.18:
+ version "0.4.18"
+ resolved "https://registry.npmjs.org/jison/-/jison-0.4.18.tgz"
+ integrity sha512-FKkCiJvozgC7VTHhMJ00a0/IApSxhlGsFIshLW6trWJ8ONX2TQJBBz6DlcO1Gffy4w9LT+uL+PA+CVnUSJMF7w==
+ dependencies:
+ cjson "0.3.0"
+ ebnf-parser "0.1.10"
+ escodegen "1.3.x"
+ esprima "1.1.x"
+ jison-lex "0.3.x"
+ JSONSelect "0.4.0"
+ lex-parser "~0.1.3"
+ nomnom "1.5.2"
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+jsdom@*, jsdom@^27.1.0:
+ version "27.1.0"
+ resolved "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz"
+ integrity sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==
+ dependencies:
+ "@acemir/cssom" "^0.9.19"
+ "@asamuzakjp/dom-selector" "^6.7.3"
+ cssstyle "^5.3.2"
+ data-urls "^6.0.0"
+ decimal.js "^10.6.0"
+ html-encoding-sniffer "^4.0.0"
+ http-proxy-agent "^7.0.2"
+ https-proxy-agent "^7.0.6"
+ is-potential-custom-element-name "^1.0.1"
+ parse5 "^8.0.0"
+ saxes "^6.0.0"
+ symbol-tree "^3.2.4"
+ tough-cookie "^6.0.0"
+ w3c-xmlserializer "^5.0.0"
+ webidl-conversions "^8.0.0"
+ whatwg-encoding "^3.1.1"
+ whatwg-mimetype "^4.0.0"
+ whatwg-url "^15.1.0"
+ ws "^8.18.3"
+ xml-name-validator "^5.0.0"
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+jsonlint@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz"
+ integrity sha512-x6YLBe6NjdpmIeiklwQOxsZuYj/SOWkT33GlTpaG1UdFGjdWjPcxJ1CWZAX3wA7tarz8E2YHF6KiW5HTapPlXw==
+ dependencies:
+ JSV ">= 4.0.x"
+ nomnom ">= 1.5.x"
+
+JSONSelect@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz"
+ integrity sha512-VRLR3Su35MH+XV2lrvh9O7qWoug/TUyj9tLDjn9rtpUCNnILLrHjgd/tB0KrhugCxUpj3UqoLqfYb3fLJdIQQQ==
+
+"JSV@>= 4.0.x":
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz"
+ integrity sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==
+
+keyv@^4.5.4:
+ version "4.5.4"
+ resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+lex-parser@~0.1.3, lex-parser@0.1.x:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz"
+ integrity sha512-DuAEISsr1H4LOpmFLkyMc8YStiRWZCO8hMsoXAXSbgyfvs2WQhSt0+/FBv3ZU/JBFZMGcE+FWzEBSzwUU7U27w==
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lru-cache@^11.2.1, lru-cache@^11.2.2:
+ version "11.2.2"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz"
+ integrity sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==
+
+magic-string@^0.30.19:
+ version "0.30.21"
+ resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz"
+ integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.5"
+
+mdn-data@2.12.2:
+ version "2.12.2"
+ resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz"
+ integrity sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==
+
+minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+mrmime@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz"
+ integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+nanoid@^3.3.11:
+ version "3.3.11"
+ resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
+ integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
+"nomnom@>= 1.5.x", nomnom@1.5.2:
+ version "1.5.2"
+ resolved "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz"
+ integrity sha512-fiVbT7BqxiQqjlR9U3FDGOSERFCKoXVCdxV2FwZuNN7/cmJ42iQx35nUFOAFDcyvemu9Adp+IlsCGlKQYLmBKw==
+ dependencies:
+ colors "0.5.x"
+ underscore "1.1.x"
+
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.5"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+parse5@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz"
+ integrity sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==
+ dependencies:
+ entities "^6.0.0"
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+pathe@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz"
+ integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
+
+picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+"picomatch@^3 || ^4", picomatch@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
+ integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
+
+postcss@^8.4.43, postcss@^8.5.6:
+ version "8.5.6"
+ resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz"
+ integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
+ dependencies:
+ nanoid "^3.3.11"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+prettier-linter-helpers@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz"
+ integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
+ dependencies:
+ fast-diff "^1.1.2"
+
+prettier@^3.6.2, prettier@>=3.0.0:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz"
+ integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
+
+punycode@^2.1.0, punycode@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+require-directory@^2.1.1:
+ 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==
+
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve-pkg-maps@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz"
+ integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
+
+rollup@^4.20.0, rollup@^4.43.0:
+ version "4.52.5"
+ resolved "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz"
+ integrity sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==
+ dependencies:
+ "@types/estree" "1.0.8"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.52.5"
+ "@rollup/rollup-android-arm64" "4.52.5"
+ "@rollup/rollup-darwin-arm64" "4.52.5"
+ "@rollup/rollup-darwin-x64" "4.52.5"
+ "@rollup/rollup-freebsd-arm64" "4.52.5"
+ "@rollup/rollup-freebsd-x64" "4.52.5"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.52.5"
+ "@rollup/rollup-linux-arm-musleabihf" "4.52.5"
+ "@rollup/rollup-linux-arm64-gnu" "4.52.5"
+ "@rollup/rollup-linux-arm64-musl" "4.52.5"
+ "@rollup/rollup-linux-loong64-gnu" "4.52.5"
+ "@rollup/rollup-linux-ppc64-gnu" "4.52.5"
+ "@rollup/rollup-linux-riscv64-gnu" "4.52.5"
+ "@rollup/rollup-linux-riscv64-musl" "4.52.5"
+ "@rollup/rollup-linux-s390x-gnu" "4.52.5"
+ "@rollup/rollup-linux-x64-gnu" "4.52.5"
+ "@rollup/rollup-linux-x64-musl" "4.52.5"
+ "@rollup/rollup-openharmony-arm64" "4.52.5"
+ "@rollup/rollup-win32-arm64-msvc" "4.52.5"
+ "@rollup/rollup-win32-ia32-msvc" "4.52.5"
+ "@rollup/rollup-win32-x64-gnu" "4.52.5"
+ "@rollup/rollup-win32-x64-msvc" "4.52.5"
+ fsevents "~2.3.2"
+
+rxjs@^7.0.0:
+ version "7.8.2"
+ resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz"
+ integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
+ dependencies:
+ tslib "^2.1.0"
+
+"safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+saxes@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz"
+ integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==
+ dependencies:
+ xmlchars "^2.2.0"
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+shell-quote@^1.7.3:
+ version "1.8.3"
+ resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz"
+ integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==
+
+siginfo@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz"
+ integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
+
+sirv@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz"
+ integrity sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==
+ dependencies:
+ "@polka/url" "^1.0.0-next.24"
+ mrmime "^2.0.0"
+ totalist "^3.0.0"
+
+source-map-js@^1.0.1, source-map-js@^1.2.1:
+ 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==
+
+source-map@~0.1.33:
+ version "0.1.43"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz"
+ integrity sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==
+ dependencies:
+ amdefine ">=0.0.4"
+
+spawn-command@^0.0.2-1:
+ version "0.0.2"
+ resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz"
+ integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==
+
+stackback@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz"
+ integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==
+
+std-env@^3.9.0:
+ version "3.10.0"
+ resolved "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz"
+ integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^7.0.0, string-width@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz"
+ integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==
+ dependencies:
+ emoji-regex "^10.3.0"
+ get-east-asian-width "^1.0.0"
+ strip-ansi "^7.1.0"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^7.1.0:
+ version "7.1.2"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz"
+ integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==
+ dependencies:
+ ansi-regex "^6.0.1"
+
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^8.1.0:
+ version "8.1.1"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+symbol-tree@^3.2.4:
+ version "3.2.4"
+ resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"
+ integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+
+synckit@^0.11.7:
+ version "0.11.11"
+ resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz"
+ integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==
+ dependencies:
+ "@pkgr/core" "^0.2.9"
+
+tinybench@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz"
+ integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
+
+tinyexec@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz"
+ integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==
+
+tinyglobby@^0.2.15:
+ version "0.2.15"
+ resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz"
+ integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
+ dependencies:
+ fdir "^6.5.0"
+ picomatch "^4.0.3"
+
+tinyrainbow@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz"
+ integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==
+
+tldts-core@^7.0.17:
+ version "7.0.17"
+ resolved "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz"
+ integrity sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==
+
+tldts@^7.0.5:
+ version "7.0.17"
+ resolved "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz"
+ integrity sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==
+ dependencies:
+ tldts-core "^7.0.17"
+
+totalist@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz"
+ integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
+
+tough-cookie@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz"
+ integrity sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==
+ dependencies:
+ tldts "^7.0.5"
+
+tr46@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz"
+ integrity sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==
+ dependencies:
+ punycode "^2.3.1"
+
+tree-kill@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz"
+ integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
+
+tslib@^2.1.0:
+ version "2.8.1"
+ resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
+tsx@^4.20.6, tsx@^4.8.1:
+ version "4.20.6"
+ resolved "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz"
+ integrity sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==
+ dependencies:
+ esbuild "~0.25.0"
+ get-tsconfig "^4.7.5"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
+typescript@*, typescript@^5.2.2:
+ version "5.9.3"
+ resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"
+ integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
+
+underscore@1.1.x:
+ version "1.1.7"
+ resolved "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz"
+ integrity sha512-w4QtCHoLBXw1mjofIDoMyexaEdWGMedWNDhlWTtT1V1lCRqi65Pnoygkh6+WRdr+Bm8ldkBNkNeCsXGMlQS9HQ==
+
+undici-types@~7.16.0:
+ version "7.16.0"
+ resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz"
+ integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+"vite@^5.0.0 || ^6.0.0", vite@^5.4.21:
+ version "5.4.21"
+ resolved "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz"
+ integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==
+ dependencies:
+ esbuild "^0.21.3"
+ postcss "^8.4.43"
+ rollup "^4.20.0"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+"vite@^6.0.0 || ^7.0.0", "vite@^6.0.0 || ^7.0.0-0":
+ version "7.1.12"
+ resolved "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz"
+ integrity sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==
+ dependencies:
+ esbuild "^0.25.0"
+ fdir "^6.5.0"
+ picomatch "^4.0.3"
+ postcss "^8.5.6"
+ rollup "^4.43.0"
+ tinyglobby "^0.2.15"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+vitest@^4.0.6, vitest@4.0.6:
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/vitest/-/vitest-4.0.6.tgz"
+ integrity sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==
+ dependencies:
+ "@vitest/expect" "4.0.6"
+ "@vitest/mocker" "4.0.6"
+ "@vitest/pretty-format" "4.0.6"
+ "@vitest/runner" "4.0.6"
+ "@vitest/snapshot" "4.0.6"
+ "@vitest/spy" "4.0.6"
+ "@vitest/utils" "4.0.6"
+ debug "^4.4.3"
+ es-module-lexer "^1.7.0"
+ expect-type "^1.2.2"
+ magic-string "^0.30.19"
+ pathe "^2.0.3"
+ picomatch "^4.0.3"
+ std-env "^3.9.0"
+ tinybench "^2.9.0"
+ tinyexec "^0.3.2"
+ tinyglobby "^0.2.15"
+ tinyrainbow "^3.0.3"
+ vite "^6.0.0 || ^7.0.0"
+ why-is-node-running "^2.3.0"
+
+vue@^3.2.25, vue@^3.3.4, vue@3.5.22:
+ version "3.5.22"
+ resolved "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz"
+ integrity sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==
+ dependencies:
+ "@vue/compiler-dom" "3.5.22"
+ "@vue/compiler-sfc" "3.5.22"
+ "@vue/runtime-dom" "3.5.22"
+ "@vue/server-renderer" "3.5.22"
+ "@vue/shared" "3.5.22"
+
+w3c-xmlserializer@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz"
+ integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==
+ dependencies:
+ xml-name-validator "^5.0.0"
+
+webidl-conversions@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz"
+ integrity sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==
+
+whatwg-encoding@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz"
+ integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==
+ dependencies:
+ iconv-lite "0.6.3"
+
+whatwg-mimetype@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz"
+ integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==
+
+whatwg-url@^15.0.0, whatwg-url@^15.1.0:
+ version "15.1.0"
+ resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz"
+ integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==
+ dependencies:
+ tr46 "^6.0.0"
+ webidl-conversions "^8.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+why-is-node-running@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz"
+ integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==
+ dependencies:
+ siginfo "^2.0.0"
+ stackback "0.0.2"
+
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^9.0.0:
+ version "9.0.2"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz"
+ integrity sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==
+ dependencies:
+ ansi-styles "^6.2.1"
+ string-width "^7.0.0"
+ strip-ansi "^7.1.0"
+
+ws@^8.18.3:
+ version "8.18.3"
+ resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
+ integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
+
+xml-name-validator@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz"
+ integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==
+
+xmlchars@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz"
+ integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs-parser@^22.0.0:
+ version "22.0.0"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz"
+ integrity sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==
+
+yargs@^17.3.1:
+ version "17.7.2"
+ resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ 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"
+
+yargs@^18.0.0:
+ version "18.0.0"
+ resolved "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz"
+ integrity sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==
+ dependencies:
+ cliui "^9.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ string-width "^7.2.0"
+ y18n "^5.0.5"
+ yargs-parser "^22.0.0"
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==