diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35630d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +.parcel-cache/ +.env +.DS_Store +*.log +.vscode/ +coverage/ diff --git a/.postcssrc b/.postcssrc new file mode 100644 index 0000000..72f908d --- /dev/null +++ b/.postcssrc @@ -0,0 +1,5 @@ +{ + "plugins": { + "@tailwindcss/postcss": {} + } +} \ No newline at end of file diff --git "a/.postcssrc\357\200\272Zone.Identifier" "b/.postcssrc\357\200\272Zone.Identifier" new file mode 100644 index 0000000..ccde74c --- /dev/null +++ "b/.postcssrc\357\200\272Zone.Identifier" @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +HostUrl=https://drive.usercontent.google.com/download?id=1iGkHH39rEFOHEDjznbzXhyafayt5nyhG&export=download&authuser=0&confirm=t&uuid=fb85a7a6-76ac-48f7-8912-7c768145bc9a&at=AN8xHoq109b4hfEhXsocz57Dilfh%3A1758315864130 diff --git a/CUSTOM_FLIGHTPLAN_COMMANDS.md b/CUSTOM_FLIGHTPLAN_COMMANDS.md new file mode 100644 index 0000000..5c9f042 --- /dev/null +++ b/CUSTOM_FLIGHTPLAN_COMMANDS.md @@ -0,0 +1,108 @@ +# Custom Flightplan Commands + +This system provides direct access to flightplan DTO objects with custom commands, allowing you to view and render flightplan data without relying on predefined hub commands. + +## Available Commands + +### Primary Command + +- **`FR `** - ERAM-style flight readout for specific aircraft + - Example: `FR UAL123` + - Shows formatted flight data like traditional ERAM systems + - Returns "REJECT - FLID NOT STORED" if aircraft not found + - Returns "REJECT - FLID DUPLICATION" with CID list if multiple matches + +### Basic Commands + +- **`FP `** - Display detailed flightplan for a specific aircraft + - Example: `FP UAL123` + +- **`FPHELP`** - Display help message with all available commands + +### List Commands + +- **`FPL [status]`** - List flightplans by status + - `FPL` or `FPL ALL` - Show all flightplans + - `FPL ACTIVE` - Show only active flightplans + - `FPL PROPOSED` - Show only proposed flightplans + - `FPL TENTATIVE` - Show only tentative flightplans + +### Search Commands + +- **`FPS [filters]`** - Search flightplans with multiple filters + - Filters: `status=`, `dep=`, `dest=`, `alt=`, `route=`, `acid=` + - Example: `FPS status=Active dep=KJFK` + - Example: `FPS dep=KJFK dest=KLAX` + +- **`FPF `** - Find flightplans by specific criteria + - Criteria: `DEP`, `DEST`, `WAYPOINT`, `ALT`, `TYPE` + - Example: `FPF DEP KJFK` - Find flights departing from JFK + - Example: `FPF WAYPOINT HOFFA` - Find flights routing via HOFFA waypoint + - Example: `FPF TYPE B738` - Find flights using Boeing 737-800 + +### Statistics Command + +- **`FPSTATS`** - Display comprehensive flightplan statistics + - Shows total counts by status + - Top aircraft types + - Average speed + - Altitude distribution + +## Usage Examples + +``` +FR UAL123 # ERAM-style flight readout for UAL123 +FP UAL123 # Show detailed info for UAL123 +FPS dep=KJFK dest=KLAX # Find all flights from JFK to LAX +FPL ACTIVE # List all active flightplans +FPF WAYPOINT HOFFA # Find flights via HOFFA waypoint +FPF DEP KORD # Find all departures from Chicago O'Hare +FPSTATS # Show statistics summary +``` + +## Features + +### Direct DTO Access +- Access to complete `ApiFlightplan` objects +- No dependency on hub-specific commands +- Full control over data filtering and display + +### Rich Filtering +- Filter by aircraft ID, departure, destination, altitude, status, route +- Combine multiple filters in search commands +- Case-insensitive searching + +### Multiple Display Modes +- Table view for multiple flightplans +- Detailed view for individual flightplans +- Statistics view for data analysis + +### Real-time Updates +- Automatically syncs with hub flightplan data +- Redux state management for consistency +- Command history tracking + +## Implementation Details + +### Files Created +- `src/services/customFlightplanService.ts` - Core flightplan operations +- `src/services/customFlightplanCommandParser.ts` - Command parsing logic +- `src/hooks/useCustomFlightplanCommands.ts` - React hook for integration +- `src/components/FlightplanVisualization.tsx` - UI components +- `src/redux/slices/customFlightplanSlice.ts` - State management + +### Integration +The system integrates seamlessly with your existing terminal interface in `App.tsx`. Commands are automatically detected and routed to the custom parser while preserving normal hub command functionality. + +### Data Sources +- Pulls flightplan data from your existing `HubContext` +- Works with the existing `Map` structure +- Maintains compatibility with current hub operations + +## Status Display +The terminal now shows: +- Current flightplan count +- Hub connection status +- Reminder to use `FPHELP` for custom commands + +This gives you complete control over flightplan data visualization and manipulation while maintaining integration with your existing vFDIO system. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8bffbce --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5244 @@ +{ + "name": "vfdio", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vfdio", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.0.0", + "@fortawesome/free-solid-svg-icons": "^6.0.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "@microsoft/signalr": "^8.0.0", + "@reduxjs/toolkit": "^2.0.0", + "@tailwindcss/postcss": "^4.1.11", + "jose": "^5.0.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-redux": "^9.0.0", + "react-router-dom": "^6.0.0", + "react-toastify": "^10.0.0", + "tailwindcss": "^4.1.11" + }, + "devDependencies": { + "@types/node": "^24.1.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.1", + "events": "^3.3.0", + "os-browserify": "^0.3.0", + "parcel": "^2.15.4", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "stream-browserify": "^3.0.0", + "string_decoder": "^1.3.0", + "vm-browserify": "^1.1.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.3.tgz", + "integrity": "sha512-HlJco8RDY8NrzFVjy23b/7mNS4g9NegcrBG3n7jinwpc2x/AmSVk53IhWniLYM4szYLxRAFTAGwGn0EIlclDeQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7", + "react": "^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.8.5.tgz", + "integrity": "sha512-KPDeVScZgA1oq0CiPBcOa3kHIqU+pTOwRFDIhxvmf8CTNvqdZQYp5cCKW0bUk69VygB2PuTiINFWbY78aR2pQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.8.5.tgz", + "integrity": "sha512-w/sLhN4T7MW1nB3R/U8WK5BgQLz904wh+/SmA2jD8NnF7BLLoUgflCNxOeSPOWp8geP6nP/+VjWzZVip7rZ1ug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.8.5.tgz", + "integrity": "sha512-c0TGMbm2M55pwTDIfkDLB6BpIsgxV4PjYck2HiOX+cy/JWiBXz32lYbarPqejKs9Flm7YVAKSILUducU9g2RVg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.8.5.tgz", + "integrity": "sha512-vtbZRHH5UDlL01TT5jB576Zox3+hdyogvpcbvVJlmU5PdL3c5V7cj1EODdh1CHPksRl+cws/58ugEHi8bcj4Ww==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.8.5.tgz", + "integrity": "sha512-Xkc8IUx9aEhP0zvgeKy7IQ3ReX2N8N1L0WPcQwnZweWmOuKfwpS3GRIYqLtK5za/w3E60zhFfNdS+3pBZPytqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.8.5.tgz", + "integrity": "sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@microsoft/signalr": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz", + "integrity": "sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "eventsource": "^2.0.2", + "fetch-cookie": "^2.0.3", + "node-fetch": "^2.6.7", + "ws": "^7.4.5" + } + }, + "node_modules/@mischnic/json-sourcemap": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.1.tgz", + "integrity": "sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0", + "@lezer/lr": "^1.0.0", + "json5": "^2.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@parcel/bundler-default": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.15.4.tgz", + "integrity": "sha512-4vkaZuwGqL8L7NqEgjRznz9/QoeVKk0Z6z2nzfpdnSWA4xX3moUj+JeoqGUbyFGuPzfCma4SA4+txnQbKu0edQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/graph": "3.5.4", + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/utils": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/cache": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.15.4.tgz", + "integrity": "sha512-x/QgMuVvXQV6uNhIF+6kz6SzhVVkwf6WPSVG/xQvGMEiBabForDVYIhIEuN3RzUXCU352CGM6d8TtLLg61W1fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/fs": "2.15.4", + "@parcel/logger": "2.15.4", + "@parcel/utils": "2.15.4", + "lmdb": "2.8.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@parcel/codeframe": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.15.4.tgz", + "integrity": "sha512-ErAPEQaJIpB+ocNZ3rl8AEK6piA7JBInwZLNU0eHMthm01Ssb10JkpAadyn1w9IVfCey+kqQcEeWv47Yh6mL1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/compressor-raw": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.15.4.tgz", + "integrity": "sha512-gECePZxVXBwyo0DYbAq4V4SimVzHaJ3p8QOgFIfOqNmlEBbhLf3QSjArFPJNKiHZaJuclh4a+IShFBN+u6tXXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/config-default": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.15.4.tgz", + "integrity": "sha512-chUE4NpcSXpMfTcSmgl4Q78zH+ZFe0qdgZLBtF4EH2QQakW7wAXAYRxS2/P3xFkUj0/51sExhbCFWgulrlGDPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/bundler-default": "2.15.4", + "@parcel/compressor-raw": "2.15.4", + "@parcel/namer-default": "2.15.4", + "@parcel/optimizer-css": "2.15.4", + "@parcel/optimizer-html": "2.15.4", + "@parcel/optimizer-image": "2.15.4", + "@parcel/optimizer-svg": "2.15.4", + "@parcel/optimizer-swc": "2.15.4", + "@parcel/packager-css": "2.15.4", + "@parcel/packager-html": "2.15.4", + "@parcel/packager-js": "2.15.4", + "@parcel/packager-raw": "2.15.4", + "@parcel/packager-svg": "2.15.4", + "@parcel/packager-wasm": "2.15.4", + "@parcel/reporter-dev-server": "2.15.4", + "@parcel/resolver-default": "2.15.4", + "@parcel/runtime-browser-hmr": "2.15.4", + "@parcel/runtime-js": "2.15.4", + "@parcel/runtime-rsc": "2.15.4", + "@parcel/runtime-service-worker": "2.15.4", + "@parcel/transformer-babel": "2.15.4", + "@parcel/transformer-css": "2.15.4", + "@parcel/transformer-html": "2.15.4", + "@parcel/transformer-image": "2.15.4", + "@parcel/transformer-js": "2.15.4", + "@parcel/transformer-json": "2.15.4", + "@parcel/transformer-node": "2.15.4", + "@parcel/transformer-postcss": "2.15.4", + "@parcel/transformer-posthtml": "2.15.4", + "@parcel/transformer-raw": "2.15.4", + "@parcel/transformer-react-refresh-wrap": "2.15.4", + "@parcel/transformer-svg": "2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@parcel/core": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.15.4.tgz", + "integrity": "sha512-+TXxTm58lFwXXObFAEclwKX1p1AdixcD+M7T4NeFIQzQ4F20Vr+6oybCSqW1exNA3uHqVDDFLx7TT78seVjvkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.1", + "@parcel/cache": "2.15.4", + "@parcel/diagnostic": "2.15.4", + "@parcel/events": "2.15.4", + "@parcel/feature-flags": "2.15.4", + "@parcel/fs": "2.15.4", + "@parcel/graph": "3.5.4", + "@parcel/logger": "2.15.4", + "@parcel/package-manager": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/profiler": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/types": "2.15.4", + "@parcel/utils": "2.15.4", + "@parcel/workers": "2.15.4", + "base-x": "^3.0.11", + "browserslist": "^4.24.5", + "clone": "^2.1.2", + "dotenv": "^16.5.0", + "dotenv-expand": "^11.0.7", + "json5": "^2.2.3", + "msgpackr": "^1.11.2", + "nullthrows": "^1.1.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/core/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@parcel/diagnostic": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.15.4.tgz", + "integrity": "sha512-8MAqefwzBKceNN3364OLm+p4HRD7AfimfFW3MntLxPB6bnelc9UBg5c9zEm34zYEctbmky8gqYgAUSDjqYC5Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/error-overlay": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/error-overlay/-/error-overlay-2.15.4.tgz", + "integrity": "sha512-xxeaWm8fV8Z4uGy/c09mOvmFSHBOgF1gCMQwLCwZvfMLqIWkdZaUQ2cRhWZIS6pOXaRVC7YpcXzk2DOiSUNSbQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/events": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.15.4.tgz", + "integrity": "sha512-SBq4zstaFr7XQaXNaQmUuVh1swCUHrhtPCOSofvkJoQGhjsuhQlh4t0NmUikyKNdj7C1j40xCS1kGHuUO29b0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/feature-flags": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/feature-flags/-/feature-flags-2.15.4.tgz", + "integrity": "sha512-DJqZVtbfjWJseM0gk7yyDkAuOhP7/FVwZ/YVqjozIqXBhmQm07xctiqNQyZX2vBbQsxmVbjpqyq+DOj45WPEzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/fs": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.15.4.tgz", + "integrity": "sha512-5cahD2ByQaSi+YN0aDvrMWXZvs3mP7C5ey8zcDTDn7JxJa51sMqOQcdU3VUTzQFtAPeRM2KxUkxLhBBXgQqHZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/feature-flags": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/types-internal": "2.15.4", + "@parcel/utils": "2.15.4", + "@parcel/watcher": "^2.0.7", + "@parcel/workers": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@parcel/graph": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.5.4.tgz", + "integrity": "sha512-uF7kyQXWK2fQZvG5eE0N3avYGLQE5Q0vyJsyypNcFW3kXNnrkZCUtbG7urmdae9mmZ2jXIVN4q4Bhd9pefGj9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/feature-flags": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/logger": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.15.4.tgz", + "integrity": "sha512-rQ7F5+FMQ7t+w5NGFRT8CWHhym0aunduufCjlafvRzUSKEN/5/nwTfCe9I5QsthGlXJWs+ZTy4zQ+wLtZQRBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/events": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/markdown-ansi": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.15.4.tgz", + "integrity": "sha512-u5Lwcr4ZVBSLFbKYht+mJqJ3ZMXvJdmDMU5eDtrIEKPpu9LrIDdPpDEXBoyO6pDsoV/2AqyXUUMzBRyCatkkoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/namer-default": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.15.4.tgz", + "integrity": "sha512-EXsoQ1S+5ZIfy8431E7F0vVS7bfH5JpZ+vFVcUpArJDkhmMG7T/eP6Kp9CXHLJmn7ki1x7iIVytrja0XXRQWBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/node-resolver-core": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.6.4.tgz", + "integrity": "sha512-g3+usMnr7pfRqbMAksOpNA7GJk7HUNW1Wxx7Shhp4w0K9JUdVrd2LRKwZxbqL7H9NqWtVvUOT9cZbMlDR6bO1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.1", + "@parcel/diagnostic": "2.15.4", + "@parcel/fs": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/utils": "2.15.4", + "nullthrows": "^1.1.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-css": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.15.4.tgz", + "integrity": "sha512-KQLuqwcvVFTNFtM+bzfvQivwunmhVAngmR4NiI8zQaykidYH28V8YkVAQmpbLbgoGad/UgG7grb0UshvnrQHpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.15.4", + "browserslist": "^4.24.5", + "lightningcss": "^1.30.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-html": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-html/-/optimizer-html-2.15.4.tgz", + "integrity": "sha512-gBvt6RdDVMyO1Flvdtc8DxpxLgIXhaKuVXEjHdAP7sEW0SMdSd6r/tl6Plmcszig7sDwhDf6IsQOIvbzGHYZZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/utils": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-image": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.15.4.tgz", + "integrity": "sha512-M8fo7eEL6JRcmLhSX9pUUGU4MPrPrE9cMNcwIt3DQLnSvQ+sshhUDa6t9hKWeHHhs16BHvxrvksN2TIbkgHODQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/utils": "2.15.4", + "@parcel/workers": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@parcel/optimizer-svg": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-svg/-/optimizer-svg-2.15.4.tgz", + "integrity": "sha512-pPdjRaLPqjAEROXIHLc6JWLLki56alhuUNbalhLqBCgktZrrq2dGCjBEVgxqRczc9D+ePCX/e/xci4tC0Tkcbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/utils": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-swc": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.15.4.tgz", + "integrity": "sha512-2m5cYESVCq6AGx252eSTArZ1Oc1Ve4GBGL7NhvgbNqOthyXlc2qAed6rCkARrBd8pfEl5+2XHeK1ijDAZdIZ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.15.4", + "@swc/core": "^1.11.24", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/package-manager": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.15.4.tgz", + "integrity": "sha512-KZONBcEJ24moQdrpU0zJh9CYk3KKbpB5RUM70utAORem1yQKms+0Y4YED3njq6nZzbgwUN/Csc+powUHLZStvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/fs": "2.15.4", + "@parcel/logger": "2.15.4", + "@parcel/node-resolver-core": "3.6.4", + "@parcel/types": "2.15.4", + "@parcel/utils": "2.15.4", + "@parcel/workers": "2.15.4", + "@swc/core": "^1.11.24", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@parcel/packager-css": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.15.4.tgz", + "integrity": "sha512-bzSaNf+I5lmJFu95wSG2k7pGwjCDesZsV6Y9sozIL2LoSxqvkGhm/ABXAa3Ed7dLe3tSAEBzJcyqShQgLzSzuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.15.4", + "lightningcss": "^1.30.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-html": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.15.4.tgz", + "integrity": "sha512-Uayux6A2Anm66Kmq22QhD0TuVp9LiRCMuPUzBd6n4ekNlG0Lzm6K3/okMkPG65nKbNjq5qcPscFWlDxggvjt2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/types": "2.15.4", + "@parcel/utils": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-js": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.15.4.tgz", + "integrity": "sha512-96bqhs1jyd28CfWQD+Yn8rSsd1ar7voHWyBtMLimsK+bDJIzL26Z7jWyRDwXRuLErYC01EoXRIRctxtmeRVJ2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/types": "2.15.4", + "@parcel/utils": "2.15.4", + "globals": "^13.24.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-raw": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.15.4.tgz", + "integrity": "sha512-CaSpDt5jjcO0SYCtsDhw6yfTDQuDFQ875H42W/ftvSQL7RfLRljPthnbdcy9chvKBbvRBQF+0z8Sxwehrd5hsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-svg": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.15.4.tgz", + "integrity": "sha512-qHsyOgnzoA2XGMLIYUnX79XAaV327VTWQvIzju/OmOjcff4o3uiEcNL8w9k3p2w2oPXOLoQ0THMiivoUQSM8GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/types": "2.15.4", + "@parcel/utils": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-wasm": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.15.4.tgz", + "integrity": "sha512-YPVij7zrBchtXr/y29P4uh3C/+19PMhhLibYF/8oMJKkFkeU3Uv00/XLm915vdBPrIPjgw0YuIfLzUKip1uGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4" + }, + "engines": { + "node": ">=16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/plugin": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.15.4.tgz", + "integrity": "sha512-XVehjmzk8ZDOFf/BXo26L76ZqCGNKIQcN2ngxAnq0KRY/WFanL8yLaL0qQq+c9whlu09hkGz1CuhFBLAIjJMYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/types": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/profiler": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.15.4.tgz", + "integrity": "sha512-ezVZlttUmQ1MQD5e8yVb07vSGYEFOB59Y/jaxL9mGSLZkVhMIIHe/7SuA+4qVAH8dlg6bslXRqlsunLMPEgPsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/events": "2.15.4", + "@parcel/types-internal": "2.15.4", + "chrome-trace-event": "^1.0.2" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-cli": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.15.4.tgz", + "integrity": "sha512-us0HIwuJqpSguf+yi4n8foabVs26JGvRB/eSOf0KkRldxFciYLn4NJ8rt3Xm1zvxlDiSkD4v2n77u+ouIZ+AEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/types": "2.15.4", + "@parcel/utils": "2.15.4", + "chalk": "^4.1.2", + "term-size": "^2.2.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-dev-server": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.15.4.tgz", + "integrity": "sha512-uCNeDyArNNXI9YThlxyTx7+5ZSxlewyUdyrLdDZCqvn8s1xNB9W8sUNVps7mJZQSc+2ZRk3wyDemURD67uJk/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/codeframe": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-tracer": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.15.4.tgz", + "integrity": "sha512-9W1xsb/FtobCQ4z847nI6hFDaTZHLeThv/z05EF77R30RX2k+unG9ac5NQB1v4KLx09Bhfre32+sjYNReWxWlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/utils": "2.15.4", + "chrome-trace-event": "^1.0.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/resolver-default": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.15.4.tgz", + "integrity": "sha512-4uKo3FFnubtIc4rM9jZiQQXpa1slawyRy5btJEfTFvbcnz0dm3WThLrsPDMfmPwNr9F/n5x8yzDLI6/fZ/elgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/node-resolver-core": "3.6.4", + "@parcel/plugin": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-browser-hmr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.15.4.tgz", + "integrity": "sha512-KRGzbxDUOQUkrJKxxY0WyU7oVaa9TvWTRlpuGJXzQJs/hw8vkAAoAm8+ptpypvBC8LnxFHzGbSyHPfL8C8MQOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/utils": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-js": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.15.4.tgz", + "integrity": "sha512-zNRK+693CMkYiA0ckjPOmz+JVHD9bVzp27itcMyuDH6l/Or8m09RgCC4DIdIxBqiplsDSe39DwEc5X7b0vvcjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/utils": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-rsc": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/runtime-rsc/-/runtime-rsc-2.15.4.tgz", + "integrity": "sha512-yHc4HEwzCQYLqa6Q1WtZ8xJeaDAk0p2i0b3ABq2I+izmRjer4jertlsEwh9mf9Z1eUGtJobdGYzl8Ai1VfhC3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/utils": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-service-worker": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.15.4.tgz", + "integrity": "sha512-NGq/wS34GIVzo2ZURBjCqgHV+PU7eTcngCzmmk/wrCEeWnr13ld+CAIxVZoqyNJwYsF6VQanrjSM2/LhCXEdyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/utils": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.15.4.tgz", + "integrity": "sha512-OxOux8z8YEYg23+15uMmYaloFp3x1RwcliBay6HqxUW7RTmtI1/z+xd8AtienCckACD60gvDGy04LjgbEGdJVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/rust-darwin-arm64": "2.15.4", + "@parcel/rust-darwin-x64": "2.15.4", + "@parcel/rust-linux-arm-gnueabihf": "2.15.4", + "@parcel/rust-linux-arm64-gnu": "2.15.4", + "@parcel/rust-linux-arm64-musl": "2.15.4", + "@parcel/rust-linux-x64-gnu": "2.15.4", + "@parcel/rust-linux-x64-musl": "2.15.4", + "@parcel/rust-win32-x64-msvc": "2.15.4" + }, + "peerDependencies": { + "napi-wasm": "^1.1.2" + }, + "peerDependenciesMeta": { + "napi-wasm": { + "optional": true + } + } + }, + "node_modules/@parcel/rust-darwin-arm64": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-darwin-arm64/-/rust-darwin-arm64-2.15.4.tgz", + "integrity": "sha512-cEpNDeEtvM5Nhj0QLN95QbcZ9yY6Z5W3+2OeHvnojEAP8Rp1XGzqVTTZdlyKyN1KTiyfzIOiQJCiEcr+kMc5Nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust-darwin-x64": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-darwin-x64/-/rust-darwin-x64-2.15.4.tgz", + "integrity": "sha512-jL9i13sXKeBXXz8Z3BNYoScPOi+ljBA0ubAE3PN5DCoAA6wS4/FsAiRSIUw+3uxqASBD7+JvaT5sDUga1Xft5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust-linux-arm-gnueabihf": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-linux-arm-gnueabihf/-/rust-linux-arm-gnueabihf-2.15.4.tgz", + "integrity": "sha512-c8HpVdDugCutlMILoOlkTioih9HGJpQrzS2G3cg/O1a5ZTacooGf3eGJGoh6dUBEv9WEaEb6zsTRwFv2BgtZcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust-linux-arm64-gnu": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-linux-arm64-gnu/-/rust-linux-arm64-gnu-2.15.4.tgz", + "integrity": "sha512-Wcfs/JY4FnuLxQaU+VX2rI4j376Qo2LkZmq4zp9frnsajaAqmloVQfnbUkdnQPEL4I38eHXerzBX3LoXSxnZKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust-linux-arm64-musl": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-linux-arm64-musl/-/rust-linux-arm64-musl-2.15.4.tgz", + "integrity": "sha512-xf9HxosEn3dU5M0zDSXqBaG8rEjLThRdTYqpkxHW/qQGzy0Se+/ntg8PeDHsSG5E9OK8xrcKH46Lhaw0QBF/Zw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust-linux-x64-gnu": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-linux-x64-gnu/-/rust-linux-x64-gnu-2.15.4.tgz", + "integrity": "sha512-RigXVCFj6h0AXmkuxU61rfgYuW+PXBR6qSkR2I20yKnAXoMfxLaZy9YJ3sAPMEjT9zXgzGAX+3syItMF+bRjaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust-linux-x64-musl": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-linux-x64-musl/-/rust-linux-x64-musl-2.15.4.tgz", + "integrity": "sha512-tHlRgonSr5ca8OvhbGzZUggCgCOirRz5dHhPSCm4ajMxeDMamwprq6lKy0sCNTXht4TXIEyugBcfEuRKEeVIBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust-win32-x64-msvc": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/rust-win32-x64-msvc/-/rust-win32-x64-msvc-2.15.4.tgz", + "integrity": "sha512-YsX6vMl/bfyxqZSN7yiaZQKLoJKELSZYcvg8gIv4CF1xkaTdmfr6gvq2iCyoV+bwrodNohN4Xfl8r7Wniu1/UA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/source-map": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", + "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": "^12.18.3 || >=14" + } + }, + "node_modules/@parcel/transformer-babel": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.15.4.tgz", + "integrity": "sha512-rb4nqZcTLkLD3nvuYJ9wwNb8x6cajBK2l6csdYMLEI4516SkIzkO/gs2cZ9M5q+CMhxAqpdEnrwektbOtQQasg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.15.4", + "browserslist": "^4.24.5", + "json5": "^2.2.3", + "nullthrows": "^1.1.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-css": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.15.4.tgz", + "integrity": "sha512-6tVwSJsOssXgcB5XMAQGsexAffoBEi8GVql3YQqzI1EwVYs9zr+B5mfbesb4aWcegR02w99NHJYFP9CrOr3SWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.15.4", + "browserslist": "^4.24.5", + "lightningcss": "^1.30.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-html": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.15.4.tgz", + "integrity": "sha512-gzYPbbyEuV8nzPojw86eD5Kf93AYUWcY8lu33gu0XHROJH7mq5MAwPwtb/U+EfpeCd0/oKbLzA2mkQksM1NncQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-image": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.15.4.tgz", + "integrity": "sha512-KOVwj2gKjUybuzHwarC/YVqRf3r2BD4/2ysckozj6DIji/bq3fd2rE9yqxWXO+zt918PsOSTzMKwRnaseaXLKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/utils": "2.15.4", + "@parcel/workers": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@parcel/transformer-js": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.15.4.tgz", + "integrity": "sha512-HX76PalPjqCLmXJnuSeMr2km8WlnUsW8oaRZ6FuZtSo9QD8BqIcwKGxSbIy9JHkObBgmrMOVpGtYrJM4/BlYbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.15.4", + "@parcel/workers": "2.15.4", + "@swc/helpers": "^0.5.0", + "browserslist": "^4.24.5", + "nullthrows": "^1.1.1", + "regenerator-runtime": "^0.14.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@parcel/transformer-json": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.15.4.tgz", + "integrity": "sha512-1ASeOSH3gPeaXyy/TZ7ce2TOfJ3ZeK5SBnDs+MM8LFcQsTwdRJKjX/4Qq9RgtMRryYAGHgMa09Gvp9FuFRyd+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "json5": "^2.2.3" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-node": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-node/-/transformer-node-2.15.4.tgz", + "integrity": "sha512-zV5jvZA971eQMcFtaWZkW1UfAH/G6XVM/87oJ2B4ip9o9aKUWIl296rrfg2xWxUQyPhy11B17CJ6b8NgieqqrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-postcss": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.15.4.tgz", + "integrity": "sha512-cNueSpOj3ulmMX85xr9clh/t0+mzVE+Q3H7Cf/OammqUkG/xjmilq4q7ZTgQFyUtUdWpE9LWWHojbJuz6k2Ulw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/utils": "2.15.4", + "clone": "^2.1.2", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-posthtml": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.15.4.tgz", + "integrity": "sha512-dETI+CeKMwu5Dpvu8BrQtex6nwzbNWKQkXseiM5x6+Wf3j9RD2NVpAMBRMjLkw1XlC9Whz1egxLSgKlMKbjg0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4", + "@parcel/utils": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-raw": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.15.4.tgz", + "integrity": "sha512-pY2j09UCW2v1fwQtVLlCztSdPOxhq0YcWmTHCk/mRp8zuUR+eyHgsz48FrUxRF7cr/EBjc0zlFcregRMRcaTMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-react-refresh-wrap": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.15.4.tgz", + "integrity": "sha512-MgoQrV8+BVjrczAns5ZZbTERGB3/U4MaCBmbg3CuiTiIyS8IJQnGi+OhYRdKAB4NlsgpMZ5T2JrRbQUIm9MM8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/error-overlay": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/utils": "2.15.4", + "react-refresh": "^0.16.0" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-svg": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.15.4.tgz", + "integrity": "sha512-Q22e0VRbx62VXFlvJWIlc8ihlLaPQgtnAZz5E1/+ojiNb+k0PmIRjNJclVWPF6IdCsLO5tnGfUOaXe2OnZz28Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/plugin": "2.15.4", + "@parcel/rust": "2.15.4" + }, + "engines": { + "node": ">= 16.0.0", + "parcel": "^2.15.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/types": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.15.4.tgz", + "integrity": "sha512-fS3UMMinLtzn/NTSx/qx38saBgRniylldh0XZEUcGeME4D2Llu/QlLv+YZ/LJqrFci3fPRM+YAn2K+JT/u+/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/types-internal": "2.15.4", + "@parcel/workers": "2.15.4" + } + }, + "node_modules/@parcel/types-internal": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/types-internal/-/types-internal-2.15.4.tgz", + "integrity": "sha512-kl5QEZ8PTWRvMkwmk7IG3VpP/5/MSGwt9Nrj9ctXLdZkDdXZpK7IbXAthLQ4zrByMaqZULL2IyDuBqBgfuAqlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/feature-flags": "2.15.4", + "@parcel/source-map": "^2.1.1", + "utility-types": "^3.11.0" + } + }, + "node_modules/@parcel/utils": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.15.4.tgz", + "integrity": "sha512-29m09sfPx0GHnmy1kkZ5XezprepdFGKKKUEJkyiYA4ERf55jjdnU2/GP4sWlZXxjh2Y+JFoCAFlCamEClq/8eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/codeframe": "2.15.4", + "@parcel/diagnostic": "2.15.4", + "@parcel/logger": "2.15.4", + "@parcel/markdown-ansi": "2.15.4", + "@parcel/rust": "2.15.4", + "@parcel/source-map": "^2.1.1", + "chalk": "^4.1.2", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/workers": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.15.4.tgz", + "integrity": "sha512-wZ/5/mfjs5aeqhXY0c6fwuaBFeNpOXoOq2CKPSMDXt+GX2u/9/1bpVxN9XeGTAJO+ZD++CLq0hyzTnIHy58nyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.15.4", + "@parcel/logger": "2.15.4", + "@parcel/profiler": "2.15.4", + "@parcel/types-internal": "2.15.4", + "@parcel/utils": "2.15.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.15.4" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", + "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/core": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.3.tgz", + "integrity": "sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.23" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.13.3", + "@swc/core-darwin-x64": "1.13.3", + "@swc/core-linux-arm-gnueabihf": "1.13.3", + "@swc/core-linux-arm64-gnu": "1.13.3", + "@swc/core-linux-arm64-musl": "1.13.3", + "@swc/core-linux-x64-gnu": "1.13.3", + "@swc/core-linux-x64-musl": "1.13.3", + "@swc/core-win32-arm64-msvc": "1.13.3", + "@swc/core-win32-ia32-msvc": "1.13.3", + "@swc/core-win32-x64-msvc": "1.13.3" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.3.tgz", + "integrity": "sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.3.tgz", + "integrity": "sha512-p0X6yhxmNUOMZrbeZ3ZNsPige8lSlSe1llllXvpCLkKKxN/k5vZt1sULoq6Nj4eQ7KeHQVm81/+AwKZyf/e0TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.3.tgz", + "integrity": "sha512-OmDoiexL2fVWvQTCtoh0xHMyEkZweQAlh4dRyvl8ugqIPEVARSYtaj55TBMUJIP44mSUOJ5tytjzhn2KFxFcBA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.3.tgz", + "integrity": "sha512-STfKku3QfnuUj6k3g9ld4vwhtgCGYIFQmsGPPgT9MK/dI3Lwnpe5Gs5t1inoUIoGNP8sIOLlBB4HV4MmBjQuhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.3.tgz", + "integrity": "sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.3.tgz", + "integrity": "sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.3.tgz", + "integrity": "sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.3.tgz", + "integrity": "sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.3.tgz", + "integrity": "sha512-nvehQVEOdI1BleJpuUgPLrclJ0TzbEMc+MarXDmmiRFwEUGqj+pnfkTSb7RZyS1puU74IXdK/YhTirHurtbI9w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.3.tgz", + "integrity": "sha512-A+JSKGkRbPLVV2Kwx8TaDAV0yXIXm/gc8m98hSkVDGlPBBmydgzNdWy3X7HTUBM7IDk7YlWE7w2+RUGjdgpTmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@swc/types": { + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz", + "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.11", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@types/node": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", + "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dev": true, + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001731", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", + "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "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, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.192", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", + "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==", + "dev": true, + "license": "ISC" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/fetch-cookie": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", + "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", + "license": "Unlicense", + "dependencies": { + "set-cookie-parser": "^2.4.8", + "tough-cookie": "^4.0.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/lmdb": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.8.5.tgz", + "integrity": "sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "msgpackr": "^1.9.5", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.1.1", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "2.8.5", + "@lmdb/lmdb-darwin-x64": "2.8.5", + "@lmdb/lmdb-linux-arm": "2.8.5", + "@lmdb/lmdb-linux-arm64": "2.8.5", + "@lmdb/lmdb-linux-x64": "2.8.5", + "@lmdb/lmdb-win32-x64": "2.8.5" + } + }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "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, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true, + "license": "MIT" + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/msgpackr": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/msgpackr-extract/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/msgpackr-extract/node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "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" + } + ], + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ordered-binary": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", + "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/parcel": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.15.4.tgz", + "integrity": "sha512-eZHQ/omuQ7yBYB9XezyzSqhc826oy/uhloCNiej1CTZ+twAqJVtp4MRvTGMcivKhE+WE8QkYD5XkJHLLQsJQcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/config-default": "2.15.4", + "@parcel/core": "2.15.4", + "@parcel/diagnostic": "2.15.4", + "@parcel/events": "2.15.4", + "@parcel/feature-flags": "2.15.4", + "@parcel/fs": "2.15.4", + "@parcel/logger": "2.15.4", + "@parcel/package-manager": "2.15.4", + "@parcel/reporter-cli": "2.15.4", + "@parcel/reporter-dev-server": "2.15.4", + "@parcel/reporter-tracer": "2.15.4", + "@parcel/utils": "2.15.4", + "chalk": "^4.1.2", + "commander": "^12.1.0", + "get-port": "^4.2.0" + }, + "bin": { + "parcel": "lib/bin.js" + }, + "engines": { + "node": ">= 16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "dev": true, + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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, + "license": "MIT" + }, + "node_modules/pbkdf2": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" + } + }, + "node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^2.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.16.0.tgz", + "integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-toastify": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", + "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5c3b6e7 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "vfdio", + "version": "0.0.1", + "description": "Virtual FDIO Emulation for VATSIM/vNAS", + "source": "src/index.html", + "scripts": { + "dev": "parcel src/index.html -p 3000", + "start": "parcel src/index.html -p 3000", + "build": "parcel build", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@types/node": "^24.1.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.1", + "events": "^3.3.0", + "os-browserify": "^0.3.0", + "parcel": "^2.15.4", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "stream-browserify": "^3.0.0", + "string_decoder": "^1.3.0", + "vm-browserify": "^1.1.2" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.0.0", + "@fortawesome/free-solid-svg-icons": "^6.0.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "@microsoft/signalr": "^8.0.0", + "@reduxjs/toolkit": "^2.0.0", + "@tailwindcss/postcss": "^4.1.11", + "jose": "^5.0.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-redux": "^9.0.0", + "react-router-dom": "^6.0.0", + "react-toastify": "^10.0.0", + "tailwindcss": "^4.1.11" + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..bff42e4 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,1040 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { Provider } from 'react-redux'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { store } from './redux/store'; +import { HubContextProvider } from './contexts/HubContext'; +import LoginProvider from './login/Login'; +import { useRootDispatch, useRootSelector } from './redux/hooks'; +import { getVnasConfig, vatsimTokenSelector, sessionSelector, logoutThunk, hubConnectedSelector } from './redux/slices/authSlice'; +import { useHubConnector } from './hooks/useHubConnector'; +import Header from './components/Header'; +import InputArea from './components/InputArea'; +import Recat from './components/Recat'; +import './styles/terminal.css'; +import { isTypedArray } from 'util/types'; +import type { ApiFlightplan } from './types/apiTypes/apiFlightplan'; + +const AppContent = () => { + const dispatch = useRootDispatch(); + const vatsimToken = useRootSelector(vatsimTokenSelector); + const session = useRootSelector(sessionSelector); + + useEffect(() => { + dispatch(getVnasConfig()); + }, [dispatch]); + + const MainApp = () => { + const [command, setCommand] = useState(''); + const [lastFeedback, setLastFeedback] = useState(''); + const [lastFeedbackErrorMessage, setLastFeedbackErrorMessage] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + const { sendCommand, disconnectHub, deleteFlightplan, amendFlightplan, requestFlightStrip, flightplans, flightStrips, hubConnection } = useHubConnector(); + const hubConnected = useRootSelector(hubConnectedSelector); + + + // Blink cursor + maintain focus + const [cursorVisible, setCursorVisible] = useState(true); + useEffect(() => { + const blinkInterval = setInterval(() => { + setCursorVisible((prev) => !prev); + }, 300); + + const ensureFocus = () => { + if (document.activeElement !== terminalInputRef.current) { + terminalInputRef.current?.focus(); + } + }; + + const focusInterval = setInterval(ensureFocus, 500); + ensureFocus(); + + return () => { + clearInterval(blinkInterval); + clearInterval(focusInterval); + }; + }, []); + // end blinking cursor/focus section + + // handle ESC clear, need to define response areas handling, REMOVE placeholder text and replace with '' when ready to deploy. + const [responseTop, setResponseTop] = useState(''); + const [responseBottom, setResponseBottom] = useState(''); + + const handleEscapeClear = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + setTypedCommand(''); + setResponseTop(''); + setResponseBottom(''); + setLastFeedback(''); + } + }; + + useEffect(() => { + window.addEventListener('keydown', handleEscapeClear); + return () => window.removeEventListener('keydown', handleEscapeClear); + }, []); + + // Listen for ReceiveStripItems events and display formatted strips + useEffect(() => { + if (!hubConnection) return; + + const handleStripPrint = (topic: any, stripItems: any[]) => { + stripItems.forEach(strip => { + if (strip?.fieldValues) { + // Format using fieldValues from strip data based on ERAM strip layout + // fieldValues: [0:callsign, 1:rev, 2:?, 3:type/equip, 4:cid, 5:beacon, 6:proptime, 7:alt, 8:dep/arr, 9-10:?, 11:route, 12:remarks] + const fieldValues = strip.fieldValues; + + // Fixed column positions based on ERAM reference (80 char width) + // Line 1: Aircraft ID (1-17), Beacon (19-23), Departure Point (25-36), Route (41-80) + const line1_aircraftId = (fieldValues[0] || '').substring(0, 7).padEnd(14); + const line1_beacon = (fieldValues[5] || '').substring(0, 4).padEnd(6); + const line1_depPoint = (fieldValues[8]?.split(' ')[0] || '').substring(0, 7).padEnd(9); + + // Remove embedded newlines from route (both literal \n and escaped \\n) and replace with spaces + let route = (fieldValues[11] || ''); + route = route.replace(/\\n/g, ' ').replace(/\n/g, ' '); + let line1_route = route.substring(0, 40); + let line2_route = ''; + + console.log('Route info:', { fullRoute: route, length: route.length, needsContinuation: route.length > 40 }); + + // Line 2: Revision Number (starts at position 3) + const line2 = ' ' + (fieldValues[1] || ''); + + // Line 3: Aircraft Type/Equipment (starts at column 1) + const line3_typeEquip = (fieldValues[3] || '').substring(0, 14).padEnd(14); + const line3_time = (fieldValues[6] || '').substring(0, 6).padEnd(6); + + // Line 4: CID (1-17), Altitude (19-23), Remarks (41-80) + const line4_cid = (fieldValues[4] || '').substring(0, 4).padEnd(14); + const line4_altitude = (fieldValues[7] || '').substring(0, 4).padEnd(15); + let line4_remarks = (fieldValues[12] || '').substring(0, 40); + if (route.split('○').length > 1) { + line4_remarks = `○${route.split('○')[1]}`.substring(0, 40); + route = route.split('○')[0]; // Show only the part before the ○ in the route field + } + // Route continuation - if route > 40 chars, continuation appears at column 41 on line 2 + // But revision number ALSO appears on line 2 at column 3 + //let line2_full = line2; + let line1_route_display = line1_route; + + if (route.length > 40) { + const first40 = route.slice(0, 40); + const lastSpace = first40.lastIndexOf(' '); + + const splitIndex = lastSpace !== -1 ? lastSpace : 40; + + // Line 1 shows route up to the word boundary + line1_route_display = route.slice(0, splitIndex); + // Line 2 shows ONLY the continuation, padded so it aligns at column 41 + const secondLine = route.slice(splitIndex).trimStart(); + line2_route = secondLine; + } + + // Build strip: Line1 + Line2(revision + route cont) + Line3(type/time) + Line4(cid/alt/remarks) + const formattedStrip = + line1_aircraftId + line1_beacon + line1_depPoint + line1_route_display + '\n' + + line2 + '\n' + + line3_typeEquip + line3_time + line2_route + '\n\n' + + line4_cid + line4_altitude + line4_remarks; + + console.log('ReceiveStripItems - Final formatted strip:', formattedStrip); + + // Move current responseBottom to responseTop and set new strip to responseBottom + setResponseTop(responseBottom); + setResponseBottom(formattedStrip); + } + }); + }; + + hubConnection.on('ReceiveStripItems', handleStripPrint); + + return () => { + hubConnection.off('ReceiveStripItems', handleStripPrint); + }; + }, [hubConnection, responseBottom]); + + + const handleCommandSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (command.trim() && !isProcessing) { + setIsProcessing(true); + setLastFeedback(''); // Clear previous feedback + + try { + const result = await sendCommand(command.trim()); + setLastFeedback(`${command.toUpperCase()}\n\n${result.toUpperCase()}`); + setCommand(''); + } catch (error) { + console.error('Failed to send command:', error); + setLastFeedback(`REJECT ${command.toUpperCase()}\n\n${error}`.toUpperCase()); + } finally { + setIsProcessing(false); + } + } + }; + + const [typedCommand, setTypedCommand] = useState(''); + const terminalInputRef = React.useRef(null); + + useEffect(() => { + terminalInputRef.current?.focus(); + }, []); + + const parseCommand = async (input: string): Promise => { + const [command, ...args] = input.trim().split(/\s+/).map(s => s.toUpperCase()); + + // Helper function to find flightplan by callsign, CID, or beacon code + const findFlightplan = (identifier: string): ApiFlightplan | undefined => { + if (!flightplans) return undefined; + + // Try direct callsign match first + let fp = flightplans.get(identifier); + if (fp) return fp; + + // Search by CID or beacon code + for (const [, flightplan] of flightplans) { + if (flightplan.cid === identifier || + flightplan.assignedBeaconCode?.toString() === identifier) { + return flightplan; + } + } + + return undefined; + }; + + switch (command) { + case 'FP': { + // Flight Plan message - FP + // Format: FP ACID TYPE/EQUIP SPEED FIX TIME ALT ROUTE REMARKS + // Example: FP UAL423 B721/A 450 HAR P1720 170 HAR.V14.DCA O 1 VOR INOP + const fpMatch = /^FP\s+(.+)$/i.exec(input.trim()); + if (!fpMatch) { + return `REJECT 01 MSG INVALID\nMESSAGE TYPE`; + } + + const fields = fpMatch[1].split(/\s+/); + if (fields.length < 7) { + return `REJECT FORMAT - INSUFFICIENT FIELDS\n${input}`; + } + + // Field 02: Aircraft ID + const aircraftId = fields[0]; + if (aircraftId.length < 2 || aircraftId.length > 20) { + return `REJECT 02 AID FLID\nFORMAT`; + } + + // Field 03: Aircraft Type / Equipment Suffix + const typeEquipMatch = fields[1].match(/^([A-Z0-9]+)\/([A-Z])$/); + if (!typeEquipMatch) { + return `REJECT 03 TYP FORMAT`; + } + const aircraftType = typeEquipMatch[1]; + const equipmentSuffix = typeEquipMatch[2]; + + // Field 05: Speed + const speed = parseInt(fields[2]); + if (isNaN(speed) || speed <= 0) { + return `REJECT 05 SPD ILLEGAL`; + } + if (speed > 3700) { + return `REJECT 05 SPD FORMAT`; + } + + // Field 06: Departure Fix (Coordination Fix) + const departureFix = fields[3]; + if (departureFix.length < 2 || departureFix.length > 12) { + return `REJECT 06 FIX FORMAT`; + } + + // Field 07: Time + const timeStr = fields[4]; + let departureTime = 0; + if (timeStr !== 'E' && timeStr !== 'P' && timeStr !== 'D') { + // Parse time - should be 4 or 5 characters (HHMM or PXXDD format) + const timeMatch = timeStr.match(/^[PE]?(\d{4})$/); + if (!timeMatch) { + return `REJECT 07 TIM FORMAT`; + } + departureTime = parseInt(timeMatch[1]); + } + + // Field 08 or 09: Altitude (Assigned or Requested) + const altStr = fields[5]; + let altitude = ''; + if (altStr === 'OTP' || altStr === 'VFR') { + altitude = altStr; + } else { + // Parse altitude - format like 170 (FL170), OTP/115, VFR/75, etc. + const altMatch = altStr.match(/^(\d+|OTP|VFR)(\/(\d+))?$/); + if (!altMatch) { + return `REJECT 08 ALT FORMAT`; + } + altitude = altStr; + } + + // Field 10: Route - everything from field 6 onwards until we hit remarks + // Route ends when we see 'O' or '@' prefix for remarks + let routeEndIdx = 6; + for (let i = 6; i < fields.length; i++) { + if (fields[i].startsWith('O') || fields[i].startsWith('@')) { + routeEndIdx = i; + break; + } + routeEndIdx = i + 1; + } + + const routeParts = fields.slice(6, routeEndIdx); + if (routeParts.length === 0) { + return `REJECT 10 RTE FORMAT`; + } + const route = routeParts.join(' '); + + // Field 11: Remarks (optional) + let remarks = ''; + if (routeEndIdx < fields.length) { + const remarksFields = fields.slice(routeEndIdx); + // Check if remarks start with O or @ + const remarksStr = remarksFields.join(' '); + if (remarksStr.startsWith('O ')) { + remarks = remarksStr.substring(2); // Interfacility remarks + } else if (remarksStr.startsWith('@')) { + remarks = remarksStr.substring(1); // Intrafacility remarks + } else { + remarks = remarksStr; + } + } + + try { + // Check for duplicate active flight plan + const existingFp = flightplans.get(aircraftId); + if (existingFp && existingFp.status === 'Active') { + return `REJECT 02 AID FLID\nDUPLICATION`; + } + + await amendFlightplan({ + aircraftId, + cid: '', + status: 'Proposed', + aircraftType: aircraftType, + faaEquipmentSuffix: equipmentSuffix, + equipment: `${aircraftType}/${equipmentSuffix}`, + icaoEquipmentCodes: '', + icaoSurveillanceCodes: '', + speed, + altitude, + departure: departureFix, + destination: '', + alternate: '', + route, + remarks, + assignedBeaconCode: null, + estimatedDepartureTime: departureTime, + actualDepartureTime: 0, + hoursEnroute: 0, + minutesEnroute: 0, + fuelHours: 0, + fuelMinutes: 0, + pilotCid: '', + holdAnnotations: null, + wakeTurbulenceCode: '', + }); + + return `ACCEPT\n${aircraftId}`; + } catch (error) { + console.error('Failed to create flightplan:', error); + const errorStr = String(error); + if (errorStr.includes('Not your control') || errorStr.includes('inactive session')) { + return `REJECT 01 MSG ILLEGAL\nSOURCE`; + } else { + return `REJECT FP ENTRY FAILED`; + } + } + } + + case 'AM': { + // Amendment Message - AM [ ...] + // Format: AM ACID 06 FIX 10 ROUTE or AM ACID SPD 225 RAL 90 + // Field references: AID, TYP, BCN, SPD, FIX, TIM, ALT, RAL, RTE, RMK + const amMatch = /^AM\s+(.+)$/i.exec(input.trim()); + if (!amMatch) { + return `REJECT 01 MSG INVALID\nMESSAGE TYPE`; + } + + const parts = amMatch[1].split(/\s+/); + if (parts.length < 3) { + return `REJECT FORMAT - INSUFFICIENT FIELDS\n${input}`; + } + + const aircraftId = parts[0]; + + // Find the existing flightplan + const existingFp = findFlightplan(aircraftId); + if (!existingFp) { + return `REJECT 02 FLID NOT\nSTORED`; + } + + // Parse field references and amendments + const amendments: { [key: string]: any } = {}; + let i = 1; + let amendingAircraftId = false; + + while (i < parts.length) { + const fieldRef = parts[i].toUpperCase(); + + // Map field references to field numbers + const fieldMap: { [key: string]: string } = { + 'AID': '02', '02': '02', '2': '02', + 'TYP': '03', '03': '03', '3': '03', + 'BCN': '04', '04': '04', '4': '04', + 'SPD': '05', '05': '05', '5': '05', + 'FIX': '06', '06': '06', '6': '06', + 'TIM': '07', '07': '07', '7': '07', + 'ALT': '08', '08': '08', '8': '08', + 'RAL': '09', '09': '09', '9': '09', + 'RTE': '10', '10': '10', + 'RMK': '11', '11': '11' + }; + + const fieldNum = fieldMap[fieldRef]; + if (!fieldNum) { + return `REJECT INVALID FIELD\nREFERENCE`; + } + + i++; + if (i >= parts.length) { + return `REJECT FORMAT - MISSING AMENDMENT DATA\n${input}`; + } + + // Check if amending Field 02 (Aircraft ID) + if (fieldNum === '02') { + if (Object.keys(amendments).length > 0) { + return `REJECT - INVALID\nAMENDMENT`; + } + amendingAircraftId = true; + amendments['aircraftId'] = parts[i]; + i++; + break; // Only Field 02 can be amended when changing aircraft ID + } + + // Collect amendment data for this field + let amendmentData: string[] = []; + + // For route (10/RTE), collect all remaining parts until we hit another field ref or end + if (fieldNum === '10') { + while (i < parts.length) { + const nextToken = parts[i].toUpperCase(); + if (fieldMap[nextToken]) { + break; // Hit another field reference + } + amendmentData.push(parts[i]); + i++; + } + + if (amendmentData.length === 0) { + return `REJECT 10 RTE FORMAT`; + } + + // Process route amendment based on ERAM rules + const routeStr = amendmentData.join(' '); + const existingRoute = existingFp.route || ''; + const routeElements = routeStr.split(/[\s.]+/).filter(e => e.length > 0); + + // Check for departure fix change (single element followed by ↑) + if (routeStr.endsWith('↑') || routeStr.endsWith('^')) { + const newDepFix = routeStr.slice(0, -1).trim().split(/[\s.]+/)[0]; + amendments['departure'] = newDepFix; + amendments['route'] = routeStr.slice(0, -1).trim(); + } + // Check for complete replacement (ends with ↓) + else if (routeStr.endsWith('↓') || routeStr.endsWith('v')) { + const newRoute = routeStr.slice(0, -1).trim(); + const newRouteElements = newRoute.split(/[\s.]+/).filter(e => e.length > 0); + if (newRouteElements.length > 0) { + // Last element becomes destination + amendments['destination'] = newRouteElements[newRouteElements.length - 1]; + // For active flights, departure fix retained with tailoring symbol (/) + if (existingFp.status === 'Active' && existingFp.departure) { + amendments['route'] = `${existingFp.departure}/.${newRoute}`; + } else { + amendments['route'] = newRoute; + } + } else { + return `REJECT 10 RTE FORMAT`; + } + } + // Tailoring symbol at beginning (/) - insert after departure fix + else if (routeStr.startsWith('/')) { + const tailoredRoute = routeStr.substring(1).trim(); + if (existingFp.departure) { + amendments['route'] = `${existingFp.departure}/.${tailoredRoute}`; + } else { + amendments['route'] = tailoredRoute; + } + } + // Merge with existing route - match first or last unambiguous element + else { + const firstElement = routeElements[0]; + const lastElement = routeElements[routeElements.length - 1]; + const existingElements = existingRoute.split(/[\s.]+/).filter(e => e.length > 0); + + // Try to find first element match + const firstMatchIdx = existingElements.indexOf(firstElement); + const lastMatchIdx = existingElements.lastIndexOf(lastElement); + + // Check if BOTH first and last match (replace between) + if (firstMatchIdx !== -1 && lastMatchIdx !== -1 && firstMatchIdx < lastMatchIdx) { + const before = existingElements.slice(0, firstMatchIdx).join('.'); + const after = existingElements.slice(lastMatchIdx + 1).join('.'); + const merged = [before, routeStr, after].filter(p => p.length > 0).join('.'); + amendments['route'] = merged; + } + // Only first element matches (replace after) + else if (firstMatchIdx !== -1) { + const before = existingElements.slice(0, firstMatchIdx + 1).join('.'); + amendments['route'] = `${before}.${routeElements.slice(1).join('.')}`; + } + // Only last element matches (replace before) + else if (lastMatchIdx !== -1) { + const after = existingElements.slice(lastMatchIdx).join('.'); + // For active flight, add tailoring symbol + if (existingFp.status === 'Active' && existingFp.departure) { + amendments['route'] = `${existingFp.departure}/.${routeElements.slice(0, -1).join('.')}.${after}`; + } else { + amendments['route'] = `${routeElements.slice(0, -1).join('.')}.${after}`; + } + } + // No match - just use new route + else { + amendments['route'] = routeStr; + } + } + + // Check if Field 06 also needs amendment (required for Field 10 amendments per ERAM rules) + // This will be validated when Field 06 is also in the amendment + } + // For other fields, just take the next token + else { + amendmentData.push(parts[i]); + i++; + + const value = amendmentData[0]; + + switch (fieldNum) { + case '03': // Type/Equipment + const typeMatch = value.match(/^([A-Z0-9]+)\/([A-Z])$/); + if (!typeMatch) { + return `REJECT 03 TYP FORMAT`; + } + const newAircraftType = typeMatch[1]; + const newFaaEquipmentSuffix = typeMatch[2]; + + // Update equipment field by replacing only the aircraft type (before first /) + // and preserving everything after the first / + // Format: "B738/M-VGDW/C" -> "B752/M-VGDW/C" + let newEquipment = `${newAircraftType}/${newFaaEquipmentSuffix}`; + if (existingFp.equipment) { + const firstSlashIndex = existingFp.equipment.indexOf('/'); + if (firstSlashIndex > 0) { + // Preserve everything after the first / + const everythingAfterSlash = existingFp.equipment.substring(firstSlashIndex + 1); + newEquipment = `${newAircraftType}/${everythingAfterSlash}`; + } + } + + amendments['equipment'] = newEquipment; + amendments['faaEquipmentSuffix'] = newFaaEquipmentSuffix; + break; + + case '04': // Beacon Code + const beaconCode = parseInt(value); + if (isNaN(beaconCode) || beaconCode < 0 || beaconCode > 7777) { + return `REJECT 04 BCN CODE FORMAT`; + } + amendments['assignedBeaconCode'] = beaconCode; + break; + + case '05': // Speed + const speed = parseInt(value); + if (isNaN(speed) || speed <= 0) { + return `REJECT 05 SPD ILLEGAL`; + } + if (speed > 3700) { + return `REJECT 05 SPD FORMAT`; + } + amendments['speed'] = speed; + break; + + case '06': // Departure Fix + if (value.length < 2 || value.length > 12) { + return `REJECT 06 FIX FORMAT`; + } + amendments['departure'] = value; + break; + + case '07': // Time + if (value !== 'E' && value !== 'P' && value !== 'D') { + const timeMatch = value.match(/^[PE]?(\d{4})$/); + if (!timeMatch) { + return `REJECT 07 TIM FORMAT`; + } + amendments['estimatedDepartureTime'] = parseInt(timeMatch[1]); + } + break; + + case '08': // Assigned Altitude + amendments['altitude'] = value; + break; + + case '09': // Requested Altitude (RAL) + // Store as altitude for now + amendments['altitude'] = value; + break; + + case '11': // Remarks + // Collect all remaining parts as remarks + while (i < parts.length) { + const nextToken = parts[i].toUpperCase(); + if (fieldMap[nextToken]) { + break; + } + amendmentData.push(parts[i]); + i++; + } + let remarks = amendmentData.join(' '); + // Remove O or @ prefix if present + if (remarks.startsWith('O ')) { + remarks = remarks.substring(2); + } else if (remarks.startsWith('@')) { + remarks = remarks.substring(1); + } + amendments['remarks'] = remarks; + break; + } + } + } + + if (Object.keys(amendments).length === 0) { + return `REJECT FORMAT - NO VALID AMENDMENTS\n${input}`; + } + + try { + // Build the amendment DTO, preserving existing values + const amendDto: any = { + aircraftId: amendments['aircraftId'] || existingFp.aircraftId, + cid: existingFp.cid, + status: existingFp.status, + aircraftType: amendments['aircraftType'] || existingFp.aircraftType, + faaEquipmentSuffix: amendments['faaEquipmentSuffix'] || existingFp.faaEquipmentSuffix, + equipment: amendments['equipment'] || existingFp.equipment, + icaoEquipmentCodes: existingFp.icaoEquipmentCodes, + icaoSurveillanceCodes: existingFp.icaoSurveillanceCodes, + speed: amendments['speed'] ?? existingFp.speed, + altitude: amendments['altitude'] || existingFp.altitude, + departure: amendments['departure'] || existingFp.departure, + destination: amendments['destination'] || existingFp.destination, + alternate: existingFp.alternate, + route: amendments['route'] || existingFp.route, + remarks: amendments['remarks'] !== undefined ? amendments['remarks'] : existingFp.remarks, + assignedBeaconCode: amendments['assignedBeaconCode'] ?? existingFp.assignedBeaconCode, + estimatedDepartureTime: amendments['estimatedDepartureTime'] ?? existingFp.estimatedDepartureTime, + actualDepartureTime: existingFp.actualDepartureTime, + hoursEnroute: existingFp.hoursEnroute, + minutesEnroute: existingFp.minutesEnroute, + fuelHours: existingFp.fuelHours, + fuelMinutes: existingFp.fuelMinutes, + pilotCid: existingFp.pilotCid, + holdAnnotations: existingFp.holdAnnotations, + wakeTurbulenceCode: existingFp.wakeTurbulenceCode, + }; + + console.log('AM Command Debug:'); + console.log(' Amendments:', amendments); + console.log(' Existing FP equipment:', existingFp.equipment); + console.log(' Existing FP faaEquipmentSuffix:', existingFp.faaEquipmentSuffix); + console.log(' New equipment:', amendDto.equipment); + console.log(' New faaEquipmentSuffix:', amendDto.faaEquipmentSuffix); + + await amendFlightplan(amendDto); + + return `ACCEPT ${amendDto.aircraftId}/${amendDto.cid}`; + } catch (error) { + console.error('Failed to amend flightplan:', error); + const errorStr = String(error); + if (errorStr.includes('Not your control') || errorStr.includes('inactive session')) { + return `REJECT 01 MSG ILLEGAL\nSOURCE`; + } else { + return `REJECT - INVALID\nAMENDMENT`; + } + } + } + + case 'GI': { + // General Information message - GI + const giMatch = /^GI\s+(\S+)\s+(.+)$/i.exec(input.trim()); + if (giMatch && giMatch.length === 3) { + const recipient = giMatch[1].toUpperCase(); + const message = giMatch[2]; + // TODO: Implement GI message sending via your hub/socket + return `ACCEPT GI TO ${recipient}\n${message}`; + } else { + return `REJECT FORMAT\n${input}`; + } + } + + case 'WR': { + // Weather Request - WR + if (args.length !== 1) { + return `REJECT FORMAT\n${input}`; + } + const station = args[0]; + + try { + // Fetch METAR from VATSIM metar API (same as EDST) + const response = await fetch( + `https://metar.vatsim.net/${station}` + ); + + if (!response.ok) { + return `REJECT WEATHER STAT REQ\nSTATION NOT FOUND`; + } + + const metar = await response.text(); + + if (!metar || metar.trim() === '' || metar.includes('No METAR')) { + return `REJECT WEATHER STAT REQ\nNO DATA FOR ${station}`; + } + + return `ACCEPT WEATHER STAT REQ\n${metar.trim()}`; + } catch (error) { + console.error('Failed to fetch METAR:', error); + return `REJECT WEATHER STAT REQ\nFETCH FAILED`; + } + } + + case 'SR': { + // Strip Request - SR + if (args.length !== 1) { + return `REJECT FORMAT\n${input}`; + } + const identifier = args[0]; + + // Find the aircraft (by callsign, CID, or beacon) + let aircraftId = identifier; + let strip = flightStrips?.get(identifier); + + if (!strip && flightStrips) { + // Search by CID or beacon code + for (const [id, s] of flightStrips) { + if (s.fieldValues && ( + s.fieldValues[0] === identifier || + s.fieldValues[4] === identifier || + s.fieldValues[5] === identifier)) { + strip = s; + aircraftId = id; + break; + } + } + } + + // Always request from server first - this triggers ReceiveStripItems event + try { + console.log('SR: Requesting flight strip for:', aircraftId); + await requestFlightStrip(aircraftId); + console.log('SR: RequestFlightStrip succeeded for:', aircraftId); + } catch (error) { + console.warn('SR: RequestFlightStrip failed:', error); + // If server request fails and we don't have a local copy, reject + if (!strip?.fieldValues) { + return `REJECT\nSTRIP NOT FOUND\n${input}`; + } + } + + // If we have a local copy, display it immediately (server response will update via ReceiveStripItems) + if (strip?.fieldValues) { + // Format using fieldValues from strip data based on ERAM strip layout + // fieldValues: [0:callsign, 1:rev, 2:?, 3:type/equip, 4:cid, 5:beacon, 6:proptime, 7:alt, 8:dep/arr, 9-10:?, 11:route, 12:remarks] + const fieldValues = strip.fieldValues; + + // Fixed column positions based on ERAM reference (80 char width) + // Line 1: Aircraft ID (1-17), Beacon (19-23), Departure Point (25-36), Route (41-80) + const line1_aircraftId = (fieldValues[0] || '').substring(0, 7).padEnd(14); + const line1_beacon = (fieldValues[5] || '').substring(0, 4).padEnd(6); + const line1_depPoint = (fieldValues[8]?.split(' ')[0] || '').substring(0, 7).padEnd(9); + + // Remove embedded newlines from route (both literal \n and escaped \\n) and replace with spaces + let route = (fieldValues[11] || ''); + route = route.replace(/\\n/g, ' ').replace(/\n/g, ' '); + let line1_route = route.substring(0, 40); + let line2_route = ''; + + console.log('Route info:', { fullRoute: route, length: route.length, needsContinuation: route.length > 40 }); + + // Line 2: Revision Number (starts at position 3) + const line2 = ' ' + (fieldValues[1] || ''); + + // Line 3: Aircraft Type/Equipment (starts at column 1) + const line3_typeEquip = (fieldValues[3] || '').substring(0, 14).padEnd(14); + const line3_time = (fieldValues[6] || '').substring(0, 6).padEnd(6); + + // Line 4: CID (1-17), Altitude (19-23), Remarks (41-80) + const line4_cid = (fieldValues[4] || '').substring(0, 4).padEnd(14); + const line4_altitude = (fieldValues[7] || '').substring(0, 4).padEnd(15); + let line4_remarks = (fieldValues[12] || '').substring(0, 40); + if (route.split('○').length > 1) { + line4_remarks = `○${route.split('○')[1]}`.substring(0, 40); + route = route.split('○')[0]; // Show only the part before the ○ in the route field + } + // Route continuation - if route > 40 chars, continuation appears at column 41 on line 2 + // But revision number ALSO appears on line 2 at column 3 + //let line2_full = line2; + let line1_route_display = line1_route; + + if (route.length > 40) { + const first40 = route.slice(0, 40); + const lastSpace = first40.lastIndexOf(' '); + + const splitIndex = lastSpace !== -1 ? lastSpace : 40; + + // Line 1 shows route up to the word boundary + line1_route_display = route.slice(0, splitIndex); + // Line 2 shows ONLY the continuation, padded so it aligns at column 41 + const secondLine = route.slice(splitIndex).trimStart(); + line2_route = secondLine; + } + + // Build strip: Line1 + Line2(revision + route cont) + Line3(type/time) + Line4(cid/alt/remarks) + const formattedStrip = + line1_aircraftId + line1_beacon + line1_depPoint + line1_route_display + '\n' + + line2 + '\n' + + line3_typeEquip + line3_time + line2_route + '\n\n' + + line4_cid + line4_altitude + line4_remarks; + + // Print the strip (move responseBottom to responseTop, set new strip to responseBottom) + setResponseTop(responseBottom); + setResponseBottom(formattedStrip); + + // Return the formatted strip data + return formattedStrip; + } + + // No local strip but server request succeeded - strip will arrive via ReceiveStripItems event + return `ACCEPT SR ${identifier}\nSTRIP REQUESTED`; + } + + case 'FR': { + // Flight Readout - FR + if (args.length !== 1) { + return `REJECT FORMAT\n${input}`; + } + const identifier = args[0]; + const flightplan = findFlightplan(identifier); + + if (flightplan) { + // Format using ApiFlightplan data + // aircraftID aircraftType assignedBeaconCode speed altitude departure route destination remarks + const cid = flightplan.cid || ''; + const aircraftId = flightplan.aircraftId || ''; + const aircraftType = flightplan.aircraftType || ''; + const beaconCode = flightplan.assignedBeaconCode?.toString() || ''; + const speed = flightplan.speed || ''; + const time = ('P' + flightplan.estimatedDepartureTime) || ''; + const altitude = flightplan.altitude || ''; + const departure = flightplan.departure || ''; + const destination = flightplan.destination || ''; + const remarks = flightplan.remarks || ''; + + // Route - break into 80 char chunks + const route = flightplan.route || ''; + const maxLineLength = 80; + const routeLines: string[] = []; + for (let i = 0; i < route.length; i += maxLineLength) { + routeLines.push(route.substring(i, i + maxLineLength)); + } + + return `${cid} ${aircraftId} ${aircraftType} ${beaconCode} ${speed} ${time} ${altitude} ${departure} ${route} ${destination} ${remarks}`; + + } else { + return `FLID NOT STORED\n${input}`; + } + } + + case 'RS': { + // Remove Strips - RS + if (args.length !== 1) { + return `REJECT FORMAT\n${input}`; + } + const identifier = args[0]; + const flightplan = findFlightplan(identifier); + if (flightplan) { + try { + await deleteFlightplan(flightplan.aircraftId); + return `${flightplan.aircraftId} ${flightplan.cid}REMOVE \nSTRIPS`; + } catch (error) { + console.error('Failed to delete flightplan:', error); + // Parse the error message to extract relevant info + const errorStr = String(error); + if (errorStr.includes('Not your control')) { + return `REJECT NOT YOUR CONTROL\n${flightplan.aircraftId}`; + } else { + return `REJECT DELETE FAILED\n${flightplan.aircraftId}`; + } + } + } else { + return `REJECT FLID NOT STORED\n${input}`; + } + } + + default: + // Send to ERAM hub for all other commands + return await sendCommand(input); + } + }; + + const handleKeyDown = async (e: React.KeyboardEvent) => { + if (isProcessing) return; + + if (e.key === 'Enter') { + e.preventDefault(); + const command = typedCommand.trim(); + if (!command) return; + + setIsProcessing(true); + setLastFeedback(''); + + try { + const result = await parseCommand(command); + console.log(` Command: ${command}, Result:`, result); + + // Check if result is a REJECT - if so, show ONLY in error area + if (result.toUpperCase().startsWith('REJECT')) { + setLastFeedbackErrorMessage(result); + // Don't update responseBottom or responseTop for errors + } else { + // Success - clear errors and update response areas + setLastFeedbackErrorMessage(''); + + // Move current responseBottom to responseTop + setResponseTop(responseBottom); + + // Set new response to responseBottom + setResponseBottom(result); + } + // then dynamically add a scroll effect for each subsequent response's data to cycle it upwards. + } catch (error) { + const errorMsg = `REJECT ${typedCommand.toUpperCase()}\n\n${String(error).toUpperCase()}`; + setLastFeedbackErrorMessage(errorMsg); + // Don't update responseBottom or responseTop for errors + } finally { + setTypedCommand(''); + setIsProcessing(false); + } + } else if (e.key === 'Backspace') { + e.preventDefault(); + setTypedCommand((prev) => prev.slice(0, -1)); + } else if (e.key.length === 1) { + e.preventDefault(); + setTypedCommand((prev) => prev + e.key.toUpperCase()); + } + }; + + return ( +
+ {/* Terminal Header */} +
+ + {/* Terminal Body */} +
+ {/* Response Section (top half) */} + {/* FDIO max character width is 80 */} +
+
+ {responseTop && '================================================================================\n'}{responseTop} +
+
+ {responseBottom && '================================================================================\n'}{responseBottom} +
+
+ {/* Command Section (Bottom Half) */} +
+ -------------------------------------------------------------------------------- + {isProcessing && ( +
M E S S A G E W A I T I N G . . .
+ )} + {lastFeedbackErrorMessage && ( +
  {lastFeedbackErrorMessage.toUpperCase()}
+ )} +
+ + {/* TO DO: BLINKING CURSOR BOX AND FORCED FOCUS */} +
+
+ {typedCommand} + +
+ + {isProcessing && ( +
+ +
+ )} +
+ + {/* Terminal Footer */} +
+
+
+ {/* VNAS HUB: {hubConnected ? 'CONNECTED' : 'DISCONNECTED'} */} +
+ +
+ {/* ARTCC: {session?.artccId?.toUpperCase() || 'N/A'} | STATUS: {session?.isActive ? 'ACTIVE' : 'INACTIVE'} */} +
+ +
+
+
+ ); + }; + + return ( + + + } + /> + + + + ) : ( + + ) + } + /> + + + ); +}; + +export default function App() { + return ( + + + + ); +} \ No newline at end of file diff --git a/src/api/vNasDataApi.ts b/src/api/vNasDataApi.ts new file mode 100644 index 0000000..ba41600 --- /dev/null +++ b/src/api/vNasDataApi.ts @@ -0,0 +1,34 @@ +import { VNAS_CONFIG_URL, VATSIM_CLIENT_ID } from '../utils/constants'; + +type LoginDto = { + nasToken: string; + vatsimToken: string; +}; + +export const login = async (apiBaseUrl: string, code: string, redirectUrl: string) => { + return fetch(`${apiBaseUrl}/auth/login?code=${code}&redirectUrl=${redirectUrl}&clientId=${VATSIM_CLIENT_ID}`, { + credentials: "include", + }).then((response) => { + return response.json().then((data: LoginDto) => ({ + ...data, + statusText: response.statusText, + ok: response.ok, + })); + }); +}; + +export const refreshToken = async (apiBaseUrl: string, vatsimToken: string) => { + return fetch(`${apiBaseUrl}/auth/refresh?vatsimToken=${vatsimToken}`).then((r) => + r.text().then((data) => ({ data, statusText: r.statusText, ok: r.ok })) + ); +}; + +export const fetchVnasConfiguration = async () => { + const response = await fetch(VNAS_CONFIG_URL); + + if (!response.ok) { + throw new Error(`Failed to fetch vNAS configuration: ${response.statusText}`); + } + + return await response.json(); +}; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..ef1430a --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { useRootDispatch } from '../redux/hooks'; +import { logoutThunk } from '../redux/slices/authSlice'; +import { useHubConnector } from '../hooks/useHubConnector'; + +const Header = () => { + const dispatch = useRootDispatch(); + const { disconnectHub } = useHubConnector(); + + const handleLogout = () => { + disconnectHub(); + dispatch(logoutThunk(true)); + }; + + return ( +
+ {/*

FDIO ALPHA

*/} + +
+ + ) +} + +export default Header \ No newline at end of file diff --git a/src/components/InputArea.tsx b/src/components/InputArea.tsx new file mode 100644 index 0000000..a40260e --- /dev/null +++ b/src/components/InputArea.tsx @@ -0,0 +1,72 @@ +import React, { useEffect, useRef, useState } from 'react'; + +const CommandInput = ({ isProcessing = false }: { isProcessing?: boolean }) => { + const terminalInputRef = useRef(null); + const [typedCommand, setTypedCommand] = useState(''); + const [cursorVisible, setCursorVisible] = useState(true); + + useEffect(() => { + const focus = () => terminalInputRef.current?.focus(); + focus(); + + const interval = setInterval(() => { + if (document.activeElement !== terminalInputRef.current) { + focus(); + } + }, 500); + + const blink = setInterval(() => { + setCursorVisible((prev) => !prev); + }, 250); + + return () => { + clearInterval(interval); + clearInterval(blink); + }; + }, []); + + const handleKeyDown = (e: React.KeyboardEvent) => { + e.preventDefault(); + + if (e.key === 'Enter') { + if (typedCommand.trim() !== '') { + console.log('Executing command:', typedCommand); + // Do something here (API call, parsing, etc.) + } + setTypedCommand(''); + } else if (e.key === 'Escape') { + setTypedCommand(''); + } else if (e.key === 'Backspace') { + setTypedCommand((prev) => prev.slice(0, -1)); + } else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) { + setTypedCommand((prev) => prev + e.key.toUpperCase()); + } + }; + + return ( +
+
+ > + {typedCommand} + +
+ + {isProcessing && ( +
+ PROCESSING... +
+ )} +
+ ); +}; + +export default CommandInput; diff --git a/src/components/Recat.tsx b/src/components/Recat.tsx new file mode 100644 index 0000000..f2537bb --- /dev/null +++ b/src/components/Recat.tsx @@ -0,0 +1,9 @@ +const Recat = () => { + return ( +
+

RECAT: ON v3.0.5

+
+ ) +} + +export default Recat \ No newline at end of file diff --git a/src/contexts/HubContext.tsx b/src/contexts/HubContext.tsx new file mode 100644 index 0000000..c769200 --- /dev/null +++ b/src/contexts/HubContext.tsx @@ -0,0 +1,505 @@ +import type { ReactNode } from "react"; +import React, { createContext, useCallback, useEffect, useRef, useState, useMemo } from "react"; +import { useNavigate } from "react-router-dom"; +import type { HubConnection } from "@microsoft/signalr"; +import { HttpTransportType, HubConnectionBuilder } from "@microsoft/signalr"; +import type { Nullable } from "../types/utility-types"; +import { + clearSession, + envSelector, + setSession, + vatsimTokenSelector, + setSessionIsActive, + setHubConnected, + hubConnectedSelector, + logout, +} from "../redux/slices/authSlice"; +import { refreshToken } from "../api/vNasDataApi"; +import type { ApiSessionInfoDto } from "../types/apiTypes/apiSessionInfoDto"; +import type { ApiFlightplan, CreateOrAmendFlightplanDto } from "../types/apiTypes/apiFlightplan"; +import { ApiTopic } from "../types/apiTypes/apiTopic"; +import { updateFlightplanThunk, deleteFlightplanThunk, initThunk } from "../redux/thunks"; +import { openWindowThunk } from "../redux/thunks/windowThunks"; +import { addOutageMessage, delOutageMessage, setFsdIsConnected } from "../redux/slices/appSlice"; +import { setMcaAcceptMessage, setMcaRejectMessage, setMraMessage } from "../redux/slices/mcaSlice"; +import { setArtccId, setSectorId } from "../redux/slices/sectorSlice"; +import { useRootDispatch, useRootSelector } from "../redux/hooks"; +import { useSocketConnector } from "../hooks/useSocketConnector"; +import { VERSION } from "../utils/constants"; +import { OutageEntry } from "../types/outageEntry"; +import { HubConnectionState } from "@microsoft/signalr/dist/esm/HubConnection"; +import { invokeHub } from "../utils/hubUtils"; +import { type ProcessEramMessageDto, type EramMessageProcessingResultDto, EramPositionType } from "../types/apiTypes/eramTypes"; + +// Simple toast for now +const toast = { + error: (message: string) => console.error(message) +}; + +type HubContextValue = { + connectHub: () => Promise; + disconnectHub: () => Promise; + hubConnection: HubConnection | null; + sendEramMessage: (eramMessage: ProcessEramMessageDto) => Promise; + sendCommand: (command: string) => Promise; + amendFlightplan: (fp: CreateOrAmendFlightplanDto) => Promise; + deleteFlightplan: (aircraftId: string) => Promise; + requestFlightStrip: (aircraftId: string) => Promise; + flightplans: Map; + flightStrips: Map; +}; + +export const HubContext = createContext({ + connectHub: () => Promise.reject(new Error('HubContext not initialized')), + disconnectHub: () => Promise.reject(new Error('HubContext not initialized')), + hubConnection: null, + sendEramMessage: () => Promise.reject(new Error('HubContext not initialized')), + sendCommand: () => Promise.reject(new Error('HubContext not initialized')), + amendFlightplan: () => Promise.reject(new Error('HubContext not initialized')), + deleteFlightplan: () => Promise.reject(new Error('HubContext not initialized')), + requestFlightStrip: () => Promise.reject(new Error('HubContext not initialized')), + flightplans: new Map(), + flightStrips: new Map(), +}); + +export const HubContextProvider = ({ children }: { children: ReactNode }) => { + const dispatch = useRootDispatch(); + const vatsimToken = useRootSelector(vatsimTokenSelector)!; + const ref = useRef>(null); + const { disconnectSocket } = useSocketConnector(); + const env = useRootSelector(envSelector); + const navigate = useNavigate(); + const hubConnected = useRootSelector(hubConnectedSelector); + const [flightplans, setFlightplans] = useState>(new Map()); + const [flightStrips, setFlightStrips] = useState>(new Map()); + const [facilityId, setFacilityId] = useState(""); + + const disconnectHub = useCallback(async () => { + try { + await ref.current?.stop(); + dispatch(setHubConnected(false)); + dispatch(setArtccId("")); + dispatch(setSectorId("")); + + try { + disconnectSocket(); + } catch (error) { + console.warn("Error disconnecting socket:", error); + } + + dispatch(clearSession()); + dispatch(logout()); + navigate("/login", { replace: true }); + } catch (error) { + console.error("Error during hub disconnect:", error); + navigate("/login", { replace: true }); + } + }, [disconnectSocket, dispatch, navigate]); + + const handleSessionStart = useCallback( + async (sessionInfo: ApiSessionInfoDto, hubConnection: HubConnection) => { + if (!sessionInfo || sessionInfo.isPseudoController) { + return; + } + + try { + const primaryPosition = sessionInfo.positions.find((p) => p.isPrimary)?.position; + + if (!primaryPosition) { + throw new Error("No primary position found"); + } + + const artccId = sessionInfo.artccId; + // For ERAM positions, use the sectorId; for other positions like tower, use the callsign + const sectorId = primaryPosition.eramConfiguration?.sectorId || primaryPosition.callsign || "UNKNOWN"; + + console.log('Primary position details:', { + callsign: primaryPosition.callsign, + name: primaryPosition.name, + hasEramConfig: !!primaryPosition.eramConfiguration, + eramSectorId: primaryPosition.eramConfiguration?.sectorId, + finalSectorId: sectorId + }); + + dispatch(setArtccId(artccId)); + dispatch(setSectorId(sectorId)); + dispatch(setSession(sessionInfo)); + dispatch(setSessionIsActive(sessionInfo.isActive ?? false)); + dispatch(initThunk()); + + if (hubConnection.state === HubConnectionState.Connected) { + const joinSessionParams = { + sessionId: sessionInfo.id, + clientName: "vTDLS", + clientVersion: '1.3.0', + hasEramConfig: true, + eramSectorId: 99 + }; + console.log('Sending joinSession with params:', joinSessionParams); + await hubConnection.invoke("joinSession", joinSessionParams); + console.log(`Joined session ${sessionInfo.id} with position ${primaryPosition.callsign} (${primaryPosition.name})`); + + const artccFacilityId = sessionInfo.artccId; // Use ARTCC ID instead of facility ID + const primaryFacilityId = sessionInfo.positions.find((p) => p.isPrimary)?.facilityId; + + // Store facility ID for later use + if (primaryFacilityId) { + setFacilityId(primaryFacilityId); + } + + if (artccFacilityId) { + try { + // Subscribe to FlightPlans using ARTCC ID (e.g., ZOA instead of OAK) + const initialFlightplans = await hubConnection.invoke("subscribe", new ApiTopic("FlightPlans", artccFacilityId)); + + if (initialFlightplans && Array.isArray(initialFlightplans)) { + setFlightplans(prev => { + const newMap = new Map(prev); + initialFlightplans.forEach(fp => { + if (fp?.aircraftId) { + newMap.set(fp.aircraftId, fp); + dispatch(updateFlightplanThunk(fp)); + } + }); + return newMap; + }); + } + + // Subscribe to FlightStrips using facility ID (e.g., OAK) + if (primaryFacilityId) { + const initialFlightStrips = await hubConnection.invoke("subscribe", new ApiTopic("FlightStrips", primaryFacilityId)); + console.log("Subscribed to FlightStrips, received:", initialFlightStrips); + + if (initialFlightStrips && Array.isArray(initialFlightStrips)) { + setFlightStrips(prev => { + const newMap = new Map(prev); + initialFlightStrips.forEach(strip => { + if (strip?.aircraftId) { + newMap.set(strip.aircraftId, strip); + } + }); + return newMap; + }); + } + } + + } catch (subscribeError) { + console.warn(`Failed to subscribe: ${subscribeError}`); + } + } + dispatch(setHubConnected(true)); + } else { + throw new Error("Hub connection not in Connected state"); + } + } catch (error: any) { + console.error("Session start failed:", error); + toast.error(error.message); + await disconnectHub(); + } + }, + [dispatch, disconnectHub] + ); + + useEffect(() => { + if (!env || !vatsimToken) { + return; + } + + const hubUrl = env.clientHubUrl; + + const getValidNasToken = async () => { + return refreshToken(env.apiBaseUrl, vatsimToken).then((r) => { + console.log("Refreshed NAS token"); + return r.data; + }); + }; + + ref.current = new HubConnectionBuilder() + .withUrl(hubUrl, { + accessTokenFactory: getValidNasToken, + transport: HttpTransportType.WebSockets, + skipNegotiation: true, + }) + .withAutomaticReconnect() + .build(); + + const hubConnection = ref.current; + + hubConnection.onclose(() => { + dispatch(setArtccId("")); + dispatch(setSectorId("")); + dispatch(setHubConnected(false)); + console.log("ATC hub disconnected"); + navigate("/login", { replace: true }); + }); + + hubConnection.on("HandleSessionStarted", (sessionInfo: ApiSessionInfoDto) => { + console.log("Session started:", sessionInfo); + handleSessionStart(sessionInfo, hubConnection); + }); + + hubConnection.on("HandleSessionEnded", () => { + console.log("clearing session"); + dispatch(clearSession()); + disconnectHub(); + }); + + hubConnection.on("ReceiveFlightPlans", async (topic: ApiTopic, flightplans: ApiFlightplan[]) => { + if (flightplans && Array.isArray(flightplans)) { + setFlightplans(prev => { + const newMap = new Map(prev); + flightplans.forEach(fp => { + if (fp?.aircraftId) { + newMap.set(fp.aircraftId, fp); + dispatch(updateFlightplanThunk(fp)); + } + }); + return newMap; + }); + } + }); + + hubConnection.on("DeleteFlightplans", async (topic: ApiTopic, flightplanIds: string[]) => { + flightplanIds.forEach((flightplanId) => { + setFlightplans(prev => { + const newMap = new Map(prev); + newMap.delete(flightplanId); + return newMap; + }); + dispatch(deleteFlightplanThunk(flightplanId)); + }); + }); + + hubConnection.on("ReceiveStripItems", async (topic: any, stripItems: any[]) => { + console.log("Received ReceiveStripItems:", stripItems); + if (stripItems && Array.isArray(stripItems)) { + setFlightStrips(prev => { + const newMap = new Map(prev); + stripItems.forEach(strip => { + if (strip?.aircraftId) { + newMap.set(strip.aircraftId, strip); + } + }); + return newMap; + }); + } + }); + + hubConnection.on("HandleFsdConnectionStateChanged", (state: boolean) => { + dispatch(setFsdIsConnected(state)); + if (!state) { + dispatch(addOutageMessage(new OutageEntry("FSD_DOWN", "FSD CONNECTION DOWN"))); + } else { + dispatch(delOutageMessage("FSD_DOWN")); + } + }); + + hubConnection.on("SetSessionActive", (isActive) => { + dispatch(setSessionIsActive(isActive)); + sessionStorage.setItem("session-active", `${isActive}`); + }); + + hubConnection.keepAliveIntervalInMilliseconds = 1000; + }, [dispatch, navigate, disconnectHub, handleSessionStart, env, vatsimToken]); + + const connectHub = useCallback(async () => { + if (!env || !vatsimToken || !ref.current) { + if (ref.current?.state === HubConnectionState.Connected) { + dispatch(setHubConnected(true)); + return; + } + dispatch(setHubConnected(false)); + throw new Error(`Cannot connect - env: ${!!env}, token: ${!!vatsimToken}, ref: ${!!ref.current}`); + } + + const hubConnection = ref.current; + + if (hubConnection.state !== HubConnectionState.Connected) { + try { + await hubConnection.start(); + console.log("Connected to hub, waiting for session..."); + + try { + const sessions = await hubConnection.invoke("GetSessions"); + const primarySession = sessions?.find((s) => !s.isPseudoController); + console.log("Available sessions:", sessions); + const primaryPosition = primarySession?.positions.find((p) => p.isPrimary)?.position; + + console.log(sessions); + console.log(primarySession); + + if (primarySession && primaryPosition) { + console.log(`Found primary position: ${primaryPosition.callsign} (${primaryPosition.name})`); + console.log(hubConnection); + await handleSessionStart(primarySession, hubConnection); + console.log(`joined existing session ${primarySession.id}`); + } else { + console.log("No primary session found, waiting for HandleSessionStarted event"); + } + } catch (error) { + console.log(error); + console.log("No active session yet, waiting for HandleSessionStarted event"); + } + } catch (error) { + dispatch(setHubConnected(false)); + throw error; + } + } + }, [dispatch, handleSessionStart, env, vatsimToken]); + + const sendEramMessage = useCallback(async (eramMessage: ProcessEramMessageDto) => { + return invokeHub(ref.current, connectHub, async (connection) => { + const result = await connection.invoke("processEramMessage", eramMessage); + if (result) { + if (result.isSuccess) { + const feedbackMessage = result.feedback.length > 0 ? result.feedback.join("\n") : "Command accepted"; + console.log("ERAM command processed successfully:", feedbackMessage); + + if (result.response) { + dispatch(setMraMessage(result.response)); + dispatch(openWindowThunk("MESSAGE_RESPONSE_AREA")); + } + } else { + const rejectMessage = result.feedback.length > 0 ? `REJECT\n${result.feedback.join("\n")}` : "REJECT\nCommand failed"; + console.log("ERAM command processing failed:", rejectMessage); + } + } + return result; + }); + }, [connectHub, dispatch]); + + const amendFlightplan = useCallback(async (fp: CreateOrAmendFlightplanDto) => { + return invokeHub(ref.current, connectHub, async (connection) => { + await connection.invoke("amendFlightPlan", fp); + }); + }, [connectHub]); + + const deleteFlightplan = useCallback(async (aircraftId: string) => { + // Workaround: "delete" by amending the flightplan to be blank/empty + try { + console.log(`Attempting to delete flightplan by clearing data: ${aircraftId}`); + await amendFlightplan({ + aircraftId: aircraftId, + cid: '', + status: 'Proposed', + aircraftType: '', + faaEquipmentSuffix: '', + equipment: '', + icaoEquipmentCodes: '', + icaoSurveillanceCodes: '', + speed: 0, + altitude: '', + departure: '', + destination: '', + alternate: '', + route: '', + remarks: '', + assignedBeaconCode: null, + estimatedDepartureTime: 0, + actualDepartureTime: 0, + hoursEnroute: 0, + minutesEnroute: 0, + fuelHours: 0, + fuelMinutes: 0, + pilotCid: '', + holdAnnotations: null, + wakeTurbulenceCode: '', + }); + console.log(`Cleared flightplan data for: ${aircraftId}`); + + // Only remove from local state if server operation succeeded + setFlightplans(prev => { + const newMap = new Map(prev); + newMap.delete(aircraftId); + return newMap; + }); + dispatch(deleteFlightplanThunk(aircraftId)); + } catch (error) { + console.error(`Failed to clear flightplan: ${error}`); + // Re-throw the error so the caller can handle it + throw error; + } + }, [amendFlightplan, dispatch]); + + const requestFlightStrip = useCallback(async (aircraftId: string) => { + return invokeHub(ref.current, connectHub, async (connection) => { + await connection.invoke("RequestFlightStrip", facilityId, aircraftId.toUpperCase()); + }); + }, [connectHub, facilityId]); + + const sendCommand = useCallback(async (command: string): Promise => { + const trimmedCommand = command.trim().toUpperCase(); + const parts = trimmedCommand.split(' '); + + try { + const elements = parts.map(token => ({ + token: token + })); + + // Always use DSide (ERAM) position type to allow ERAM commands from any position + const eramMessage: ProcessEramMessageDto = { + source: EramPositionType.DSide, + elements, + invertNumericKeypad: false + }; + + const result = await sendEramMessage(eramMessage); + + if (result) { + if (result.isSuccess) { + console.log(result); + const feedback = result.feedback?.length > 0 ? result.feedback.join("\n") : ""; + const response = result.response || ""; + + if (feedback && response) { + return `${feedback}\n${response}`; + } else if (response) { + return response; + } else if (feedback) { + return feedback; + } else { + return "COMMAND ACCEPTED"; + } + } else { + return `REJECT: ${result.feedback.join("\n") || "COMMAND FAILED"}`; + } + } else { + return "ERROR: NO RESPONSE FROM SERVER"; + } + + } catch (error) { + console.error('Command processing failed:', error); + return `ERROR: ${error}`; + } + }, [flightplans, sendEramMessage]); + + // Auto-connect when the provider is mounted and we have token + env + useEffect(() => { + if (vatsimToken && env) { + const timer = setTimeout(() => { + console.log("Auto-connecting to hub..."); + connectHub().catch((error) => { + console.error("Auto-connect failed:", error); + }); + }, 1000); // Give a moment for the component to settle + + return () => clearTimeout(timer); + } + }, [vatsimToken, env, connectHub]); + + const contextValue = useMemo(() => ({ + hubConnection: ref.current, + hubConnected, + connectHub, + disconnectHub, + sendEramMessage, + sendCommand, + amendFlightplan, + deleteFlightplan, + requestFlightStrip, + flightplans, + flightStrips, + }), [hubConnected, connectHub, disconnectHub, sendEramMessage, sendCommand, amendFlightplan, deleteFlightplan, requestFlightStrip, flightplans, flightStrips]); + + return {children}; +}; diff --git a/src/contexts/SocketContext.tsx b/src/contexts/SocketContext.tsx new file mode 100644 index 0000000..0278bfa --- /dev/null +++ b/src/contexts/SocketContext.tsx @@ -0,0 +1,13 @@ +import React, { createContext, ReactNode } from 'react'; + +type SocketContextValue = { + // Add socket-related properties here +}; + +export const SocketContext = createContext({}); + +export const SocketContextProvider = ({ children }: { children: ReactNode }) => { + const contextValue = {}; + + return {children}; +}; diff --git a/src/hooks/useHubConnector.ts b/src/hooks/useHubConnector.ts new file mode 100644 index 0000000..5fd2ec9 --- /dev/null +++ b/src/hooks/useHubConnector.ts @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { HubContext } from '../contexts/HubContext'; + +export const useHubConnector = () => { + const context = useContext(HubContext); + + if (!context) { + throw new Error('useHubConnector must be used within a HubContextProvider'); + } + + return context; +}; diff --git a/src/hooks/useSocketConnector.ts b/src/hooks/useSocketConnector.ts new file mode 100644 index 0000000..dc327c1 --- /dev/null +++ b/src/hooks/useSocketConnector.ts @@ -0,0 +1,10 @@ +import { useCallback } from 'react'; + +export const useSocketConnector = () => { + const disconnectSocket = useCallback(() => { + console.log('Disconnecting socket'); + // Implementation for socket disconnection + }, []); + + return { disconnectSocket }; +}; diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..eefa8aa --- /dev/null +++ b/src/index.html @@ -0,0 +1,9 @@ + + + Minimal React application + + +
+ + + diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..3057b2a --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import App from './App'; + +const container = document.getElementById('root'); + +const root = ReactDOM.createRoot(container); +root.render(); diff --git a/src/login/Login.tsx b/src/login/Login.tsx new file mode 100644 index 0000000..82921f1 --- /dev/null +++ b/src/login/Login.tsx @@ -0,0 +1,142 @@ +import { faGear } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import React, { useEffect } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { + configSelector, + envSelector, + login, + setEnv, + vatsimTokenSelector, + logout, + sessionSelector, + logoutThunk +} from "../redux/slices/authSlice"; +import { useRootDispatch, useRootSelector } from "../redux/hooks"; +import { DOMAIN, VATSIM_CLIENT_ID } from "../utils/constants"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; + +// Simple styles for now +const loginStyles = { + bg: { + position: 'fixed' as const, + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: '#1a1a1a', + zIndex: -1, + }, + root: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + minHeight: '100vh', + color: 'white', + }, + waiting: { + textAlign: 'center' as const, + margin: '20px 0', + }, + logoutButton: { + padding: '10px 20px', + backgroundColor: '#dc3545', + color: 'white', + border: 'none', + borderRadius: '4px', + cursor: 'pointer', + margin: '10px', + }, +}; + +function redirectLogin() { + window.location.href = `https://auth.vatsim.net/oauth/authorize?client_id=${VATSIM_CLIENT_ID}&redirect_uri=${encodeURIComponent( + `${DOMAIN}/login` + )}&response_type=code&scope=vatsim_details`; +} + +const Login = () => { + const dispatch = useRootDispatch(); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const code = searchParams.get("code"); + const vatsimToken = useRootSelector(vatsimTokenSelector); + const config = useRootSelector(configSelector); + const env = useRootSelector(envSelector); + const hubSession = useRootSelector(sessionSelector); + + useEffect(() => { + if (code && env) { + dispatch( + login({ + code, + redirectUrl: encodeURIComponent(`${DOMAIN}/login`), + }) + ); + } + }, [code, dispatch, env]); + + const handleLogout = () => { + dispatch(logoutThunk(true)); + }; + + useEffect(() => { + if (vatsimToken) { + // Navigate to main app after successful login + // The hub connection will be handled by HubContextProvider + navigate("/", { replace: true }); + } + }, [navigate, vatsimToken]); + + return ( + <> +
+
+
+

vFDIO Login

+ {vatsimToken ? ( + <> +
+
+ Login successful! Redirecting... +
+
+ + + ) : ( + <> + + + + )} +
+
+ + ); +}; + +const LoginProvider = () => ( + + + +); + +export default LoginProvider; diff --git a/src/redux/hooks.ts b/src/redux/hooks.ts new file mode 100644 index 0000000..f239d81 --- /dev/null +++ b/src/redux/hooks.ts @@ -0,0 +1,5 @@ +import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; +import type { RootState, AppDispatch } from './store'; + +export const useRootDispatch = () => useDispatch(); +export const useRootSelector: TypedUseSelectorHook = useSelector; diff --git a/src/redux/slices/appSlice.ts b/src/redux/slices/appSlice.ts new file mode 100644 index 0000000..dbf9aea --- /dev/null +++ b/src/redux/slices/appSlice.ts @@ -0,0 +1,31 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { OutageEntry } from '../../types/outageEntry'; + +interface AppState { + fsdIsConnected: boolean; + outageMessages: OutageEntry[]; +} + +const initialState: AppState = { + fsdIsConnected: false, + outageMessages: [], +}; + +const appSlice = createSlice({ + name: 'app', + initialState, + reducers: { + setFsdIsConnected: (state, action: PayloadAction) => { + state.fsdIsConnected = action.payload; + }, + addOutageMessage: (state, action: PayloadAction) => { + state.outageMessages.push(action.payload); + }, + delOutageMessage: (state, action: PayloadAction) => { + state.outageMessages = state.outageMessages.filter(msg => msg.id !== action.payload); + }, + }, +}); + +export const { setFsdIsConnected, addOutageMessage, delOutageMessage } = appSlice.actions; +export default appSlice.reducer; diff --git a/src/redux/slices/authSlice.ts b/src/redux/slices/authSlice.ts new file mode 100644 index 0000000..0d4e788 --- /dev/null +++ b/src/redux/slices/authSlice.ts @@ -0,0 +1,214 @@ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import type { PayloadAction } from "@reduxjs/toolkit"; +import type { Nullable } from "../../types/utility-types"; +import type { ApiSessionInfoDto } from "../../types/apiTypes/apiSessionInfoDto"; +import { login as apiLogin, fetchVnasConfiguration } from "../../api/vNasDataApi"; +import type { RootState } from "../store"; +import * as jose from "jose"; + +// Simple toast function for now - displays both console error and alert +const toast = { + error: (message: string, options?: any) => { + console.error(message); + // For development, use alert. In production, you'd want to use a proper toast library + if (typeof window !== 'undefined') { + alert(`Error: ${message}`); + } + } +}; + +function tokenHasExpired(token: jose.JWTPayload) { + return token.exp! - Math.trunc(Date.now() / 1000) < 0; +} + +function getLocalVatsimToken() { + const vatsimToken = localStorage.getItem("vatsim-token"); + if (!vatsimToken) { + return null; + } + + const decodedToken = jose.decodeJwt(vatsimToken); + if (!tokenHasExpired(decodedToken)) { + return vatsimToken; + } + + return null; +} + +type Environment = { + name: string; + apiBaseUrl: string; + clientHubUrl: string; + isSweatbox: boolean; + isPrimary?: boolean; + isDisabled?: boolean; +}; + +type AuthState = { + vnasConfiguration: Nullable; + vatsimCode: Nullable; + vatsimToken: Nullable; + environment: Nullable; + session: Nullable; + sessionActive: boolean; + hubConnected: boolean; +}; + +type Config = { + artccBoundariesUrl: string; + artccAoisUrl: string; + environments: Environment[]; +}; + +type CodeExchangeProps = { + code: string; + redirectUrl: string; +}; + +const initialState: AuthState = { + vnasConfiguration: null, + vatsimCode: null, + vatsimToken: getLocalVatsimToken(), + environment: null, + session: null, + sessionActive: false, + hubConnected: false, +}; + +export const getVnasConfig = createAsyncThunk("auth/getVnasConfig", async () => { + try { + const config = await fetchVnasConfiguration(); + return config; + } catch (error) { + console.error('Failed to fetch vNAS configuration:', error); + throw error; + } +}); + +export const login = createAsyncThunk>, CodeExchangeProps, { state: RootState }>( + "auth/login", + async (data, thunkAPI) => { + const environment = thunkAPI.getState().auth.environment; + if (!environment) { + toast.error(`vNAS Environment not set. Failed to log in`); + throw new Error("Environment not set"); + } + return apiLogin(environment.apiBaseUrl, data.code, data.redirectUrl); + } +); + +export const logoutThunk = createAsyncThunk("auth/logoutThunk", async (shouldReload: boolean = false, { dispatch }) => { + dispatch(authSlice.actions.logout()); + dispatch(setEnv("")); + localStorage.removeItem("vatsim-token"); + localStorage.removeItem("vedst-environment"); + + if (shouldReload) { + const currentPath = window.location.pathname; + window.history.replaceState({}, document.title, currentPath); + window.location.reload(); + } +}); + +export const authSlice = createSlice({ + name: "auth", + initialState, + extraReducers: (builder) => { + builder.addCase(getVnasConfig.fulfilled, (state, action) => { + const config = action.payload; + state.vnasConfiguration = action.payload; + const localEnvironment = localStorage.getItem("vedst-environment"); + if (localEnvironment !== null) { + const availableEnvironment = config.environments.find((e) => e.name === localEnvironment); + if (availableEnvironment) { + state.environment = availableEnvironment; + } else { + localStorage.removeItem("vedst-environment"); + state.environment = config.environments[0]; + } + } else { + state.environment = config.environments[0]; + } + }); + builder.addCase(getVnasConfig.rejected, (state, action) => { + console.error('Failed to load vNAS configuration:', action.error.message); + toast.error(`Failed to load vNAS configuration: ${action.error.message}`); + // Fallback to a default configuration for development + state.vnasConfiguration = { + artccBoundariesUrl: "", + artccAoisUrl: "", + environments: [ + { + name: "Test", + apiBaseUrl: "https://test.virtualnas.net/api", + clientHubUrl: "https://test.virtualnas.net/hubs/client", + isSweatbox: true, + isPrimary: true, + isDisabled: false + } + ] + }; + state.environment = state.vnasConfiguration.environments[0]; + }); + builder.addCase(login.fulfilled, (state, action) => { + if (action.payload.ok) { + const newToken = action.payload.vatsimToken; + state.vatsimToken = newToken; + localStorage.setItem("vatsim-token", newToken); + } else { + toast.error(`Failed to log in: ${action.payload.statusText}`); + console.log(`Failed to log in: ${action.payload.statusText}`); + } + }); + builder.addCase(login.rejected, (state, action) => { + state.vatsimToken = null; + state.vatsimCode = null; + localStorage.removeItem("vatsim-token"); + toast.error(`Failed to log in: ${action.error.message}`); + console.log(`Failed to log in: ${action.error.message}`); + }); + }, + reducers: { + setSession(state, action: PayloadAction) { + state.session = action.payload; + }, + clearSession(state) { + state.session = null; + }, + setEnv(state, action: PayloadAction) { + if (state.vnasConfiguration) { + state.environment = state.vnasConfiguration.environments.find((e) => e.name === action.payload) ?? null; + localStorage.setItem("vedst-environment", action.payload); + } + }, + setSessionIsActive(state, action: PayloadAction) { + if (!state.session) { + toast.error(`Failed to set session active status. Session is not defined.`); + return; + } + const active = action.payload; + state.sessionActive = active; + }, + setHubConnected(state, action: PayloadAction) { + state.hubConnected = action.payload; + }, + logout(state) { + state.vatsimCode = null; + state.vatsimToken = null; + state.session = null; + state.environment = null; + state.sessionActive = false; + state.hubConnected = false; + }, + }, +}); + +export const { setSession, clearSession, setEnv, setSessionIsActive, setHubConnected, logout } = authSlice.actions; +export default authSlice.reducer; + +export const vatsimTokenSelector = () => localStorage.getItem("vatsim-token"); +export const configSelector = (state: RootState) => state.auth.vnasConfiguration; +export const envSelector = (state: RootState) => state.auth.environment; +export const sessionActiveSelector = (state: RootState) => state.auth.sessionActive; +export const hubConnectedSelector = (state: RootState) => state.auth.hubConnected; +export const sessionSelector = (state: RootState) => state.auth.session; diff --git a/src/redux/slices/customFlightplanSlice.ts b/src/redux/slices/customFlightplanSlice.ts new file mode 100644 index 0000000..eca048f --- /dev/null +++ b/src/redux/slices/customFlightplanSlice.ts @@ -0,0 +1,209 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import type { ApiFlightplan } from '../../types/apiTypes/apiFlightplan'; +import type { RootState } from '../store'; + +export interface CustomFlightplanState { + // Current flightplans data - using plain object instead of Map for Redux serialization + flightplans: Record; + + // UI state + selectedFlightplanId: string | null; + viewMode: 'table' | 'detail' | 'stats'; + + // Search and filter state + lastSearchResults: ApiFlightplan[]; + searchCriteria: { + aircraftId?: string; + departure?: string; + destination?: string; + status?: ApiFlightplan['status']; + altitude?: string; + route?: string; + }; + + // Command history + commandHistory: Array<{ + command: string; + timestamp: number; + result: string; + success: boolean; + }>; + + // Loading and error states + isLoading: boolean; + error: string | null; +} + +const initialState: CustomFlightplanState = { + flightplans: {}, + selectedFlightplanId: null, + viewMode: 'table', + lastSearchResults: [], + searchCriteria: {}, + commandHistory: [], + isLoading: false, + error: null, +}; + +const customFlightplanSlice = createSlice({ + name: 'customFlightplan', + initialState, + reducers: { + // Flightplan data management + setFlightplans: (state, action: PayloadAction>) => { + // Payload is already a plain object + state.flightplans = action.payload; + state.error = null; + }, + + addFlightplan: (state, action: PayloadAction) => { + state.flightplans[action.payload.aircraftId] = action.payload; + }, + + updateFlightplan: (state, action: PayloadAction) => { + state.flightplans[action.payload.aircraftId] = action.payload; + }, + + removeFlightplan: (state, action: PayloadAction) => { + delete state.flightplans[action.payload]; + if (state.selectedFlightplanId === action.payload) { + state.selectedFlightplanId = null; + } + }, + + // UI state management + setSelectedFlightplan: (state, action: PayloadAction) => { + state.selectedFlightplanId = action.payload; + }, + + setViewMode: (state, action: PayloadAction<'table' | 'detail' | 'stats'>) => { + state.viewMode = action.payload; + }, + + // Search and filter management + setSearchResults: (state, action: PayloadAction) => { + state.lastSearchResults = action.payload; + }, + + setSearchCriteria: (state, action: PayloadAction) => { + state.searchCriteria = action.payload; + }, + + clearSearchResults: (state) => { + state.lastSearchResults = []; + state.searchCriteria = {}; + }, + + // Command history management + addCommandToHistory: (state, action: PayloadAction<{ + command: string; + result: string; + success: boolean; + }>) => { + const entry = { + ...action.payload, + timestamp: Date.now(), + }; + + state.commandHistory.unshift(entry); + + // Keep only last 50 commands + if (state.commandHistory.length > 50) { + state.commandHistory = state.commandHistory.slice(0, 50); + } + }, + + clearCommandHistory: (state) => { + state.commandHistory = []; + }, + + // Loading and error states + setLoading: (state, action: PayloadAction) => { + state.isLoading = action.payload; + }, + + setError: (state, action: PayloadAction) => { + state.error = action.payload; + state.isLoading = false; + }, + + clearError: (state) => { + state.error = null; + }, + }, +}); + +export const { + setFlightplans, + addFlightplan, + updateFlightplan, + removeFlightplan, + setSelectedFlightplan, + setViewMode, + setSearchResults, + setSearchCriteria, + clearSearchResults, + addCommandToHistory, + clearCommandHistory, + setLoading, + setError, + clearError, +} = customFlightplanSlice.actions; + +// Selectors +export const selectFlightplans = (state: RootState) => state.customFlightplan.flightplans; +export const selectFlightplansArray = (state: RootState) => + Object.values(state.customFlightplan.flightplans); +export const selectSelectedFlightplan = (state: RootState) => { + const id = state.customFlightplan.selectedFlightplanId; + return id ? state.customFlightplan.flightplans[id] : null; +}; +export const selectViewMode = (state: RootState) => state.customFlightplan.viewMode; +export const selectSearchResults = (state: RootState) => state.customFlightplan.lastSearchResults; +export const selectSearchCriteria = (state: RootState) => state.customFlightplan.searchCriteria; +export const selectCommandHistory = (state: RootState) => state.customFlightplan.commandHistory; +export const selectIsLoading = (state: RootState) => state.customFlightplan.isLoading; +export const selectError = (state: RootState) => state.customFlightplan.error; + +// Complex selectors +export const selectFlightplanById = (state: RootState, aircraftId: string) => + state.customFlightplan.flightplans[aircraftId]; + +export const selectFlightplansByStatus = (state: RootState, status: ApiFlightplan['status']) => + Object.values(state.customFlightplan.flightplans).filter((fp: ApiFlightplan) => fp.status === status); + +export const selectFlightplanStatistics = (state: RootState) => { + const flightplans = Object.values(state.customFlightplan.flightplans); + + const stats = { + total: flightplans.length, + byStatus: { + Active: 0, + Proposed: 0, + Tentative: 0, + }, + byEquipmentType: new Map(), + averageSpeed: 0, + altitudeDistribution: new Map(), + }; + + let totalSpeed = 0; + + flightplans.forEach((fp: ApiFlightplan) => { + stats.byStatus[fp.status]++; + + const currentCount = stats.byEquipmentType.get(fp.aircraftType) || 0; + stats.byEquipmentType.set(fp.aircraftType, currentCount + 1); + + totalSpeed += fp.speed; + + const altCount = stats.altitudeDistribution.get(fp.altitude) || 0; + stats.altitudeDistribution.set(fp.altitude, altCount + 1); + }); + + stats.averageSpeed = flightplans.length > 0 ? Math.round(totalSpeed / flightplans.length) : 0; + + return stats; +}; + +export default customFlightplanSlice.reducer; \ No newline at end of file diff --git a/src/redux/slices/mcaSlice.ts b/src/redux/slices/mcaSlice.ts new file mode 100644 index 0000000..026981a --- /dev/null +++ b/src/redux/slices/mcaSlice.ts @@ -0,0 +1,32 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface McaState { + acceptMessage: string; + rejectMessage: string; + responseMessage: string; +} + +const initialState: McaState = { + acceptMessage: '', + rejectMessage: '', + responseMessage: '', +}; + +const mcaSlice = createSlice({ + name: 'mca', + initialState, + reducers: { + setMcaAcceptMessage: (state, action: PayloadAction) => { + state.acceptMessage = action.payload; + }, + setMcaRejectMessage: (state, action: PayloadAction) => { + state.rejectMessage = action.payload; + }, + setMraMessage: (state, action: PayloadAction) => { + state.responseMessage = action.payload; + }, + }, +}); + +export const { setMcaAcceptMessage, setMcaRejectMessage, setMraMessage } = mcaSlice.actions; +export default mcaSlice.reducer; diff --git a/src/redux/slices/sectorSlice.ts b/src/redux/slices/sectorSlice.ts new file mode 100644 index 0000000..2840191 --- /dev/null +++ b/src/redux/slices/sectorSlice.ts @@ -0,0 +1,27 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface SectorState { + artccId: string; + sectorId: string; +} + +const initialState: SectorState = { + artccId: '', + sectorId: '', +}; + +const sectorSlice = createSlice({ + name: 'sector', + initialState, + reducers: { + setArtccId: (state, action: PayloadAction) => { + state.artccId = action.payload; + }, + setSectorId: (state, action: PayloadAction) => { + state.sectorId = action.payload; + }, + }, +}); + +export const { setArtccId, setSectorId } = sectorSlice.actions; +export default sectorSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts new file mode 100644 index 0000000..c206ea3 --- /dev/null +++ b/src/redux/store.ts @@ -0,0 +1,19 @@ +import { configureStore } from '@reduxjs/toolkit'; +import authReducer from './slices/authSlice'; +import appReducer from './slices/appSlice'; +import sectorReducer from './slices/sectorSlice'; +import mcaReducer from './slices/mcaSlice'; +import customFlightplanReducer from './slices/customFlightplanSlice'; + +export const store = configureStore({ + reducer: { + auth: authReducer, + app: appReducer, + sector: sectorReducer, + mca: mcaReducer, + customFlightplan: customFlightplanReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/src/redux/thunks/index.ts b/src/redux/thunks/index.ts new file mode 100644 index 0000000..329a093 --- /dev/null +++ b/src/redux/thunks/index.ts @@ -0,0 +1,47 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import type { ApiFlightplan, EramTrackDto } from '../../types/apiTypes'; + +export const updateFlightplanThunk = createAsyncThunk( + 'flightplan/update', + async (flightplan: ApiFlightplan) => { + // Implementation for updating flightplan + console.log('Updating flightplan:', flightplan); + return flightplan; + } +); + +export const deleteFlightplanThunk = createAsyncThunk( + 'flightplan/delete', + async (flightplanId: string) => { + // Implementation for deleting flightplan + console.log('Deleting flightplan:', flightplanId); + return flightplanId; + } +); + +export const updateTrackThunk = createAsyncThunk( + 'track/update', + async (track: EramTrackDto) => { + // Implementation for updating track + console.log('Updating track:', track); + return track; + } +); + +export const deleteTrackThunk = createAsyncThunk( + 'track/delete', + async (trackId: string) => { + // Implementation for deleting track + console.log('Deleting track:', trackId); + return trackId; + } +); + +export const initThunk = createAsyncThunk( + 'app/init', + async () => { + // Implementation for app initialization + console.log('Initializing app'); + return true; + } +); diff --git a/src/redux/thunks/windowThunks.ts b/src/redux/thunks/windowThunks.ts new file mode 100644 index 0000000..0ab2442 --- /dev/null +++ b/src/redux/thunks/windowThunks.ts @@ -0,0 +1,10 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; + +export const openWindowThunk = createAsyncThunk( + 'window/open', + async (windowType: string) => { + console.log('Opening window:', windowType); + // Implementation for opening windows + return windowType; + } +); diff --git a/src/services/customFlightplanCommandParser.ts b/src/services/customFlightplanCommandParser.ts new file mode 100644 index 0000000..84ebf23 --- /dev/null +++ b/src/services/customFlightplanCommandParser.ts @@ -0,0 +1,818 @@ +import type { ApiFlightplan } from '../types/apiTypes/apiFlightplan';import type { ApiFlightplan } from '../types/apiTypes/apiFlightplan'; + +import { CustomFlightplanService } from './customFlightplanService';import { CustomFlightplanService } from './customFlightplanService'; + + + +export interface CommandResult {export interface CommandResult { + + output: string; output: string; + + success: boolean; success: boolean; + + data?: any; data?: any; + +}} + + + +/**/** + + * Parser for custom flightplan commands * Parser for custom flightplan commands + + * Simplified to only handle FR (Flight Readout) command * Simplified to only handle FR (Flight Readout) command + + */ */ + +export class CustomFlightplanCommandParser {export class CustomFlightplanCommandParser { + + private flightplanService: CustomFlightplanService; private flightplanService: CustomFlightplanService; + + + + constructor(flightplansMap: Map) { constructor(flightplansMap: Map) { + + this.flightplanService = new CustomFlightplanService(flightplansMap); this.flightplanService = new CustomFlightplanService(flightplansMap); + + } } + + + + /** /** + + * Update flightplan data * Update flightplan data + + */ */ + + updateFlightplans(flightplansMap: Map) { updateFlightplans(flightplansMap: Map) { + + this.flightplanService = new CustomFlightplanService(flightplansMap); this.flightplanService = new CustomFlightplanService(flightplansMap); + + } } + + + + /** /** + + * Execute a command and return the result * Execute a command and return the result + + */ */ + + executeCommand(commandLine: string): CommandResult { executeCommand(commandLine: string): CommandResult { + + const parts = commandLine.trim().split(/\s+/); const parts = commandLine.trim().split(/\s+/); + + const command = parts[0].toUpperCase(); const command = parts[0].toUpperCase(); + + + + try { try { + + switch (command) { switch (command) { + + case 'FR': case 'FR': + + return this.handleFlightReadoutCommand(parts.slice(1)); return this.handleFlightReadoutCommand(parts.slice(1)); + + + + default: default: + + return { return { + + output: `Unknown command: ${command}. Only 'FR ' is supported.`, output: `Unknown command: ${command}. Only 'FR ' is supported.`, + + success: false success: false + + }; }; + + } } + + } catch (error) { } catch (error) { + + return { return { + + output: `Error executing command: ${error}`, output: `Error executing command: ${error}`, + + success: false success: false + + }; }; + + } } + + } } + + + + /** /** + + * Handle FR (Flight Readout) command - ERAM style flightplan readout * Handle FR (Flight Readout) command - ERAM style flightplan readout + + * Usage: FR * Usage: FR + + */ */ + + private handleFlightReadoutCommand(args: string[]): CommandResult { private handleFlightReadoutCommand(args: string[]): CommandResult { + + if (args.length === 0) { if (args.length === 0) { + + return { return { + + output: 'Usage: FR ', output: 'Usage: FR ', + + success: false success: false + + }; }; + + } } + + + + const aircraftId = args[0].toUpperCase(); const aircraftId = args[0].toUpperCase(); + + const allFlightplans = this.flightplanService.getAllFlightplans(); const allFlightplans = this.flightplanService.getAllFlightplans(); + + const flightplan = this.flightplanService.getFlightplanById(aircraftId); const flightplan = this.flightplanService.getFlightplanById(aircraftId); + + + + if (!flightplan) { if (!flightplan) { + + // More helpful error message for debugging // More helpful error message for debugging + + const similarIds = allFlightplans const similarIds = allFlightplans + + .filter(fp => fp.aircraftId.toUpperCase().includes(aircraftId)) .filter(fp => fp.aircraftId.toUpperCase().includes(aircraftId)) + + .map(fp => fp.aircraftId) .map(fp => fp.aircraftId) + + .slice(0, 3); .slice(0, 3); + + + + if (similarIds.length > 0) { if (similarIds.length > 0) { + + return { return { + + output: `REJECT - FLID NOT STORED\nSimilar IDs found: ${similarIds.join(', ')}\nTotal flightplans: ${allFlightplans.length}`, output: `REJECT - FLID NOT STORED\\nSimilar IDs found: ${similarIds.join(', ')}\\nTotal flightplans: ${allFlightplans.length}`, + + success: false success: false + + }; }; + + } } + + + + return { return { + + output: `REJECT - FLID NOT STORED\nTotal flightplans: ${allFlightplans.length}`, output: `REJECT - FLID NOT STORED\\nTotal flightplans: ${allFlightplans.length}`, + + success: false success: false + + }; }; + + } } + + + + // Check for duplicate ACIDs (simplified - in real ERAM this would check CID combinations) // Check for duplicate ACIDs (simplified - in real ERAM this would check CID combinations) + + const duplicates = this.flightplanService.getAllFlightplans().filter(fp => const duplicates = this.flightplanService.getAllFlightplans().filter(fp => + + fp.aircraftId.toUpperCase() === aircraftId fp.aircraftId.toUpperCase() === aircraftId + + ); ); + + + + if (duplicates.length > 1) { if (duplicates.length > 1) { + + // Format duplicate list with CID, departure, and ETD // Format duplicate list with CID, departure, and ETD + + const duplicateList = duplicates.map(fp => { const duplicateList = duplicates.map(fp => { + + const etd = new Date(fp.estimatedDepartureTime * 1000); const etd = new Date(fp.estimatedDepartureTime * 1000); + + const etdFormatted = etd.toISOString().substring(11, 16).replace(':', ''); // HHMM format const etdFormatted = etd.toISOString().substring(11, 16).replace(':', ''); // HHMM format + + return `${fp.cid} ${fp.departure} ${etdFormatted}`; return `${fp.cid} ${fp.departure} ${etdFormatted}`; + + }).join('\n'); }).join('\\n'); + + + + return { return { + + output: `REJECT - FLID DUPLICATION\n\n${duplicateList}`, output: `REJECT - FLID DUPLICATION\\n\\n${duplicateList}`, + + success: false success: false + + }; }; + + } } + + + + // Format the ERAM-style flight readout // Format the ERAM-style flight readout + + const readout = this.formatERAMFlightReadout(flightplan); const readout = this.formatERAMFlightReadout(flightplan); + + + + return { return { + + output: readout, output: readout, + + success: true, success: true, + + data: flightplan data: flightplan + + }; }; + + } } + + + + /** /** + + * Format flightplan in ERAM FR command style * Format flightplan in ERAM FR command style + + */ */ + + private formatERAMFlightReadout(fp: ApiFlightplan): string { private formatERAMFlightReadout(fp: ApiFlightplan): string { + + // Format estimated departure time // Format estimated departure time + + const etd = new Date(fp.estimatedDepartureTime * 1000); const etd = new Date(fp.estimatedDepartureTime * 1000); + + const etdDate = etd.toISOString().substring(5, 10).replace('-', ''); // MMDD format const etdDate = etd.toISOString().substring(5, 10).replace('-', ''); // MMDD format + + const etdTime = etd.toISOString().substring(11, 16).replace(':', ''); // HHMM format const etdTime = etd.toISOString().substring(11, 16).replace(':', ''); // HHMM format + + + + // Format actual departure time if available // Format actual departure time if available + + let atd = ''; let atd = ''; + + if (fp.actualDepartureTime > 0) { if (fp.actualDepartureTime > 0) { + + const atdDate = new Date(fp.actualDepartureTime * 1000); const atdDate = new Date(fp.actualDepartureTime * 1000); + + atd = atdDate.toISOString().substring(11, 16).replace(':', ''); // HHMM format atd = atdDate.toISOString().substring(11, 16).replace(':', ''); // HHMM format + + } } + + + + // Format beacon code // Format beacon code + + const beacon = fp.assignedBeaconCode ? fp.assignedBeaconCode.toString().padStart(4, '0') : ''; const beacon = fp.assignedBeaconCode ? fp.assignedBeaconCode.toString().padStart(4, '0') : ''; + + + + // Format fuel time // Format fuel time + + const fuelTime = `${fp.fuelHours.toString().padStart(2, '0')}${fp.fuelMinutes.toString().padStart(2, '0')}`; const fuelTime = `${fp.fuelHours.toString().padStart(2, '0')}${fp.fuelMinutes.toString().padStart(2, '0')}`; + + + + // Format enroute time // Format enroute time + + const enrouteTime = `${fp.hoursEnroute.toString().padStart(2, '0')}${fp.minutesEnroute.toString().padStart(2, '0')}`; const enrouteTime = `${fp.hoursEnroute.toString().padStart(2, '0')}${fp.minutesEnroute.toString().padStart(2, '0')}`; + + + + // Create the ERAM-style readout // Create the ERAM-style readout + + const lines = [ const lines = [ + + `${fp.aircraftId.padEnd(8)} ${fp.aircraftType.padEnd(4)} ${fp.departure.padEnd(4)} ${fp.destination.padEnd(4)} ${atd.padEnd(4)} ${fp.altitude.padEnd(6)} ${fp.speed.toString().padEnd(4)}`, `${fp.aircraftId.padEnd(8)} ${fp.aircraftType.padEnd(4)} ${fp.departure.padEnd(4)} ${fp.destination.padEnd(4)} ${atd.padEnd(4)} ${fp.altitude.padEnd(6)} ${fp.speed.toString().padEnd(4)}`, + + `${fp.cid.padEnd(8)}`, `${fp.cid.padEnd(8)}`, + + `A${fp.alternate.padEnd(4)}`, `A${fp.alternate.padEnd(4)}`, + + `${fp.equipment.padEnd(10)}`, `${fp.equipment.padEnd(10)}`, + + '', '', + + `${fp.route}`, `${fp.route}`, + + '', '', + + `RMK/${fp.remarks || ''}` `RMK/${fp.remarks || ''}` + + ]; ]; + + + + // Add the formatted header similar to ERAM // Add the formatted header similar to ERAM + + const header = `${fp.aircraftId} ${etdDate} ${etdTime} ${beacon} ${fuelTime} ${enrouteTime}`; const header = `${fp.aircraftId} ${etdDate} ${etdTime} ${beacon} ${fuelTime} ${enrouteTime}`; + + + + return [header, '', ...lines].join('\n'); return [header, '', ...lines].join('\\n'); + + } } + +}} + private flightplanService: CustomFlightplanService; + + constructor(flightplansMap: Map) { + this.flightplanService = new CustomFlightplanService(flightplansMap); + } + + /** + * Update the flightplan service with new data + */ + updateFlightplans(flightplansMap: Map) { + this.flightplanService = new CustomFlightplanService(flightplansMap); + } + + /** + * Parse and execute a custom command + */ + executeCommand(commandString: string): CommandResult { + const parts = commandString.trim().split(/\s+/); + const command = parts[0]?.toUpperCase(); + + try { + switch (command) { + case 'FR': + return this.handleFlightReadoutCommand(parts.slice(1)); + + default: + return { + output: `Unknown command: ${command}. Only 'FR ' is supported.`, + success: false + }; + } + } catch (error) { + return { + output: `Error executing command: ${error}`, + success: false + }; + } + } + + /** + * Debug command to show current flightplan data + */ + private handleDebugCommand(): CommandResult { + const allFlightplans = this.flightplanService.getAllFlightplans(); + + if (allFlightplans.length === 0) { + return { + output: [ + 'DEBUG: No flightplans in service', + '', + 'Possible causes:', + '1. Hub not subscribed to FlightPlans topic', + '2. No active flightplans in the system', + '3. Facility ID not set correctly', + '4. Session not active', + '', + 'Check browser console for subscription logs:', + '- "Subscribing to FlightPlans for facility: [ID]"', + '- "received flightplan: [data]"', + '', + 'Try regular ERAM commands to see if any flightplans exist', + 'in the system that should be displayed.' + ].join('\\n'), + success: true + }; + } + + const aircraftIds = allFlightplans.map(fp => fp.aircraftId).slice(0, 10); + const output = [ + `DEBUG: Flightplan Service Status`, + `Total flightplans: ${allFlightplans.length}`, + `First 10 Aircraft IDs: ${aircraftIds.join(', ')}`, + '', + 'Sample flightplan data:', + allFlightplans[0] ? `${allFlightplans[0].aircraftId}: ${allFlightplans[0].departure} -> ${allFlightplans[0].destination}` : 'None', + '', + 'Ready for FR commands with any of the above Aircraft IDs' + ].join('\\n'); + + return { + output, + success: true, + data: allFlightplans + }; + } + + /** + * Handle FR (Flight Readout) command - ERAM style flightplan readout + * Usage: FR + */ + private handleFlightReadoutCommand(args: string[]): CommandResult { + if (args.length === 0) { + return { + output: 'REJECT - FLID NOT STORED', + success: false + }; + } + + const aircraftId = args[0].toUpperCase(); + + // Debug: Check how many flightplans we have and list some IDs + const allFlightplans = this.flightplanService.getAllFlightplans(); + console.log(`FR Debug: Looking for ${aircraftId}, have ${allFlightplans.length} flightplans`); + console.log('Available Aircraft IDs:', allFlightplans.slice(0, 5).map(fp => fp.aircraftId)); + + const flightplan = this.flightplanService.getFlightplanById(aircraftId); + + if (!flightplan) { + // More helpful error message for debugging + const similarIds = allFlightplans + .filter(fp => fp.aircraftId.toUpperCase().includes(aircraftId)) + .map(fp => fp.aircraftId) + .slice(0, 3); + + if (similarIds.length > 0) { + return { + output: `REJECT - FLID NOT STORED\\nSimilar IDs found: ${similarIds.join(', ')}\\nTotal flightplans: ${allFlightplans.length}`, + success: false + }; + } + + return { + output: `REJECT - FLID NOT STORED\\nTotal flightplans: ${allFlightplans.length}`, + success: false + }; + } + + // Check for duplicate ACIDs (simplified - in real ERAM this would check CID combinations) + const duplicates = this.flightplanService.getAllFlightplans().filter(fp => + fp.aircraftId.toUpperCase() === aircraftId + ); + + if (duplicates.length > 1) { + // Format duplicate list with CID, departure, and ETD + const duplicateList = duplicates.map(fp => { + const etd = new Date(fp.estimatedDepartureTime * 1000); + const etdFormatted = etd.toISOString().substring(11, 16).replace(':', ''); // HHMM format + return `${fp.cid} ${fp.departure} ${etdFormatted}`; + }).join('\\n'); + + return { + output: `REJECT - FLID DUPLICATION\\n\\n${duplicateList}`, + success: false + }; + } + + // Format the ERAM-style flight readout + const readout = this.formatERAMFlightReadout(flightplan); + + return { + output: readout, + success: true, + data: flightplan + }; + } + + /** + * Format flightplan in ERAM FR command style + */ + private formatERAMFlightReadout(fp: ApiFlightplan): string { + // Format estimated departure time + const etd = new Date(fp.estimatedDepartureTime * 1000); + const etdDate = etd.toISOString().substring(5, 10).replace('-', ''); // MMDD format + const etdTime = etd.toISOString().substring(11, 16).replace(':', ''); // HHMM format + + // Format actual departure time if available + let atd = ''; + if (fp.actualDepartureTime > 0) { + const atdDate = new Date(fp.actualDepartureTime * 1000); + atd = atdDate.toISOString().substring(11, 16).replace(':', ''); // HHMM format + } + + // Format beacon code + const beacon = fp.assignedBeaconCode ? fp.assignedBeaconCode.toString().padStart(4, '0') : ''; + + // Format fuel time + const fuelTime = `${fp.fuelHours.toString().padStart(2, '0')}${fp.fuelMinutes.toString().padStart(2, '0')}`; + + // Format enroute time + const enrouteTime = `${fp.hoursEnroute.toString().padStart(2, '0')}${fp.minutesEnroute.toString().padStart(2, '0')}`; + + // Create the ERAM-style readout + const lines = [ + `${fp.aircraftId.padEnd(8)} ${fp.aircraftType.padEnd(4)} ${fp.departure.padEnd(4)} ${fp.destination.padEnd(4)} ${atd.padEnd(4)} ${fp.altitude.padEnd(6)} ${fp.speed.toString().padEnd(4)}`, + `${fp.cid.padEnd(8)}`, + `A${fp.alternate.padEnd(4)}`, + `${fp.equipment.padEnd(10)}`, + '', + `${fp.route}`, + '', + `RMK/${fp.remarks || ''}` + ]; + + // Add the formatted header similar to ERAM + const header = `${fp.aircraftId} ${etdDate} ${etdTime} ${beacon} ${fuelTime} ${enrouteTime}`; + + return [header, '', ...lines].join('\\n'); + } +} + + const aircraftId = args[0].toUpperCase(); + const flightplan = this.flightplanService.getFlightplanById(aircraftId); + + if (!flightplan) { + return { + output: `No flightplan found for aircraft: ${aircraftId}`, + success: false + }; + } + + return { + output: this.flightplanService.formatFlightplanForDisplay(flightplan), + success: true, + data: flightplan + }; + } + + /** + * Handle multiple flightplans command with filters + * Usage: FPS [status=] [dep=] [dest=] [alt=] + */ + private handleFlightplansCommand(args: string[]): CommandResult { + const filter: FlightplanFilter = {}; + + // Parse key=value arguments + for (const arg of args) { + const [key, value] = arg.split('='); + if (!key || !value) continue; + + switch (key.toLowerCase()) { + case 'status': + if (['Active', 'Proposed', 'Tentative'].includes(value)) { + filter.status = value as ApiFlightplan['status']; + } + break; + case 'dep': + case 'departure': + filter.departure = value; + break; + case 'dest': + case 'destination': + filter.destination = value; + break; + case 'alt': + case 'altitude': + filter.altitude = value; + break; + case 'route': + filter.route = value; + break; + case 'acid': + case 'aircraftid': + filter.aircraftId = value; + break; + } + } + + const result = this.flightplanService.searchFlightplans(filter); + + if (result.flightplans.length === 0) { + return { + output: 'No flightplans match the specified criteria.', + success: true, + data: result + }; + } + + const output = [ + `Found ${result.filteredCount} of ${result.totalCount} flightplans:`, + '', + this.flightplanService.formatFlightplansTable(result.flightplans) + ].join('\\n'); + + return { + output, + success: true, + data: result + }; + } + + /** + * Handle flightplan list command with simple filters + * Usage: FPL [ALL|ACTIVE|PROPOSED|TENTATIVE] + */ + private handleFlightplanListCommand(args: string[]): CommandResult { + let flightplans: ApiFlightplan[]; + let title = 'All Flightplans'; + + if (args.length > 0) { + const filter = args[0].toUpperCase(); + switch (filter) { + case 'ACTIVE': + flightplans = this.flightplanService.getFlightplansByStatus('Active'); + title = 'Active Flightplans'; + break; + case 'PROPOSED': + flightplans = this.flightplanService.getFlightplansByStatus('Proposed'); + title = 'Proposed Flightplans'; + break; + case 'TENTATIVE': + flightplans = this.flightplanService.getFlightplansByStatus('Tentative'); + title = 'Tentative Flightplans'; + break; + case 'ALL': + default: + flightplans = this.flightplanService.getAllFlightplans(); + break; + } + } else { + flightplans = this.flightplanService.getAllFlightplans(); + } + + if (flightplans.length === 0) { + return { + output: `No ${title.toLowerCase()} found.`, + success: true, + data: flightplans + }; + } + + const output = [ + `${title} (${flightplans.length}):`, + '', + this.flightplanService.formatFlightplansTable(flightplans) + ].join('\\n'); + + return { + output, + success: true, + data: flightplans + }; + } + + /** + * Handle flightplan find command with various search criteria + * Usage: FPF + * Criteria: DEP, DEST, WAYPOINT, ALT, TYPE + */ + private handleFlightplanFindCommand(args: string[]): CommandResult { + if (args.length < 2) { + return { + output: 'Usage: FPF \\nCriteria: DEP, DEST, WAYPOINT, ALT, TYPE\\nExample: FPF DEP KJFK', + success: false + }; + } + + const criteria = args[0].toUpperCase(); + const value = args[1].toUpperCase(); + let flightplans: ApiFlightplan[]; + let description = ''; + + switch (criteria) { + case 'DEP': + case 'DEPARTURE': + flightplans = this.flightplanService.getFlightplansByDeparture(value); + description = `departing from ${value}`; + break; + + case 'DEST': + case 'DESTINATION': + flightplans = this.flightplanService.getFlightplansByDestination(value); + description = `arriving at ${value}`; + break; + + case 'WAYPOINT': + case 'WPT': + flightplans = this.flightplanService.getFlightplansByWaypoint(value); + description = `routing via ${value}`; + break; + + case 'ALT': + case 'ALTITUDE': + flightplans = this.flightplanService.getFlightplansByAltitude(value); + description = `at altitude ${value}`; + break; + + case 'TYPE': + case 'AIRCRAFT': + flightplans = this.flightplanService.getAllFlightplans().filter(fp => + fp.aircraftType.toUpperCase().includes(value) + ); + description = `aircraft type containing ${value}`; + break; + + default: + return { + output: `Unknown search criteria: ${criteria}\\nValid criteria: DEP, DEST, WAYPOINT, ALT, TYPE`, + success: false + }; + } + + if (flightplans.length === 0) { + return { + output: `No flightplans found ${description}.`, + success: true, + data: flightplans + }; + } + + const output = [ + `Found ${flightplans.length} flightplans ${description}:`, + '', + this.flightplanService.formatFlightplansTable(flightplans) + ].join('\\n'); + + return { + output, + success: true, + data: flightplans + }; + } + + /** + * Handle flightplan statistics command + */ + private handleFlightplanStatsCommand(): CommandResult { + const stats = this.flightplanService.getFlightplanStatistics(); + + const equipmentList = Array.from(stats.byEquipmentType.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5) // Top 5 + .map(([type, count]) => ` ${type}: ${count}`) + .join('\\n'); + + const altitudeList = Array.from(stats.altitudeDistribution.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) // Top 10 + .map(([alt, count]) => ` ${alt}: ${count}`) + .join('\\n'); + + const output = [ + 'FLIGHTPLAN STATISTICS', + '=====================', + `Total Flightplans: ${stats.total}`, + '', + 'Status Distribution:', + ` Active: ${stats.byStatus.Active}`, + ` Proposed: ${stats.byStatus.Proposed}`, + ` Tentative: ${stats.byStatus.Tentative}`, + '', + 'Top Aircraft Types:', + equipmentList || ' None', + '', + 'Average Speed: ' + stats.averageSpeed + ' knots', + '', + 'Top Altitudes:', + altitudeList || ' None' + ].join('\\n'); + + return { + output, + success: true, + data: stats + }; + } + + /** + * Handle help command + */ + private handleHelpCommand(): CommandResult { + const helpText = [ + 'CUSTOM FLIGHTPLAN COMMANDS', + '==========================', + '', + 'FR - ERAM-style flight readout for specific aircraft', + 'FPDEBUG - Debug current flightplan data and status', + 'FPMOCK - Create mock test data (DAL123, UAL456) for testing', + '', + 'FP - Display detailed flightplan for specific aircraft', + 'FPS [filters] - Display flightplans with optional filters', + ' Filters: status= dep= dest= alt= route=', + ' Example: FPS status=Active dep=KJFK', + '', + 'FPL [status] - List flightplans by status (ALL|ACTIVE|PROPOSED|TENTATIVE)', + 'FPF - Find flightplans by criteria', + ' Criteria: DEP, DEST, WAYPOINT, ALT, TYPE', + ' Example: FPF DEP KJFK', + '', + 'FPSTATS - Display flightplan statistics', + 'FPHELP - Display this help message', + '', + 'Examples:', + ' FPMOCK - Create test data for demonstration', + ' FR DAL123 - ERAM flight readout for DAL123 (after FPMOCK)', + ' FP UAL123 - Show details for UAL123', + ' FPS dep=KJFK dest=KLAX - Find flights from JFK to LAX', + ' FPL ACTIVE - List all active flightplans', + ' FPF WAYPOINT HOFFA - Find flights routing via HOFFA', + ' FPSTATS - Show statistics summary' + ].join('\\n'); + + return { + output: helpText, + success: true + }; + } +} \ No newline at end of file diff --git a/src/services/customFlightplanService.ts b/src/services/customFlightplanService.ts new file mode 100644 index 0000000..1e3b1ec --- /dev/null +++ b/src/services/customFlightplanService.ts @@ -0,0 +1,230 @@ +import type { ApiFlightplan, CreateOrAmendFlightplanDto } from '../types/apiTypes/apiFlightplan'; + +export interface FlightplanFilter { + aircraftId?: string; + callsign?: string; + departure?: string; + destination?: string; + status?: ApiFlightplan['status']; + altitude?: string; + route?: string; +} + +export interface FlightplanSearchResult { + flightplans: ApiFlightplan[]; + totalCount: number; + filteredCount: number; +} + +export class CustomFlightplanService { + private flightplans: Map; + + constructor(flightplansMap: Map) { + this.flightplans = flightplansMap; + } + + /** + * Get all flightplans as an array + */ + getAllFlightplans(): ApiFlightplan[] { + return Array.from(this.flightplans.values()); + } + + /** + * Get a specific flightplan by aircraft ID + */ + getFlightplanById(aircraftId: string): ApiFlightplan | undefined { + return this.flightplans.get(aircraftId); + } + + /** + * Search and filter flightplans based on criteria + */ + searchFlightplans(filter: FlightplanFilter): FlightplanSearchResult { + const allFlightplans = this.getAllFlightplans(); + + let filteredFlightplans = allFlightplans; + + if (filter.aircraftId) { + const aircraftIdUpper = filter.aircraftId.toUpperCase(); + filteredFlightplans = filteredFlightplans.filter(fp => + fp.aircraftId.toUpperCase().includes(aircraftIdUpper) + ); + } + + if (filter.departure) { + const depUpper = filter.departure.toUpperCase(); + filteredFlightplans = filteredFlightplans.filter(fp => + fp.departure.toUpperCase().includes(depUpper) + ); + } + + if (filter.destination) { + const destUpper = filter.destination.toUpperCase(); + filteredFlightplans = filteredFlightplans.filter(fp => + fp.destination.toUpperCase().includes(destUpper) + ); + } + + if (filter.status) { + filteredFlightplans = filteredFlightplans.filter(fp => + fp.status === filter.status + ); + } + + if (filter.altitude) { + filteredFlightplans = filteredFlightplans.filter(fp => + fp.altitude.includes(filter.altitude!) + ); + } + + if (filter.route) { + const routeUpper = filter.route.toUpperCase(); + filteredFlightplans = filteredFlightplans.filter(fp => + fp.route.toUpperCase().includes(routeUpper) + ); + } + + return { + flightplans: filteredFlightplans, + totalCount: allFlightplans.length, + filteredCount: filteredFlightplans.length + }; + } + + /** + * Get flightplans by status + */ + getFlightplansByStatus(status: ApiFlightplan['status']): ApiFlightplan[] { + return this.getAllFlightplans().filter(fp => fp.status === status); + } + + /** + * Get flightplans by departure airport + */ + getFlightplansByDeparture(departure: string): ApiFlightplan[] { + const depUpper = departure.toUpperCase(); + return this.getAllFlightplans().filter(fp => + fp.departure.toUpperCase() === depUpper + ); + } + + /** + * Get flightplans by destination airport + */ + getFlightplansByDestination(destination: string): ApiFlightplan[] { + const destUpper = destination.toUpperCase(); + return this.getAllFlightplans().filter(fp => + fp.destination.toUpperCase() === destUpper + ); + } + + /** + * Get flightplans by route containing a specific waypoint + */ + getFlightplansByWaypoint(waypoint: string): ApiFlightplan[] { + const waypointUpper = waypoint.toUpperCase(); + return this.getAllFlightplans().filter(fp => + fp.route.toUpperCase().includes(waypointUpper) + ); + } + + /** + * Get flightplans by altitude + */ + getFlightplansByAltitude(altitude: string): ApiFlightplan[] { + return this.getAllFlightplans().filter(fp => fp.altitude === altitude); + } + + /** + * Get statistics about current flightplans + */ + getFlightplanStatistics() { + const allFlightplans = this.getAllFlightplans(); + const stats = { + total: allFlightplans.length, + byStatus: { + Active: 0, + Proposed: 0, + Tentative: 0 + }, + byEquipmentType: new Map(), + averageSpeed: 0, + altitudeDistribution: new Map() + }; + + let totalSpeed = 0; + + allFlightplans.forEach(fp => { + // Count by status + stats.byStatus[fp.status]++; + + // Count by aircraft type + const currentCount = stats.byEquipmentType.get(fp.aircraftType) || 0; + stats.byEquipmentType.set(fp.aircraftType, currentCount + 1); + + // Sum speeds for average + totalSpeed += fp.speed; + + // Count by altitude + const altCount = stats.altitudeDistribution.get(fp.altitude) || 0; + stats.altitudeDistribution.set(fp.altitude, altCount + 1); + }); + + stats.averageSpeed = allFlightplans.length > 0 ? Math.round(totalSpeed / allFlightplans.length) : 0; + + return stats; + } + + /** + * Format a flightplan for display + */ + formatFlightplanForDisplay(flightplan: ApiFlightplan): string { + const lines = [ + `Aircraft ID: ${flightplan.aircraftId}`, + `CID: ${flightplan.cid}`, + `Status: ${flightplan.status}`, + `Equipment: ${flightplan.equipment} (${flightplan.aircraftType})`, + `Speed: ${flightplan.speed} knots`, + `Altitude: ${flightplan.altitude}`, + `Route: ${flightplan.departure} -> ${flightplan.destination}`, + `Full Route: ${flightplan.route}`, + `Alternate: ${flightplan.alternate}`, + `Beacon Code: ${flightplan.assignedBeaconCode || 'Not Assigned'}`, + `Pilot CID: ${flightplan.pilotCid}`, + `ETD: ${new Date(flightplan.estimatedDepartureTime * 1000).toISOString()}`, + `Fuel: ${flightplan.fuelHours}:${flightplan.fuelMinutes.toString().padStart(2, '0')}`, + `Enroute Time: ${flightplan.hoursEnroute}:${flightplan.minutesEnroute.toString().padStart(2, '0')}`, + `Remarks: ${flightplan.remarks || 'None'}` + ]; + + return lines.join('\n'); + } + + /** + * Format multiple flightplans in a table-like format + */ + formatFlightplansTable(flightplans: ApiFlightplan[]): string { + if (flightplans.length === 0) { + return 'No flightplans found.'; + } + + const header = 'ACID TYPE DEP DEST ALT SPEED STATUS PILOT'; + const separator = '-'.repeat(header.length); + + const rows = flightplans.map(fp => { + const acid = fp.aircraftId.padEnd(8); + const type = fp.aircraftType.substring(0, 4).padEnd(5); + const dep = fp.departure.padEnd(4); + const dest = fp.destination.padEnd(4); + const alt = fp.altitude.padEnd(6); + const speed = fp.speed.toString().padEnd(5); + const status = fp.status.substring(0, 8).padEnd(9); + const pilot = fp.pilotCid.substring(0, 7); + + return `${acid} ${type} ${dep} ${dest} ${alt} ${speed} ${status} ${pilot}`; + }); + + return [header, separator, ...rows].join('\n'); + } +} \ No newline at end of file diff --git "a/src/styles/fonts/FDIO-font.ttf\357\200\272Zone.Identifier" "b/src/styles/fonts/FDIO-font.ttf\357\200\272Zone.Identifier" new file mode 100644 index 0000000..f0e6673 --- /dev/null +++ "b/src/styles/fonts/FDIO-font.ttf\357\200\272Zone.Identifier" @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://l.facebook.com/ +HostUrl=https://cdn.fbsbx.com/v/t59.2708-21/62047884_3313478595345010_6921569440815382528_n.ttf/FDIO-font.ttf?_nc_cat=108&_nc_sid=0cab14&_nc_ohc=eW4Wjh1cuVAAX9tOUpN&_nc_ht=cdn.fbsbx.com&oh=fc20d475eb6094fab793ff64b5d5b42e&oe=5EA2C623&dl=1&fbclid=IwAR0cqA8-Uguy2NOWwKvdaA5xTnecwSGPfjshtkR_0YdQAydVZh1N56woiTY diff --git a/src/styles/fonts/FDIOv1.ttf b/src/styles/fonts/FDIOv1.ttf new file mode 100644 index 0000000..dd94ca5 Binary files /dev/null and b/src/styles/fonts/FDIOv1.ttf differ diff --git a/src/styles/fonts/FDIOv2.ttf b/src/styles/fonts/FDIOv2.ttf new file mode 100644 index 0000000..10e6a3b Binary files /dev/null and b/src/styles/fonts/FDIOv2.ttf differ diff --git "a/src/styles/fonts/FDIOv2.ttf\357\200\272Zone.Identifier" "b/src/styles/fonts/FDIOv2.ttf\357\200\272Zone.Identifier" new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/terminal.css b/src/styles/terminal.css new file mode 100644 index 0000000..10e24ae --- /dev/null +++ b/src/styles/terminal.css @@ -0,0 +1,11 @@ +@import "tailwindcss"; + +@font-face { + font-family: FDIO; + src: url('./fonts/FDIOv2.ttf'); +} + +@theme { + --font-FDIO: FDIO, 'sans-serif'; + --color-fdio-green: #00e10f; +} \ No newline at end of file diff --git a/src/types/aircraftId.ts b/src/types/aircraftId.ts new file mode 100644 index 0000000..cdeb013 --- /dev/null +++ b/src/types/aircraftId.ts @@ -0,0 +1 @@ +export type AircraftId = string; diff --git a/src/types/apiTypes/apiFlightplan.ts b/src/types/apiTypes/apiFlightplan.ts new file mode 100644 index 0000000..94603ea --- /dev/null +++ b/src/types/apiTypes/apiFlightplan.ts @@ -0,0 +1,59 @@ +import type { HoldAnnotations } from "../hold/holdAnnotations"; +import type { Nullable } from "../utility-types"; + +export type ApiFlightplan = { + aircraftId: string; + cid: string; + status: "Proposed" | "Active" | "Tentative"; + assignedBeaconCode: Nullable; + equipment: string; + aircraftType: string; + icaoEquipmentCodes: string; + icaoSurveillanceCodes: string; + faaEquipmentSuffix: string; + speed: number; + altitude: string; + departure: string; + destination: string; + alternate: string; + route: string; + estimatedDepartureTime: number; + actualDepartureTime: number; + fuelHours: number; + fuelMinutes: number; + hoursEnroute: number; + minutesEnroute: number; + pilotCid: string; + remarks: string; + holdAnnotations: null; + wakeTurbulenceCode: string; +}; + +type CreateOrAmendFlightplanDtoKeys = + | "aircraftId" + | "cid" + | "status" + | "assignedBeaconCode" + | "equipment" + | "aircraftType" + | "icaoEquipmentCodes" + | "icaoSurveillanceCodes" + | "faaEquipmentSuffix" + | "speed" + | "altitude" + | "departure" + | "destination" + | "alternate" + | "route" + | "estimatedDepartureTime" + | "actualDepartureTime" + | "fuelHours" + | "fuelMinutes" + | "hoursEnroute" + | "minutesEnroute" + | "pilotCid" + | "remarks" + | "holdAnnotations" + | "wakeTurbulenceCode"; + +export type CreateOrAmendFlightplanDto = Pick; diff --git a/src/types/apiTypes/apiSessionInfoDto.ts b/src/types/apiTypes/apiSessionInfoDto.ts new file mode 100644 index 0000000..3afdc28 --- /dev/null +++ b/src/types/apiTypes/apiSessionInfoDto.ts @@ -0,0 +1,24 @@ +export interface ApiSessionInfoDto { + id: string; + artccId: string; + isActive?: boolean; + isPseudoController: boolean; + callsign?: string; + role?: string; + positions: Array<{ + isPrimary: boolean; + facilityId: string; + position: { + id: string; + callsign: string; + name: string; + radioName: string; + frequency: number; + starred: boolean; + eramConfiguration: { + sectorId: string; + } | null; + starsConfiguration: any | null; + }; + }>; +} diff --git a/src/types/apiTypes/apiTopic.ts b/src/types/apiTypes/apiTopic.ts new file mode 100644 index 0000000..0eb91ea --- /dev/null +++ b/src/types/apiTypes/apiTopic.ts @@ -0,0 +1,8 @@ +export class ApiTopic { + constructor( + public category: string, + public facilityId: string, + public subset: number | null = null, + public sectorId: string | null = null + ) {} +} \ No newline at end of file diff --git a/src/types/apiTypes/eramTypes.ts b/src/types/apiTypes/eramTypes.ts new file mode 100644 index 0000000..d281d7c --- /dev/null +++ b/src/types/apiTypes/eramTypes.ts @@ -0,0 +1,23 @@ +export enum EramPositionType { + RSide, + DSide +} + +export interface EramMessageElement { + token?: string | null; + targetAircraftId?: string | null; + trackAircraftId?: string | null; +} + +export type ProcessEramMessageDto = { + source: EramPositionType | null; + elements: EramMessageElement[]; + invertNumericKeypad: boolean; +}; + +export interface EramMessageProcessingResultDto { + isSuccess: boolean; + autoRecall: boolean; + feedback: string[]; + response?: string; +} diff --git a/src/types/apiTypes/index.ts b/src/types/apiTypes/index.ts new file mode 100644 index 0000000..c95d22a --- /dev/null +++ b/src/types/apiTypes/index.ts @@ -0,0 +1,19 @@ +export type { ApiFlightplan, CreateOrAmendFlightplanDto } from './apiFlightplan'; + +export interface EramTrackDto { + id: string; + callsign: string; + // Add other track properties as needed +} + +export interface ApiAircraftTrack { + id: string; + callsign: string; + // Add other aircraft track properties as needed +} + +export interface OpenPositionDto { + id: string; + name: string; + // Add other position properties as needed +} diff --git a/src/types/hold/holdAnnotations.ts b/src/types/hold/holdAnnotations.ts new file mode 100644 index 0000000..1b5ffa4 --- /dev/null +++ b/src/types/hold/holdAnnotations.ts @@ -0,0 +1,4 @@ +export interface HoldAnnotations { + // Add hold annotation properties as needed + [key: string]: any; +} diff --git a/src/types/outageEntry.ts b/src/types/outageEntry.ts new file mode 100644 index 0000000..8858086 --- /dev/null +++ b/src/types/outageEntry.ts @@ -0,0 +1,3 @@ +export class OutageEntry { + constructor(public id: string, public message: string) {} +} diff --git a/src/types/utility-types.ts b/src/types/utility-types.ts new file mode 100644 index 0000000..aa32b2d --- /dev/null +++ b/src/types/utility-types.ts @@ -0,0 +1 @@ +export type Nullable = T | null; diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..8444045 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,4 @@ +export const DOMAIN = process.env.DOMAIN; +export const VATSIM_CLIENT_ID = process.env.VATSIM_CLIENT_ID; +export const VERSION = process.env.VERSION; +export const VNAS_CONFIG_URL = process.env.VNAS_CONFIG_URL; \ No newline at end of file diff --git a/src/utils/hubUtils.ts b/src/utils/hubUtils.ts new file mode 100644 index 0000000..8a644ee --- /dev/null +++ b/src/utils/hubUtils.ts @@ -0,0 +1,35 @@ +import type { HubConnection } from "@microsoft/signalr"; +import { HubConnectionState } from "@microsoft/signalr/dist/esm/HubConnection"; + +export type HubInvocation = (connection: HubConnection) => Promise; + +const ensureConnected = async ( + hubConnection: HubConnection | null, + connectHub: () => Promise +): Promise => { + if (!hubConnection) { + await connectHub(); + return hubConnection; + } + + if (hubConnection.state !== HubConnectionState.Connected) { + await connectHub(); + } + + return hubConnection; +}; + +export const invokeHub = async ( + hubConnection: HubConnection | null, + connectHub: () => Promise, + invocation: HubInvocation +): Promise => { + const connection = await ensureConnected(hubConnection, connectHub); + if (!connection) return; + + try { + return await invocation(connection); + } catch (error) { + console.log("Hub invocation error:", error); + } +};