From 7a75ff2c4c55bb7b614fa945784f5d0a483571d0 Mon Sep 17 00:00:00 2001 From: Muawiya-contact Date: Mon, 20 Jul 2026 18:11:28 +0500 Subject: [PATCH 1/2] feat(site): add marketing landing page + GitHub Pages deploy React + Vite + React Router site under site/, styled with plain CSS custom properties for dark/light theming (in-memory toggle, no localStorage). Fetches the latest GitHub release on load and renders per-OS download buttons parsed from asset filenames, with a loading state and a graceful fallback to the Releases page on API failure or when no release is published yet. Deploys via .github/workflows/deploy-pages.yml on push to main (paths: site/**), using actions/deploy-pages. Copies index.html to 404.html post-build so React Router's client-side routing survives direct links once more pages (e.g. /docs) are added. vite.config.js base is set to /diskern/ to match the GitHub Pages project-site URL. --- .claude/launch.json | 11 + .github/workflows/deploy-pages.yml | 57 ++ site/.gitignore | 24 + site/.oxlintrc.json | 8 + site/README.md | 28 + site/index.html | 17 + site/package-lock.json | 1444 ++++++++++++++++++++++++++++ site/package.json | 24 + site/public/favicon.svg | 13 + site/src/App.jsx | 18 + site/src/components/Downloads.jsx | 139 +++ site/src/components/Footer.jsx | 17 + site/src/components/Hero.jsx | 19 + site/src/components/HowItWorks.jsx | 48 + site/src/components/Layout.jsx | 16 + site/src/components/NavBar.jsx | 49 + site/src/components/icons.jsx | 150 +++ site/src/context/ThemeContext.jsx | 34 + site/src/hooks/useLatestRelease.js | 41 + site/src/lib/releases.js | 89 ++ site/src/main.jsx | 16 + site/src/pages/Home.jsx | 13 + site/src/pages/NotFound.jsx | 12 + site/src/styles/index.css | 454 +++++++++ site/vite.config.js | 11 + 25 files changed, 2752 insertions(+) create mode 100644 .claude/launch.json create mode 100644 .github/workflows/deploy-pages.yml create mode 100644 site/.gitignore create mode 100644 site/.oxlintrc.json create mode 100644 site/README.md create mode 100644 site/index.html create mode 100644 site/package-lock.json create mode 100644 site/package.json create mode 100644 site/public/favicon.svg create mode 100644 site/src/App.jsx create mode 100644 site/src/components/Downloads.jsx create mode 100644 site/src/components/Footer.jsx create mode 100644 site/src/components/Hero.jsx create mode 100644 site/src/components/HowItWorks.jsx create mode 100644 site/src/components/Layout.jsx create mode 100644 site/src/components/NavBar.jsx create mode 100644 site/src/components/icons.jsx create mode 100644 site/src/context/ThemeContext.jsx create mode 100644 site/src/hooks/useLatestRelease.js create mode 100644 site/src/lib/releases.js create mode 100644 site/src/main.jsx create mode 100644 site/src/pages/Home.jsx create mode 100644 site/src/pages/NotFound.jsx create mode 100644 site/src/styles/index.css create mode 100644 site/vite.config.js 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/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 ( +
+

+ Couldn't load the latest release automatically — GitHub's API limits + anonymous requests, or your connection blipped. +

+

+ + + {' '}View all releases on GitHub + +

+
+ ) +} + +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 ? ( +
+

+ No installers are attached to this release yet.{' '} + + Check the release page + + . +

+
+ ) : ( +
+ {downloads.map((d) => { + const Icon = OS_ICONS[d.os] ?? DownloadIcon + const recommended = d.os === visitorOS + return ( + + + + {d.label} + + {d.sublabel} + {d.size ? ` · ${formatBytes(d.size)}` : ''} + + + {recommended && You} + + ) + })} +
+ )} + + {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 logo +

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 + + +
+ + + + + +
+
+
+ ) +} 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()], +}) From abc5b4d6ba4b576ef3dd5f75e625fb198513fc40 Mon Sep 17 00:00:00 2001 From: Muawiya-contact Date: Mon, 20 Jul 2026 18:12:18 +0500 Subject: [PATCH 2/2] feat(app): replace old logo with the new logo everywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regenerated the full app icon set (Windows/macOS/Linux/Android/iOS, Store tiles) via `tauri icon site/public/favicon.svg`, replacing the artwork that was previously generated from app/src/img/deskearn.png. Deleted deskearn.png — it was already unreferenced in code (the app header is plain text), confirmed via repo-wide search before removal. The new logo is now the only logo anywhere in the repo. --- app/src-tauri/icons/128x128.png | Bin 12456 -> 3920 bytes app/src-tauri/icons/128x128@2x.png | Bin 36042 -> 7626 bytes app/src-tauri/icons/32x32.png | Bin 1746 -> 1104 bytes app/src-tauri/icons/64x64.png | Bin 4714 -> 1988 bytes app/src-tauri/icons/Square107x107Logo.png | Bin 9597 -> 3327 bytes app/src-tauri/icons/Square142x142Logo.png | Bin 14501 -> 4341 bytes app/src-tauri/icons/Square150x150Logo.png | Bin 15685 -> 4611 bytes app/src-tauri/icons/Square284x284Logo.png | Bin 43276 -> 8594 bytes app/src-tauri/icons/Square30x30Logo.png | Bin 1595 -> 967 bytes app/src-tauri/icons/Square310x310Logo.png | Bin 50863 -> 8992 bytes app/src-tauri/icons/Square44x44Logo.png | Bin 2724 -> 1505 bytes app/src-tauri/icons/Square71x71Logo.png | Bin 5403 -> 2191 bytes app/src-tauri/icons/Square89x89Logo.png | Bin 7466 -> 2823 bytes app/src-tauri/icons/StoreLogo.png | Bin 3206 -> 1644 bytes .../icons/android/mipmap-hdpi/ic_launcher.png | Bin 2916 -> 2396 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 17598 -> 4923 bytes .../android/mipmap-hdpi/ic_launcher_round.png | Bin 3316 -> 2294 bytes .../icons/android/mipmap-mdpi/ic_launcher.png | Bin 2750 -> 2268 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 9651 -> 3342 bytes .../android/mipmap-mdpi/ic_launcher_round.png | Bin 3248 -> 2116 bytes .../android/mipmap-xhdpi/ic_launcher.png | Bin 7339 -> 4943 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 26962 -> 6297 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 8588 -> 4167 bytes .../android/mipmap-xxhdpi/ic_launcher.png | Bin 12937 -> 7807 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 55291 -> 9615 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 14969 -> 6871 bytes .../android/mipmap-xxxhdpi/ic_launcher.png | Bin 19747 -> 10468 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 94914 -> 12660 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 22924 -> 9189 bytes app/src-tauri/icons/icon.icns | Bin 968011 -> 90320 bytes app/src-tauri/icons/icon.ico | Bin 48156 -> 14975 bytes app/src-tauri/icons/icon.png | Bin 133065 -> 15265 bytes app/src-tauri/icons/ios/AppIcon-20x20@1x.png | Bin 855 -> 705 bytes .../icons/ios/AppIcon-20x20@2x-1.png | Bin 2178 -> 1297 bytes app/src-tauri/icons/ios/AppIcon-20x20@2x.png | Bin 2178 -> 1297 bytes app/src-tauri/icons/ios/AppIcon-20x20@3x.png | Bin 3876 -> 1929 bytes app/src-tauri/icons/ios/AppIcon-29x29@1x.png | Bin 1363 -> 989 bytes .../icons/ios/AppIcon-29x29@2x-1.png | Bin 3783 -> 1862 bytes app/src-tauri/icons/ios/AppIcon-29x29@2x.png | Bin 3783 -> 1862 bytes app/src-tauri/icons/ios/AppIcon-29x29@3x.png | Bin 6646 -> 2770 bytes app/src-tauri/icons/ios/AppIcon-40x40@1x.png | Bin 2178 -> 1297 bytes .../icons/ios/AppIcon-40x40@2x-1.png | Bin 5912 -> 2450 bytes app/src-tauri/icons/ios/AppIcon-40x40@2x.png | Bin 5912 -> 2450 bytes app/src-tauri/icons/ios/AppIcon-40x40@3x.png | Bin 10571 -> 3657 bytes app/src-tauri/icons/ios/AppIcon-512@2x.png | Bin 565335 -> 33849 bytes app/src-tauri/icons/ios/AppIcon-60x60@2x.png | Bin 10571 -> 3657 bytes app/src-tauri/icons/ios/AppIcon-60x60@3x.png | Bin 19350 -> 5494 bytes app/src-tauri/icons/ios/AppIcon-76x76@1x.png | Bin 5536 -> 2394 bytes app/src-tauri/icons/ios/AppIcon-76x76@2x.png | Bin 15008 -> 4614 bytes .../icons/ios/AppIcon-83.5x83.5@2x.png | Bin 17256 -> 5169 bytes app/src/img/deskearn.png | Bin 1453876 -> 0 bytes 51 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/src/img/deskearn.png diff --git a/app/src-tauri/icons/128x128.png b/app/src-tauri/icons/128x128.png index b1b4f1208d5688fbdafdd667431ea212b075bc65..84f789fbfe3995c384a0a6795f9af92c517a9f2f 100644 GIT binary patch literal 3920 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 diff --git a/app/src-tauri/icons/128x128@2x.png b/app/src-tauri/icons/128x128@2x.png index e70a2e30e049b2ffa0d3fa619294600baff19314..2fc0cb1ff08b7aad70ab9c51fb06ce4694dec19a 100644 GIT binary patch literal 7626 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