diff --git a/.claude/launch.json b/.claude/launch.json
new file mode 100644
index 0000000..3648408
--- /dev/null
+++ b/.claude/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.0.1",
+ "configurations": [
+ {
+ "name": "diskern-site",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": ["--prefix", "site", "run", "dev"],
+ "port": 5173
+ }
+ ]
+}
diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml
new file mode 100644
index 0000000..5e156ce
--- /dev/null
+++ b/.github/workflows/deploy-pages.yml
@@ -0,0 +1,57 @@
+name: Deploy site to GitHub Pages
+
+on:
+ push:
+ branches: [main]
+ paths: ["site/**", ".github/workflows/deploy-pages.yml"]
+ workflow_dispatch: {}
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+ cache-dependency-path: site/package-lock.json
+
+ - run: npm ci
+ working-directory: site
+
+ - run: npm run build
+ working-directory: site
+
+ # GitHub Pages has no server-side routing: a direct visit to
+ # /diskern/docs would 404 before React Router ever loads. Serving
+ # the same SPA shell as the 404 page lets the client-side router
+ # take over once the JS boots, so deep links and refreshes on
+ # future pages (e.g. /docs, /changelog) work.
+ - name: Add SPA fallback for client-side routing
+ run: cp dist/index.html dist/404.html
+ working-directory: site
+
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: site/dist
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/app/src-tauri/icons/128x128.png b/app/src-tauri/icons/128x128.png
index b1b4f12..84f789f 100644
Binary files a/app/src-tauri/icons/128x128.png and b/app/src-tauri/icons/128x128.png differ
diff --git a/app/src-tauri/icons/128x128@2x.png b/app/src-tauri/icons/128x128@2x.png
index e70a2e3..2fc0cb1 100644
Binary files a/app/src-tauri/icons/128x128@2x.png and b/app/src-tauri/icons/128x128@2x.png differ
diff --git a/app/src-tauri/icons/32x32.png b/app/src-tauri/icons/32x32.png
index 0087fdc..0eaaa66 100644
Binary files a/app/src-tauri/icons/32x32.png and b/app/src-tauri/icons/32x32.png differ
diff --git a/app/src-tauri/icons/64x64.png b/app/src-tauri/icons/64x64.png
index e8d508e..5b2e717 100644
Binary files a/app/src-tauri/icons/64x64.png and b/app/src-tauri/icons/64x64.png differ
diff --git a/app/src-tauri/icons/Square107x107Logo.png b/app/src-tauri/icons/Square107x107Logo.png
index 18054ee..db2c548 100644
Binary files a/app/src-tauri/icons/Square107x107Logo.png and b/app/src-tauri/icons/Square107x107Logo.png differ
diff --git a/app/src-tauri/icons/Square142x142Logo.png b/app/src-tauri/icons/Square142x142Logo.png
index 194575c..99185d0 100644
Binary files a/app/src-tauri/icons/Square142x142Logo.png and b/app/src-tauri/icons/Square142x142Logo.png differ
diff --git a/app/src-tauri/icons/Square150x150Logo.png b/app/src-tauri/icons/Square150x150Logo.png
index 8462720..f772d5f 100644
Binary files a/app/src-tauri/icons/Square150x150Logo.png and b/app/src-tauri/icons/Square150x150Logo.png differ
diff --git a/app/src-tauri/icons/Square284x284Logo.png b/app/src-tauri/icons/Square284x284Logo.png
index aafc4fa..2a5dcc2 100644
Binary files a/app/src-tauri/icons/Square284x284Logo.png and b/app/src-tauri/icons/Square284x284Logo.png differ
diff --git a/app/src-tauri/icons/Square30x30Logo.png b/app/src-tauri/icons/Square30x30Logo.png
index 6fa37c1..6d5bd4c 100644
Binary files a/app/src-tauri/icons/Square30x30Logo.png and b/app/src-tauri/icons/Square30x30Logo.png differ
diff --git a/app/src-tauri/icons/Square310x310Logo.png b/app/src-tauri/icons/Square310x310Logo.png
index 503288d..17da3de 100644
Binary files a/app/src-tauri/icons/Square310x310Logo.png and b/app/src-tauri/icons/Square310x310Logo.png differ
diff --git a/app/src-tauri/icons/Square44x44Logo.png b/app/src-tauri/icons/Square44x44Logo.png
index 6f76891..3c9c1db 100644
Binary files a/app/src-tauri/icons/Square44x44Logo.png and b/app/src-tauri/icons/Square44x44Logo.png differ
diff --git a/app/src-tauri/icons/Square71x71Logo.png b/app/src-tauri/icons/Square71x71Logo.png
index 6b45976..871b0a1 100644
Binary files a/app/src-tauri/icons/Square71x71Logo.png and b/app/src-tauri/icons/Square71x71Logo.png differ
diff --git a/app/src-tauri/icons/Square89x89Logo.png b/app/src-tauri/icons/Square89x89Logo.png
index e8f3bee..53cf075 100644
Binary files a/app/src-tauri/icons/Square89x89Logo.png and b/app/src-tauri/icons/Square89x89Logo.png differ
diff --git a/app/src-tauri/icons/StoreLogo.png b/app/src-tauri/icons/StoreLogo.png
index b192ebc..0046d05 100644
Binary files a/app/src-tauri/icons/StoreLogo.png and b/app/src-tauri/icons/StoreLogo.png differ
diff --git a/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
index 04dfe85..2838e40 100644
Binary files a/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and b/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
index aaae622..5afb737 100644
Binary files a/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and b/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
index 592ace4..60a9366 100644
Binary files a/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and b/app/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
index 1ad3a7f..dd65195 100644
Binary files a/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and b/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
index 1dfb204..3ca7835 100644
Binary files a/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and b/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
index 1117d8f..6a2b649 100644
Binary files a/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and b/app/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
index 98aaf4e..39ae5ec 100644
Binary files a/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and b/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
index 1425509..bcb58ef 100644
Binary files a/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
index 27718fc..ec381f6 100644
Binary files a/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and b/app/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
index d58c7f9..ce2846b 100644
Binary files a/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and b/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
index c0c486f..8c57ac4 100644
Binary files a/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
index 9f61409..311dbea 100644
Binary files a/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and b/app/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
index 9b53bd5..b580b1e 100644
Binary files a/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and b/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
index 45f1e04..a8a0abf 100644
Binary files a/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
index fd30ab5..4c49c24 100644
Binary files a/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src-tauri/icons/icon.icns b/app/src-tauri/icons/icon.icns
index e322758..1579093 100644
Binary files a/app/src-tauri/icons/icon.icns and b/app/src-tauri/icons/icon.icns differ
diff --git a/app/src-tauri/icons/icon.ico b/app/src-tauri/icons/icon.ico
index 8de970e..93fb9df 100644
Binary files a/app/src-tauri/icons/icon.ico and b/app/src-tauri/icons/icon.ico differ
diff --git a/app/src-tauri/icons/icon.png b/app/src-tauri/icons/icon.png
index f5cc05a..55f8649 100644
Binary files a/app/src-tauri/icons/icon.png and b/app/src-tauri/icons/icon.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@1x.png b/app/src-tauri/icons/ios/AppIcon-20x20@1x.png
index 0dbf028..92a4d63 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/app/src-tauri/icons/ios/AppIcon-20x20@1x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
index db2d4eb..b4445ac 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/app/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@2x.png b/app/src-tauri/icons/ios/AppIcon-20x20@2x.png
index db2d4eb..b4445ac 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/app/src-tauri/icons/ios/AppIcon-20x20@2x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-20x20@3x.png b/app/src-tauri/icons/ios/AppIcon-20x20@3x.png
index 9aaf579..a82ae13 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/app/src-tauri/icons/ios/AppIcon-20x20@3x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@1x.png b/app/src-tauri/icons/ios/AppIcon-29x29@1x.png
index 15b9dc1..a5e07bf 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/app/src-tauri/icons/ios/AppIcon-29x29@1x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
index 4d685d6..e195f42 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/app/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@2x.png b/app/src-tauri/icons/ios/AppIcon-29x29@2x.png
index 4d685d6..e195f42 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/app/src-tauri/icons/ios/AppIcon-29x29@2x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-29x29@3x.png b/app/src-tauri/icons/ios/AppIcon-29x29@3x.png
index 77e8a0d..21494d6 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/app/src-tauri/icons/ios/AppIcon-29x29@3x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@1x.png b/app/src-tauri/icons/ios/AppIcon-40x40@1x.png
index db2d4eb..b4445ac 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/app/src-tauri/icons/ios/AppIcon-40x40@1x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
index 2933045..7ab800e 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/app/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@2x.png b/app/src-tauri/icons/ios/AppIcon-40x40@2x.png
index 2933045..7ab800e 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/app/src-tauri/icons/ios/AppIcon-40x40@2x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-40x40@3x.png b/app/src-tauri/icons/ios/AppIcon-40x40@3x.png
index 174b4c0..14130ef 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/app/src-tauri/icons/ios/AppIcon-40x40@3x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-512@2x.png b/app/src-tauri/icons/ios/AppIcon-512@2x.png
index 30a53cc..05c5e72 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-512@2x.png and b/app/src-tauri/icons/ios/AppIcon-512@2x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-60x60@2x.png b/app/src-tauri/icons/ios/AppIcon-60x60@2x.png
index 174b4c0..14130ef 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/app/src-tauri/icons/ios/AppIcon-60x60@2x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-60x60@3x.png b/app/src-tauri/icons/ios/AppIcon-60x60@3x.png
index 0a93aba..fc51bf3 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/app/src-tauri/icons/ios/AppIcon-60x60@3x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-76x76@1x.png b/app/src-tauri/icons/ios/AppIcon-76x76@1x.png
index 31ebb68..54b90c4 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/app/src-tauri/icons/ios/AppIcon-76x76@1x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-76x76@2x.png b/app/src-tauri/icons/ios/AppIcon-76x76@2x.png
index 403e4e2..61acdee 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/app/src-tauri/icons/ios/AppIcon-76x76@2x.png differ
diff --git a/app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
index a61f0e5..d030b26 100644
Binary files a/app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/app/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ
diff --git a/app/src/img/deskearn.png b/app/src/img/deskearn.png
deleted file mode 100644
index 4851a52..0000000
Binary files a/app/src/img/deskearn.png and /dev/null differ
diff --git a/site/.gitignore b/site/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/site/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/site/.oxlintrc.json b/site/.oxlintrc.json
new file mode 100644
index 0000000..1255078
--- /dev/null
+++ b/site/.oxlintrc.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "plugins": ["react", "oxc"],
+ "rules": {
+ "react/rules-of-hooks": "error",
+ "react/only-export-components": ["warn", { "allowConstantExport": true }]
+ }
+}
diff --git a/site/README.md b/site/README.md
new file mode 100644
index 0000000..59e8ef6
--- /dev/null
+++ b/site/README.md
@@ -0,0 +1,28 @@
+# diskern-site
+
+Marketing/landing page for [Diskern](https://github.com/Coding-Moves/diskern), deployed to GitHub Pages.
+
+## Develop
+
+```sh
+npm install
+npm run dev
+```
+
+## Build
+
+```sh
+npm run build # outputs to dist/
+npm run preview # serve the production build locally
+```
+
+## Deploy
+
+Pushing to `main` with changes under `site/` triggers
+`.github/workflows/deploy-pages.yml`, which builds this project and
+publishes `dist/` to GitHub Pages. See the root `docs/` for more.
+
+The Vite `base` in `vite.config.js` is set to `/diskern/` to match this
+repo's GitHub Pages URL (`coding-moves.github.io/diskern/` or your
+configured custom domain). If the repo is ever renamed, update `base`
+to match.
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..a2c7709
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ Diskern — Understand your disk before you clean it
+
+
+
+
+
+
diff --git a/site/package-lock.json b/site/package-lock.json
new file mode 100644
index 0000000..5b90ae2
--- /dev/null
+++ b/site/package-lock.json
@@ -0,0 +1,1444 @@
+{
+ "name": "site",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "site",
+ "version": "0.0.0",
+ "dependencies": {
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
+ "react-router-dom": "^7.18.1"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.3",
+ "oxlint": "^1.71.0",
+ "vite": "^8.1.1"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
+ "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
+ "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
+ "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.139.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
+ "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@oxlint/binding-android-arm-eabi": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.74.0.tgz",
+ "integrity": "sha512-+gHd12muVI9ZLBaWLPkHt3Fj7jihFjgQ1MGtBaRL8vWrWrI0P7dLUty/cHrHS0oqPYIRgQUJsPu2CExQuMcwNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-android-arm64": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.74.0.tgz",
+ "integrity": "sha512-xjKdoMB+H+RCOByv/7l7nfIGW9mlOisqYdcyC75UqYuQecLpReAeEYUf2CNeDEI3KtmUgxpRw/+c63y4AeF/Bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-darwin-arm64": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.74.0.tgz",
+ "integrity": "sha512-iUK7wvc6sejMKsC+Pt67mntoF5weFcyEunhZfLJceU6gL419mexz5wBkSx/EnkFBExMLNtOi9fnDSc5xfK0IzQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-darwin-x64": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.74.0.tgz",
+ "integrity": "sha512-ggKc/tn5SJ1u2yG2izC6VKODfYKV8MQ2AicJlNzOjuyrC29udvOef6/JzK2r32xqCnBDLFouR1VCkjzEI0/N9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-freebsd-x64": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.74.0.tgz",
+ "integrity": "sha512-u++dH/43jy9hTLbneaWlS0gla/Bp1JdwJ2zgevCl8nDFUh6qRCGMxcL0f0lb7By3A9p/LfFr+7cG4HU1hG856g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm-gnueabihf": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.74.0.tgz",
+ "integrity": "sha512-Sj1zmtFDVTPeIbIz4ZfcXAbFHqCmKCXdCUlAJzvTF7I20NTH1RDpoF2PhkqNODutJzVhJYmm3oz0GwgY+tvE2g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm-musleabihf": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.74.0.tgz",
+ "integrity": "sha512-//PKyQb/tQXcHArx2f7z+oVI/eMS2Jpv+edNuAtOrgIhWdGcpHxogveAxzmF2rpH1AIHp4Hq04RF/rgJdiICnQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm64-gnu": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.74.0.tgz",
+ "integrity": "sha512-/k1Me+aX2tjuH10K62mLS0y8cLkJBHX6Ce0xPK+eWeel4bSdEGZ8dv4+hYMzg0GrSmjwy4yAYsDPeEeKBft/2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm64-musl": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.74.0.tgz",
+ "integrity": "sha512-3tFSjBxc5D8/zvjEuLvOqcA8ZXKD0+6NuaVO/edeamNc49MoAsbfaC9s1UiwODwgF6slGaF8yJA2TPkukd77tg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-ppc64-gnu": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.74.0.tgz",
+ "integrity": "sha512-9QggtPkSPXOCTu8Szis7auOK/sC7KdQaN+/TujP7YVVhzCAOhgdRfgv8uEz0r2tk5xdgus5rLYUrCDoZNtiRUw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-gnu": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.74.0.tgz",
+ "integrity": "sha512-VM5VPUJ4DJIWiK+AZn8FScUqMr6OFrCAYybMYjEEi7W13ParI64MByiXTkKMqZpBmvQ9zxl9Ebq2VUOiZRJYUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-musl": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.74.0.tgz",
+ "integrity": "sha512-SaDY1gh9rOA592J54g+gu5hkOFFQBZsMmIYHs+NRHG+Uq0OxtuuCXMWQ3vu1830Eugv5uMXyjG+bv2Z9y4IXjw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-s390x-gnu": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.74.0.tgz",
+ "integrity": "sha512-ZATQeHZCyr6MbDveg0obD5sxLHFOghtOdC5jwVwYlvFWqtFOxctgFEG6Ef/64hYvZrWyhyCckB10AelqLopeDA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-gnu": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.74.0.tgz",
+ "integrity": "sha512-+aIvJyrdeD7LwCQ2WYLMUWNmnbeDRSPb40aBYtPjD9+PTqUwgJnk+HK5yLfSMeqXrMrDhE9uTmtt2y50tvjhHw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-musl": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.74.0.tgz",
+ "integrity": "sha512-XyktaR8lhK2qWiCK0Tk8oYD+/cgn+oHA6ddRnxSSXUKkkojkV78CmShZUxQF+yrBFs0SuW+JBOPG6hecyc/iZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-openharmony-arm64": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.74.0.tgz",
+ "integrity": "sha512-mzbjrPl4neaVUiJ1fUiEUxTGaSZBoiKtaoB6jmIpz9S+VOA2vDYmJpihQ82w6178V5jxziclTg8Cgj5yF6tTDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-arm64-msvc": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.74.0.tgz",
+ "integrity": "sha512-vUAe9okpS2Oa5+lX67lqHMuNUvfkleRKwrUDJ/WJBsgmddvZ1mrsh2HVmuFDRzqFELhaJhFaCNOuR6a7L3rtIA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-ia32-msvc": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.74.0.tgz",
+ "integrity": "sha512-yyXXJyYYSXL4I8K8jAWjJs+J3fa9gH2JmEbo4f5adm+1tNC9itseicBNuwK7BDHvqQ5J534s+yDULu89vYL2ZQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-x64-msvc": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.74.0.tgz",
+ "integrity": "sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
+ "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
+ "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
+ "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
+ "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
+ "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
+ "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
+ "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
+ "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
+ "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
+ "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
+ "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
+ "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
+ "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.11.1",
+ "@emnapi/runtime": "1.11.1",
+ "@napi-rs/wasm-runtime": "^1.1.6"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
+ "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
+ "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
+ "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.17",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
+ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
+ "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "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==",
+ "dev": true,
+ "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/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "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.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "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.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "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/nanoid": {
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/oxlint": {
+ "version": "1.74.0",
+ "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.74.0.tgz",
+ "integrity": "sha512-odGl2s2x5IOJoj3A0v1k0PGBXVFBZeZ2+AK/+K2MJur7Ghi3bkyX5NuLUWHKqa4js1wjep3hJeuTQJOlr+4+dA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "oxlint": "bin/oxlint"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxlint/binding-android-arm-eabi": "1.74.0",
+ "@oxlint/binding-android-arm64": "1.74.0",
+ "@oxlint/binding-darwin-arm64": "1.74.0",
+ "@oxlint/binding-darwin-x64": "1.74.0",
+ "@oxlint/binding-freebsd-x64": "1.74.0",
+ "@oxlint/binding-linux-arm-gnueabihf": "1.74.0",
+ "@oxlint/binding-linux-arm-musleabihf": "1.74.0",
+ "@oxlint/binding-linux-arm64-gnu": "1.74.0",
+ "@oxlint/binding-linux-arm64-musl": "1.74.0",
+ "@oxlint/binding-linux-ppc64-gnu": "1.74.0",
+ "@oxlint/binding-linux-riscv64-gnu": "1.74.0",
+ "@oxlint/binding-linux-riscv64-musl": "1.74.0",
+ "@oxlint/binding-linux-s390x-gnu": "1.74.0",
+ "@oxlint/binding-linux-x64-gnu": "1.74.0",
+ "@oxlint/binding-linux-x64-musl": "1.74.0",
+ "@oxlint/binding-openharmony-arm64": "1.74.0",
+ "@oxlint/binding-win32-arm64-msvc": "1.74.0",
+ "@oxlint/binding-win32-ia32-msvc": "1.74.0",
+ "@oxlint/binding-win32-x64-msvc": "1.74.0"
+ },
+ "peerDependencies": {
+ "oxlint-tsgolint": ">=0.24.0",
+ "vite-plus": "*"
+ },
+ "peerDependenciesMeta": {
+ "oxlint-tsgolint": {
+ "optional": true
+ },
+ "vite-plus": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.20",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz",
+ "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.16",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
+ "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
+ "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.7"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz",
+ "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz",
+ "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
+ "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.139.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.1.5",
+ "@rolldown/binding-darwin-arm64": "1.1.5",
+ "@rolldown/binding-darwin-x64": "1.1.5",
+ "@rolldown/binding-freebsd-x64": "1.1.5",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
+ "@rolldown/binding-linux-arm64-gnu": "1.1.5",
+ "@rolldown/binding-linux-arm64-musl": "1.1.5",
+ "@rolldown/binding-linux-ppc64-gnu": "1.1.5",
+ "@rolldown/binding-linux-s390x-gnu": "1.1.5",
+ "@rolldown/binding-linux-x64-gnu": "1.1.5",
+ "@rolldown/binding-linux-x64-musl": "1.1.5",
+ "@rolldown/binding-openharmony-arm64": "1.1.5",
+ "@rolldown/binding-wasm32-wasi": "1.1.5",
+ "@rolldown/binding-win32-arm64-msvc": "1.1.5",
+ "@rolldown/binding-win32-x64-msvc": "1.1.5"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/vite": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
+ "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.17",
+ "rolldown": "~1.1.5",
+ "tinyglobby": "^0.2.17"
+ },
+ "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",
+ "@vitejs/devtools": "^0.3.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.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
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/site/package.json b/site/package.json
new file mode 100644
index 0000000..3a745e6
--- /dev/null
+++ b/site/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "diskern-site",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "oxlint",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
+ "react-router-dom": "^7.18.1"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.3",
+ "oxlint": "^1.71.0",
+ "vite": "^8.1.1"
+ }
+}
diff --git a/site/public/favicon.svg b/site/public/favicon.svg
new file mode 100644
index 0000000..edc42da
--- /dev/null
+++ b/site/public/favicon.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/site/src/App.jsx b/site/src/App.jsx
new file mode 100644
index 0000000..f23ffdc
--- /dev/null
+++ b/site/src/App.jsx
@@ -0,0 +1,18 @@
+import { Routes, Route } from 'react-router-dom'
+import Layout from './components/Layout.jsx'
+import Home from './pages/Home.jsx'
+import NotFound from './pages/NotFound.jsx'
+
+// All routes share (nav + footer). To add a new page later —
+// e.g. /docs or /changelog — create src/pages/Docs.jsx and add
+// } /> below.
+export default function App() {
+ return (
+
+ }>
+ } />
+ } />
+
+
+ )
+}
diff --git a/site/src/components/Downloads.jsx b/site/src/components/Downloads.jsx
new file mode 100644
index 0000000..d8d0913
--- /dev/null
+++ b/site/src/components/Downloads.jsx
@@ -0,0 +1,139 @@
+import { useMemo } from 'react'
+import { useLatestRelease } from '../hooks/useLatestRelease.js'
+import {
+ RELEASES_PAGE,
+ matchDownloads,
+ detectOS,
+ formatBytes,
+ formatDate,
+} from '../lib/releases.js'
+import { WindowsIcon, LinuxIcon, DownloadIcon } from './icons.jsx'
+
+const OS_ICONS = {
+ windows: WindowsIcon,
+ linux: LinuxIcon,
+}
+
+export default function Downloads() {
+ const { status, release } = useLatestRelease()
+ const visitorOS = useMemo(() => detectOS(), [])
+
+ return (
+
+ Download
+
+ Pick your platform. All builds are unsigned for now — see the release
+ notes for details.
+
+
+
+ {status === 'loading' && }
+ {status === 'error' && }
+ {status === 'success' && (
+
+ )}
+
+
+ )
+}
+
+function LoadingState() {
+ return (
+
+
+
Fetching the latest release…
+
+ )
+}
+
+function FallbackState() {
+ return (
+
+ )
+}
+
+function ReleaseState({ release, visitorOS }) {
+ const downloads = matchDownloads(release.assets)
+ const version = release.name || release.tag_name
+ const notes = (release.body || '').trim()
+ const truncatedNotes =
+ notes.length > 420 ? `${notes.slice(0, 420).trimEnd()}…` : notes
+
+ return (
+ <>
+
+ {version}
+
+ Released {formatDate(release.published_at)}
+
+
+
+ {downloads.length === 0 ? (
+
+ ) : (
+
+ )}
+
+ {visitorOS === 'mac' && (
+
+ A native macOS build isn't available yet — it's on the roadmap.
+
+ )}
+
+ {notes && (
+ <>
+ {truncatedNotes}
+
+ Read full release notes on GitHub →
+
+ >
+ )}
+ >
+ )
+}
diff --git a/site/src/components/Footer.jsx b/site/src/components/Footer.jsx
new file mode 100644
index 0000000..d87866c
--- /dev/null
+++ b/site/src/components/Footer.jsx
@@ -0,0 +1,17 @@
+export default function Footer() {
+ return (
+
+ )
+}
diff --git a/site/src/components/Hero.jsx b/site/src/components/Hero.jsx
new file mode 100644
index 0000000..894ab81
--- /dev/null
+++ b/site/src/components/Hero.jsx
@@ -0,0 +1,19 @@
+export default function Hero() {
+ return (
+
+
+ Diskern
+ Understand your disk before you clean it.
+
+ Diskern scans your drives and shows you exactly where the space went —
+ biggest folders, duplicate files, old caches — before you delete
+ anything. It's a read-only analyzer first: nothing gets removed until
+ you tell it to.
+
+
+ )
+}
diff --git a/site/src/components/HowItWorks.jsx b/site/src/components/HowItWorks.jsx
new file mode 100644
index 0000000..ba5c0d8
--- /dev/null
+++ b/site/src/components/HowItWorks.jsx
@@ -0,0 +1,48 @@
+import { EyeIcon, CopyIcon, ShieldIcon, NoTrashIcon } from './icons.jsx'
+
+const FEATURES = [
+ {
+ icon: EyeIcon,
+ title: 'Read-only scanning',
+ description:
+ "Diskern only reads your filesystem to build its picture of what's using space. It never touches a file during a scan.",
+ },
+ {
+ icon: CopyIcon,
+ title: 'Duplicate detection',
+ description:
+ 'Finds exact duplicate files by content hash, not just name, so you can see what’s actually safe to consolidate.',
+ },
+ {
+ icon: ShieldIcon,
+ title: 'Rules-based safety',
+ description:
+ 'System and application-critical paths are recognized and flagged, so you don’t accidentally target something the OS needs.',
+ },
+ {
+ icon: NoTrashIcon,
+ title: 'Never hard-deletes',
+ description:
+ 'Anything you remove goes to your OS trash/recycle bin first — Diskern never permanently erases a file on your behalf.',
+ },
+]
+
+export default function HowItWorks() {
+ return (
+
+ How it works
+
+ Diskern is built to be trustworthy first, fast second.
+
+
+ {FEATURES.map(({ icon: Icon, title, description }) => (
+
+
+
{title}
+
{description}
+
+ ))}
+
+
+ )
+}
diff --git a/site/src/components/Layout.jsx b/site/src/components/Layout.jsx
new file mode 100644
index 0000000..d0e19c1
--- /dev/null
+++ b/site/src/components/Layout.jsx
@@ -0,0 +1,16 @@
+import { Outlet } from 'react-router-dom'
+import NavBar from './NavBar.jsx'
+import Footer from './Footer.jsx'
+
+// Shared shell for every route: nav + footer stay put, swaps
+// in whichever page matched. Add new routes in App.jsx and they get
+// the nav/theme-toggle/footer for free.
+export default function Layout() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/site/src/components/NavBar.jsx b/site/src/components/NavBar.jsx
new file mode 100644
index 0000000..269707c
--- /dev/null
+++ b/site/src/components/NavBar.jsx
@@ -0,0 +1,49 @@
+import { Link } from 'react-router-dom'
+import { useTheme } from '../context/ThemeContext.jsx'
+import { SunIcon, MoonIcon, GithubIcon } from './icons.jsx'
+
+const REPO_URL = 'https://github.com/Coding-Moves/diskern'
+
+export default function NavBar() {
+ const { theme, toggleTheme } = useTheme()
+
+ return (
+
+
+
+
+ Diskern
+
+
+
+
+ {theme === 'dark' ? : }
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/site/src/components/icons.jsx b/site/src/components/icons.jsx
new file mode 100644
index 0000000..0bf2dd8
--- /dev/null
+++ b/site/src/components/icons.jsx
@@ -0,0 +1,150 @@
+// Small hand-rolled icon set so the site doesn't need an icon-library
+// dependency for half a dozen glyphs. Each icon inherits color from
+// its parent via `currentColor` and sizes via the CSS on its wrapper.
+
+export function SunIcon(props) {
+ return (
+
+
+
+
+
+
+ )
+}
+
+export function MoonIcon(props) {
+ return (
+
+
+
+ )
+}
+
+export function GithubIcon(props) {
+ return (
+
+
+
+ )
+}
+
+export function WindowsIcon(props) {
+ return (
+
+
+
+ )
+}
+
+export function LinuxIcon(props) {
+ return (
+
+
+
+
+
+ )
+}
+
+export function DownloadIcon(props) {
+ return (
+
+
+
+ )
+}
+
+export function EyeIcon(props) {
+ return (
+
+
+
+
+ )
+}
+
+export function CopyIcon(props) {
+ return (
+
+
+
+
+ )
+}
+
+export function ShieldIcon(props) {
+ return (
+
+
+
+
+ )
+}
+
+export function NoTrashIcon(props) {
+ return (
+
+
+
+
+ )
+}
diff --git a/site/src/context/ThemeContext.jsx b/site/src/context/ThemeContext.jsx
new file mode 100644
index 0000000..90f1f48
--- /dev/null
+++ b/site/src/context/ThemeContext.jsx
@@ -0,0 +1,34 @@
+import { createContext, useContext, useMemo, useState } from 'react'
+
+const ThemeContext = createContext(null)
+
+// Plain React state, not localStorage: the choice lives only for the
+// current page load and resets on refresh. That's a deliberate
+// tradeoff — localStorage would persist the preference across visits,
+// but it also silently fails in some embedded/private-browsing
+// contexts, and this is a small marketing site where that edge case
+// isn't worth the complexity. If you want persistence later, swap the
+// useState below for a small useEffect-backed localStorage hook.
+export function ThemeProvider({ children }) {
+ const [theme, setTheme] = useState('dark')
+
+ const value = useMemo(
+ () => ({
+ theme,
+ toggleTheme: () => setTheme((t) => (t === 'dark' ? 'light' : 'dark')),
+ }),
+ [theme],
+ )
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function useTheme() {
+ const ctx = useContext(ThemeContext)
+ if (!ctx) throw new Error('useTheme must be used within a ThemeProvider')
+ return ctx
+}
diff --git a/site/src/hooks/useLatestRelease.js b/site/src/hooks/useLatestRelease.js
new file mode 100644
index 0000000..87e2681
--- /dev/null
+++ b/site/src/hooks/useLatestRelease.js
@@ -0,0 +1,41 @@
+import { useEffect, useState } from 'react'
+import { RELEASES_API } from '../lib/releases.js'
+
+// Fetches the latest GitHub release once on mount. GitHub's REST API
+// allows 60 unauthenticated requests per hour per IP — plenty for a
+// visitor loading this page a few times, but easy to exhaust while
+// developing locally with fast refresh. When that happens (or the
+// network fails, or the repo somehow has no releases yet) `status`
+// becomes 'error' and the caller should fall back to linking straight
+// at the GitHub Releases page instead of trying to render assets.
+export function useLatestRelease() {
+ const [status, setStatus] = useState('loading') // 'loading' | 'success' | 'error'
+ const [release, setRelease] = useState(null)
+
+ useEffect(() => {
+ let cancelled = false
+
+ fetch(RELEASES_API, {
+ headers: { Accept: 'application/vnd.github+json' },
+ })
+ .then((res) => {
+ if (!res.ok) throw new Error(`GitHub API responded ${res.status}`)
+ return res.json()
+ })
+ .then((data) => {
+ if (cancelled) return
+ setRelease(data)
+ setStatus('success')
+ })
+ .catch(() => {
+ if (cancelled) return
+ setStatus('error')
+ })
+
+ return () => {
+ cancelled = true
+ }
+ }, [])
+
+ return { status, release }
+}
diff --git a/site/src/lib/releases.js b/site/src/lib/releases.js
new file mode 100644
index 0000000..c26798b
--- /dev/null
+++ b/site/src/lib/releases.js
@@ -0,0 +1,89 @@
+export const REPO = 'Coding-Moves/diskern'
+export const RELEASES_API = `https://api.github.com/repos/${REPO}/releases/latest`
+export const RELEASES_PAGE = `https://github.com/${REPO}/releases`
+
+// One entry per download button we're able to offer. `match` looks at
+// the release asset's filename (lowercased) and returns true if this
+// button should be built from it. Order matters: first match wins,
+// so more specific extensions (.appimage) should stay ahead of
+// anything that could double-match.
+const ASSET_TYPES = [
+ {
+ id: 'windows',
+ os: 'windows',
+ label: 'Download for Windows',
+ sublabel: '.msi installer',
+ match: (name) => name.endsWith('.msi') || name.endsWith('.exe'),
+ },
+ {
+ id: 'debian',
+ os: 'linux',
+ label: 'Download for Debian/Ubuntu',
+ sublabel: '.deb package',
+ match: (name) => name.endsWith('.deb'),
+ },
+ {
+ id: 'fedora',
+ os: 'linux',
+ label: 'Download for Fedora/RHEL',
+ sublabel: '.rpm package',
+ match: (name) => name.endsWith('.rpm'),
+ },
+ {
+ id: 'appimage',
+ os: 'linux',
+ label: 'Download for Linux (AppImage)',
+ sublabel: '.AppImage — runs on most distros',
+ match: (name) => name.endsWith('.appimage'),
+ },
+]
+
+// Builds the list of download buttons to render from a GitHub release's
+// `assets` array, matching each known asset type against the first
+// release asset whose filename fits.
+export function matchDownloads(assets = []) {
+ return ASSET_TYPES.map((type) => {
+ const asset = assets.find((a) => type.match(a.name.toLowerCase()))
+ if (!asset) return null
+ return {
+ id: type.id,
+ os: type.os,
+ label: type.label,
+ sublabel: type.sublabel,
+ url: asset.browser_download_url,
+ size: asset.size,
+ }
+ }).filter(Boolean)
+}
+
+// Best-effort OS guess from the browser, used only to highlight the
+// most likely download for the visitor — never to hide the others.
+export function detectOS() {
+ if (typeof navigator === 'undefined') return 'unknown'
+ const ua = `${navigator.userAgent} ${navigator.platform ?? ''}`.toLowerCase()
+ if (ua.includes('win')) return 'windows'
+ if (ua.includes('mac')) return 'mac'
+ if (ua.includes('linux') || ua.includes('x11')) return 'linux'
+ return 'unknown'
+}
+
+export function formatBytes(bytes) {
+ if (!bytes) return ''
+ const units = ['B', 'KB', 'MB', 'GB']
+ let value = bytes
+ let unitIndex = 0
+ while (value >= 1024 && unitIndex < units.length - 1) {
+ value /= 1024
+ unitIndex += 1
+ }
+ return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`
+}
+
+export function formatDate(iso) {
+ if (!iso) return ''
+ return new Date(iso).toLocaleDateString(undefined, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ })
+}
diff --git a/site/src/main.jsx b/site/src/main.jsx
new file mode 100644
index 0000000..2023922
--- /dev/null
+++ b/site/src/main.jsx
@@ -0,0 +1,16 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
+import './styles/index.css'
+import App from './App.jsx'
+import { ThemeProvider } from './context/ThemeContext.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+
+
+
+
+ ,
+)
diff --git a/site/src/pages/Home.jsx b/site/src/pages/Home.jsx
new file mode 100644
index 0000000..977a75c
--- /dev/null
+++ b/site/src/pages/Home.jsx
@@ -0,0 +1,13 @@
+import Hero from '../components/Hero.jsx'
+import Downloads from '../components/Downloads.jsx'
+import HowItWorks from '../components/HowItWorks.jsx'
+
+export default function Home() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/site/src/pages/NotFound.jsx b/site/src/pages/NotFound.jsx
new file mode 100644
index 0000000..f7963eb
--- /dev/null
+++ b/site/src/pages/NotFound.jsx
@@ -0,0 +1,12 @@
+import { Link } from 'react-router-dom'
+
+export default function NotFound() {
+ return (
+
+ Page not found
+
+ Back to the Diskern homepage
+
+
+ )
+}
diff --git a/site/src/styles/index.css b/site/src/styles/index.css
new file mode 100644
index 0000000..7c6389b
--- /dev/null
+++ b/site/src/styles/index.css
@@ -0,0 +1,454 @@
+/* ---------- Theme variables ---------- */
+/* Everything that changes between dark/light lives here as a CSS
+ variable. Components never hardcode colors — they reference
+ var(--something), so flipping [data-theme] repaints the whole page. */
+
+[data-theme='dark'] {
+ --bg: #0b0d10;
+ --bg-elevated: #12161c;
+ --bg-elevated-hover: #171c24;
+ --text: #e9edf1;
+ --text-muted: #9aa4b2;
+ --accent: #3ddc97;
+ --accent-strong: #2bb989;
+ --accent-text: #05130d;
+ --border: #232a33;
+ --shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
+ --danger: #ff6b6b;
+ color-scheme: dark;
+}
+
+[data-theme='light'] {
+ --bg: #f7f8fa;
+ --bg-elevated: #ffffff;
+ --bg-elevated-hover: #f0f2f5;
+ --text: #12161c;
+ --text-muted: #5b6472;
+ --accent: #1f9d6c;
+ --accent-strong: #178056;
+ --accent-text: #ffffff;
+ --border: #e2e5ea;
+ --shadow: 0 8px 24px rgba(18, 22, 28, 0.08);
+ --danger: #d1453b;
+ color-scheme: light;
+}
+
+/* ---------- Reset ---------- */
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
+ sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
+}
+
+#root {
+ min-height: 100vh;
+}
+
+img,
+svg {
+ display: block;
+ max-width: 100%;
+}
+
+a {
+ color: inherit;
+}
+
+button {
+ font: inherit;
+}
+
+/* ---------- App shell ---------- */
+
+.app-shell {
+ min-height: 100vh;
+ background: var(--bg);
+ color: var(--text);
+ transition:
+ background-color 0.15s ease,
+ color 0.15s ease;
+}
+
+.container {
+ width: 100%;
+ max-width: 1080px;
+ margin: 0 auto;
+ padding: 0 24px;
+}
+
+/* ---------- Nav ---------- */
+
+.nav {
+ border-bottom: 1px solid var(--border);
+ position: sticky;
+ top: 0;
+ background: var(--bg);
+ z-index: 10;
+}
+
+.nav-inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 24px;
+ max-width: 1080px;
+ margin: 0 auto;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ color: var(--text);
+ font-weight: 700;
+ font-size: 1.1rem;
+}
+
+.brand-logo {
+ width: 28px;
+ height: 28px;
+ border-radius: 7px;
+}
+
+.nav-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.icon-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border-radius: 8px;
+ border: 1px solid var(--border);
+ background: var(--bg-elevated);
+ color: var(--text);
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+}
+
+.icon-button:hover {
+ background: var(--bg-elevated-hover);
+}
+
+.icon-button svg {
+ width: 18px;
+ height: 18px;
+}
+
+/* ---------- Hero ---------- */
+
+.hero {
+ padding: 88px 24px 64px;
+ text-align: center;
+}
+
+.hero-logo {
+ width: 72px;
+ height: 72px;
+ border-radius: 16px;
+ margin: 0 auto 24px;
+}
+
+.hero h1 {
+ font-size: clamp(2.1rem, 5vw, 3.2rem);
+ margin: 0 0 12px;
+ letter-spacing: -0.02em;
+}
+
+.hero-tagline {
+ font-size: clamp(1.05rem, 2.4vw, 1.3rem);
+ color: var(--accent);
+ font-weight: 600;
+ margin: 0 0 20px;
+}
+
+.hero-description {
+ max-width: 640px;
+ margin: 0 auto;
+ color: var(--text-muted);
+ font-size: 1.05rem;
+ line-height: 1.65;
+}
+
+/* ---------- Section shared ---------- */
+
+section {
+ padding: 56px 24px;
+}
+
+.section-title {
+ text-align: center;
+ font-size: 1.6rem;
+ margin: 0 0 8px;
+}
+
+.section-subtitle {
+ text-align: center;
+ color: var(--text-muted);
+ margin: 0 0 40px;
+ max-width: 560px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+/* ---------- Downloads ---------- */
+
+.downloads-card {
+ max-width: 720px;
+ margin: 0 auto;
+ background: var(--bg-elevated);
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ padding: 32px;
+ box-shadow: var(--shadow);
+}
+
+.downloads-meta {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ justify-content: center;
+ gap: 8px 12px;
+ text-align: center;
+ margin-bottom: 6px;
+}
+
+.downloads-version {
+ font-size: 1.3rem;
+ font-weight: 700;
+}
+
+.downloads-date {
+ color: var(--text-muted);
+ font-size: 0.9rem;
+}
+
+.downloads-notes {
+ color: var(--text-muted);
+ font-size: 0.92rem;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ max-height: 130px;
+ overflow: hidden;
+ text-align: left;
+ margin: 20px 0 0;
+ padding-top: 20px;
+ border-top: 1px solid var(--border);
+}
+
+.downloads-notes-link {
+ display: block;
+ text-align: center;
+ margin-top: 12px;
+ font-size: 0.9rem;
+ color: var(--accent);
+ text-decoration: none;
+}
+
+.downloads-notes-link:hover {
+ text-decoration: underline;
+}
+
+.downloads-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 12px;
+ margin-top: 24px;
+}
+
+.download-button {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 14px 16px;
+ border-radius: 10px;
+ border: 1px solid var(--border);
+ background: var(--bg);
+ color: var(--text);
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 0.95rem;
+ transition:
+ border-color 0.15s ease,
+ transform 0.1s ease;
+}
+
+.download-button:hover {
+ transform: translateY(-1px);
+ border-color: var(--accent);
+}
+
+.download-button.is-recommended {
+ border-color: var(--accent);
+ background: color-mix(in srgb, var(--accent) 10%, var(--bg));
+}
+
+.download-button .os-icon {
+ width: 22px;
+ height: 22px;
+ flex-shrink: 0;
+}
+
+.download-button .download-label {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ min-width: 0;
+}
+
+.download-button .download-sub {
+ font-weight: 400;
+ font-size: 0.78rem;
+ color: var(--text-muted);
+}
+
+.recommended-badge {
+ display: inline-block;
+ margin-left: auto;
+ font-size: 0.7rem;
+ font-weight: 700;
+ color: var(--accent-text);
+ background: var(--accent);
+ padding: 2px 8px;
+ border-radius: 999px;
+ white-space: nowrap;
+}
+
+.downloads-fallback {
+ text-align: center;
+ color: var(--text-muted);
+}
+
+.downloads-fallback a {
+ color: var(--accent);
+ font-weight: 600;
+ text-decoration: none;
+}
+
+.downloads-fallback a:hover {
+ text-decoration: underline;
+}
+
+.downloads-skeleton {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ color: var(--text-muted);
+ padding: 20px 0;
+}
+
+.spinner {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ border: 3px solid var(--border);
+ border-top-color: var(--accent);
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* ---------- How it works ---------- */
+
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
+ gap: 16px;
+ max-width: 900px;
+ margin: 0 auto;
+}
+
+.feature-card {
+ background: var(--bg-elevated);
+ border: 1px solid var(--border);
+ border-radius: 14px;
+ padding: 22px;
+}
+
+.feature-icon {
+ width: 32px;
+ height: 32px;
+ color: var(--accent);
+ margin-bottom: 14px;
+}
+
+.feature-card h3 {
+ margin: 0 0 8px;
+ font-size: 1.02rem;
+}
+
+.feature-card p {
+ margin: 0;
+ color: var(--text-muted);
+ font-size: 0.92rem;
+ line-height: 1.55;
+}
+
+/* ---------- Footer ---------- */
+
+.footer {
+ border-top: 1px solid var(--border);
+ padding: 28px 24px;
+ text-align: center;
+ color: var(--text-muted);
+ font-size: 0.85rem;
+}
+
+.footer a {
+ color: var(--text-muted);
+ text-decoration: none;
+}
+
+.footer a:hover {
+ color: var(--text);
+ text-decoration: underline;
+}
+
+/* ---------- Not found ---------- */
+
+.not-found {
+ text-align: center;
+ padding: 120px 24px;
+}
+
+.not-found a {
+ color: var(--accent);
+ font-weight: 600;
+}
+
+/* ---------- Responsive ---------- */
+
+@media (max-width: 640px) {
+ .hero {
+ padding: 56px 20px 48px;
+ }
+
+ section {
+ padding: 40px 20px;
+ }
+
+ .downloads-card {
+ padding: 22px;
+ }
+}
diff --git a/site/vite.config.js b/site/vite.config.js
new file mode 100644
index 0000000..1507479
--- /dev/null
+++ b/site/vite.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// `base` must match the repo name because GitHub Pages serves project
+// sites from https://.github.io// — every asset URL Vite
+// generates gets this prefix. Wrong value = blank page with 404s in
+// the browser console.
+export default defineConfig({
+ base: '/diskern/',
+ plugins: [react()],
+})