diff --git a/checklist.md b/checklist.md
index b39d815..c096a98 100644
--- a/checklist.md
+++ b/checklist.md
@@ -33,11 +33,11 @@ This checklist tracks the implementation progress of the Intelligent Divar Ads C
- [ ] Connect full pipeline (Scrape -> AI Flag -> Telegram Alert)
## 6. Frontend Dashboard (React + Vite)
-- [ ] Initialize React project in `frontend/`
-- [ ] Create layout and custom CSS styling
-- [ ] Build Crawler Management page (list, create, edit, trigger, log view)
-- [ ] Build Ads Feed page (display filtered ads with AI evaluation details)
-- [ ] Connect React frontend to Django REST APIs
+- [x] Initialize React project in `frontend/`
+- [x] Create layout and custom CSS styling
+- [x] Build Crawler Management page (list, create, edit, trigger, log view)
+- [x] Build Ads Feed page (display filtered ads with AI evaluation details)
+- [x] Connect React frontend to Django REST APIs
## 7. Verification & Production Readiness
- [ ] Run full system manual QA (Edge cases, errors, network failure)
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/frontend/.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/frontend/.oxlintrc.json b/frontend/.oxlintrc.json
new file mode 100644
index 0000000..1255078
--- /dev/null
+++ b/frontend/.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/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000..d937833
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,16 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the Oxlint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and Oxlint's TypeScript related rules in your project.
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..fe8a9c8
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ دیدبان دیوار - سامانه کرالر هوشمند
+
+
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..ce0bbdd
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,1582 @@
+{
+ "name": "frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "axios": "^1.19.0",
+ "lucide-react": "^1.30.0",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.4",
+ "oxlint": "^1.75.0",
+ "vite": "^8.2.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz",
+ "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@oxlint/binding-android-arm-eabi": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.77.0.tgz",
+ "integrity": "sha512-E06sKWS6PiI6HRxS1wyQg22HvApt01hI7fV+T3wUk3OSbaaP4a3hYGY/MIQDmASqCiRjBdpRQYkgMkqH82cWmQ==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.77.0.tgz",
+ "integrity": "sha512-NvsKz0KZxTp9cYWPLf+FXaSZwB3oO3peAjtukpOMBgse2vhQSoIIVqeO1yR0lEo/UcdZIDL18uq+kL0LzQ0ytA==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.77.0.tgz",
+ "integrity": "sha512-bgjTn6nW4bQCFBvSvuHCpDD+sONvmpo4lGI4PxzMt1quBA+xYxhczk6RiCn3GZ9gY8uhaBbwhj9MdKGfu6T9DA==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.77.0.tgz",
+ "integrity": "sha512-aotaIttH1R6j1Rwhx0M0htgeZyGtVQqYNTVEYMN/UcgHPquGA6kmk9OyuDc3a2GKUQBC+3C3GVQCcrRPMYqAFA==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.77.0.tgz",
+ "integrity": "sha512-nNx/wta7ksRAdYvq+l4AWjXkLxEXHALhENxjj2cYbQAIR4ybaA5L+hCbE63HOmft5czQ6ks+hb8vmEAnn7YGPg==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.77.0.tgz",
+ "integrity": "sha512-tMLLjM7xXtzXisVCzkOTXNCy9bZVId2wteNwjohlFDR/jY6WagpEDA1c1wu4xRc20Hojaxj+V6DSR7gbKxijWA==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.77.0.tgz",
+ "integrity": "sha512-MiAFDFaqR0tmHTAyo0YDcZ5hyLREdYw/RQhc2R3cbT+8O3tB+zqPM2th9TTQ+Uo3jn/embS+DO+HyX9ztCPkOQ==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.77.0.tgz",
+ "integrity": "sha512-/xqQ3B16i1T4cyt/9Mn+4CpzhUXoBXp7kVpIwzOXNFLj5JmK1bIjsbSnX296Gg8A/o7oDtKWikFgBx0SLwztkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm64-musl": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.77.0.tgz",
+ "integrity": "sha512-LSbwuRKiNCenPDcbARqAZ5RfBy7gmj7vOvfJRLeCDU3gFtSxWbhv/+VTlaUqzUhNj1gFLHB8h7ALnxa/Az6z6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-ppc64-gnu": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.77.0.tgz",
+ "integrity": "sha512-QWdcH31mXEUe5Nq1s0CfCpceaKjIo9uZtwDjAuL681g1axf+5x8xrg/eXWaw//4NCxYZ4V4e5Hu5tvdR+pTBlg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-gnu": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.77.0.tgz",
+ "integrity": "sha512-GnOfYgJxbcElOiPZaDFDl406ONddwvOWk2jvAAAEjwAl4GofNoHF+/HHUIBYa6bFCArlcGPi0XjC4cU1pkgF/Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-musl": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.77.0.tgz",
+ "integrity": "sha512-AyEMTUCf0xY+hHF+IxqXFQIX0yQOIR8ykpY0lJNOw9xYqOzUX8dyZfRvlG0RfXwuQn2eonf/8NrMmDSZJjdqsA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-s390x-gnu": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.77.0.tgz",
+ "integrity": "sha512-sPLzEcNvxd/oyVQ5oZo92CiHkFkpBeRop13E/P3TPY+hZfXHKCOWKI70TE2RYwMKFJDc20EMjH16L7NZICtKTw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-gnu": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.77.0.tgz",
+ "integrity": "sha512-1Oh2ssH2L7lwyvkdSqaMUfsGfwU2Wfvew+obBUYjRVqhpBcUpwnsPSEr1IzVi9XqkuY10geiLsNKecqaZC34Dw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-musl": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.77.0.tgz",
+ "integrity": "sha512-0j/2wRgNGO+Qj/M1uu/p57h/hFTTWWcfie0ufkbabeus2s5+/QqkCflnMOwLLN5m2GsNeWp4xdl4cPa4n7QCOQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-openharmony-arm64": {
+ "version": "1.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.77.0.tgz",
+ "integrity": "sha512-BJ/j54qS0usEnyDkLYURMj2iiD9h5Cyy+ppzeMSXBGRXaGRNWnj1Mw14NqWMR5E/PzdgB30OOCCzLzbRoduafw==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.77.0.tgz",
+ "integrity": "sha512-Yh8w+g2Lpx7StrvtYkoz9JJvXjB9wxgFChFNb85nrXm/wj/XTwGWS1hve9+900HL7llrntYB3YP+y32E3tRqzA==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.77.0.tgz",
+ "integrity": "sha512-zja5b7+6a7UsRFgAQSrnax5vrzliEyNPLCjfXONu/vTWswaIVZGFajJZptaeRvPE4LghtFdAzVFlexTm7MVTGA==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.77.0.tgz",
+ "integrity": "sha512-+teyvPDZ2RjUvo+SuCqS/UhaJl1QtdW5fWT5NJTV61V5MIuIS90Db9LixmtEGvXixyttiK62P96MSu3UlpviBw==",
+ "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.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz",
+ "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==",
+ "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.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz",
+ "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==",
+ "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.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz",
+ "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==",
+ "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.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz",
+ "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==",
+ "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.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz",
+ "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==",
+ "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.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz",
+ "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz",
+ "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz",
+ "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz",
+ "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz",
+ "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz",
+ "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==",
+ "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.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz",
+ "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==",
+ "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/@types/react": {
+ "version": "19.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz",
+ "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==",
+ "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/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz",
+ "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.16.0",
+ "form-data": "^4.0.6",
+ "https-proxy-agent": "^5.0.1",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "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/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "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/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "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,
+ "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,
+ "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,
+ "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,
+ "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/lucide-react": {
+ "version": "1.30.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.30.0.tgz",
+ "integrity": "sha512-tUIr2jXLbWpCkdtH8XP7P7YppM9ueWgTky99lpWDY6z5REs6B+O6ZQ3U5tHkUUY59ANyOv/PBcs8E4Fe3KO3eA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "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.77.0",
+ "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.77.0.tgz",
+ "integrity": "sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==",
+ "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.77.0",
+ "@oxlint/binding-android-arm64": "1.77.0",
+ "@oxlint/binding-darwin-arm64": "1.77.0",
+ "@oxlint/binding-darwin-x64": "1.77.0",
+ "@oxlint/binding-freebsd-x64": "1.77.0",
+ "@oxlint/binding-linux-arm-gnueabihf": "1.77.0",
+ "@oxlint/binding-linux-arm-musleabihf": "1.77.0",
+ "@oxlint/binding-linux-arm64-gnu": "1.77.0",
+ "@oxlint/binding-linux-arm64-musl": "1.77.0",
+ "@oxlint/binding-linux-ppc64-gnu": "1.77.0",
+ "@oxlint/binding-linux-riscv64-gnu": "1.77.0",
+ "@oxlint/binding-linux-riscv64-musl": "1.77.0",
+ "@oxlint/binding-linux-s390x-gnu": "1.77.0",
+ "@oxlint/binding-linux-x64-gnu": "1.77.0",
+ "@oxlint/binding-linux-x64-musl": "1.77.0",
+ "@oxlint/binding-openharmony-arm64": "1.77.0",
+ "@oxlint/binding-win32-arm64-msvc": "1.77.0",
+ "@oxlint/binding-win32-ia32-msvc": "1.77.0",
+ "@oxlint/binding-win32-x64-msvc": "1.77.0"
+ },
+ "peerDependencies": {
+ "oxlint-tsgolint": ">=7.0.2001",
+ "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.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "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.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.8"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz",
+ "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.143.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.2.3",
+ "@rolldown/binding-darwin-arm64": "1.2.3",
+ "@rolldown/binding-darwin-x64": "1.2.3",
+ "@rolldown/binding-freebsd-x64": "1.2.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.3",
+ "@rolldown/binding-linux-arm64-musl": "1.2.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-musl": "1.2.3",
+ "@rolldown/binding-openharmony-arm64": "1.2.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.3",
+ "@rolldown/binding-win32-x64-msvc": "1.2.3"
+ }
+ },
+ "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/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/vite": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
+ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.25",
+ "rolldown": "~1.2.1",
+ "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.4.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/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..892bdaf
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "oxlint",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "axios": "^1.19.0",
+ "lucide-react": "^1.30.0",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.4",
+ "oxlint": "^1.75.0",
+ "vite": "^8.2.0"
+ }
+}
diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg
new file mode 100644
index 0000000..6893eb1
--- /dev/null
+++ b/frontend/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg
new file mode 100644
index 0000000..e952219
--- /dev/null
+++ b/frontend/public/icons.svg
@@ -0,0 +1,24 @@
+
diff --git a/frontend/src/App.css b/frontend/src/App.css
new file mode 100644
index 0000000..a11da39
--- /dev/null
+++ b/frontend/src/App.css
@@ -0,0 +1 @@
+/* App-specific styles have been moved to index.css */
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
new file mode 100644
index 0000000..f650a56
--- /dev/null
+++ b/frontend/src/App.jsx
@@ -0,0 +1,761 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import {
+ ShieldAlert,
+ Layers,
+ Newspaper,
+ History,
+ Plus,
+ Search,
+ Trash2,
+ Edit2,
+ Play,
+ X,
+ AlertTriangle,
+ RefreshCw,
+ ExternalLink,
+ Info
+} from 'lucide-react';
+
+function App() {
+ const [activeTab, setActiveTab] = useState('crawlers');
+ const [crawlers, setCrawlers] = useState([]);
+ const [ads, setAds] = useState([]);
+ const [logs, setLogs] = useState([]);
+ const [health, setHealth] = useState({ database: 'loading', redis: 'loading' });
+
+ // Loading states
+ const [loadingCrawlers, setLoadingCrawlers] = useState(true);
+ const [loadingAds, setLoadingAds] = useState(true);
+ const [loadingLogs, setLoadingLogs] = useState(true);
+
+ // Filters
+ const [filterTaskId, setFilterTaskId] = useState('');
+ const [filterOnlyFlagged, setFilterOnlyFlagged] = useState(false);
+ const [searchTerm, setSearchTerm] = useState('');
+
+ // Modal states
+ const [modalOpen, setModalOpen] = useState(false);
+ const [editingCrawler, setEditingCrawler] = useState(null);
+
+ // Form state
+ const [formTitle, setFormTitle] = useState('');
+ const [formUrl, setFormUrl] = useState('');
+ const [formPrompt, setFormPrompt] = useState('');
+ const [formInterval, setFormInterval] = useState('15');
+ const [formTelegram, setFormTelegram] = useState('');
+ const [formStartHour, setFormStartHour] = useState('08:00');
+ const [formEndHour, setFormEndHour] = useState('23:00');
+ const [formIsActive, setFormIsActive] = useState(true);
+ const [formError, setFormError] = useState('');
+
+ // Toast alert
+ const [toast, setToast] = useState(null);
+
+ const showToast = (message, type = 'info') => {
+ setToast({ message, type });
+ setTimeout(() => {
+ setToast(null);
+ }, 4000);
+ };
+
+ // Initial Fetch & Health Check Polling
+ useEffect(() => {
+ fetchHealth();
+ fetchCrawlers();
+
+ const healthInterval = setInterval(fetchHealth, 15000);
+ return () => clearInterval(healthInterval);
+ }, []);
+
+ // Fetch ads and logs based on active tab and filtering criteria
+ useEffect(() => {
+ if (activeTab === 'ads') {
+ fetchAds();
+ } else if (activeTab === 'logs') {
+ fetchLogs();
+ }
+ }, [activeTab, filterTaskId, filterOnlyFlagged]);
+
+ // Dynamic status polling for active logs
+ useEffect(() => {
+ let logsInterval = null;
+ if (activeTab === 'logs' && logs.some(l => l.status === 'RUNNING')) {
+ logsInterval = setInterval(fetchLogs, 5000);
+ }
+ return () => {
+ if (logsInterval) clearInterval(logsInterval);
+ };
+ }, [activeTab, logs]);
+
+ const fetchHealth = async () => {
+ try {
+ const res = await axios.get('/api/health/');
+ setHealth({
+ database: res.data.database === 'healthy' ? 'healthy' : 'unhealthy',
+ redis: res.data.redis === 'healthy' ? 'healthy' : 'unhealthy'
+ });
+ } catch (err) {
+ setHealth({
+ database: err.response?.data?.database === 'healthy' ? 'healthy' : 'unhealthy',
+ redis: err.response?.data?.redis === 'healthy' ? 'healthy' : 'unhealthy'
+ });
+ }
+ };
+
+ const fetchCrawlers = async () => {
+ setLoadingCrawlers(true);
+ try {
+ const res = await axios.get('/api/crawlers/');
+ setCrawlers(res.data);
+ } catch (err) {
+ showToast('خطا در دریافت لیست کرالرها', 'danger');
+ console.error(err);
+ } finally {
+ setLoadingCrawlers(false);
+ }
+ };
+
+ const fetchAds = async () => {
+ setLoadingAds(true);
+ try {
+ let url = '/api/ads/';
+ const params = {};
+ if (filterTaskId) params.crawl_task = filterTaskId;
+ if (filterOnlyFlagged) params.is_flagged = 'true';
+
+ const res = await axios.get(url, { params });
+ setAds(res.data);
+ } catch (err) {
+ showToast('خطا در دریافت لیست آگهیها', 'danger');
+ console.error(err);
+ } finally {
+ setLoadingAds(false);
+ }
+ };
+
+ const fetchLogs = async () => {
+ setLoadingLogs(true);
+ try {
+ // In Django REST, runs are queried per-crawler. We fetch all crawlers,
+ // then request runs for each and merge/sort them.
+ const crawlersList = crawlers.length > 0 ? crawlers : (await axios.get('/api/crawlers/')).data;
+ const promises = crawlersList.map(async (c) => {
+ try {
+ const res = await axios.get(`/api/crawlers/${c.id}/runs/`);
+ return res.data.map(run => ({ ...run, crawler_title: c.title }));
+ } catch (e) {
+ return [];
+ }
+ });
+ const results = await Promise.all(promises);
+ const merged = results.flat().sort((a, b) => new Date(b.started_at) - new Date(a.started_at));
+ setLogs(merged);
+ } catch (err) {
+ showToast('خطا در دریافت تاریخچه اجرا', 'danger');
+ console.error(err);
+ } finally {
+ setLoadingLogs(false);
+ }
+ };
+
+ // Switch tabs
+ const switchTab = (tab) => {
+ setActiveTab(tab);
+ };
+
+ // Open Edit/Create Modal
+ const openCrawlerModal = (crawler = null) => {
+ setEditingCrawler(crawler);
+ setFormError('');
+ if (crawler) {
+ setFormTitle(crawler.title);
+ setFormUrl(crawler.divar_url);
+ setFormPrompt(crawler.detection_prompt);
+ setFormInterval(String(crawler.interval_minutes));
+ setFormTelegram(crawler.telegram_channel_id || '');
+ setFormStartHour(crawler.start_hour.substring(0, 5));
+ setFormEndHour(crawler.end_hour.substring(0, 5));
+ setFormIsActive(crawler.is_active);
+ } else {
+ setFormTitle('');
+ setFormUrl('');
+ setFormPrompt('');
+ setFormInterval('15');
+ setFormTelegram('');
+ setFormStartHour('08:00');
+ setFormEndHour('23:00');
+ setFormIsActive(true);
+ }
+ setModalOpen(true);
+ };
+
+ const closeCrawlerModal = () => {
+ setModalOpen(false);
+ setEditingCrawler(null);
+ };
+
+ const handleFormSubmit = async (e) => {
+ e.preventDefault();
+ setFormError('');
+
+ if (!formUrl.startsWith('https://divar.ir/s/')) {
+ setFormError("لینک دیوار باید با 'https://divar.ir/s/' شروع شود");
+ return;
+ }
+
+ const payload = {
+ title: formTitle,
+ divar_url: formUrl,
+ detection_prompt: formPrompt,
+ interval_minutes: parseInt(formInterval),
+ start_hour: `${formStartHour}:00`,
+ end_hour: `${formEndHour}:00`,
+ telegram_channel_id: formTelegram || null,
+ is_active: formIsActive
+ };
+
+ try {
+ if (editingCrawler) {
+ await axios.put(`/api/crawlers/${editingCrawler.id}/`, payload);
+ showToast('کرالر با موفقیت ویرایش شد', 'success');
+ } else {
+ await axios.post('/api/crawlers/', payload);
+ showToast('کرالر جدید با موفقیت ایجاد شد', 'success');
+ }
+ closeCrawlerModal();
+ fetchCrawlers();
+ } catch (err) {
+ const errorDetail = err.response?.data ? JSON.stringify(err.response.data) : 'خطا در ثبت اطلاعات';
+ setFormError(errorDetail);
+ }
+ };
+
+ const toggleCrawlerActive = async (crawler) => {
+ try {
+ await axios.patch(`/api/crawlers/${crawler.id}/`, {
+ is_active: !crawler.is_active
+ });
+ showToast(`کرالر ${!crawler.is_active ? 'فعال' : 'غیرفعال'} شد`, 'info');
+ fetchCrawlers();
+ } catch (err) {
+ showToast('خطا در تغییر وضعیت کرالر', 'danger');
+ }
+ };
+
+ const deleteCrawler = async (id) => {
+ if (!confirm('آیا از حذف این کرالر اطمینان دارید؟')) return;
+ try {
+ await axios.delete(`/api/crawlers/${id}/`);
+ showToast('کرالر با موفقیت حذف شد', 'success');
+ fetchCrawlers();
+ } catch (err) {
+ showToast('خطا در حذف کرالر', 'danger');
+ }
+ };
+
+ const triggerCrawler = async (id) => {
+ try {
+ const res = await axios.post(`/api/crawlers/${id}/trigger/`);
+ showToast('درخواست اجرای کرالر با موفقیت ارسال شد', 'success');
+ if (activeTab === 'logs') {
+ fetchLogs();
+ }
+ } catch (err) {
+ showToast('خطا در اجرای کرالر', 'danger');
+ }
+ };
+
+ // Format date helper
+ const formatDate = (isoStr) => {
+ if (!isoStr) return '-';
+ const date = new Date(isoStr);
+ return date.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }) + ' ' +
+ date.toLocaleDateString('fa-IR', { month: '2-digit', day: '2-digit' });
+ };
+
+ // Filter ads on client side based on Search Term
+ const getFilteredAds = () => {
+ if (!searchTerm) return ads;
+ const term = searchTerm.toLowerCase();
+ return ads.filter(adEval => {
+ const ad = adEval.ad;
+ return (
+ ad.title?.toLowerCase().includes(term) ||
+ ad.description?.toLowerCase().includes(term) ||
+ adEval.reason?.toLowerCase().includes(term)
+ );
+ });
+ };
+
+ return (
+
+
+ {/* Toast Notification */}
+ {toast && (
+
+ {toast.message}
+
+ )}
+
+ {/* Sidebar */}
+
+
+ {/* Main Content */}
+
+
+ {/* Header */}
+
+
+ {/* Viewport */}
+
+
+ {/* TAB 1: Crawlers View */}
+ {activeTab === 'crawlers' && (
+
+
+
+
+
+ {loadingCrawlers ? (
+
+
+
در حال بارگذاری کرالرها...
+
+ ) : crawlers.length === 0 ? (
+
+
+
هیچ کرالری تعریف نشده است. جهت پایش دیوار، یک کرالر جدید ایجاد کنید.
+
+ ) : (
+
+ {crawlers.map(c => (
+
+
+
{c.title}
+
+
+
+
+
+
+
+ لینک فیلتر دیوار
+ {c.divar_url}
+
+
+ پرامپت ارزیابی AI
+ {c.detection_prompt}
+
+
+
+ فاصله زمانی پایش
+ {c.interval_minutes} دقیقه
+
+
+ بازه زمانی مجاز
+ {c.start_hour.substring(0,5)} الی {c.end_hour.substring(0,5)}
+
+
+ {c.telegram_channel_id && (
+
+ کانال اعلانات تلگرام
+ {c.telegram_channel_id}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* TAB 2: Ads View */}
+ {activeTab === 'ads' && (
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+ فقط پرچمگذاری شده (AI)
+
+
+
+
+ {loadingAds ? (
+
+
+
در حال بارگذاری آگهیها...
+
+ ) : getFilteredAds().length === 0 ? (
+
+
+
هیچ آگهی با معیارهای فیلتر یافت نشد.
+
+ ) : (
+
+ {getFilteredAds().map(adEval => (
+
+
+ {formatDate(adEval.evaluated_at)}
+ {adEval.ad.price && (
+ {adEval.ad.price}
+ )}
+
+
+
+ {adEval.ad.title}
+
+
+
{adEval.ad.description}
+
+ {adEval.is_flagged && (
+
+
+ تحلیل هوش مصنوعی (پرچمگذاری شده)
+ {adEval.confidence && (
+ اطمینان: {Math.round(adEval.confidence * 100)}٪
+ )}
+
+
{adEval.reason}
+
+ )}
+
+
+ توکن: {adEval.ad.divar_token}
+ {adEval.ad.category && (
+ {adEval.ad.category}
+ )}
+
+
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* TAB 3: Logs View */}
+ {activeTab === 'logs' && (
+
+ {loadingLogs ? (
+
+
+
در حال دریافت تاریخچه اجرا...
+
+ ) : logs.length === 0 ? (
+
+
+
هیچ لاگ تاریخی ثبت نشده است.
+
+ ) : (
+
+
+
+
+ | کرالر |
+ شروع اجرا |
+ پایان اجرا |
+ وضعیت |
+ آگهی یافت شده |
+ پردازش AI |
+ پرچمگذاری شده |
+ لاگ خطا |
+
+
+
+ {logs.map(run => (
+
+ | {run.crawler_title} |
+ {formatDate(run.started_at)} |
+ {formatDate(run.finished_at)} |
+
+
+ {run.status === 'RUNNING' && 'در حال اجرا'}
+ {run.status === 'SUCCESS' && 'موفق'}
+ {run.status === 'FAILED' && 'ناموفق'}
+
+ |
+ {run.ads_fetched_count} |
+ {run.ads_evaluated_count} |
+ 0 ? 'var(--accent)' : 'inherit', fontWeight: run.ads_flagged_count > 0 ? 700 : 'normal' }}>
+ {run.ads_flagged_count}
+ |
+
+ {run.error_log || '-'}
+ |
+
+ ))}
+
+
+
+ )}
+
+ )}
+
+
+
+
+ {/* Form Dialog Modal */}
+
+
+
+
{editingCrawler ? 'ویرایش کرالر' : 'تعریف کرالر جدید'}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png
new file mode 100644
index 0000000..02251f4
Binary files /dev/null and b/frontend/src/assets/hero.png differ
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/frontend/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg
new file mode 100644
index 0000000..5101b67
--- /dev/null
+++ b/frontend/src/assets/vite.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..f504f9d
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1,991 @@
+/* Core Styling & Design System - Divar Watcher Dashboard */
+
+:root {
+ /* Font Variables */
+ --font-fa: 'Vazirmatn', system-ui, -apple-system, sans-serif;
+ --font-en: 'Inter', system-ui, -apple-system, sans-serif;
+
+ /* Color Tokens (Dark Theme Base) */
+ --bg-main: #0b0f19;
+ --bg-sidebar: #111827;
+ --bg-card: rgba(31, 41, 55, 0.4);
+ --bg-card-hover: rgba(31, 41, 55, 0.6);
+ --border-color: rgba(255, 255, 255, 0.08);
+ --border-hover: rgba(168, 85, 247, 0.4);
+
+ /* Text Colors */
+ --text-primary: #f3f4f6;
+ --text-secondary: #9ca3af;
+ --text-muted: #6b7280;
+
+ /* Accents & Statuses */
+ --accent: #a855f7; /* Purple */
+ --accent-glow: rgba(168, 85, 247, 0.2);
+ --success: #10b981; /* Emerald */
+ --success-glow: rgba(16, 185, 129, 0.15);
+ --danger: #ef4444; /* Rose Red */
+ --danger-glow: rgba(239, 68, 68, 0.15);
+ --warning: #f59e0b; /* Amber */
+ --warning-glow: rgba(245, 158, 11, 0.15);
+ --info: #3b82f6; /* Blue */
+ --info-glow: rgba(59, 130, 246, 0.15);
+
+ /* Shadows & Glassmorphism */
+ --glass-blur: blur(12px);
+ --card-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
+ --transition-speed: 0.25s;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ font-family: var(--font-fa);
+}
+
+body {
+ background-color: var(--bg-main);
+ color: var(--text-primary);
+ min-height: 100vh;
+ overflow-x: hidden;
+ line-height: 1.6;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ background-image:
+ radial-gradient(at 0% 0%, rgba(124, 58, 237, 0.08) 0px, transparent 50%),
+ radial-gradient(at 100% 100%, rgba(59, 130, 246, 0.08) 0px, transparent 50%);
+}
+
+/* App Container Layout */
+.app-container {
+ display: flex;
+ min-height: 100vh;
+ position: relative;
+}
+
+/* Sidebar Styling */
+.sidebar {
+ width: 280px;
+ background-color: var(--bg-sidebar);
+ border-left: 1px solid var(--border-color);
+ display: flex;
+ flex-direction: column;
+ padding: 1.5rem;
+ position: sticky;
+ top: 0;
+ height: 100vh;
+ z-index: 10;
+}
+
+.sidebar-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ margin-bottom: 2.5rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.brand-logo {
+ background: linear-gradient(135deg, #a855f7 0%, #3b82f6 100%);
+ width: 44px;
+ height: 44px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 0 15px rgba(168, 85, 247, 0.3);
+}
+
+.brand-icon {
+ width: 24px;
+ height: 24px;
+ color: white;
+}
+
+.brand-info h1 {
+ font-size: 1.25rem;
+ font-weight: 800;
+ color: var(--text-primary);
+}
+
+.brand-info span {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+.sidebar-nav {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ flex-grow: 1;
+}
+
+.nav-item {
+ background: transparent;
+ border: none;
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ padding: 0.85rem 1rem;
+ border-radius: 10px;
+ cursor: pointer;
+ font-size: 0.95rem;
+ font-weight: 500;
+ transition: all var(--transition-speed) ease;
+ text-align: right;
+ width: 100%;
+}
+
+.nav-item:hover {
+ background-color: rgba(255, 255, 255, 0.03);
+ color: var(--text-primary);
+}
+
+.nav-item.active {
+ background: linear-gradient(90deg, rgba(168, 85, 247, 0.15) 0%, transparent 100%);
+ border-right: 3px solid var(--accent);
+ color: var(--accent);
+ font-weight: 700;
+}
+
+.nav-item svg {
+ width: 20px;
+ height: 20px;
+}
+
+.sidebar-footer {
+ padding-top: 1rem;
+ border-top: 1px solid var(--border-color);
+ text-align: center;
+}
+
+.version-label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ font-family: var(--font-en);
+}
+
+/* Main Content Area */
+.main-content {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ min-width: 0; /* Prevents flex items from overflowing */
+}
+
+/* App Header */
+.app-header {
+ background-color: rgba(11, 15, 25, 0.7);
+ backdrop-filter: var(--glass-blur);
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1.25rem 2rem;
+ position: sticky;
+ top: 0;
+ z-index: 9;
+}
+
+.header-title h2 {
+ font-size: 1.35rem;
+ font-weight: 700;
+}
+
+.system-health {
+ display: flex;
+ align-items: center;
+ gap: 1.25rem;
+ background-color: rgba(255, 255, 255, 0.02);
+ padding: 0.45rem 1rem;
+ border-radius: 30px;
+ border: 1px solid var(--border-color);
+}
+
+.health-item {
+ display: flex;
+ align-items: center;
+ gap: 0.45rem;
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+}
+
+.dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.dot.green {
+ background-color: var(--success);
+ box-shadow: 0 0 8px var(--success);
+}
+
+.dot.red {
+ background-color: var(--danger);
+ box-shadow: 0 0 8px var(--danger);
+}
+
+.dot.yellow {
+ background-color: var(--warning);
+ box-shadow: 0 0 8px var(--warning);
+}
+
+/* View Viewport Container */
+.view-viewport {
+ padding: 2rem;
+ flex-grow: 1;
+}
+
+.tab-view {
+ display: none;
+ animation: fadeIn 0.3s ease;
+}
+
+.tab-view.active {
+ display: block;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(6px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* Button Component */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ border: 1px solid transparent;
+ border-radius: 8px;
+ padding: 0.6rem 1.2rem;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all var(--transition-speed) ease;
+}
+
+.btn svg {
+ width: 18px;
+ height: 18px;
+}
+
+.btn-primary {
+ background-color: var(--accent);
+ color: white;
+ box-shadow: 0 4px 14px 0 rgba(168, 85, 247, 0.3);
+}
+
+.btn-primary:hover {
+ background-color: #9333ea;
+ transform: translateY(-1px);
+ box-shadow: 0 6px 20px 0 rgba(168, 85, 247, 0.4);
+}
+
+.btn-primary:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.btn-secondary {
+ background-color: rgba(255, 255, 255, 0.05);
+ border-color: var(--border-color);
+ color: var(--text-primary);
+}
+
+.btn-secondary:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.btn-danger {
+ background-color: var(--danger);
+ color: white;
+ box-shadow: 0 4px 14px 0 rgba(239, 68, 68, 0.2);
+}
+
+.btn-danger:hover {
+ background-color: #dc2626;
+ transform: translateY(-1px);
+}
+
+.btn-icon-only {
+ padding: 0.6rem;
+ border-radius: 8px;
+}
+
+/* View Actions Bar */
+.view-actions-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+/* Crawlers Grid Layout */
+.crawlers-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 1.5rem;
+}
+
+/* Card Component (Glassmorphism style) */
+.card {
+ background-color: var(--bg-card);
+ backdrop-filter: var(--glass-blur);
+ border: 1px solid var(--border-color);
+ border-radius: 16px;
+ padding: 1.5rem;
+ box-shadow: var(--card-shadow);
+ transition: all var(--transition-speed) ease;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ overflow: hidden;
+}
+
+.card:hover {
+ background-color: var(--bg-card-hover);
+ border-color: var(--border-hover);
+ transform: translateY(-3px);
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.4);
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 1rem;
+ gap: 1rem;
+}
+
+.card-title {
+ font-size: 1.15rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 200px;
+}
+
+.card-badge {
+ background-color: rgba(255, 255, 255, 0.05);
+ padding: 0.2rem 0.6rem;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ border: 1px solid var(--border-color);
+}
+
+.card-body {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ margin-bottom: 1.25rem;
+}
+
+.card-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.card-field-label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+.card-field-value {
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+ word-break: break-all;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.card-field-value.url {
+ font-family: var(--font-en);
+ direction: ltr;
+ font-size: 0.75rem;
+ color: var(--info);
+}
+
+.card-field-value.prompt {
+ line-height: 1.4;
+}
+
+.card-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-top: 1px solid var(--border-color);
+ padding-top: 1rem;
+ margin-top: auto;
+}
+
+.card-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+/* Custom Switch Toggle Component */
+.switch-container {
+ position: relative;
+ display: inline-block;
+ width: 44px;
+ height: 22px;
+}
+
+.switch-container input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.switch-slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(255, 255, 255, 0.1);
+ border: 1px solid var(--border-color);
+ transition: .3s;
+ border-radius: 34px;
+}
+
+.switch-slider:before {
+ position: absolute;
+ content: "";
+ height: 14px;
+ width: 14px;
+ left: 4px;
+ bottom: 3px;
+ background-color: var(--text-secondary);
+ transition: .3s;
+ border-radius: 50%;
+}
+
+.switch-container input:checked + .switch-slider {
+ background-color: var(--accent);
+}
+
+.switch-container input:checked + .switch-slider:before {
+ transform: translateX(20px);
+ background-color: white;
+}
+
+/* Switch toggle labels */
+.toggle-group {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+/* Loading & Empty States */
+.loading-state, .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 4rem 2rem;
+ text-align: center;
+ width: 100%;
+ grid-column: 1 / -1;
+ background-color: var(--bg-card);
+ border: 1px dashed var(--border-color);
+ border-radius: 16px;
+ color: var(--text-secondary);
+}
+
+.loading-state svg, .empty-state svg {
+ width: 48px;
+ height: 48px;
+ margin-bottom: 1rem;
+ color: var(--text-muted);
+}
+
+.spinner {
+ border: 3px solid rgba(255, 255, 255, 0.05);
+ border-top: 3px solid var(--accent);
+ border-radius: 50%;
+ width: 36px;
+ height: 36px;
+ animation: spin 0.8s linear infinite;
+ margin-bottom: 1rem;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Data Filtering Bar for Ads Tab */
+.ads-filters-bar {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 1.25rem;
+ background-color: var(--bg-card);
+ padding: 1.25rem;
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ margin-bottom: 2rem;
+ align-items: center;
+}
+
+.search-box {
+ position: relative;
+ flex-grow: 1;
+ max-width: 400px;
+}
+
+.search-icon {
+ position: absolute;
+ right: 0.75rem;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 18px;
+ height: 18px;
+ color: var(--text-muted);
+}
+
+.search-box input {
+ width: 100%;
+ background-color: rgba(0, 0, 0, 0.2);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 0.55rem 2.5rem 0.55rem 0.75rem;
+ color: var(--text-primary);
+ font-size: 0.85rem;
+ transition: all var(--transition-speed) ease;
+}
+
+.search-box input:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 10px rgba(168, 85, 247, 0.15);
+}
+
+.filters-group {
+ display: flex;
+ align-items: center;
+ gap: 1.5rem;
+ flex-wrap: wrap;
+}
+
+.filter-item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+}
+
+.filter-item select {
+ background-color: rgba(0, 0, 0, 0.2);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 0.45rem 1rem;
+ color: var(--text-primary);
+ font-size: 0.85rem;
+ outline: none;
+}
+
+.filter-item select:focus {
+ border-color: var(--accent);
+}
+
+/* Ads Grid Layout */
+.ads-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
+ gap: 1.5rem;
+}
+
+/* Ad Card Customizations */
+.ad-card {
+ border-top: 4px solid var(--border-color);
+}
+
+.ad-card.flagged {
+ border-top: 4px solid var(--accent);
+ background: linear-gradient(180deg, rgba(168, 85, 247, 0.03) 0%, var(--bg-card) 100%);
+ box-shadow: 0 8px 32px 0 rgba(168, 85, 247, 0.05);
+}
+
+.ad-meta-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.5rem;
+ width: 100%;
+}
+
+.ad-published-at {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+.ad-price {
+ font-size: 0.95rem;
+ font-weight: 700;
+ color: var(--accent);
+}
+
+.ad-desc {
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+ line-height: 1.5;
+ word-break: break-all;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ margin-bottom: 1rem;
+}
+
+/* AI Flag Box on Ad Cards */
+.ai-flag-box {
+ background-color: rgba(168, 85, 247, 0.08);
+ border: 1px solid rgba(168, 85, 247, 0.2);
+ border-radius: 10px;
+ padding: 0.85rem 1rem;
+ font-size: 0.85rem;
+ margin-bottom: 1rem;
+}
+
+.ai-flag-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ color: var(--accent);
+ font-weight: 700;
+ margin-bottom: 0.35rem;
+}
+
+.ai-flag-header svg {
+ width: 16px;
+ height: 16px;
+}
+
+.ai-flag-reason {
+ color: var(--text-secondary);
+ line-height: 1.4;
+}
+
+.ai-flag-reason.arabic-num {
+ font-family: var(--font-en);
+}
+
+.ai-confidence-badge {
+ font-size: 0.7rem;
+ background-color: rgba(168, 85, 247, 0.2);
+ padding: 0.1rem 0.4rem;
+ border-radius: 4px;
+ font-family: var(--font-en);
+ direction: ltr;
+}
+
+.ad-tag-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+}
+
+.ad-tag {
+ background-color: rgba(255, 255, 255, 0.02);
+ border: 1px solid var(--border-color);
+ padding: 0.15rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+}
+
+/* Table Container (Glassmorphic Table) */
+.table-container {
+ background-color: var(--bg-card);
+ backdrop-filter: var(--glass-blur);
+ border: 1px solid var(--border-color);
+ border-radius: 16px;
+ box-shadow: var(--card-shadow);
+ overflow: hidden;
+ width: 100%;
+}
+
+.data-table {
+ border-collapse: collapse;
+ width: 100%;
+ text-align: right;
+ font-size: 0.9rem;
+}
+
+.data-table th, .data-table td {
+ padding: 1rem 1.25rem;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.data-table th {
+ background-color: rgba(0, 0, 0, 0.2);
+ color: var(--text-primary);
+ font-weight: 700;
+}
+
+.data-table tr:last-child td {
+ border-bottom: none;
+}
+
+.data-table tr:hover td {
+ background-color: rgba(255, 255, 255, 0.01);
+}
+
+.badge-status {
+ padding: 0.2rem 0.6rem;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.badge-status.success {
+ background-color: rgba(16, 185, 129, 0.1);
+ color: var(--success);
+ border: 1px solid rgba(16, 185, 129, 0.3);
+}
+
+.badge-status.failed {
+ background-color: rgba(239, 68, 68, 0.1);
+ color: var(--danger);
+ border: 1px solid rgba(239, 68, 68, 0.3);
+}
+
+.badge-status.running {
+ background-color: rgba(245, 158, 11, 0.1);
+ color: var(--warning);
+ border: 1px solid rgba(245, 158, 11, 0.3);
+}
+
+.arabic-num-font {
+ font-family: var(--font-en);
+}
+
+/* Modals Overlay (Dialog Card) */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+ backdrop-filter: blur(8px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 100;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity var(--transition-speed) ease;
+}
+
+.modal-overlay.open {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.modal-card {
+ background-color: var(--bg-sidebar);
+ border: 1px solid var(--border-color);
+ border-radius: 20px;
+ width: 600px;
+ max-width: 90%;
+ max-height: 90vh;
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
+ display: flex;
+ flex-direction: column;
+ transform: scale(0.95);
+ transition: transform var(--transition-speed) ease;
+}
+
+.modal-overlay.open .modal-card {
+ transform: scale(1);
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1.25rem 1.5rem;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.modal-header h3 {
+ font-size: 1.2rem;
+ font-weight: 700;
+}
+
+.close-btn {
+ background: transparent;
+ border: none;
+ color: var(--text-secondary);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+}
+
+.close-btn svg {
+ width: 20px;
+ height: 20px;
+}
+
+.modal-body {
+ padding: 1.5rem;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+}
+
+.modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.75rem;
+ padding: 1.25rem 1.5rem;
+ border-top: 1px solid var(--border-color);
+}
+
+/* Forms Customization */
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.form-group label {
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+ font-weight: 500;
+}
+
+.form-group input[type="text"],
+.form-group input[type="url"],
+.form-group input[type="time"],
+.form-group select,
+.form-group textarea {
+ background-color: rgba(0, 0, 0, 0.2);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 0.65rem 0.85rem;
+ color: var(--text-primary);
+ font-size: 0.9rem;
+ outline: none;
+ transition: all var(--transition-speed) ease;
+ width: 100%;
+}
+
+.form-group input:focus,
+.form-group select:focus,
+.form-group textarea:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 10px rgba(168, 85, 247, 0.2);
+}
+
+.form-group textarea {
+ resize: vertical;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+.form-group.toggle-group {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ background-color: rgba(255, 255, 255, 0.01);
+ padding: 0.75rem 1rem;
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+}
+
+.field-error {
+ font-size: 0.75rem;
+ color: var(--danger);
+ margin-top: 0.15rem;
+}
+
+/* Responsive Customizations */
+@media (max-width: 768px) {
+ .app-container {
+ flex-direction: column;
+ }
+
+ .sidebar {
+ width: 100%;
+ height: auto;
+ border-left: none;
+ border-bottom: 1px solid var(--border-color);
+ padding: 1rem;
+ }
+
+ .sidebar-brand {
+ margin-bottom: 1rem;
+ padding-bottom: 0.5rem;
+ }
+
+ .sidebar-nav {
+ flex-direction: row;
+ overflow-x: auto;
+ gap: 0.5rem;
+ padding-bottom: 0.5rem;
+ }
+
+ .nav-item {
+ padding: 0.6rem 0.85rem;
+ font-size: 0.85rem;
+ white-space: nowrap;
+ width: auto;
+ }
+
+ .nav-item.active {
+ border-right: none;
+ border-bottom: 3px solid var(--accent);
+ }
+
+ .app-header {
+ padding: 1rem;
+ }
+
+ .view-viewport {
+ padding: 1rem;
+ }
+
+ .form-row {
+ grid-template-columns: 1fr;
+ }
+
+ .ads-filters-bar {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .filters-group {
+ justify-content: space-between;
+ }
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
new file mode 100644
index 0000000..b9a1a6d
--- /dev/null
+++ b/frontend/src/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 0000000..93c4f48
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,15 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://127.0.0.1:8000',
+ changeOrigin: true,
+ }
+ }
+ }
+})