diff --git a/.gitignore b/.gitignore index 9d6042a0..aa52e6dc 100644 --- a/.gitignore +++ b/.gitignore @@ -129,7 +129,7 @@ output/ .scannerwork/ # Demo prototype (standalone showcase, lives on demo branch) -demo/ +/demo/ # Generated lazy-loaded font assets (built by scripts/extract-fonts.mjs) ui/public/fonts/ diff --git a/apps/demo/index.html b/apps/demo/index.html new file mode 100644 index 00000000..3076bfe2 --- /dev/null +++ b/apps/demo/index.html @@ -0,0 +1,34 @@ + + + + + + + + + + + Drydock Demo — Interactive Container Update Monitoring + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/apps/demo/package-lock.json b/apps/demo/package-lock.json new file mode 100644 index 00000000..251bce72 --- /dev/null +++ b/apps/demo/package-lock.json @@ -0,0 +1,3078 @@ +{ + "name": "drydock-demo", + "version": "1.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "drydock-demo", + "version": "1.4.0", + "dependencies": { + "@fontsource/ibm-plex-mono": "^5.2.7", + "iconify-icon": "^3.0.2", + "msw": "^2.10.2", + "tailwindcss": "^4.2.1", + "vue": "^3.5.29", + "vue-router": "^5.0.2" + }, + "devDependencies": { + "@fontsource/comic-mono": "^5.2.5", + "@fontsource/commit-mono": "^5.2.5", + "@fontsource/inconsolata": "^5.2.7", + "@fontsource/jetbrains-mono": "^5.2.7", + "@fontsource/source-code-pro": "^5.2.7", + "@iconify-json/fa6-solid": "^1.2.4", + "@iconify-json/heroicons": "^1.2.3", + "@iconify-json/iconoir": "^1.2.10", + "@iconify-json/lucide": "^1.2.95", + "@iconify-json/ph": "^1.2.2", + "@iconify-json/tabler": "^1.2.28", + "@tailwindcss/vite": "^4.2.1", + "@vitejs/plugin-vue": "^6.0.4", + "typescript": "^5.9.3", + "vite": "^7.3.1" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fontsource/comic-mono": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@fontsource/comic-mono/-/comic-mono-5.2.5.tgz", + "integrity": "sha512-LkTanqEt2YSFors2j+VSnvJlVO5n8zDyjlQU+hz9yl30MHyRsyJfgWyrVHKfE0NjbMVlVScZ4xT5Q72oR0MyXA==", + "dev": true, + "license": "mit", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/commit-mono": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@fontsource/commit-mono/-/commit-mono-5.2.5.tgz", + "integrity": "sha512-htX8yQWtiPt5L1Hzh4sirvfUJT2+KYiquDB/Q2sY2tWQYplpBUOD5zHnIM3k36Hnm4V+JIIqA/wmwupSQ68WjA==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/ibm-plex-mono": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.7.tgz", + "integrity": "sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/inconsolata": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.2.8.tgz", + "integrity": "sha512-lIZW+WOZYpUH91g9r6rYYhfTmptF3YPPM54ZOs8IYVeeL4SeiAu4tfj7mdr8llYEq31DLYgi6JtGIJa192gB0Q==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/jetbrains-mono": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", + "integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/source-code-pro": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.2.7.tgz", + "integrity": "sha512-7papq9TH94KT+S5VSY8cU7tFmwuGkIe3qxXRMscuAXH6AjMU+KJI75f28FzgBVDrlMfA0jjlTV4/x5+H5o/5EQ==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@iconify-json/fa6-solid": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@iconify-json/fa6-solid/-/fa6-solid-1.2.4.tgz", + "integrity": "sha512-LmDNNdJVyvF5mPm1yxWvL8KjCc/E8LzoqnF1LNTVpyY2ZJRUlGOWuPIThdbuFBF2IovgttkIyumhyqfmlHdwKg==", + "dev": true, + "license": "CC-BY-4.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify-json/heroicons": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@iconify-json/heroicons/-/heroicons-1.2.3.tgz", + "integrity": "sha512-n+vmCEgTesRsOpp5AB5ILB6srsgsYK+bieoQBNlafvoEhjVXLq8nIGN4B0v/s4DUfa0dOrjwE/cKJgIKdJXOEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify-json/iconoir": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@iconify-json/iconoir/-/iconoir-1.2.10.tgz", + "integrity": "sha512-NnbdB9S5G++6wE5aEZhzpFR0HRcaZFSbJJIHOGF2axaNVKnSUs4NBW2z0uhZnM00iUkiK848Sp81EZPg52DL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify-json/lucide": { + "version": "1.2.96", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.96.tgz", + "integrity": "sha512-EICTusj67lvSmEaH/Lhe68ZyzcgfcPNpY00exAOkoo+z2fnLeNy31mdE3E/4/q8WjzZrICAZDxY3d6j7LzkgNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify-json/ph": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@iconify-json/ph/-/ph-1.2.2.tgz", + "integrity": "sha512-PgkEZNtqa8hBGjHXQa4pMwZa93hmfu8FUSjs/nv4oUU6yLsgv+gh9nu28Kqi8Fz9CCVu4hj1MZs9/60J57IzFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify-json/tabler": { + "version": "1.2.31", + "resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.2.31.tgz", + "integrity": "sha512-Jfcw5TpGhfKKWyz1dGk7e79zIgDmpMKNYL0bjt17sURBPifAxowQcWAzcEhuiWU7FGXUM2NT6UhvACFZp7Hnjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", + "integrity": "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==", + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", + "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue-macros/common": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", + "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz", + "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.30", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz", + "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", + "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.30", + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz", + "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.7.tgz", + "integrity": "sha512-tc1TXAxclsn55JblLkFVcIRG7MeSJC4fWsPjfM7qu/IcmPUYnQ5Q8vzWwBpyDY24ZjmZTUCCwjRSNbx58IhlAA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.0.7" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.7.tgz", + "integrity": "sha512-H6esJGHGl5q0E9iV3m2EoBQHJ+V83WMW83A0/+Fn95eZ2iIvdsq4+UCS6yT/Fdd4cGZSchx/MdWDreM3WqMsDw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.7", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.7.tgz", + "integrity": "sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz", + "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz", + "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz", + "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/runtime-core": "3.5.30", + "@vue/shared": "3.5.30", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz", + "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "vue": "3.5.30" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz", + "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", + "integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "ast-kit": "^2.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/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==", + "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==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphql": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.1.tgz", + "integrity": "sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "license": "MIT" + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/iconify-icon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/iconify-icon/-/iconify-icon-3.0.2.tgz", + "integrity": "sha512-DYPAumiUeUeT/GHT8x2wrAVKn1FqZJqFH0Y5pBefapWRreV1BBvqBVMb0020YQ2njmbR59r/IathL2d2OrDrxA==", + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "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-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/mlly": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz", + "integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/msw": { + "version": "2.12.10", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.10.tgz", + "integrity": "sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.41.2", + "@open-draft/deferred-promise": "^2.2.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.0.2", + "graphql": "^16.12.0", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.10.1", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.0", + "type-fest": "^5.2.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rettime": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", + "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz", + "integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.25" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz", + "integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/type-fest": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz", + "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-sfc": "3.5.30", + "@vue/runtime-dom": "3.5.30", + "@vue/server-renderer": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.3.tgz", + "integrity": "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.28.6", + "@vue-macros/common": "^3.1.1", + "@vue/devtools-api": "^8.0.6", + "ast-walker-scope": "^0.8.3", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.0", + "muggle-string": "^0.4.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "scule": "^1.3.0", + "tinyglobby": "^0.2.15", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1", + "yaml": "^2.8.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.17", + "pinia": "^3.0.4", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + } + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/apps/demo/package.json b/apps/demo/package.json new file mode 100644 index 00000000..208316af --- /dev/null +++ b/apps/demo/package.json @@ -0,0 +1,41 @@ +{ + "name": "drydock-demo", + "version": "1.4.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@fontsource/ibm-plex-mono": "^5.2.7", + "iconify-icon": "^3.0.2", + "msw": "^2.10.2", + "tailwindcss": "^4.2.1", + "vue": "^3.5.29", + "vue-router": "^5.0.2" + }, + "devDependencies": { + "@fontsource/comic-mono": "^5.2.5", + "@fontsource/commit-mono": "^5.2.5", + "@fontsource/inconsolata": "^5.2.7", + "@fontsource/jetbrains-mono": "^5.2.7", + "@fontsource/source-code-pro": "^5.2.7", + "@iconify-json/fa6-solid": "^1.2.4", + "@iconify-json/heroicons": "^1.2.3", + "@iconify-json/iconoir": "^1.2.10", + "@iconify-json/lucide": "^1.2.95", + "@iconify-json/ph": "^1.2.2", + "@iconify-json/tabler": "^1.2.28", + "@tailwindcss/vite": "^4.2.1", + "@vitejs/plugin-vue": "^6.0.4", + "typescript": "^5.9.3", + "vite": "^7.3.1" + }, + "msw": { + "workerDirectory": [ + "public" + ] + } +} diff --git a/apps/demo/public/apple-touch-icon.png b/apps/demo/public/apple-touch-icon.png new file mode 100644 index 00000000..839ad65b Binary files /dev/null and b/apps/demo/public/apple-touch-icon.png differ diff --git a/apps/demo/public/favicon-96x96.png b/apps/demo/public/favicon-96x96.png new file mode 100644 index 00000000..900cd78d Binary files /dev/null and b/apps/demo/public/favicon-96x96.png differ diff --git a/apps/demo/public/favicon.ico b/apps/demo/public/favicon.ico new file mode 100644 index 00000000..52972c7f Binary files /dev/null and b/apps/demo/public/favicon.ico differ diff --git a/apps/demo/public/favicon.svg b/apps/demo/public/favicon.svg new file mode 100644 index 00000000..363c7f2d --- /dev/null +++ b/apps/demo/public/favicon.svg @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/apps/demo/public/mockServiceWorker.js b/apps/demo/public/mockServiceWorker.js new file mode 100644 index 00000000..a255338c --- /dev/null +++ b/apps/demo/public/mockServiceWorker.js @@ -0,0 +1,336 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.12.10'; +const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'; +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse'); +const activeClientIds = new Set(); + +addEventListener('install', () => { + self.skipWaiting(); +}); + +addEventListener('activate', (event) => { + event.waitUntil(self.clients.claim()); +}); + +addEventListener('message', async (event) => { + const clientId = Reflect.get(event.source || {}, 'id'); + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +addEventListener('fetch', (event) => { + const requestInterceptedAt = Date.now(); + + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been terminated (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + const requestId = crypto.randomUUID(); + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)); +}); + +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { + const client = await resolveMainClient(event); + const requestCloneForEvents = event.request.clone(); + const response = await getResponse(event, client, requestId, requestInterceptedAt); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents); + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone(); + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ); + } + + return response; +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (activeClientIds.has(event.clientId)) { + return client; + } + + if (client?.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone(); + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers); + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept'); + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()); + const filteredValues = values.filter((value) => value !== 'msw/passthrough'); + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')); + } else { + headers.delete('accept'); + } + } + + return fetch(requestClone, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request); + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + interceptedAt: requestInterceptedAt, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data); + } + + case 'PASSTHROUGH': { + return passthrough(); + } + } + + return passthrough(); +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(message, [channel.port2, ...transferrables.filter(Boolean)]); + }); +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error(); + } + + const mockedResponse = new Response(response.body, response); + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }); + + return mockedResponse; +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + }; +} diff --git a/apps/demo/public/og-image.png b/apps/demo/public/og-image.png new file mode 100644 index 00000000..7bbb6ab4 Binary files /dev/null and b/apps/demo/public/og-image.png differ diff --git a/apps/demo/public/site.webmanifest b/apps/demo/public/site.webmanifest new file mode 100644 index 00000000..94e72d3f --- /dev/null +++ b/apps/demo/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Drydock Demo", + "short_name": "Drydock Demo", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#000000", + "background_color": "#000000", + "display": "standalone" +} diff --git a/apps/demo/public/web-app-manifest-192x192.png b/apps/demo/public/web-app-manifest-192x192.png new file mode 100644 index 00000000..86d606a2 Binary files /dev/null and b/apps/demo/public/web-app-manifest-192x192.png differ diff --git a/apps/demo/public/web-app-manifest-512x512.png b/apps/demo/public/web-app-manifest-512x512.png new file mode 100644 index 00000000..bd6910de Binary files /dev/null and b/apps/demo/public/web-app-manifest-512x512.png differ diff --git a/apps/demo/src/demo.css b/apps/demo/src/demo.css new file mode 100644 index 00000000..3523d112 --- /dev/null +++ b/apps/demo/src/demo.css @@ -0,0 +1,2 @@ +@import "tailwindcss"; +@source "../../../ui/src/**/*.{vue,ts}"; diff --git a/apps/demo/src/main.ts b/apps/demo/src/main.ts new file mode 100644 index 00000000..3afa25b9 --- /dev/null +++ b/apps/demo/src/main.ts @@ -0,0 +1,80 @@ +/** + * Drydock Demo — entry point + * + * 1. Patch EventSource with FakeEventSource (before any UI code loads) + * 2. Start MSW service worker to intercept all fetch() calls + * 3. Boot the real Vue UI (imported from ../../ui/src via Vite alias) + */ + +import { FakeEventSource } from './mocks/sse'; + +// Patch EventSource BEFORE any UI code loads — the SSE service +// creates an EventSource in AppLayout, so this must happen first. +(globalThis as unknown as { EventSource: typeof FakeEventSource }).EventSource = FakeEventSource; + +async function boot() { + // Start MSW — must be running before the UI makes any fetch() calls + const { worker } = await import('./mocks/browser'); + await worker.start({ + onUnhandledRequest: 'bypass', + quiet: true, + }); + + // Import demo CSS for Tailwind @source directive + await import('./demo.css'); + + // Now boot the real UI + await import('@/main'); + + // Tell the parent frame (website) we loaded successfully + if (window.parent !== window) { + window.parent.postMessage({ type: 'drydock-demo-ready' }, '*'); + } + + // Auto-fill login credentials so demo visitors just click "Sign in". + // Uses MutationObserver to catch the login form whenever it appears. + autofillLoginForm(); +} + +/** + * Watch for the login form and pre-fill demo credentials. + * Vue renders asynchronously, so we observe DOM mutations until the + * username input appears, fill both fields, then keep watching in case + * the user logs out and the form re-renders. + */ +function autofillLoginForm() { + const fill = () => { + const usernameInput = document.querySelector( + 'input[autocomplete="username"]', + ); + const passwordInput = document.querySelector( + 'input[autocomplete="current-password"]', + ); + if (usernameInput && passwordInput) { + if (!usernameInput.value) { + setNativeValue(usernameInput, 'demo'); + } + if (!passwordInput.value) { + setNativeValue(passwordInput, 'demo'); + } + } + }; + + // Trigger Vue reactivity by dispatching an input event after setting value + // via the native setter — v-model listens on 'input' events. + const setNativeValue = (el: HTMLInputElement, value: string) => { + const nativeSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set; + if (nativeSetter) { + nativeSetter.call(el, value); + el.dispatchEvent(new Event('input', { bubbles: true })); + } + }; + + const observer = new MutationObserver(() => fill()); + observer.observe(document.body, { childList: true, subtree: true }); + + // Also try immediately in case the form is already rendered + fill(); +} + +boot(); diff --git a/apps/demo/src/mocks/browser.ts b/apps/demo/src/mocks/browser.ts new file mode 100644 index 00000000..0a564278 --- /dev/null +++ b/apps/demo/src/mocks/browser.ts @@ -0,0 +1,4 @@ +import { setupWorker } from 'msw/browser'; +import { handlers } from './handlers'; + +export const worker = setupWorker(...handlers); diff --git a/apps/demo/src/mocks/data/agents.ts b/apps/demo/src/mocks/data/agents.ts new file mode 100644 index 00000000..542782b1 --- /dev/null +++ b/apps/demo/src/mocks/data/agents.ts @@ -0,0 +1,19 @@ +export const agents = [ + { + name: 'nas-agent', + host: '192.168.1.50', + port: 3001, + connected: true, + version: '1.4.0', + os: 'linux', + arch: 'amd64', + cpus: 4, + memoryGb: 16, + uptimeSeconds: 864000, + lastSeen: new Date().toISOString(), + logLevel: 'info', + pollInterval: '*/30 * * * *', + containers: { total: 3, running: 3, stopped: 0 }, + images: 3, + }, +]; diff --git a/apps/demo/src/mocks/data/audit.ts b/apps/demo/src/mocks/data/audit.ts new file mode 100644 index 00000000..8f28f366 --- /dev/null +++ b/apps/demo/src/mocks/data/audit.ts @@ -0,0 +1,212 @@ +export const auditEntries = [ + { + id: 'aud-001', + timestamp: '2026-03-10T08:00:00.000Z', + action: 'system:start', + details: 'Drydock v1.4.0 started', + }, + { + id: 'aud-002', + timestamp: '2026-03-10T08:00:05.000Z', + action: 'container:watch', + container: 'grafana', + details: 'Started watching grafana/grafana:11.3.0', + }, + { + id: 'aud-003', + timestamp: '2026-03-10T08:00:05.000Z', + action: 'container:watch', + container: 'prometheus', + details: 'Started watching prom/prometheus:v2.54.0', + }, + { + id: 'aud-004', + timestamp: '2026-03-10T08:00:06.000Z', + action: 'container:watch', + container: 'traefik', + details: 'Started watching traefik:v3.2.0', + }, + { + id: 'aud-005', + timestamp: '2026-03-10T07:45:00.000Z', + action: 'auth:login', + user: 'admin', + details: 'Login from 192.168.1.10', + }, + { + id: 'aud-006', + timestamp: '2026-03-09T14:22:00.000Z', + action: 'container:check', + container: 'grafana', + details: 'Checking grafana/grafana for updates', + }, + { + id: 'aud-007', + timestamp: '2026-03-09T14:22:02.000Z', + action: 'container:update-detected', + container: 'grafana', + details: 'Minor update available: 11.3.0 -> 11.4.0', + }, + { + id: 'aud-008', + timestamp: '2026-03-09T14:22:03.000Z', + action: 'trigger:fired', + container: 'grafana', + details: 'Notified slack.homelab: minor update for grafana', + }, + { + id: 'aud-009', + timestamp: '2026-03-09T14:22:04.000Z', + action: 'trigger:fired', + container: 'grafana', + details: 'Notified discord.updates: minor update for grafana', + }, + { + id: 'aud-010', + timestamp: '2026-03-09T11:30:00.000Z', + action: 'container:update-detected', + container: 'jellyfin', + details: 'Patch update available: 10.10.3 -> 10.10.6', + }, + { + id: 'aud-011', + timestamp: '2026-03-09T11:30:02.000Z', + action: 'trigger:fired', + container: 'jellyfin', + details: 'Notified slack.homelab: patch update for jellyfin', + }, + { + id: 'aud-012', + timestamp: '2026-03-09T08:00:00.000Z', + action: 'container:update-detected', + container: 'authelia', + details: 'Minor update available: 4.38.16 -> 4.39.0', + }, + { + id: 'aud-013', + timestamp: '2026-03-09T08:00:02.000Z', + action: 'container:scan', + container: 'authelia', + details: 'Security scan completed for ghcr.io/authelia/authelia:4.38.16', + }, + { + id: 'aud-014', + timestamp: '2026-03-09T08:00:04.000Z', + action: 'trigger:fired', + container: 'authelia', + details: 'Notified discord.updates: minor update for authelia', + }, + { + id: 'aud-015', + timestamp: '2026-03-08T16:00:00.000Z', + action: 'container:scan', + container: 'vaultwarden', + details: 'Security scan completed: 1 critical, 1 high, 1 medium, 2 low vulnerabilities', + }, + { + id: 'aud-016', + timestamp: '2026-03-08T16:00:02.000Z', + action: 'trigger:fired', + container: 'vaultwarden', + details: 'Notified slack.homelab: critical CVE in vaultwarden', + }, + { + id: 'aud-017', + timestamp: '2026-03-08T16:00:03.000Z', + action: 'trigger:fired', + container: 'vaultwarden', + details: 'Notified smtp.email: critical CVE in vaultwarden', + }, + { + id: 'aud-018', + timestamp: '2026-03-08T14:22:00.000Z', + action: 'container:update-detected', + container: 'grafana', + details: 'Minor update available: 11.3.0 -> 11.4.0', + }, + { + id: 'aud-019', + timestamp: '2026-03-07T09:15:00.000Z', + action: 'container:update-detected', + container: 'loki', + details: 'Minor update available: 3.2.0 -> 3.3.1', + }, + { + id: 'aud-020', + timestamp: '2026-03-07T09:15:02.000Z', + action: 'trigger:fired', + container: 'loki', + details: 'Notified slack.homelab: minor update for loki', + }, + { + id: 'aud-021', + timestamp: '2026-03-06T16:45:00.000Z', + action: 'container:update-detected', + container: 'prowlarr', + details: 'Minor update available: 1.25.0 -> 1.26.1', + }, + { + id: 'aud-022', + timestamp: '2026-03-06T16:45:02.000Z', + action: 'trigger:fired', + container: 'prowlarr', + details: 'Notified slack.homelab: minor update for prowlarr', + }, + { + id: 'aud-023', + timestamp: '2026-03-06T10:00:00.000Z', + action: 'container:check', + container: 'traefik', + details: 'Checking traefik:v3.2.0 for updates', + }, + { + id: 'aud-024', + timestamp: '2026-03-05T22:00:00.000Z', + action: 'settings:update', + user: 'admin', + details: 'Updated poll interval to */15 * * * *', + }, + { + id: 'aud-025', + timestamp: '2026-03-05T12:00:00.000Z', + action: 'container:scan', + container: 'jellyfin', + details: 'Security scan completed: 0 critical, 1 high, 2 medium, 5 low vulnerabilities', + }, + { + id: 'aud-026', + timestamp: '2026-03-05T08:30:00.000Z', + action: 'container:check', + container: 'sonarr', + details: 'Checking lscr.io/linuxserver/sonarr:4.0.10 for updates', + }, + { + id: 'aud-027', + timestamp: '2026-03-04T20:00:00.000Z', + action: 'container:scan', + container: 'grafana', + details: 'Security scan completed: 0 critical, 0 high, 1 medium, 3 low vulnerabilities', + }, + { + id: 'aud-028', + timestamp: '2026-03-04T14:00:00.000Z', + action: 'container:policy:snooze', + container: 'portainer', + user: 'admin', + details: 'Snoozed updates for portainer/portainer-ce', + }, + { + id: 'aud-029', + timestamp: '2026-03-04T10:00:00.000Z', + action: 'auth:login', + user: 'admin', + details: 'Login from 192.168.1.10', + }, + { + id: 'aud-030', + timestamp: '2026-03-03T18:00:00.000Z', + action: 'container:watch', + container: 'drydock', + details: 'Started watching ghcr.io/codeswhat/drydock:1.4.0', + }, +]; diff --git a/apps/demo/src/mocks/data/containers.ts b/apps/demo/src/mocks/data/containers.ts new file mode 100644 index 00000000..082259bb --- /dev/null +++ b/apps/demo/src/mocks/data/containers.ts @@ -0,0 +1,360 @@ +/** + * Mock container data in the **API format** expected by mapApiContainer(). + * + * API shape: + * id, name, displayName, displayIcon, status, watcher, + * image: { name, variant, registry: { name, url }, tag: { value, semver } }, + * result?: { tag, link, noUpdateReason }, + * updateAvailable, updateKind?: { kind, semverDiff }, + * security?: { scan?: { status, summary }, updateScan?: { status, summary } }, + * labels?: Record, + * updateDetectedAt, details?: { ports, volumes, env } + */ + +function c(opts: { + id: string; + name: string; + displayName: string; + displayIcon: string; + image: string; + tag: string; + status?: string; + registryType: string; + registryUrl?: string; + newTag?: string; + semverDiff?: string; + scanStatus?: string; + scanSummary?: Record; + updateScanStatus?: string; + updateScanSummary?: Record; + updateDetectedAt?: string; + group: string; + ports?: string[]; + volumes?: string[]; + env?: { key: string; value: string; sensitive?: boolean }[]; +}) { + const hasUpdate = !!opts.newTag; + return { + id: opts.id, + name: opts.name, + displayName: opts.displayName, + displayIcon: opts.displayIcon, + status: opts.status ?? 'running', + watcher: 'local', + image: { + name: opts.image, + registry: { name: opts.registryType, url: opts.registryUrl ?? '' }, + tag: { value: opts.tag, semver: true }, + }, + updateAvailable: hasUpdate, + ...(hasUpdate + ? { + result: { tag: opts.newTag }, + updateKind: { kind: 'tag', semverDiff: opts.semverDiff ?? 'minor' }, + } + : {}), + ...(opts.updateDetectedAt ? { updateDetectedAt: opts.updateDetectedAt } : {}), + security: { + scan: opts.scanStatus + ? { status: opts.scanStatus, summary: opts.scanSummary ?? null } + : undefined, + updateScan: opts.updateScanStatus + ? { status: opts.updateScanStatus, summary: opts.updateScanSummary ?? null } + : undefined, + }, + labels: { + 'dd.watch': 'true', + 'dd.group': opts.group, + 'dd.display.name': opts.displayName, + }, + details: { + ports: opts.ports ?? [], + volumes: opts.volumes ?? [], + env: opts.env ?? [], + }, + }; +} + +export const containers = [ + c({ + id: 'a1b2c3d4e5f6', + name: 'grafana', + displayName: 'Grafana', + displayIcon: 'sh-grafana', + image: 'grafana/grafana', + tag: '11.3.0', + registryType: 'hub', + newTag: '11.4.0', + semverDiff: 'minor', + updateDetectedAt: '2026-03-08T14:22:00.000Z', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 3, medium: 1, high: 0, critical: 0 }, + group: 'monitoring', + ports: ['3000:3000/tcp'], + volumes: ['grafana-data:/var/lib/grafana'], + env: [ + { key: 'GF_SECURITY_ADMIN_USER', value: 'admin' }, + { key: 'GF_SECURITY_ADMIN_PASSWORD', value: '********', sensitive: true }, + { key: 'GF_SERVER_ROOT_URL', value: 'https://grafana.local' }, + ], + }), + c({ + id: 'b2c3d4e5f6a7', + name: 'prometheus', + displayName: 'Prometheus', + displayIcon: 'sh-prometheus', + image: 'prom/prometheus', + tag: 'v2.54.0', + registryType: 'hub', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'monitoring', + ports: ['9090:9090/tcp'], + volumes: [ + 'prometheus-data:/prometheus', + '/etc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro', + ], + }), + c({ + id: 'c3d4e5f6a7b8', + name: 'loki', + displayName: 'Loki', + displayIcon: 'sh-loki', + image: 'grafana/loki', + tag: '3.2.0', + registryType: 'hub', + newTag: '3.3.1', + semverDiff: 'minor', + updateDetectedAt: '2026-03-07T09:15:00.000Z', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'monitoring', + ports: ['3100:3100/tcp'], + volumes: ['loki-data:/loki'], + }), + c({ + id: 'd4e5f6a7b8c9', + name: 'alertmanager', + displayName: 'Alertmanager', + displayIcon: 'sh-prometheus', + image: 'prom/alertmanager', + tag: 'v0.27.0', + registryType: 'hub', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'monitoring', + ports: ['9093:9093/tcp'], + volumes: ['alertmanager-data:/alertmanager'], + }), + c({ + id: 'e5f6a7b8c9d0', + name: 'node-exporter', + displayName: 'Node Exporter', + displayIcon: 'sh-prometheus', + image: 'prom/node-exporter', + tag: 'v1.8.2', + registryType: 'hub', + scanStatus: 'not-scanned', + group: 'monitoring', + ports: ['9100:9100/tcp'], + volumes: ['/proc:/host/proc:ro', '/sys:/host/sys:ro', '/:/rootfs:ro'], + }), + c({ + id: 'f6a7b8c9d0e1', + name: 'jellyfin', + displayName: 'Jellyfin', + displayIcon: 'sh-jellyfin', + image: 'jellyfin/jellyfin', + tag: '10.10.3', + registryType: 'hub', + newTag: '10.10.6', + semverDiff: 'patch', + updateDetectedAt: '2026-03-09T11:30:00.000Z', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 5, medium: 2, high: 1, critical: 0 }, + group: 'media', + ports: ['8096:8096/tcp', '8920:8920/tcp'], + volumes: [ + 'jellyfin-config:/config', + 'jellyfin-cache:/cache', + '/media/movies:/media/movies:ro', + '/media/tv:/media/tv:ro', + ], + env: [{ key: 'JELLYFIN_PublishedServerUrl', value: 'https://jellyfin.local' }], + }), + c({ + id: 'a7b8c9d0e1f2', + name: 'sonarr', + displayName: 'Sonarr', + displayIcon: 'sh-sonarr', + image: 'linuxserver/sonarr', + tag: '4.0.10', + registryType: 'lscr', + registryUrl: 'https://lscr.io', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'media', + ports: ['8989:8989/tcp'], + volumes: ['sonarr-config:/config', '/media/tv:/tv', '/media/downloads:/downloads'], + env: [ + { key: 'PUID', value: '1000' }, + { key: 'PGID', value: '1000' }, + { key: 'TZ', value: 'America/New_York' }, + ], + }), + c({ + id: 'b8c9d0e1f2a3', + name: 'radarr', + displayName: 'Radarr', + displayIcon: 'sh-radarr', + image: 'linuxserver/radarr', + tag: '5.14.0', + registryType: 'lscr', + registryUrl: 'https://lscr.io', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'media', + ports: ['7878:7878/tcp'], + volumes: ['radarr-config:/config', '/media/movies:/movies', '/media/downloads:/downloads'], + env: [ + { key: 'PUID', value: '1000' }, + { key: 'PGID', value: '1000' }, + { key: 'TZ', value: 'America/New_York' }, + ], + }), + c({ + id: 'c9d0e1f2a3b4', + name: 'prowlarr', + displayName: 'Prowlarr', + displayIcon: 'sh-prowlarr', + image: 'linuxserver/prowlarr', + tag: '1.25.0', + registryType: 'lscr', + registryUrl: 'https://lscr.io', + newTag: '1.26.1', + semverDiff: 'minor', + updateDetectedAt: '2026-03-06T16:45:00.000Z', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'media', + ports: ['9696:9696/tcp'], + volumes: ['prowlarr-config:/config'], + env: [ + { key: 'PUID', value: '1000' }, + { key: 'PGID', value: '1000' }, + { key: 'TZ', value: 'America/New_York' }, + ], + }), + c({ + id: 'd0e1f2a3b4c5', + name: 'traefik', + displayName: 'Traefik', + displayIcon: 'sh-traefik', + image: 'traefik', + tag: 'v3.2.0', + registryType: 'hub', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'infra', + ports: ['80:80/tcp', '443:443/tcp', '8080:8080/tcp'], + volumes: ['/var/run/docker.sock:/var/run/docker.sock:ro', 'traefik-certs:/certs'], + env: [ + { key: 'CF_API_EMAIL', value: 'admin@example.com' }, + { key: 'CF_DNS_API_TOKEN', value: '********', sensitive: true }, + ], + }), + c({ + id: 'e1f2a3b4c5d6', + name: 'authelia', + displayName: 'Authelia', + displayIcon: 'sh-authelia', + image: 'authelia/authelia', + tag: '4.38.16', + registryType: 'ghcr', + registryUrl: 'https://ghcr.io', + newTag: '4.39.0', + semverDiff: 'minor', + updateDetectedAt: '2026-03-09T08:00:00.000Z', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 1, medium: 0, high: 0, critical: 0 }, + updateScanStatus: 'scanned', + updateScanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'infra', + ports: ['9091:9091/tcp'], + volumes: ['authelia-config:/config'], + env: [ + { key: 'AUTHELIA_JWT_SECRET', value: '********', sensitive: true }, + { key: 'AUTHELIA_SESSION_SECRET', value: '********', sensitive: true }, + ], + }), + c({ + id: 'f2a3b4c5d6e7', + name: 'vaultwarden', + displayName: 'Vaultwarden', + displayIcon: 'sh-vaultwarden', + image: 'vaultwarden/server', + tag: '1.32.5', + registryType: 'hub', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 2, medium: 1, high: 1, critical: 1 }, + group: 'infra', + ports: ['8082:80/tcp'], + volumes: ['vaultwarden-data:/data'], + env: [ + { key: 'ADMIN_TOKEN', value: '********', sensitive: true }, + { key: 'DOMAIN', value: 'https://vault.local' }, + { key: 'SIGNUPS_ALLOWED', value: 'false' }, + ], + }), + c({ + id: 'a3b4c5d6e7f8', + name: 'adguard', + displayName: 'AdGuard Home', + displayIcon: 'sh-adguard-home', + image: 'adguardteam/adguardhome', + tag: 'v0.107.55', + registryType: 'hub', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'infra', + ports: ['53:53/tcp', '53:53/udp', '3003:3000/tcp'], + volumes: ['adguard-work:/opt/adguardhome/work', 'adguard-conf:/opt/adguardhome/conf'], + }), + c({ + id: 'b4c5d6e7f8a9', + name: 'drydock', + displayName: 'Drydock', + displayIcon: 'sh-drydock', + image: 'codeswhat/drydock', + tag: '1.4.0', + registryType: 'ghcr', + registryUrl: 'https://ghcr.io', + scanStatus: 'scanned', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'self', + ports: ['3000:3000/tcp'], + volumes: ['/var/run/docker.sock:/var/run/docker.sock:ro', 'drydock-store:/store'], + env: [ + { key: 'DD_WATCHER_LOCAL_SOCKET', value: '/var/run/docker.sock' }, + { key: 'DD_REGISTRY_HUB_PUBLIC_AUTH', value: 'anonymous' }, + { key: 'DD_REGISTRY_GHCR_PRIVATE_TOKEN', value: '********', sensitive: true }, + ], + }), + { + ...c({ + id: 'c5d6e7f8a9b0', + name: 'portainer', + displayName: 'Portainer', + displayIcon: 'sh-portainer', + image: 'portainer/portainer-ce', + tag: '2.22.0', + status: 'stopped', + registryType: 'hub', + scanStatus: 'blocked', + scanSummary: { unknown: 0, low: 0, medium: 0, high: 0, critical: 0 }, + group: 'self', + ports: ['9443:9443/tcp'], + volumes: ['/var/run/docker.sock:/var/run/docker.sock', 'portainer-data:/data'], + }), + }, +]; diff --git a/apps/demo/src/mocks/data/logs.ts b/apps/demo/src/mocks/data/logs.ts new file mode 100644 index 00000000..58669b1a --- /dev/null +++ b/apps/demo/src/mocks/data/logs.ts @@ -0,0 +1,155 @@ +const now = Date.now(); +const m = (minutes: number) => new Date(now - minutes * 60_000).toISOString(); + +export const logEntries = [ + { + timestamp: m(1), + level: 'info' as const, + component: 'api', + message: 'GET /api/containers 200 12ms', + }, + { + timestamp: m(2), + level: 'debug' as const, + component: 'store', + message: 'Container collection query: 15 results', + }, + { + timestamp: m(3), + level: 'info' as const, + component: 'watcher', + message: 'Poll cycle completed for docker.local: 15 containers', + }, + { + timestamp: m(5), + level: 'info' as const, + component: 'scanner', + message: 'Scan completed for vaultwarden/server:1.32.5 — 5 vulnerabilities found', + }, + { + timestamp: m(6), + level: 'warn' as const, + component: 'scanner', + message: 'Critical vulnerability CVE-2024-45678 detected in vaultwarden/server:1.32.5', + }, + { + timestamp: m(8), + level: 'info' as const, + component: 'registry', + message: 'Fetched tags for grafana/grafana from Docker Hub (42 tags)', + }, + { + timestamp: m(10), + level: 'info' as const, + component: 'registry', + message: 'Fetched tags for prom/prometheus from Docker Hub (38 tags)', + }, + { + timestamp: m(11), + level: 'debug' as const, + component: 'registry', + message: 'Token exchange for ghcr.io/authelia/authelia succeeded', + }, + { + timestamp: m(12), + level: 'info' as const, + component: 'registry', + message: 'Fetched tags for ghcr.io/authelia/authelia from GHCR (25 tags)', + }, + { + timestamp: m(14), + level: 'info' as const, + component: 'trigger', + message: 'Fired slack.homelab for grafana minor update 11.3.0 -> 11.4.0', + }, + { + timestamp: m(14), + level: 'info' as const, + component: 'trigger', + message: 'Fired discord.updates for grafana minor update 11.3.0 -> 11.4.0', + }, + { + timestamp: m(15), + level: 'info' as const, + component: 'watcher', + message: 'Container grafana: update available 11.3.0 -> 11.4.0 (minor)', + }, + { + timestamp: m(16), + level: 'info' as const, + component: 'watcher', + message: 'Container authelia: update available 4.38.16 -> 4.39.0 (minor)', + }, + { + timestamp: m(18), + level: 'debug' as const, + component: 'store', + message: 'Persisted store to /store/dd.json (24KB)', + }, + { + timestamp: m(20), + level: 'info' as const, + component: 'api', + message: 'SSE client connected from 192.168.1.10', + }, + { + timestamp: m(22), + level: 'info' as const, + component: 'agent', + message: 'Agent nas-agent heartbeat received (3 containers, uptime 864000s)', + }, + { + timestamp: m(25), + level: 'info' as const, + component: 'registry', + message: 'Fetched tags for lscr.io/linuxserver/sonarr from LSCR (18 tags)', + }, + { + timestamp: m(28), + level: 'warn' as const, + component: 'scanner', + message: 'High vulnerability CVE-2024-56789 detected in jellyfin/jellyfin:10.10.3', + }, + { + timestamp: m(30), + level: 'info' as const, + component: 'scanner', + message: 'Scan completed for jellyfin/jellyfin:10.10.3 — 8 vulnerabilities found', + }, + { + timestamp: m(35), + level: 'info' as const, + component: 'watcher', + message: 'Poll cycle completed for docker.local: 15 containers', + }, + { + timestamp: m(40), + level: 'debug' as const, + component: 'api', + message: 'Session refreshed for user admin', + }, + { + timestamp: m(45), + level: 'info' as const, + component: 'registry', + message: 'Fetched tags for traefik from Docker Hub (30 tags)', + }, + { + timestamp: m(50), + level: 'info' as const, + component: 'trigger', + message: 'Fired slack.homelab for jellyfin patch update 10.10.3 -> 10.10.6', + }, + { + timestamp: m(55), + level: 'info' as const, + component: 'watcher', + message: 'Container jellyfin: update available 10.10.3 -> 10.10.6 (patch)', + }, + { + timestamp: m(60), + level: 'info' as const, + component: 'api', + message: 'Server started on port 3000', + }, +]; diff --git a/apps/demo/src/mocks/data/notifications.ts b/apps/demo/src/mocks/data/notifications.ts new file mode 100644 index 00000000..b6f3bb3b --- /dev/null +++ b/apps/demo/src/mocks/data/notifications.ts @@ -0,0 +1,37 @@ +export const notificationRules = [ + { + id: 'rule-1', + name: 'All Updates', + description: 'Notify on all container updates', + enabled: true, + triggers: ['slack.homelab', 'discord.updates'], + }, + { + id: 'rule-2', + name: 'Security Alerts', + description: 'Notify on critical/high CVEs', + enabled: true, + triggers: ['slack.homelab', 'smtp.email'], + }, + { + id: 'rule-3', + name: 'Major Updates Only', + description: 'Notify only on major version updates', + enabled: false, + triggers: ['http.webhook'], + }, + { + id: 'rule-4', + name: 'Infra Stack', + description: 'Notify on infrastructure container updates', + enabled: true, + triggers: ['discord.updates'], + }, + { + id: 'rule-5', + name: 'Media Stack', + description: 'Notify on media container updates', + enabled: true, + triggers: ['slack.homelab'], + }, +]; diff --git a/apps/demo/src/mocks/data/registries.ts b/apps/demo/src/mocks/data/registries.ts new file mode 100644 index 00000000..a26b7e6d --- /dev/null +++ b/apps/demo/src/mocks/data/registries.ts @@ -0,0 +1,26 @@ +export const registries = [ + { + id: 'hub.public', + type: 'hub', + name: 'public', + configuration: { auth: 'anonymous' }, + }, + { + id: 'ghcr.private', + type: 'ghcr', + name: 'private', + configuration: { token: '***' }, + }, + { + id: 'quay.public', + type: 'quay', + name: 'public', + configuration: { auth: 'anonymous' }, + }, + { + id: 'lscr.public', + type: 'lscr', + name: 'public', + configuration: { auth: 'anonymous' }, + }, +]; diff --git a/apps/demo/src/mocks/data/server.ts b/apps/demo/src/mocks/data/server.ts new file mode 100644 index 00000000..0cfd0eda --- /dev/null +++ b/apps/demo/src/mocks/data/server.ts @@ -0,0 +1,42 @@ +export const serverInfo = { + version: '1.4.0', + uptime: 864000, + hostname: 'drydock-demo', + platform: 'linux', + arch: 'amd64', + nodeVersion: 'v24.0.0', + configuration: { + feature: { + containerActions: true, + delete: true, + }, + poll: '*/15 * * * *', + logLevel: 'info', + }, +}; + +export const securityRuntime = { + checkedAt: new Date(Date.now() - 3600000).toISOString(), + ready: true, + scanner: { + enabled: true, + command: 'trivy', + commandAvailable: true, + status: 'ready', + message: 'Trivy 0.58.0 installed and ready', + scanner: 'trivy', + server: '', + }, + signature: { + enabled: true, + command: 'cosign', + commandAvailable: true, + status: 'ready', + message: 'Cosign 2.4.1 installed and ready', + }, + sbom: { + enabled: true, + formats: ['spdx-json', 'cyclonedx-json'], + }, + requirements: [], +}; diff --git a/apps/demo/src/mocks/data/triggers.ts b/apps/demo/src/mocks/data/triggers.ts new file mode 100644 index 00000000..674586ec --- /dev/null +++ b/apps/demo/src/mocks/data/triggers.ts @@ -0,0 +1,40 @@ +export const triggers = [ + { + id: 'slack.homelab', + type: 'slack', + name: 'homelab', + configuration: { + channel: '#homelab-updates', + url: 'https://example.com/slack-webhook-placeholder', + }, + }, + { + id: 'discord.updates', + type: 'discord', + name: 'updates', + configuration: { + webhookUrl: + 'https://discord.com/api/webhooks/000000000000000000/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + }, + }, + { + id: 'http.webhook', + type: 'http', + name: 'webhook', + configuration: { + url: 'https://automation.local/api/webhook/drydock', + method: 'POST', + }, + }, + { + id: 'smtp.email', + type: 'smtp', + name: 'email', + configuration: { + host: 'smtp.gmail.com', + port: 587, + to: 'admin@example.com', + from: 'drydock@example.com', + }, + }, +]; diff --git a/apps/demo/src/mocks/data/vulnerabilities.ts b/apps/demo/src/mocks/data/vulnerabilities.ts new file mode 100644 index 00000000..f1e5e017 --- /dev/null +++ b/apps/demo/src/mocks/data/vulnerabilities.ts @@ -0,0 +1,127 @@ +export const securityOverview = { + totalContainers: 15, + scannedContainers: 14, + latestScannedAt: new Date(Date.now() - 3600000).toISOString(), + images: [ + { + image: 'vaultwarden/server:1.32.5', + containerIds: ['f2a3b4c5d6e7'], + updateSummary: { unknown: 0, low: 2, medium: 1, high: 1, critical: 1 }, + vulnerabilities: [ + { + id: 'CVE-2024-45678', + severity: 'critical', + package: 'openssl', + version: '3.1.4', + fixedIn: '3.1.5', + title: 'Buffer overflow in X.509 certificate verification', + target: 'vaultwarden/server:1.32.5', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-45678', + publishedDate: '2024-11-15', + }, + { + id: 'CVE-2024-34567', + severity: 'high', + package: 'libcurl', + version: '8.4.0', + fixedIn: '8.5.0', + title: 'HTTP/2 stream cancellation memory leak', + target: 'vaultwarden/server:1.32.5', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-34567', + publishedDate: '2024-10-20', + }, + { + id: 'CVE-2024-23456', + severity: 'medium', + package: 'zlib', + version: '1.3', + fixedIn: '1.3.1', + title: 'Heap-based buffer over-read in deflate', + target: 'vaultwarden/server:1.32.5', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-23456', + publishedDate: '2024-09-10', + }, + { + id: 'CVE-2024-12345', + severity: 'low', + package: 'sqlite', + version: '3.44.0', + fixedIn: '3.45.0', + title: 'Integer overflow in JSON parser', + target: 'vaultwarden/server:1.32.5', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-12345', + publishedDate: '2024-08-01', + }, + { + id: 'CVE-2024-11111', + severity: 'low', + package: 'expat', + version: '2.5.0', + fixedIn: null, + title: 'XML entity expansion DoS', + target: 'vaultwarden/server:1.32.5', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-11111', + publishedDate: '2024-07-15', + }, + ], + }, + { + image: 'jellyfin/jellyfin:10.10.3', + containerIds: ['f6a7b8c9d0e1'], + updateSummary: { unknown: 0, low: 5, medium: 2, high: 1, critical: 0 }, + vulnerabilities: [ + { + id: 'CVE-2024-56789', + severity: 'high', + package: 'ffmpeg', + version: '6.1', + fixedIn: '6.1.2', + title: 'Heap buffer overflow in HEVC decoder', + target: 'jellyfin/jellyfin:10.10.3', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-56789', + publishedDate: '2024-11-01', + }, + { + id: 'CVE-2024-55555', + severity: 'medium', + package: 'dotnet-runtime', + version: '8.0.6', + fixedIn: '8.0.7', + title: 'HTTP/2 denial of service', + target: 'jellyfin/jellyfin:10.10.3', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-55555', + publishedDate: '2024-10-15', + }, + { + id: 'CVE-2024-44444', + severity: 'medium', + package: 'libwebp', + version: '1.3.2', + fixedIn: '1.4.0', + title: 'Out-of-bounds write in BuildHuffmanTable', + target: 'jellyfin/jellyfin:10.10.3', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-44444', + publishedDate: '2024-09-05', + }, + ], + }, + { + image: 'grafana/grafana:11.3.0', + containerIds: ['a1b2c3d4e5f6'], + updateSummary: { unknown: 0, low: 3, medium: 1, high: 0, critical: 0 }, + vulnerabilities: [ + { + id: 'CVE-2024-33333', + severity: 'medium', + package: 'golang.org/x/net', + version: '0.17.0', + fixedIn: '0.23.0', + title: 'HTTP/2 CONTINUATION flood causing DoS', + target: 'grafana/grafana:11.3.0', + primaryUrl: 'https://nvd.nist.gov/vuln/detail/CVE-2024-33333', + publishedDate: '2024-10-01', + }, + ], + }, + ], +}; diff --git a/apps/demo/src/mocks/data/watchers.ts b/apps/demo/src/mocks/data/watchers.ts new file mode 100644 index 00000000..b7734f63 --- /dev/null +++ b/apps/demo/src/mocks/data/watchers.ts @@ -0,0 +1,23 @@ +export const watchers = [ + { + id: 'docker.local', + type: 'docker', + name: 'local', + configuration: { + socket: '/var/run/docker.sock', + watchByDefault: true, + pollInterval: '*/15 * * * *', + }, + }, + { + id: 'docker.remote', + type: 'docker', + name: 'remote', + configuration: { + socket: '/var/run/docker.sock', + watchByDefault: true, + pollInterval: '*/30 * * * *', + }, + agent: 'nas-agent', + }, +]; diff --git a/apps/demo/src/mocks/handlers.ts b/apps/demo/src/mocks/handlers.ts new file mode 100644 index 00000000..f04c2c66 --- /dev/null +++ b/apps/demo/src/mocks/handlers.ts @@ -0,0 +1,37 @@ +import { agentHandlers } from './handlers/agents'; +import { appHandlers } from './handlers/app'; +import { auditHandlers } from './handlers/audit'; +import { authHandlers } from './handlers/auth'; +import { authenticationHandlers } from './handlers/authentications'; +import { containerHandlers } from './handlers/containers'; +import { fontHandlers } from './handlers/fonts'; +import { iconHandlers } from './handlers/icons'; +import { logHandlers } from './handlers/log'; +import { notificationHandlers } from './handlers/notifications'; +import { registryHandlers } from './handlers/registries'; +import { securityHandlers } from './handlers/security'; +import { serverHandlers } from './handlers/server'; +import { settingsHandlers } from './handlers/settings'; +import { storeHandlers } from './handlers/store'; +import { triggerHandlers } from './handlers/triggers'; +import { watcherHandlers } from './handlers/watchers'; + +export const handlers = [ + ...authHandlers, + ...containerHandlers, + ...securityHandlers, + ...registryHandlers, + ...watcherHandlers, + ...triggerHandlers, + ...agentHandlers, + ...auditHandlers, + ...notificationHandlers, + ...settingsHandlers, + ...serverHandlers, + ...appHandlers, + ...storeHandlers, + ...logHandlers, + ...authenticationHandlers, + ...fontHandlers, + ...iconHandlers, +]; diff --git a/apps/demo/src/mocks/handlers/agents.ts b/apps/demo/src/mocks/handlers/agents.ts new file mode 100644 index 00000000..eb6f2cec --- /dev/null +++ b/apps/demo/src/mocks/handlers/agents.ts @@ -0,0 +1,70 @@ +import { HttpResponse, http } from 'msw'; +import { agents } from '../data/agents'; + +export const agentHandlers = [ + http.get('/api/agents', () => HttpResponse.json({ data: agents })), + + http.get('/api/agents/:name/log', () => + HttpResponse.json({ + entries: [ + { + timestamp: new Date(Date.now() - 300000).toISOString(), + level: 'info', + message: 'Agent connected to controller', + }, + { + timestamp: new Date(Date.now() - 240000).toISOString(), + level: 'info', + message: 'Starting container watch cycle', + }, + { + timestamp: new Date(Date.now() - 180000).toISOString(), + level: 'info', + message: 'Found 3 watched containers', + }, + { + timestamp: new Date(Date.now() - 120000).toISOString(), + level: 'info', + message: 'Registry check completed for all images', + }, + { + timestamp: new Date(Date.now() - 60000).toISOString(), + level: 'info', + message: 'Watch cycle completed — next run in 30m', + }, + ], + }), + ), + + http.get('/api/agents/:name/log/entries', () => + HttpResponse.json({ + entries: [ + { + timestamp: new Date(Date.now() - 300000).toISOString(), + level: 'info', + message: 'Agent connected to controller', + }, + { + timestamp: new Date(Date.now() - 240000).toISOString(), + level: 'info', + message: 'Starting container watch cycle', + }, + { + timestamp: new Date(Date.now() - 180000).toISOString(), + level: 'debug', + message: 'Pulling manifest for prom/prometheus:v2.54.0', + }, + { + timestamp: new Date(Date.now() - 120000).toISOString(), + level: 'info', + message: 'Registry check completed', + }, + { + timestamp: new Date(Date.now() - 60000).toISOString(), + level: 'info', + message: 'Watch cycle completed', + }, + ], + }), + ), +]; diff --git a/apps/demo/src/mocks/handlers/app.ts b/apps/demo/src/mocks/handlers/app.ts new file mode 100644 index 00000000..3b434bbc --- /dev/null +++ b/apps/demo/src/mocks/handlers/app.ts @@ -0,0 +1,13 @@ +import { HttpResponse, http } from 'msw'; + +export const appHandlers = [ + http.get('/api/app', () => + HttpResponse.json({ + name: 'Drydock', + version: '1.4.0', + description: 'Docker container update manager', + repository: 'https://github.com/CodesWhat/drydock', + documentation: 'https://drydock.codeswhat.com/docs', + }), + ), +]; diff --git a/apps/demo/src/mocks/handlers/audit.ts b/apps/demo/src/mocks/handlers/audit.ts new file mode 100644 index 00000000..68f4b1c3 --- /dev/null +++ b/apps/demo/src/mocks/handlers/audit.ts @@ -0,0 +1,34 @@ +import { HttpResponse, http } from 'msw'; +import { auditEntries } from '../data/audit'; + +export const auditHandlers = [ + http.get('/api/audit', ({ request }) => { + const url = new URL(request.url); + const limit = Number(url.searchParams.get('limit')) || 50; + const offset = Number(url.searchParams.get('offset')) || 0; + const action = url.searchParams.get('action'); + const container = url.searchParams.get('container'); + const from = url.searchParams.get('from'); + const to = url.searchParams.get('to'); + + let filtered = [...auditEntries]; + + if (action) { + filtered = filtered.filter((e) => e.action === action); + } + if (container) { + filtered = filtered.filter((e) => e.container === container); + } + if (from) { + filtered = filtered.filter((e) => e.timestamp >= from); + } + if (to) { + filtered = filtered.filter((e) => e.timestamp <= to); + } + + const total = filtered.length; + const entries = filtered.slice(offset, offset + limit); + + return HttpResponse.json({ entries, total, offset, limit }); + }), +]; diff --git a/apps/demo/src/mocks/handlers/auth.ts b/apps/demo/src/mocks/handlers/auth.ts new file mode 100644 index 00000000..979e528a --- /dev/null +++ b/apps/demo/src/mocks/handlers/auth.ts @@ -0,0 +1,44 @@ +import { HttpResponse, http } from 'msw'; + +const demoUser = { username: 'demo', displayName: 'Demo User' }; + +/** + * Simulate a brief "server offline" period after logout so the login page + * shows the "Connection Lost" overlay for a few seconds before recovering. + * Each browser tab tracks its own logout timestamp (module-level state is + * per service-worker client in MSW), giving every user an independent + * experience. + */ +const OFFLINE_DURATION_MS = 5_000; +let offlineUntil = 0; + +function isOffline(): boolean { + return Date.now() < offlineUntil; +} + +export const authHandlers = [ + http.get('/auth/user', () => { + if (isOffline()) { + return new HttpResponse(null, { status: 401 }); + } + return HttpResponse.json(demoUser); + }), + + http.get('/auth/strategies', () => { + if (isOffline()) { + return HttpResponse.error(); + } + return HttpResponse.json([{ type: 'basic', name: 'basic' }]); + }), + + http.post('/auth/login', () => HttpResponse.json(demoUser)), + + http.post('/auth/logout', () => { + offlineUntil = Date.now() + OFFLINE_DURATION_MS; + return HttpResponse.json({ success: true }); + }), + + http.post('/auth/remember', () => HttpResponse.json({ success: true })), + + http.get('/auth/oidc/:name/redirect', () => HttpResponse.json({ redirectUrl: '/' })), +]; diff --git a/apps/demo/src/mocks/handlers/authentications.ts b/apps/demo/src/mocks/handlers/authentications.ts new file mode 100644 index 00000000..7d44c0a2 --- /dev/null +++ b/apps/demo/src/mocks/handlers/authentications.ts @@ -0,0 +1,5 @@ +import { HttpResponse, http } from 'msw'; + +export const authenticationHandlers = [ + http.get('/api/authentications', () => HttpResponse.json({ data: [] })), +]; diff --git a/apps/demo/src/mocks/handlers/containers.ts b/apps/demo/src/mocks/handlers/containers.ts new file mode 100644 index 00000000..cc3a71b7 --- /dev/null +++ b/apps/demo/src/mocks/handlers/containers.ts @@ -0,0 +1,144 @@ +import { HttpResponse, http } from 'msw'; +import { containers } from '../data/containers'; + +type MockContainer = (typeof containers)[number] & Record; + +function groupContainers() { + const groups = new Map(); + for (const c of containers as MockContainer[]) { + const groupName = c.labels?.['dd.group'] ?? null; + const list = groups.get(groupName) ?? []; + list.push(c); + groups.set(groupName, list); + } + return [...groups.entries()].map(([name, members]) => ({ + name, + containers: members.map((m) => ({ + id: m.id, + name: m.name, + displayName: m.displayName ?? m.name, + updateAvailable: !!m.updateAvailable, + })), + containerCount: members.length, + updatesAvailable: members.filter((m) => !!m.updateAvailable).length, + })); +} + +export const containerHandlers = [ + http.get('/api/containers', ({ request }) => { + const url = new URL(request.url); + const limit = Number(url.searchParams.get('limit')) || containers.length; + const offset = Number(url.searchParams.get('offset')) || 0; + const slice = containers.slice(offset, offset + limit); + return HttpResponse.json({ data: slice }); + }), + + http.get('/api/containers/summary', () => { + const running = containers.filter((c) => c.status === 'running').length; + const stopped = containers.filter((c) => c.status === 'stopped').length; + const issues = (containers as MockContainer[]).reduce((sum, c) => { + const summary = c.security?.scan?.summary; + if (!summary) return sum; + return sum + ((summary.high ?? 0) + (summary.critical ?? 0)); + }, 0); + return HttpResponse.json({ + containers: { total: containers.length, running, stopped }, + security: { issues }, + }); + }), + + http.get('/api/containers/recent-status', () => { + const statuses: Record = {}; + for (const c of containers as MockContainer[]) { + if (c.updateAvailable) statuses[c.id] = 'pending'; + } + return HttpResponse.json({ statuses }); + }), + + http.get('/api/containers/groups', () => HttpResponse.json({ data: groupContainers() })), + + http.post('/api/containers/watch', () => HttpResponse.json({ success: true })), + + // Single container + http.get('/api/containers/:id', ({ params }) => { + const container = containers.find((c) => c.id === params.id); + if (!container) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(container); + }), + + http.delete('/api/containers/:id', () => HttpResponse.json({ success: true })), + + http.post('/api/containers/:id/watch', ({ params }) => { + const container = containers.find((c) => c.id === params.id); + if (!container) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(container); + }), + + // Container triggers + http.get('/api/containers/:id/triggers', () => + HttpResponse.json({ + data: [ + { type: 'slack', name: 'homelab', threshold: 'all' }, + { type: 'discord', name: 'updates', threshold: 'minor' }, + ], + }), + ), + + http.post('/api/containers/:id/triggers/:type/:name', () => HttpResponse.json({ success: true })), + + http.post('/api/containers/:id/triggers/:type/:name/:agent', () => + HttpResponse.json({ success: true }), + ), + + // Container logs + http.get('/api/containers/:id/logs', () => + HttpResponse.json({ + lines: [ + 'Starting container...', + 'Listening on port 3000', + 'Health check passed', + 'Connected to database', + 'Ready to serve requests', + ], + }), + ), + + // Update operations + http.get('/api/containers/:id/update-operations', () => HttpResponse.json({ data: [] })), + + // Update policy + http.patch('/api/containers/:id/update-policy', () => HttpResponse.json({ success: true })), + + // Scan + http.post('/api/containers/:id/scan', ({ params }) => { + const container = containers.find((c) => c.id === params.id) as MockContainer | undefined; + return HttpResponse.json({ + success: true, + summary: container?.security?.scan?.summary ?? { + unknown: 0, + low: 0, + medium: 0, + high: 0, + critical: 0, + }, + }); + }), + + // Env reveal + http.post('/api/containers/:id/env/reveal', ({ params }) => { + const container = containers.find((c) => c.id === params.id) as MockContainer | undefined; + if (!container) return new HttpResponse(null, { status: 404 }); + const env = container.details?.env ?? []; + return HttpResponse.json({ + env: env.map((e: { key: string; value: string; sensitive?: boolean }) => ({ + ...e, + value: e.sensitive ? 'revealed-secret-value' : e.value, + })), + }); + }), + + // Backups + http.get('/api/containers/:id/backups', () => HttpResponse.json({ data: [] })), + + http.post('/api/containers/:id/rollback', () => HttpResponse.json({ success: true })), +]; diff --git a/apps/demo/src/mocks/handlers/fonts.ts b/apps/demo/src/mocks/handlers/fonts.ts new file mode 100644 index 00000000..d096b71d --- /dev/null +++ b/apps/demo/src/mocks/handlers/fonts.ts @@ -0,0 +1,42 @@ +import { HttpResponse, http } from 'msw'; + +/** + * Intercept /fonts/{fontId}/{weight}.css requests and return @font-face CSS + * that loads WOFF2 from the fontsource CDN. In a real Drydock instance these + * files are generated by scripts/extract-fonts.mjs into ui/public/fonts/. + */ + +const FONT_MAP: Record = { + 'jetbrains-mono': { family: 'JetBrains Mono', pkg: '@fontsource/jetbrains-mono' }, + 'source-code-pro': { family: 'Source Code Pro', pkg: '@fontsource/source-code-pro' }, + inconsolata: { family: 'Inconsolata', pkg: '@fontsource/inconsolata' }, + 'commit-mono': { family: 'Commit Mono', pkg: '@fontsource/commit-mono' }, + 'comic-mono': { family: 'Comic Mono', pkg: '@fontsource/comic-mono' }, +}; + +export const fontHandlers = [ + http.get('/fonts/:fontId/:weightCss', ({ params }) => { + const fontId = params.fontId as string; + const weightCss = params.weightCss as string; + const weight = weightCss.replace('.css', ''); + const font = FONT_MAP[fontId]; + + if (!font) { + return new HttpResponse(null, { status: 404 }); + } + + // Return CSS that uses the fontsource CDN + const css = `@font-face { + font-family: '${font.family}'; + font-style: normal; + font-display: swap; + font-weight: ${weight}; + src: url('https://cdn.jsdelivr.net/fontsource/fonts/${fontId}@latest/latin-${weight}-normal.woff2') format('woff2'); + unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0304,U+0306-0307,U+030A,U+030C,U+0312,U+0327,U+0331,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; +}`; + + return new HttpResponse(css, { + headers: { 'Content-Type': 'text/css' }, + }); + }), +]; diff --git a/apps/demo/src/mocks/handlers/icons.ts b/apps/demo/src/mocks/handlers/icons.ts new file mode 100644 index 00000000..6282554b --- /dev/null +++ b/apps/demo/src/mocks/handlers/icons.ts @@ -0,0 +1,78 @@ +import { HttpResponse, http } from 'msw'; + +/** + * Icon proxy handler — mirrors the real backend's /api/icons/:provider/:slug + * endpoint by redirecting to the jsDelivr CDN. + * + * For the selfhst provider, if the primary CDN returns 404, falls back to the + * homarr dashboard-icons repo (same pattern the real backend uses for missing + * icons). The Docker icon from selfhst is the final fallback. + */ + +const CDN: Record string; contentType: string }> = { + selfhst: { + url: (slug) => `https://cdn.jsdelivr.net/gh/selfhst/icons/png/${slug}.png`, + contentType: 'image/png', + }, + homarr: { + url: (slug) => `https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/${slug}.png`, + contentType: 'image/png', + }, + simple: { + url: (slug) => `https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/${slug}.svg`, + contentType: 'image/svg+xml', + }, +}; + +const DOCKER_FALLBACK_URL = CDN.selfhst.url('docker'); + +async function tryFetch(url: string): Promise { + try { + const res = await fetch(url); + if (res.ok) return res; + } catch { + /* network error — treat as miss */ + } + return null; +} + +export const iconHandlers = [ + http.get('/api/icons/:provider/:slug', async ({ params }) => { + const provider = params.provider as string; + const slug = (params.slug as string).replace(/\.(png|svg)$/i, ''); + + const config = CDN[provider]; + if (!config) { + return new HttpResponse(null, { status: 400 }); + } + + // Try primary provider + let upstream = await tryFetch(config.url(slug)); + + // Selfhst miss → try homarr fallback + if (!upstream && provider === 'selfhst') { + upstream = await tryFetch(CDN.homarr.url(slug)); + } + + // Still nothing → Docker icon as final fallback + if (!upstream) { + upstream = await tryFetch(DOCKER_FALLBACK_URL); + } + + if (!upstream) { + return new HttpResponse(null, { status: 404 }); + } + + const buffer = await upstream.arrayBuffer(); + const contentType = upstream.headers.get('content-type') ?? config.contentType; + + return new HttpResponse(buffer, { + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=31536000, immutable', + }, + }); + }), + + http.delete('/api/icons/cache', () => HttpResponse.json({ cleared: 0 })), +]; diff --git a/apps/demo/src/mocks/handlers/log.ts b/apps/demo/src/mocks/handlers/log.ts new file mode 100644 index 00000000..7a0be3eb --- /dev/null +++ b/apps/demo/src/mocks/handlers/log.ts @@ -0,0 +1,29 @@ +import { HttpResponse, http } from 'msw'; +import { logEntries } from '../data/logs'; + +export const logHandlers = [ + http.get('/api/log', () => + HttpResponse.json({ + level: 'info', + transports: ['console'], + }), + ), + + http.get('/api/log/entries', ({ request }) => { + const url = new URL(request.url); + const level = url.searchParams.get('level'); + const component = url.searchParams.get('component'); + const tail = Number(url.searchParams.get('tail')) || 100; + + let filtered = [...logEntries]; + + if (level && level !== 'all') { + filtered = filtered.filter((e) => e.level === level); + } + if (component) { + filtered = filtered.filter((e) => e.component === component); + } + + return HttpResponse.json({ entries: filtered.slice(-tail) }); + }), +]; diff --git a/apps/demo/src/mocks/handlers/notifications.ts b/apps/demo/src/mocks/handlers/notifications.ts new file mode 100644 index 00000000..3ceb3d58 --- /dev/null +++ b/apps/demo/src/mocks/handlers/notifications.ts @@ -0,0 +1,13 @@ +import { HttpResponse, http } from 'msw'; +import { notificationRules } from '../data/notifications'; + +export const notificationHandlers = [ + http.get('/api/notifications', () => HttpResponse.json({ data: notificationRules })), + + http.patch('/api/notifications/:id', async ({ params, request }) => { + const rule = notificationRules.find((r) => r.id === params.id); + if (!rule) return new HttpResponse(null, { status: 404 }); + const body = (await request.json()) as Record; + return HttpResponse.json({ ...rule, ...body }); + }), +]; diff --git a/apps/demo/src/mocks/handlers/registries.ts b/apps/demo/src/mocks/handlers/registries.ts new file mode 100644 index 00000000..59a58a67 --- /dev/null +++ b/apps/demo/src/mocks/handlers/registries.ts @@ -0,0 +1,18 @@ +import { HttpResponse, http } from 'msw'; +import { registries } from '../data/registries'; + +export const registryHandlers = [ + http.get('/api/registries', () => HttpResponse.json({ data: registries })), + + http.get('/api/registries/:type/:name', ({ params }) => { + const reg = registries.find((r) => r.type === params.type && r.name === params.name); + if (!reg) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(reg); + }), + + http.get('/api/registries/:type/:name/:agent', ({ params }) => { + const reg = registries.find((r) => r.type === params.type && r.name === params.name); + if (!reg) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(reg); + }), +]; diff --git a/apps/demo/src/mocks/handlers/security.ts b/apps/demo/src/mocks/handlers/security.ts new file mode 100644 index 00000000..fd57c575 --- /dev/null +++ b/apps/demo/src/mocks/handlers/security.ts @@ -0,0 +1,45 @@ +import { HttpResponse, http } from 'msw'; +import { containers } from '../data/containers'; +import { securityOverview } from '../data/vulnerabilities'; + +type MockContainer = (typeof containers)[number] & Record; + +export const securityHandlers = [ + http.get('/api/containers/security/vulnerabilities', () => HttpResponse.json(securityOverview)), + + http.get('/api/containers/:id/vulnerabilities', ({ params }) => { + const container = containers.find((c) => c.id === params.id) as MockContainer | undefined; + if (!container) return new HttpResponse(null, { status: 404 }); + + // Find vulnerabilities for this container's image + const imageEntry = securityOverview.images.find((img) => + img.containerIds.includes(container.id), + ); + + return HttpResponse.json({ + vulnerabilities: imageEntry?.vulnerabilities ?? [], + summary: container.security?.scan?.summary ?? { + unknown: 0, + low: 0, + medium: 0, + high: 0, + critical: 0, + }, + }); + }), + + http.get('/api/containers/:id/sbom', () => + HttpResponse.json({ + spdxVersion: 'SPDX-2.3', + dataLicense: 'CC0-1.0', + name: 'demo-sbom', + packages: [ + { name: 'openssl', versionInfo: '3.1.4', supplier: 'Organization: OpenSSL' }, + { name: 'zlib', versionInfo: '1.3', supplier: 'Organization: zlib' }, + { name: 'libcurl', versionInfo: '8.4.0', supplier: 'Organization: curl' }, + { name: 'sqlite', versionInfo: '3.44.0', supplier: 'Organization: SQLite' }, + { name: 'expat', versionInfo: '2.5.0', supplier: 'Organization: Expat' }, + ], + }), + ), +]; diff --git a/apps/demo/src/mocks/handlers/server.ts b/apps/demo/src/mocks/handlers/server.ts new file mode 100644 index 00000000..3bac0438 --- /dev/null +++ b/apps/demo/src/mocks/handlers/server.ts @@ -0,0 +1,8 @@ +import { HttpResponse, http } from 'msw'; +import { securityRuntime, serverInfo } from '../data/server'; + +export const serverHandlers = [ + http.get('/api/server', () => HttpResponse.json(serverInfo)), + + http.get('/api/server/security/runtime', () => HttpResponse.json(securityRuntime)), +]; diff --git a/apps/demo/src/mocks/handlers/settings.ts b/apps/demo/src/mocks/handlers/settings.ts new file mode 100644 index 00000000..7b51b8ec --- /dev/null +++ b/apps/demo/src/mocks/handlers/settings.ts @@ -0,0 +1,11 @@ +import { HttpResponse, http } from 'msw'; + +const settings = { internetlessMode: false }; + +export const settingsHandlers = [ + http.get('/api/settings', () => HttpResponse.json(settings)), + + http.patch('/api/settings', () => HttpResponse.json(settings)), + + http.delete('/api/icons/cache', () => HttpResponse.json({ cleared: 12 })), +]; diff --git a/apps/demo/src/mocks/handlers/store.ts b/apps/demo/src/mocks/handlers/store.ts new file mode 100644 index 00000000..7eb739d6 --- /dev/null +++ b/apps/demo/src/mocks/handlers/store.ts @@ -0,0 +1,10 @@ +import { HttpResponse, http } from 'msw'; + +export const storeHandlers = [ + http.get('/api/store', () => + HttpResponse.json({ + collections: ['app', 'audit', 'backup', 'container'], + size: 524288, + }), + ), +]; diff --git a/apps/demo/src/mocks/handlers/triggers.ts b/apps/demo/src/mocks/handlers/triggers.ts new file mode 100644 index 00000000..a0aa7447 --- /dev/null +++ b/apps/demo/src/mocks/handlers/triggers.ts @@ -0,0 +1,22 @@ +import { HttpResponse, http } from 'msw'; +import { triggers } from '../data/triggers'; + +export const triggerHandlers = [ + http.get('/api/triggers', () => HttpResponse.json({ data: triggers })), + + http.get('/api/triggers/:type/:name', ({ params }) => { + const trigger = triggers.find((t) => t.type === params.type && t.name === params.name); + if (!trigger) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(trigger); + }), + + http.get('/api/triggers/:type/:name/:agent', ({ params }) => { + const trigger = triggers.find((t) => t.type === params.type && t.name === params.name); + if (!trigger) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(trigger); + }), + + http.post('/api/triggers/:type/:name', () => HttpResponse.json({ success: true })), + + http.post('/api/triggers/:type/:name/:agent', () => HttpResponse.json({ success: true })), +]; diff --git a/apps/demo/src/mocks/handlers/watchers.ts b/apps/demo/src/mocks/handlers/watchers.ts new file mode 100644 index 00000000..8ab6cfae --- /dev/null +++ b/apps/demo/src/mocks/handlers/watchers.ts @@ -0,0 +1,18 @@ +import { HttpResponse, http } from 'msw'; +import { watchers } from '../data/watchers'; + +export const watcherHandlers = [ + http.get('/api/watchers', () => HttpResponse.json({ data: watchers })), + + http.get('/api/watchers/:type/:name', ({ params }) => { + const watcher = watchers.find((w) => w.type === params.type && w.name === params.name); + if (!watcher) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(watcher); + }), + + http.get('/api/watchers/:type/:name/:agent', ({ params }) => { + const watcher = watchers.find((w) => w.type === params.type && w.name === params.name); + if (!watcher) return new HttpResponse(null, { status: 404 }); + return HttpResponse.json(watcher); + }), +]; diff --git a/apps/demo/src/mocks/sse.ts b/apps/demo/src/mocks/sse.ts new file mode 100644 index 00000000..03b61856 --- /dev/null +++ b/apps/demo/src/mocks/sse.ts @@ -0,0 +1,80 @@ +/** + * FakeEventSource — replaces the native EventSource so the UI's SSE service + * gets the events it expects without a real backend. + */ + +type EventSourceListener = (event: MessageEvent) => void; + +export class FakeEventSource { + static readonly CONNECTING = 0; + static readonly OPEN = 1; + static readonly CLOSED = 2; + + readonly CONNECTING = 0; + readonly OPEN = 1; + readonly CLOSED = 2; + + readyState = FakeEventSource.CONNECTING; + url: string; + withCredentials = false; + + onopen: ((ev: Event) => void) | null = null; + onmessage: ((ev: MessageEvent) => void) | null = null; + onerror: ((ev: Event) => void) | null = null; + + private listeners = new Map>(); + private timer: ReturnType | undefined; + + constructor(url: string, _init?: EventSourceInit) { + this.url = url; + + // Simulate async connection — fire connected on next microtask + queueMicrotask(() => { + if (this.readyState === FakeEventSource.CLOSED) return; + this.readyState = FakeEventSource.OPEN; + this.onopen?.(new Event('open')); + + // Fire dd:connected immediately with a client ID + this.dispatch( + 'dd:connected', + JSON.stringify({ clientId: 'demo-client', clientToken: 'demo-token' }), + ); + + // Fire dd:container-updated every 30 s to keep the UI refreshing + this.timer = setInterval(() => { + if (this.readyState !== FakeEventSource.OPEN) return; + this.dispatch('dd:container-updated', ''); + }, 30_000); + }); + } + + addEventListener(type: string, listener: EventSourceListener): void { + let set = this.listeners.get(type); + if (!set) { + set = new Set(); + this.listeners.set(type, set); + } + set.add(listener); + } + + removeEventListener(type: string, listener: EventSourceListener): void { + this.listeners.get(type)?.delete(listener); + } + + dispatchEvent(_event: Event): boolean { + return true; + } + + close(): void { + this.readyState = FakeEventSource.CLOSED; + if (this.timer) { + clearInterval(this.timer); + this.timer = undefined; + } + } + + private dispatch(type: string, data: string): void { + const event = new MessageEvent(type, { data }); + this.listeners.get(type)?.forEach((fn) => fn(event)); + } +} diff --git a/apps/demo/tsconfig.json b/apps/demo/tsconfig.json new file mode 100644 index 00000000..82fba122 --- /dev/null +++ b/apps/demo/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": false, + "jsx": "preserve", + "downlevelIteration": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "isolatedModules": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "baseUrl": ".", + "types": ["node"], + "paths": { + "@/*": ["../../ui/src/*"] + }, + "lib": ["esnext", "dom", "dom.iterable"] + }, + "include": [ + "src/**/*.ts", + "../../ui/src/**/*.ts", + "../../ui/src/**/*.vue", + "../../ui/src/**/*.d.ts" + ], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/demo/vercel.json b/apps/demo/vercel.json new file mode 100644 index 00000000..d3890290 --- /dev/null +++ b/apps/demo/vercel.json @@ -0,0 +1,19 @@ +{ + "rewrites": [{ "source": "/((?!mockServiceWorker\\.js).*)", "destination": "/index.html" }], + "headers": [ + { + "source": "/mockServiceWorker.js", + "headers": [{ "key": "Service-Worker-Allowed", "value": "/" }] + }, + { + "source": "/(.*)", + "headers": [ + { "key": "X-Frame-Options", "value": "ALLOW-FROM https://drydock.codeswhat.com" }, + { + "key": "Content-Security-Policy", + "value": "frame-ancestors 'self' https://drydock.codeswhat.com https://*.vercel.app" + } + ] + } + ] +} diff --git a/apps/demo/vite.config.ts b/apps/demo/vite.config.ts new file mode 100644 index 00000000..d3d633ad --- /dev/null +++ b/apps/demo/vite.config.ts @@ -0,0 +1,45 @@ +import { fileURLToPath, URL } from 'node:url'; +import tailwindcss from '@tailwindcss/vite'; +import vue from '@vitejs/plugin-vue'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + vue({ + template: { + compilerOptions: { + isCustomElement: (tag) => tag === 'iconify-icon', + }, + }, + }), + tailwindcss(), + ], + + resolve: { + extensions: ['.vue', '.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'], + alias: { + '@': fileURLToPath(new URL('../../ui/src', import.meta.url)), + }, + }, + + server: { + fs: { + allow: [ + // Allow serving files from the UI source and shared node_modules + fileURLToPath(new URL('../..', import.meta.url)), + ], + }, + }, + + build: { + outDir: 'dist', + assetsDir: 'assets', + sourcemap: false, + }, + + define: { + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, + }, +}); diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index be3e8283..97b5fd54 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -29,6 +29,14 @@ export const metadata: Metadata = { siteName: "Drydock", locale: "en_US", type: "website", + images: [ + { + url: "/og-image.png", + width: 1200, + height: 630, + alt: "Drydock - Container Update Monitoring", + }, + ], }, twitter: { card: "summary_large_image", @@ -36,6 +44,7 @@ export const metadata: Metadata = { description: "Open source container update monitoring built in TypeScript. Auto-discover containers, detect image updates, and trigger notifications across 20+ services.", creator: "@codeswhat", + images: ["/og-image.png"], }, icons: { icon: [ diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 521f2737..ae3c200e 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -19,8 +19,8 @@ import { } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; +import { DemoSection } from "@/components/demo-section"; import { RoadmapTimeline } from "@/components/roadmap-timeline"; -import { ScreenshotsSection } from "@/components/screenshots-section"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; @@ -136,39 +136,6 @@ const features = [ }, ]; -const screenshots = [ - { - srcLight: "/screenshots/dashboard-light-desktop.png", - srcDark: "/screenshots/dashboard-dark-desktop.png", - alt: "Drydock Dashboard", - label: "Dashboard", - }, - { - srcLight: "/screenshots/containers-light-desktop.png", - srcDark: "/screenshots/containers-dark-desktop.png", - alt: "Container List", - label: "Containers", - }, - { - srcLight: "/screenshots/container-detail-light-desktop.png", - srcDark: "/screenshots/container-detail-dark-desktop.png", - alt: "Container Detail View", - label: "Detail View", - }, - { - srcLight: "/screenshots/security-light-desktop.png", - srcDark: "/screenshots/security-dark-desktop.png", - alt: "Security Vulnerability Scanner", - label: "Security", - }, - { - srcLight: "/screenshots/login-light-desktop.png", - srcDark: "/screenshots/login-dark-desktop.png", - alt: "Login Page", - label: "Login", - }, -]; - const roadmap = [ { version: "v1.0.0", @@ -797,8 +764,8 @@ export default function Home() { - {/* Screenshots Section */} - + {/* Interactive Demo */} + {/* Roadmap Timeline */} diff --git a/apps/web/components/demo-section.tsx b/apps/web/components/demo-section.tsx new file mode 100644 index 00000000..5ff619d0 --- /dev/null +++ b/apps/web/components/demo-section.tsx @@ -0,0 +1,315 @@ +"use client"; + +import { ExternalLink, Maximize2, Palette, Share, X } from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; + +const DEMO_URL = process.env.NEXT_PUBLIC_DEMO_URL || "https://demo.drydock.codeswhat.com"; + +export function DemoSection() { + const [mode, setMode] = useState<"inline" | "expanding" | "fullscreen" | "collapsing">("inline"); + const [iframeStatus, setIframeStatus] = useState<"loading" | "ready" | "failed">("loading"); + const containerRef = useRef(null); + const inlineRectRef = useRef(null); + const iframeRef = useRef(null); + + // Listen for demo app ready signal, fallback after timeout + useEffect(() => { + const demoOrigin = new URL(DEMO_URL).origin; + + function onMessage(e: MessageEvent) { + if (e.origin !== demoOrigin) return; + if (e.data?.type === "drydock-demo-ready") { + setIframeStatus("ready"); + } + } + + window.addEventListener("message", onMessage); + const timeout = setTimeout(() => { + setIframeStatus((prev) => (prev === "loading" ? "failed" : prev)); + }, 5000); + + return () => { + window.removeEventListener("message", onMessage); + clearTimeout(timeout); + }; + }, []); + + function openFullscreen() { + if (!containerRef.current) return; + // Capture current position before going fixed + inlineRectRef.current = containerRef.current.getBoundingClientRect(); + setMode("expanding"); + } + + const closeFullscreen = useCallback(() => { + if (!containerRef.current) return; + // Re-capture where the inline slot is so we can animate back + // We need the placeholder position — store it before we started + setMode("collapsing"); + }, []); + + // Handle expand animation + useEffect(() => { + if (mode !== "expanding" || !containerRef.current || !inlineRectRef.current) return; + + const el = containerRef.current; + const rect = inlineRectRef.current; + + // Start at the inline position + el.style.transition = "none"; + el.style.top = `${rect.top}px`; + el.style.left = `${rect.left}px`; + el.style.width = `${rect.width}px`; + el.style.height = `${rect.height}px`; + el.style.borderRadius = "12px"; + + // Force reflow + el.getBoundingClientRect(); + + // Animate to fullscreen + el.style.transition = "all 350ms cubic-bezier(0.4, 0, 0.2, 1)"; + el.style.top = "0px"; + el.style.left = "0px"; + el.style.width = "100vw"; + el.style.height = "100vh"; + el.style.borderRadius = "0px"; + + function onEnd() { + el.removeEventListener("transitionend", onEnd); + // Clear inline styles, let CSS class take over + el.style.transition = ""; + el.style.top = ""; + el.style.left = ""; + el.style.width = ""; + el.style.height = ""; + el.style.borderRadius = ""; + setMode("fullscreen"); + } + + el.addEventListener("transitionend", onEnd, { once: true }); + // Safety timeout in case transitionend doesn't fire + const timeout = setTimeout(onEnd, 400); + return () => clearTimeout(timeout); + }, [mode]); + + // Handle collapse animation + useEffect(() => { + if (mode !== "collapsing" || !containerRef.current) return; + + const el = containerRef.current; + + // Find where the placeholder is now + const placeholder = document.getElementById("demo-placeholder"); + const target = placeholder ? placeholder.getBoundingClientRect() : inlineRectRef.current; + + if (!target) { + setMode("inline"); + return; + } + + // Start at fullscreen + el.style.transition = "none"; + el.style.top = "0px"; + el.style.left = "0px"; + el.style.width = "100vw"; + el.style.height = "100vh"; + el.style.borderRadius = "0px"; + + // Force reflow + el.getBoundingClientRect(); + + // Animate back to inline position + el.style.transition = "all 350ms cubic-bezier(0.4, 0, 0.2, 1)"; + el.style.top = `${target.top}px`; + el.style.left = `${target.left}px`; + el.style.width = `${target.width}px`; + el.style.height = `${target.height}px`; + el.style.borderRadius = "12px"; + + function onEnd() { + el.removeEventListener("transitionend", onEnd); + el.style.transition = ""; + el.style.top = ""; + el.style.left = ""; + el.style.width = ""; + el.style.height = ""; + el.style.borderRadius = ""; + setMode("inline"); + } + + el.addEventListener("transitionend", onEnd, { once: true }); + const timeout = setTimeout(onEnd, 400); + return () => clearTimeout(timeout); + }, [mode]); + + // Escape key closes fullscreen + useEffect(() => { + if (mode === "inline") return; + + function handleKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") closeFullscreen(); + } + + document.addEventListener("keydown", handleKeyDown); + document.body.style.overflow = "hidden"; + + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.body.style.overflow = ""; + }; + }, [mode, closeFullscreen]); + + async function shareDemo() { + const shareData = { + title: "Drydock Interactive Demo", + text: "Try Drydock — open source container update monitoring. Interactive demo, no install required.", + url: "https://demo.drydock.codeswhat.com", + }; + + if (navigator.share) { + await navigator.share(shareData); + } else { + await navigator.clipboard.writeText(shareData.url); + } + } + + function navigateIframe(path: string) { + if (iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.postMessage({ type: "navigate", path }, DEMO_URL); + } + if (iframeRef.current) { + iframeRef.current.src = `${DEMO_URL}${path}`; + } + } + + const isFixed = mode !== "inline"; + + return ( +
+
+
+
+

+ Screenshots +

+

+ See it in action! +

+

+ Try the fully interactive demo below — real UI, real data*, no install required. +

+
+ + {/* Action Buttons (inline only) */} + {mode === "inline" && ( +
+ + +
+ )} + + {/* Placeholder keeps the page layout stable when iframe goes fixed */} + {isFixed && ( +
+ )} + + {/* Backdrop */} + {isFixed && ( + +
+ +
+ + +
+
+ )} + +
+